『Unity』 簡単なシンプルターン制システムの作り方
どうもみなさん!Sonoraです。今回はUnityで簡単なターン制システムの作り方をご紹介していきたいと思います。悪魔で私個人のやり方なので、もっといい方法があるのに!と思う方もいらっしゃると思いますが、そこは配慮していただければ幸いです。この記事では基本コードのみとなりますのでUnityの使い方はご理解してる上で進めていきます。
キャラクターステータスクラス
まずターンシステムを作る前に必要なキャラクターステータスを作ります。
プレイヤーと敵で分けていきたいと思います。
プレイヤーと敵には『HP』、『攻撃力』、『防御力』と共通するステータスがあるので、ここら辺は継承を使って書きたいと思います。
using UnityEngine;
public class CharacterStatus
{
//キャラクターステータス
public int hp;
public int attackPoint;
public int defenctPoint;
//攻撃関数
public virtual void Attack(CharacterStatus attacker, CharacterStatus defencer)
{
defencer.hp -= CalcDamage(attacker, defencer);
}
//ダメージ計算
public int CalcDamage(CharacterStatus attacker, CharacterStatus defencer)
{
float damageCalc = attacker.attackPoint - defencer.defenctPoint;
float ratio = 0;
for(int i=0;i < 100; i++)
{
ratio = Random.Range(0.7f, 1f);
}
int damage = (int)(damageCalc *= ratio);
if (damage < 0) damage = 0;
return damage;
}
}
MonoBehaviourは継承せずにただのクラスにします。
必要なキャラクターステータス、攻撃関数に関してはプレイヤーと敵で攻撃の仕方を変えるかもしれないので『virtual』にしてます。でも、今回の記事では書かなくても問題はありません。
次にダメージ計算に関しては、人それぞれ違うと思うので参考程度に見ていただければ幸いです。
これでキャラクターステータスクラスは完成です。
プレイヤークラス
続いてプレイヤークラスを作成していきます。
スクリプト名は『Player』にします。
public class Player : CharacterStatus
{
public override void Attack(CharacterStatus attacker, CharacterStatus defencer)
{
base.Attack(attacker, defencer);
}
public Player(int hp, int attackPoint, int defenctPoint)
{
this.hp = hp;
this.attackPoint = attackPoint;
this.defenctPoint = defenctPoint;
}
}
プレイヤークラスでは先程作成した『CharacterStatus』を継承しています。
こちらの『public override 何ちゃら』というのは書かなくてもOKです!
下の方はコンストラクタになります。インスタンスが生成された時に呼び出されるものです。
引数に設定された値がプレイヤーのステータスになるわけです!
敵クラス
続いて敵クラスになります。
スクリプト名は『Enemy』にします!
public class Enemy : CharacterStatus
{
public override void Attack(CharacterStatus attacker, CharacterStatus defencer)
{
base.Attack(attacker, defencer);
}
public Enemy(int hp, int attackPoint, int defenctPoint)
{
this.hp = hp;
this.attackPoint = attackPoint;
this.defenctPoint = defenctPoint;
}
}
やっていることは先程のプレイヤークラスと同じなので説明は割愛します。
バトルマネージャークラス
いよいよ今回の本題!ターンシステムを作っていきます!
スクリプト名は『BattleManager』にします。
結構長めのコード数になっております。
一つ一つ解説していきますので大丈夫です!
using System.Collections;
using UnityEngine;
public class BattleManager : MonoBehaviour
{
//プレイヤーと敵を取得
private Player player;
private Enemy enemy;
//負けフラグ
private bool _lose = false;
//バトルの状態を管理する変数
enum BattleStatus
{
PlayerTurnStart,
PlayerAttack,
EnemyTurnStart,
EnemyAttack,
}
//最初はプレイヤーからスタート
BattleStatus currentStatus = BattleStatus.PlayerTurnStart;
private void Start()
{
//キャラクターを生成
player = new Player(100,30,20);
enemy = new Enemy(40, 25, 14);
//バトル開始
StartCoroutine(Battle());
}
//バトルコルーチン
IEnumerator Battle()
{
Debug.Log("バトル開始");
//プレイヤーか敵どちらかのHPが0だったらループから抜ける
while(true)
{
//プレイヤーターン
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.PlayerTurnStart);
//プレイヤーの攻撃
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.PlayerAttack);
if (BattleWin(enemy)) break;
//敵のターン
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.EnemyTurnStart);
//敵の攻撃
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.EnemyAttack);
if (BattleLose(player))
{
_lose = true;
break;
}
}
yield return new WaitForSeconds(1f);
//敵のHPが0だったら勝利
if (!_lose)
{
Debug.Log("あなたは勝ちました");
}
//プレイヤーのHPが0だったら負け
else
{
Debug.Log("あなたは負けました");
}
}
//バトルターンの変更関数
void ChangeTurn(BattleStatus battleStatus)
{
currentStatus = battleStatus;
switch (currentStatus)
{
case BattleStatus.PlayerTurnStart:
Debug.Log("PlayerTurn");
break;
case BattleStatus.PlayerAttack:
player.Attack(player, enemy);
Debug.Log("Playerの攻撃!");
Debug.Log("Playerは" + "Enemyに" + player.CalcDamage(player, enemy) + "ダメージ与えた");
break;
case BattleStatus.EnemyTurnStart:
Debug.Log("EnemyTurn");
break;
case BattleStatus.EnemyAttack:
player.Attack(enemy, player);
Debug.Log("Enemyの攻撃!");
Debug.Log("Enemyは" + "Playerに" + enemy.CalcDamage(enemy, player) + "ダメージ与えた");
break;
}
}
//勝利関数
bool BattleWin(CharacterStatus enemy)
{
if (enemy.hp <= 0)
{
enemy.hp = 0;
return true;
}
else return false;
}
//負け関数
bool BattleLose(CharacterStatus player)
{
if (player.hp <= 0)
{
player.hp = 0;
return true;
}
else return false;
}
}
プレイヤークラスの取得
//プレイヤーと敵を取得
private Player player;
private Enemy enemy;
ここでは、プレイヤーと敵を取得しています。
これで、プレイヤーと敵を生成することができます。
負けフラグ
//負けフラグ
private bool _lose = false;
この変数はプレイヤーが負けたかを判定するフラグです。
現在のターンを管理
//バトルの状態を管理する変数
enum BattleStatus
{
PlayerTurnStart,
PlayerAttack,
EnemyTurnStart,
EnemyAttack,
}
//最初はプレイヤーからスタート
BattleStatus currentStatus = BattleStatus.PlayerTurnStart;
ここではターンの管理を列挙型を使って管理しています。
プレイヤーがターンをスタートした時のターン、
プレイヤーが攻撃する時のターン、
敵がターンをスタートした時のターン、
敵が攻撃する時のターン、
に分かれています。
最初はプレイヤーからスタートさせたいので、現在のターンをPlayerTurnStartに変更します(ここはいらないかも)。
キャラクター生成とバトルの開始
private void Start()
{
//キャラクターを生成
player = new Player(100,30,20);
enemy = new Enemy(40, 25, 14);
//バトル開始
StartCoroutine(Battle());
}
スタート関数では、キャラクター生成を最初に行っています。
プレイヤーのステータス、敵のステータスを決めてやります。
そして、バトルの開始になります。
バトルのターン変更関数
先にバトルコルーチンの説明の前にバトルターンの変更から説明していきます。
//バトルターンの変更関数
void ChangeTurn(BattleStatus battleStatus)
{
currentStatus = battleStatus;
switch (currentStatus)
{
case BattleStatus.PlayerTurnStart:
Debug.Log("PlayerTurn");
break;
case BattleStatus.PlayerAttack:
player.Attack(player, enemy);
Debug.Log("Playerの攻撃!");
Debug.Log("Playerは" + "Enemyに" + player.CalcDamage(player, enemy) + "ダメージ与えた");
break;
case BattleStatus.EnemyTurnStart:
Debug.Log("EnemyTurn");
break;
case BattleStatus.EnemyAttack:
player.Attack(enemy, player);
Debug.Log("Enemyの攻撃!");
Debug.Log("Enemyは" + "Playerに" + enemy.CalcDamage(enemy, player) + "ダメージ与えた");
break;
}
}
ここでは引数に与えられたターンを現在のターンに反映する関数です。
反映した後に、スイッチ文を使用し各ターンごとに処理を書いています。
一つ一つのターンの説明をしていきます。
PlayerTurnStart・・・ ログにPlayerTurnと表示
PlayerAttack. ・・・ プレイヤーが敵に攻撃、その情報をログに表示(与えたダメージなど)。
EnemyTurnStart・・・ ログにEnemyTurnと表示
EnemyAttack. ・・・ 敵がプレイヤーに攻撃、その情報をログに表示(与えたダメージなど)。
バトルコルーチン
バトルの処理をコルーチンを使って処理します。
//バトルコルーチン
IEnumerator Battle()
{
Debug.Log("バトル開始");
//プレイヤーか敵どちらかのHPが0だったらループから抜ける
while(true)
{
//プレイヤーターン
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.PlayerTurnStart);
//プレイヤーの攻撃
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.PlayerAttack);
if (BattleWin(enemy)) break;
//敵のターン
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.EnemyTurnStart);
//敵の攻撃
yield return new WaitForSeconds(1f);
ChangeTurn(BattleStatus.EnemyAttack);
if (BattleLose(player))
{
_lose = true;
break;
}
}
yield return new WaitForSeconds(1f);
//敵のHPが0だったら勝利
if (!_lose)
{
Debug.Log("あなたは勝ちました");
}
//プレイヤーのHPが0だったら負け
else
{
Debug.Log("あなたは負けました");
}
}
いきなり長いコードで驚きだと思いますが、一つ一つ解説していきます。
まずWhile文の中ですね、見ての通りなんですが、
それぞれのターンを1秒ごとに切り替えています。
プレイヤーの攻撃の後では、エネミーのHP0なのかを判定し、そうであるならループを抜けるようにしています。
続いて敵の攻撃の後にプレイヤーのHPが0なのかを判定し、そうであるなら負けフラグをTrueにしてループを抜けています。
プレイヤーも敵もHPが0でなければ、プレイヤーのターンに戻るって感じになります。ここの『BattleWin()』や『BattleLose()』の関数は後ほど説明します。
次に、ループが抜けた後の処理を説明していきます。
負けフラグが立っているなら、負けイベント。立っていないなら、勝利イベントを出しています。
勝利か負けかを判定
//勝利関数
bool BattleWin(CharacterStatus enemy)
{
if (enemy.hp <= 0)
{
enemy.hp = 0;
return true;
}
else return false;
}
//負け関数
bool BattleLose(CharacterStatus player)
{
if (player.hp <= 0)
{
player.hp = 0;
return true;
}
else return false;
}
ここでは引数に設定されたキャラクターのHPが0以下だったらの処理を書いています。勝利関数では敵のHPが0以下だったらHPを0にしてTrueを返す。
それ以外だったらFalseを返すにしています。
負け関数も同じで、引数がプレイヤーになっているところですね。
実際にプレイしてみよう!
これにて、ターン制システムの完成です!
実際にプレイを押してテストしてみましょう!
ログに結果が出てると思います。あとは自分でプレイヤーや敵のステータスを変えてみたり、
プレイヤーや敵はScriptableObjectを使うと管理が楽になったりするので修正したり、このバトルをUIに反映したらもっとゲームらしくなりますね!
もし機会があったら私もやってみたいと思います。
それではみなさんお疲れ様でした!