
(#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移植が完了しました。一度やっている処理を付け足ししてるだけだったので、簡単でした。次回は敵の生成をしていこうかなと思います。