まるとこ学習帳

Unityでゲームを作っています。学んだことの記録を書きます。

RPG風ターン制バトルを作ってみた【Unity】

*素人がこうやってみたよっていう記事なので参考程度にお願いします。
Unityバージョン2019.4.18f1

UnityでRPGの戦闘のようなターン制バトルを作ってみたいなあと思っていたところ、
こんな記事を見つけたので参考にして試しに作ってみました。

kamuiblog.com

boolを使ってプレイヤーターンと敵ターンを切り替えます。
できあがりはこんな感じ。
*イラスト素材は「いらすとや」からお借りしています。
かわいいフリー素材集 いらすとや

f:id:marutoko:20210123192431g:plain

パラメータはHPと攻撃力のみのシンプルな戦闘シーンです。
敵HPが0になるとクリア画像、プレイヤーHPが0になるとゲームオーバー画像が出ます。

下準備

オブジェクトをアニメーションさせるのにアセット「DOTween」(無料版)を使いました。

DOTween Asset Store
assetstore.unity.com

DOTweenの入手・セットアップはこちらが参考になります。
qiita.com

Unityでオブジェクトを配置

f:id:marutoko:20210123202114p:plain
画像素材を配置します。
ヒエラルキーからUI→imageで草原(BackGround)、勇者(Player1)、モンスター(Enemy1)を配置。
クリア画像(Clear)とゲームオーバー画像(GameOver)は右からスライドインさせて表示するため、
初期位置はCanvasの範囲の外に置きました。
ダメージ数値はUI→Textから、P1DamageとE1Damageをそれぞれ勇者の上とモンスターの上に配置しました。
ダメージ値はスクリプトで表示させるので、Text内の文字は空にしています。
正しく表示されるように、適当に数字を入れてみてあらかじめ位置を調整してから
文字を消すとスムーズかと思います。

戦闘シーンを動かすスクリプトを貼り付けるための空オブジェクトを用意しておきます。
ヒエラルキーからCreate Emptyでオブジェクト作成、名前を「BattleController」に変更。

スクリプト作成

スクリプトを書きます。

