見出し画像

(#04)UnityでタクティスRPGが作りたい!! - 3D移植編 -

どうもみなさん!今回は、前回まで2Dで作成していたので、3Dに移植していきたいと思います。基本的にはコードのみとなると思います。
前回までのコードと変わってる部分もあるので、初見の方は#01から見なくても大丈夫です。


Tileクラス

Tileクラスでは、そのタイルが移動可能なのかや位置座標などを定義します。

using UnityEngine;

public class Tile : MonoBehaviour
{
    //タイルが移動可能判定
    public bool IsMovable;

    //ノーマルタイル
    [SerializeField] private GameObject _normalTile;

    //移動可能タイル
    [SerializeField] private GameObject _movableTile;

    //タイルの座標
    public Vector3Int CurrentPos { get; set; }

    //キャラクター情報
    public BaseCharacter CharacterInfomation { get; set; }

    /// <summary>
    /// 移動可能タイルに変更
    /// </summary>
    public void SetMovableTile()
    {
        _normalTile.SetActive(false);
        _movableTile.SetActive(true);
    }

    /// <summary>
    /// ノーマルタイルに変更
    /// </summary>
    public void SetNormalTile()
    {
        _normalTile.SetActive(true);
        _movableTile.SetActive(false);
    }
}

ノーマルタイルは通常時(移動などしないとき)に表示されるもので、
移動可能タイルはそのままの通りです。もしかしたら、ピンとこないかもしれないので、Xのほうで動画をアップしてますので、そちらをご確認ください。

BaseCharacterクラス

ここでは、キャラクターの情報(ステータスや移動処理など)を記入していきます。


using DG.Tweening;
using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;

public class BaseCharacter : MonoBehaviour
{
    //現在の座標
    public Vector3 CurrentPos { get; set; }

    //移動範囲(上下左右)
    public Vector2Int MovableRange;

    CancellationTokenSource cancellationTokenSource;
    Animator animator;

    //アニメのパラメーターのハッシュ値(高速化のため)
    private int WalkHash = Animator.StringToHash("Move");

    private void Start()
    {
        animator = GetComponent<Animator>();
    }

    /// <summary>
    /// 移動関数
    /// </summary>
    /// <param name="targetPos"></param>
    public async UniTask Move(Vector3 targetPos)
    {
        //キャンセルトークンソースの初期化
        bool isDone = false;
        if (cancellationTokenSource != null)
        {
            cancellationTokenSource.Cancel();
        }
        cancellationTokenSource = new CancellationTokenSource();
        
        //Y座標調整
        targetPos.y = 0.5f;

        //現在位置を指定した位置に移動
        transform.DOMove(targetPos, 1).OnComplete(() => isDone = true);
        //現在の位置データを記録
        CurrentPos = targetPos;

        //アニメ
        animator.SetBool(WalkHash, !isDone);

        try
        {
            //移動が終わるまで待機
            await UniTask.WaitUntil(() => isDone == true, PlayerLoopTiming.Update, cancellationTokenSource.Token);

            //アニメ終了
            animator.SetBool(WalkHash, !isDone);
        }
        catch(System.OperationCanceledException e)
        {
            Debug.Log(e);
        }
    }
}

移動処理にUniTaskを使用して、移動が終わるまで待機する処理を作成しています。後は、このベースクラスを継承して、Playerクラスを作成しました。

TileManagerクラス

このクラスでは、タイルの生成、座標からタイル情報を取得やキャラクターの移動可能範囲内のタイルを取得する関数(これがメインかな)作成します。

using System.Collections.Generic;
using UnityEngine;

public class TileManager : MonoBehaviour
{
    //縦・横幅
    const int WIDTH = 9;
    const int HEIGHT = 9;

    //タイルプレファブ
    [SerializeField] private Tile MovableTile, UnMovableTile;
    //親
    [SerializeField] private Transform _tileParent;

    //タイルデータを格納する配列
    public Tile[,] tiles = new Tile[WIDTH, HEIGHT];

    /// <summary>
    /// 初期化
    /// </summary>
    public void Init()
    {
        for (int i = 0; i < WIDTH; i++)
        {
            for(int j = 0; j < HEIGHT; j++)
            {
                //位置
                Vector3Int SpawnPos = new Vector3Int(i - (WIDTH / 2), 0, j - (HEIGHT / 2));

                //スポーン
                Tile spawnTarget = null;
                if (SpawnPos != new Vector3Int(0, 0, -4)) spawnTarget = Random.Range(0, 100) < 10 ? UnMovableTile : MovableTile;
                else spawnTarget = MovableTile;
                Tile tile = Instantiate(spawnTarget, SpawnPos, Quaternion.identity, _tileParent);
                tile.CurrentPos = SpawnPos;

                //データ記録
                tiles[i, j] = tile;
            }
        }
    }

    /// <summary>
    /// 座標からタイルを取得
    /// </summary>
    /// <param name="pos"></param>
    /// <returns></returns>
    public Tile GetTileFromPosition(Vector3 pos)
    {
        pos.y = 0;
        for (int i = 0; i < WIDTH; i++)
        {
            for (int j = 0; j < HEIGHT; j++)
            {
                if (tiles[i, j].CurrentPos == pos) return tiles[i, j];
            }
        }
        return null;
    }

    /// <summary>
    /// 指定されたキャラクターの移動可能タイルを取得
    /// </summary>
    /// <param name="character"></param>
    /// <returns></returns>
    public List<Tile> GetReachableTiles(BaseCharacter character)
    {
        List<Tile> returnList = new List<Tile>();
        for(int i = 0;  i < WIDTH;i++)
        {
            for (int j = 0; j < HEIGHT; j++)
            {
                if (tiles[i,j].CharacterInfomation == character)
                {
                    //右側の移動可能範囲
                    for (int k = 1; k < character.MovableRange.x + 1; k++)
                        if ((i + k) < WIDTH && tiles[i + k, j].CurrentPos.x <= WIDTH / 2 && tiles[i + k, j].CharacterInfomation == null && tiles[i + k, j].IsMovable) 
                            returnList.Add(tiles[i + k, j]);
                        else break;

                    //左側の移動可能範囲
                    for (int k = -1; k > -(character.MovableRange.x + 1); k--)
                        if ((i + k) >= 0 && tiles[i + k, j].CurrentPos.x >= -(WIDTH / 2) && tiles[i + k, j].CharacterInfomation == null && tiles[i + k, j].IsMovable) 
                            returnList.Add(tiles[i + k, j]);
                        else break;

                    //上側の移動可能範囲
                    for (int k = 1; k < character.MovableRange.y + 1; k++)
                        if ((j + k) < HEIGHT && tiles[i, j + k].CurrentPos.y <= HEIGHT / 2 && tiles[i, j + k].CharacterInfomation == null && tiles[i, j + k].IsMovable)
                            returnList.Add(tiles[i, j + k]);
                        else break;

                    //下側の移動可能範囲
                    for (int k = -1; k > -(character.MovableRange.y + 1); k--)
                        if ((j + k) >= 0 && tiles[i, j + k].CurrentPos.y >= -(HEIGHT / 2) && tiles[i, j + k].CharacterInfomation == null && tiles[i, j + k].IsMovable)
                            returnList.Add(tiles[i, j + k]);
                        else break;
                }
            }
        }
        return returnList;
    }
}

CharacterManagerクラス

ここでは、キャラクターを生成してタイルにキャラクター情報を記録する感じです。

using UnityEngine;

public class CharacterManager : MonoBehaviour
{
    //タイルマネージャー
    private TileManager _tileManager;

    //プレイヤーPrefab
    [SerializeField] private Player[] playersPrefab = new Player[3];

    //プレイヤーデータを格納する
    public Player[] players = new Player[3];

    public void Init()
    {
        _tileManager = GetComponent<TileManager>();
        for (int i = 0; i < playersPrefab.Length; i++)
        {
            if (playersPrefab[i] == null) break;

            //スポーン位置を決定
            Vector3 SpawnPos = new Vector3(i, 0.5f, -4);
            
            //スポーン
            var p_unit = Instantiate(playersPrefab[i], SpawnPos, Quaternion.identity);
            p_unit.CurrentPos = SpawnPos;

            //キャラクター情報をタイルに記録
            Tile tile = _tileManager.GetTileFromPosition(SpawnPos);
            if(tile != null) tile.CharacterInfomation = p_unit;

            //データを記録
            players[i] = p_unit;
        }
    }
}

GameDirectorクラス

ここでは、ゲームのステート管理やタイルのクリック判定などして、キャラの移動をしています。

using System.Collections.Generic;
using UnityEngine;

public class GameDirector : MonoBehaviour
{
    //キャラクターマネージャー
    private CharacterManager _characterManager;
    //タイルマネージャー
    private TileManager _tileManager;

    //移動前のタイル情報
    private Tile oldTile = null;

    //選択中のキャラ
    Player SelectedPlayer = null;                
    
    //移動可能タイル
    List<Tile> ReachableTileList = new List<Tile>();

    //プレイヤーが移動中か
    bool PlayerMoving = false;

    //ゲームステート
    public enum GameState
    {
        PlayerTurn_Begin,
        PlayerTurn_SelectTile,
        PlayerTurn_SelectCommand,
        PlayerTurn_Attack,
        PlayerTurn_End,
    }
    public GameState CurrentState = GameState.PlayerTurn_Begin;

    private void Start()
    {
        _characterManager = GetComponent<CharacterManager>();
        _tileManager = GetComponent<TileManager>();

        _tileManager.Init();
        _characterManager.Init();
    }

    private async void Update()
    {
        if(Input.GetMouseButtonDown(0) && !PlayerMoving)
        {
            //レイを飛ばす
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray, out var hit))
            {
                //タイル情報を取得
                Tile SelectTile = _tileManager.GetTileFromPosition(hit.collider.transform.position);
                switch (CurrentState)
                {
                    case GameState.PlayerTurn_Begin:
                        if(SelectTile.CharacterInfomation != null)
                        {
                            //選択したタイルを移動前タイルに格納
                            oldTile = SelectTile;

                            //選択したプレイヤーを取得
                            SelectedPlayer = SelectTile.CharacterInfomation as Player;

                            //プレイヤーの位置から移動可能タイルを取得
                            ReachableTileList = _tileManager.GetReachableTiles(SelectedPlayer);

                            //移動可能タイルを視覚化
                            ShowReachableTile(ReachableTileList);

                            //ターン変更
                            ChangeTurn(GameState.PlayerTurn_SelectTile);
                        }
                        break;
                    case GameState.PlayerTurn_SelectTile:
                        if(SelectTile.IsMovable)
                        {
                            //移動中判定
                            PlayerMoving = true;

                            //移動可能タイルに選択されたタイルが含まれていなければ終了
                            if (!ReachableTileList.Contains(SelectTile)) break;

                            //移動可能タイルの表示リセット
                            ResetReachableTile(ReachableTileList);

                            //プレイヤーを移動
                            await SelectedPlayer.Move(SelectTile.CurrentPos);

                            //移動後のタイルにプレイヤー情報を格納
                            SelectTile.CharacterInfomation = SelectedPlayer;

                            //移動前のタイルのプレイヤー情報を消去
                            oldTile.CharacterInfomation = null;

                            //移動終了
                            PlayerMoving = false;

                            //ターン変更
                            ChangeTurn(GameState.PlayerTurn_SelectCommand);
                        }
                        break;
                }
            }
        }
    }

    /// <summary>
    /// ターン変更
    /// </summary>
    /// <param name="state"></param>
    public void ChangeTurn(GameState state)
    {
        CurrentState = state;
    }

    /// <summary>
    /// 移動可能範囲を視覚化
    /// </summary>
    /// <param name="reachableTiles"></param>
    void ShowReachableTile(List<Tile> reachableTiles)
    {
        foreach(Tile tile in reachableTiles)
        {
            tile.SetMovableTile();
        }
    }

    /// <summary>
    /// 移動可能範囲をリセット
    /// </summary>
    /// <param name="reachableTiles"></param>
    void ResetReachableTile(List<Tile> reachableTiles)
    {
        foreach (Tile tile in reachableTiles)
        {
            tile.SetNormalTile();
        }
    }
}

完成動画

完成した動画はXにてアップロードしております。そちらでご確認いただければ幸いです。

最後に

これにて、3D移植が完了しました。一度やっている処理を付け足ししてるだけだったので、簡単でした。次回は敵の生成をしていこうかなと思います。

いいなと思ったら応援しよう!