BattleController.cs(メインのスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BattleController : MonoBehaviour
{
    public bool playerTurn; //trueのときプレイヤーのターン
    public bool enemyalive; //敵が生きているか
    public bool playeralive; //プレイヤーが生きているか

    public GameObject clear; //ゲームクリアの画像
    ClearTween script;  //クリア画像を動かすスクリプト
    public GameObject gameover; //ゲームオーバーの画像
    GameOverTween scriptGO;  //ゲームオーバー画像を動かすスクリプト


    public GameObject player1;  //プレイヤーオブジェクト
    public GameObject enemy1;  //敵オブジェクト
    P1Tween scriptP;  //プレイヤーを動かすスクリプト
    E1Tween scriptE;  //敵を動かすスクリプト

    public GameObject p1dmgtxt; //プレイヤーダメージ表記オブジェクト
    public GameObject e1dmgtxt; //敵ダメージ表記オブジェクト
    private Text P1DamageText; //プレイヤーダメージ表記テキスト
    private Text E1DamageText; //敵ダメージ表記テキスト
    E1dmgTween scriptE1dmg; //敵ダメージ表記を動かすスクリプト
    P1dmgTween scriptP1dmg; //プレイヤーダメージ表記を動かすオブジェクト

    int Php = 100; //プレイヤーHP
    int Ehp = 100; //敵HP
    int Patk = 40; //プレイヤー攻撃力
    int Eatk = 30; //敵攻撃力

    void Start()
    {
        playerTurn = true;
        enemyalive = true;
        playeralive = true;

        player1 = GameObject.Find("Player1");
        enemy1 = GameObject.Find("Enemy1");

        p1dmgtxt = GameObject.Find("P1Damage");
        p1dmgtxt.SetActive(false);
        e1dmgtxt = GameObject.Find("E1Damage");
        e1dmgtxt.SetActive(false);

        Debug.Log("バトルスタート");
        Debug.Log("敵HP" + Ehp);
        Debug.Log("プレイヤーHP" + Php);
    }

    void Update()
    {
        if (playerTurn == true && playeralive == true)
        {
            if (Input.GetKeyDown(KeyCode.Return))
            {
                Debug.Log("プレイヤーターン");
                Invoke("Player1Move", 0.5f); //プレイヤー動かす
                Ehp -= Patk; //敵のHPを減らす
                Debug.Log("敵HP" + Ehp);

                E1DamageText = e1dmgtxt.GetComponent<Text>();
                    //敵ダメージ表記オブジェクトからテキストを取得
                E1DamageText.text = "-" + Patk; //テキストにダメージを入れる
                scriptE1dmg = e1dmgtxt.GetComponent<E1dmgTween>();
                    //E1Damageにアタッチされているスクリプトを取得
                scriptE1dmg.E1dmg(); //ダメージを動かしながら表示

                if (Ehp <= 0) //敵HPが0以下のとき
                {
                    enemyalive = false;
                    Invoke("Enemy1Dead", 1f); //敵を非アクティブにする
                    Invoke("AllEnemyDead", 1f); //ゲームクリア画面表示
                }
                
                playerTurn = false;
            }
        }

        if (playerTurn == false && enemyalive == true)
        {
            if (Input.GetKeyDown(KeyCode.Return))
            {
                Debug.Log("敵ターン");

                Invoke("Enemy1Move", 1.5f); //敵動かす
                Php -= Eatk; //プレイヤーのHPを減らす
                Debug.Log("プレイヤーHP" + Php);

                P1DamageText = p1dmgtxt.GetComponent<Text>();
                    //プレイヤーダメージ表記オブジェクトからテキストを取得
                P1DamageText.text = "-" + Eatk; //テキストにダメージを入れる
                scriptP1dmg = p1dmgtxt.GetComponent<P1dmgTween>();
                    //P1Damageにアタッチされているスクリプトを取得
                scriptP1dmg.P1dmg(); //ダメージを動かしながら表示

                if (Php <= 0) //プレイヤーHPが0以下のとき
                {
                    playeralive = false;
                    Invoke("Player1Dead", 1f); //プレイヤーを非アクティブにする
                    Invoke("GameOver", 1f); //ゲームオーバー画面表示
                }

                playerTurn = true;
            }
        }
    }

    void Player1Move()
    {
        scriptP = player1.GetComponent<P1Tween>();
        scriptP.P1Move();
    }

    void Enemy1Move()
    {
        scriptE = enemy1.GetComponent<E1Tween>();
        scriptE.E1Move();
    }

    void Player1Dead()
    {
        player1.SetActive(false);
    }

    void Enemy1Dead()
    {
        enemy1.SetActive(false);
    }

    void GameOver()
    {
        gameover = GameObject.Find("GameOver");
        scriptGO = gameover.GetComponent<GameOverTween>();
        scriptGO.GameOver1();
        Debug.Log("ゲームオーバー");
    }

    void AllEnemyDead()
    {
        clear = GameObject.Find("Clear");
        script = clear.GetComponent<ClearTween>();
        script.Clear();
        Debug.Log("ゲームクリア");
    }
}

BattleController.csをUnityで先ほど作った空オブジェクトBattleControllerにアタッチ。
続いて、DOTweenを使ってプレイヤーオブジェクトを動かすスクリプトを書きます。

P1Tween.cs(プレイヤーをアニメーションさせるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening; //DOTweenを使うために追加

public class P1Tween : MonoBehaviour
{
    public void P1Move()
    {
        MoveGo();
    }
    private void MoveGo() //プレイヤーを敵に向かって移動
    {
        transform.DOLocalMove(new Vector3(-389f, 0, 0), 0.1f)
            .SetRelative() //相対座標で動かす
            .OnComplete(() => MoveBack()); //終わったらMoveBackを実行
    }
    private void MoveBack() //プレイヤーを元の位置に戻す
    {
        transform.DOLocalMove(new Vector3(389f, 0, 0), 0.1f)
            .SetRelative();
    }
}

P1Tween.csはUnityのPlayer1オブジェクトにアタッチします。
続いて同様に、敵を動かすスクリプトを書きます。

E1Tween.cs(敵をアニメーションさせるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class E1Tween : MonoBehaviour
{
    public void E1Move()
    {
        EMoveGo();
    }
    private void EMoveGo()
    {
        transform.DOLocalMove(new Vector3(391, 0, 0), 0.1f)
            .SetRelative()
            .OnComplete(() => EMoveBack());
    }
    private void EMoveBack()
    {
        transform.DOLocalMove(new Vector3(-391, 0, 0), 0.1f)
            .SetRelative();
    }
}

E1Tween.csをEnemy1オブジェクトにアタッチ。

次は、ダメージを与えた/受けた時にキャラクターの上にダメージ数値を表示させるスクリプトを書いていきます。
まずは敵の上にダメージを表示させるスクリプトから。

E1dmgTween.cs(敵に与えたダメージを表示させるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public class E1dmgTween : MonoBehaviour
{
    public void E1dmg()
    {
        Invoke("E1dmgjump", 0.5f);
    }
        private void E1dmgjump() //ダメージ値を表示してジャンプさせる
    {
            this.gameObject.SetActive(true); //ダメージ値を表示
            transform.DOJump(new Vector3(0, 0, 0), 15f, 1, 0.3f)
              //ジャンプさせる(移動終了地点, ジャンプ力, ジャンプ回数, アニメーション時間)
                .SetEase(Ease.Linear) //滑らかに
                .SetRelative() //相対座標で
                .OnComplete(() => Endfunc()); //終わったらEndfunc実行
        }
    private void Endfunc()
    {
        this.gameObject.SetActive(false); //ダメージ値を非表示
    }
}

E1dmgTween.csはE1Damageオブジェクトにアタッチしておきます。
続いて同様に、プレイヤーの上にダメージ表示させるスクリプトを書きます。

P1dmgTween.cs(敵に与えたダメージを表示させるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public class P1dmgTween : MonoBehaviour
{
    public void P1dmg()
    {
        Invoke("E1dmgjump", 1.5f);
    }
        private void E1dmgjump()
        {
            this.gameObject.SetActive(true);
            transform.DOJump(new Vector3(0, 0, 0), 15f, 1, 0.3f)
                .SetRelative()
                .OnComplete(() => Endfunc());
        }
    private void Endfunc()
    {
        this.gameObject.SetActive(false);
    }
}

P1dmgTween.csをP1Damageオブジェクトにアタッチ。

長くなってきましたがもうすぐ完成です!
最後に、敵を倒した場合とプレイヤーが倒された場合に、
それぞれ「GAME CLEAR」画像と「GAME OVER」画像を表示させるためのスクリプトを書きます。

まずはゲームクリア画像から。

ClearTween.cs(ゲームクリア画像を表示させるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class ClearTween : MonoBehaviour
{
    public void Clear()
    {
        Invoke("Move", 0.5f);
    }
        private void Move()
        {
            transform.DOLocalMove(new Vector3(-14f, -18f, 0), 0.5f);
              //オブジェクトをローカル座標で移動(座標,アニメーション時間)
        }
}

ClearTween.csはClearオブジェクトにアタッチします。
同様にゲームオーバー画像を動かすスクリプトを書きます。

GameOverTween.cs(ゲームオーバー画像を表示させるスクリプト)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class GameOverTween : MonoBehaviour
{
    public void GameOver1()
    {
        Invoke("GameOverMove", 0.5f);
    }
        private void GameOverMove()
        {
            transform.DOLocalMove(new Vector3(-14f, -18f, 0), 0.5f);
        }
}

GameOverTween.csをGameOverオブジェクトにアタッチ。
やっと完成です!

注意点

参考にしていただく場合、スクリプト内の座標はプロジェクトによると思いますので
オブジェクトの位置に合わせて調整してみてください。
プレイヤーと敵のHPと攻撃力を変えたりパラメータを追加したり色々遊ぶと面白いかも。
ずらずらっと書いていきましたが実際にゲームとして完成させるには
ここからさらに工夫が必要だと思います。

また、簡易的にタイミングを合わせるために、設定した時間後(単位/秒)にメソッドを呼び出す
Invoke」を使いましたが、いろいろな理由から非推奨のようです。
より汎用性の高い「コルーチン」に書き換えたほうがいいかもしれません。

InvokeのデメリットとCoroutineの使い方についての参考記事
www.wwwmaplesyrup-cs6.work

忘備録的な記事になりますが、少しでも役にたてば嬉しいです。

まだUnityの入門書をやっていない方は「Unityの教科書」がおすすめです!
私はC#,Unityともに初めてで、この本の2019年度版を読みましたが
解説がとても丁寧で、猫の可愛いイラストを挟みつつわかりやすく書かれています。
簡単にスマホで作ったゲームを動かしてみることができるのも良かったです。
(iPhoneの場合はMacが必要なので注意)

ではではこの辺で〜!