注意
本記事で掲載されている動作の実装方法及びプログラムのソースコードは最適な方法ではない可能性があります。今後不具合等が判明した場合には修正及び改良をおこなう可能性があります。
また今後自分で同機能を実装する場合の参考にする可能性もあるためソースコードだけではなく説明しつつ記事を進めていきます。
Unityによる自作ゲーム開発進捗その20になります!
今回は遊撃キャラクターを作成して戦闘レーンの変更指示を実装しました。
戦闘画面のUIはまだ作成していないので【1, 2, 3】【8, 9, 0】のキーボードボタンで指示を出せるようにしています。
既存のスクリプトを2つ新しいスクリプトを2つ更新します。
以下に動画でのコピー用にソースコードを掲載しています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour{
static class Number {
public const int Left = 0;
public const int Center = 1;
public const int Right = 2;
}
[SerializeField]
private CharacterManager m_character_script;
private GameObject m_range_object;
private RangeSystem m_range_system;
private GameObject m_direction_object;
private DirectionSystem m_direction_system;
private Vector3 m_vector3;
private Rigidbody m_rigidbody;
private Animator m_animator;
private Quaternion m_rotation = Quaternion.identity;
private const float m_turn_speed = 20.0f;
private float m_move_speed = 0.1f;
private float m_horizontal = 0.0f;
private float m_vertical = 0.0f;
private bool m_range_mode = false;
private bool m_range_cancel = false;
private bool m_direction_mode = false;
private bool m_direction_cancel = false;
enum STATE{
MOVE,
RANGE,
DIRECTION
}; STATE e_state;
private void Start(){
m_rigidbody = GetComponent<Rigidbody>();
m_animator = GetComponent<Animator>();
m_direction_object = GameObject.Find("DirectionObject");
m_direction_system = m_direction_object.GetComponent<DirectionSystem>();
m_direction_system.DirectionSetting(false, this.transform);
m_range_object = GameObject.Find("RangeObject");
m_range_system = m_range_object.GetComponent<RangeSystem>();
m_range_system.RangeSetting(false, this.transform.position, 0.0f);
}
private void Update(){
m_horizontal = Input.GetAxis("Horizontal"); // タスク2
m_vertical = Input.GetAxis("Vertical"); // タスク2
if ((Input.GetKeyDown("r")) && (!m_range_cancel) && (!m_direction_mode)) {
m_range_mode = !m_range_mode;
if (m_range_mode) {
m_range_object.SetActive(m_range_mode);
m_range_system.RangeSetting(m_range_mode, this.transform.position, 10.0f); // タスク3
e_state = STATE.RANGE;
m_animator.SetBool("animation_move", false);
} else {
m_range_cancel = true;
}
}
if ((Input.GetKeyDown("t")) && (m_range_mode)) m_range_system.TowerCreate(); // タスク4
if ((Input.GetKeyDown("f")) && (!m_direction_cancel) && (!m_range_mode)) {
m_direction_mode = !m_direction_mode;
if (m_direction_mode) {
m_direction_object.SetActive(m_direction_mode);
m_direction_system.DirectionSetting(m_direction_mode, this.transform);
e_state = STATE.DIRECTION;
m_animator.SetBool("animation_move", false);
} else {
m_direction_cancel = true;
}
}
if ((Input.GetKeyDown("g")) && (m_direction_mode)) m_direction_system.AttackStart(); // タスク5
if (Input.GetKeyDown("1")) m_character_script.AttackerCommand(1, Number.Left); // タスク6
if (Input.GetKeyDown("2")) m_character_script.AttackerCommand(1, Number.Center); // タスク6
if (Input.GetKeyDown("3")) m_character_script.AttackerCommand(1, Number.Right); // タスク6
if (Input.GetKeyDown("8")) m_character_script.AttackerCommand(2, Number.Left); // タスク6
if (Input.GetKeyDown("9")) m_character_script.AttackerCommand(2, Number.Center); // タスク6
if (Input.GetKeyDown("0")) m_character_script.AttackerCommand(2, Number.Right); // タスク6
}
private void FixedUpdate(){
switch(e_state){
case STATE.MOVE:
m_vector3.Set(m_horizontal, 0f, m_vertical);
m_vector3.Normalize();
bool l_horizontal_input = !Mathf.Approximately (m_horizontal, 0f);
bool l_vertical_input = !Mathf.Approximately (m_vertical, 0f);
bool l_move = l_horizontal_input || l_vertical_input;
m_animator.SetBool ("animation_move", l_move);
Vector3 l_rotation = Vector3.RotateTowards (transform.forward, m_vector3, m_turn_speed * Time.deltaTime, 0f);
m_rotation = Quaternion.LookRotation (l_rotation);
m_rigidbody.MovePosition (m_rigidbody.position + m_vector3 * m_move_speed);
m_rigidbody.MoveRotation (m_rotation);
break;
case STATE.RANGE:
if ((m_range_cancel) && (m_range_system.RangeSetting(false, this.transform.position, 0.0f))) {
m_range_cancel = false;
e_state = STATE.MOVE;
}
break;
case STATE.DIRECTION:
this.transform.rotation = m_direction_object.transform.rotation;
if ((m_direction_cancel) && (m_direction_system.DirectionSetting(false, this.transform))) {
m_direction_cancel = false;
e_state = STATE.MOVE;
}
break;
}
m_horizontal = m_vertical = 0.0f;
}
public void PlayerSpeedSet(float a_speed){
m_move_speed = a_speed; // タスク1
}
}
/*
[規則]
p_ 外部アクセス
m_ メンバー変数
l_ ローカル変数
a_ 引数
e_ 列挙型
[説明]
[バージョン]
2020-12-30 キーボードによるプレイヤーキャラの移動実装(回転無し)
2021-01-09 プレイヤーキャラのモデルとアニメーション反映及び回転動作
2021-04-23 座標指定モードへの切り替えとタワーの設置
2021-04-30 攻撃向き指定モードへの切り替えと臨時エフェクトの発生
2021-05-19 遊撃隊へのライン変更指示追加
[タスク]
タスク1 上限値の制限が必要
タスク2 コントローラーによる動作も追加する
タスク3 射程を制限する臨時半径
タスク4 タワーの種類を渡す必要がある
タスク5 攻撃の種類やステータスを渡すようにする
タスク6 対応ボタンは臨時
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterManager : MonoBehaviour {
static class Number {
public const int Left = 0;
public const int Center = 1;
public const int Right = 2;
}
[SerializeField]
private GameObject m_mob_prefab;
[SerializeField]
private HeroPlayer m_hero_script;
[SerializeField]
private MagePlayer m_mage_script;
[SerializeField]
private DragoonPlayer m_dragoon_script;
[SerializeField]
private KnightPlayer m_knight_script;
[SerializeField]
private BishopPlayer m_bishop_script;
[SerializeField]
private PopSystem[] m_pop_point = new PopSystem[3];
private List<GameObject> m_mob_prefab_list = new List<GameObject>();
private List<AssultEnemy> m_mob_prefab_script = new List<AssultEnemy>();
private GameObject[] m_left_base = new GameObject[5];
private GameObject[] m_center_base = new GameObject[5];
private GameObject[] m_right_base = new GameObject[5];
private float m_time_count = 0.0f;
private int m_left_state = 2;
private int m_center_state = 2;
private int m_right_state = 2;
private int m_attacker1_position = Number.Center;
private int m_attacker2_position = Number.Center;
private short m_mob_count = 0;
public void AttackerCommand(int a_attacker, int a_position) {
switch (a_position) {
case Number.Left:
if (a_attacker == 1) {
m_attacker1_position = Number.Left;
m_knight_script.LineChange("Left", m_left_state); // タスク3
} else {
m_attacker2_position = Number.Left;
m_bishop_script.LineChange("Left", m_left_state); // タスク3
}
break;
case Number.Center:
if (a_attacker == 1) {
m_attacker1_position = Number.Center;
m_knight_script.LineChange("Center", m_center_state); // タスク3
} else {
m_attacker2_position = Number.Center;
m_bishop_script.LineChange("Center", m_center_state); // タスク3
}
break;
case Number.Right:
if (a_attacker == 1) {
m_attacker1_position = Number.Right;
m_knight_script.LineChange("Right", m_right_state); // タスク3
} else {
m_attacker2_position = Number.Right;
m_bishop_script.LineChange("Right", m_right_state); // タスク3
}
break;
}
}
private void Start() {
m_mob_count = 30;
string l_line = "Left";
for (int l_loop = 0; l_loop < m_mob_count; l_loop += 1) {
if (l_loop == m_mob_count / 3) l_line = "Center";
if (l_loop == m_mob_count - (m_mob_count / 3)) l_line = "Right";
var l_instance = Instantiate(m_mob_prefab, new Vector3(75.0f, 0.0f, 65.0f), Quaternion.Euler(0, 0, 0));
l_instance.SetActive(false);
m_mob_prefab_list.Add(l_instance);
m_mob_prefab_script.Add(l_instance.GetComponent<AssultEnemy>());
m_mob_prefab_script[l_loop].Initialize(l_line);
}
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
// タスク2
m_left_base[l_loop] = GameObject.Find(string.Format("LeftBase{0}", l_loop));
m_center_base[l_loop] = GameObject.Find(string.Format("CenterBase{0}", l_loop));
m_right_base[l_loop] = GameObject.Find(string.Format("RightBase{0}", l_loop));
}
m_hero_script.Initialize("Left"); // タスク3
m_mage_script.Initialize("Center"); // タスク3
m_dragoon_script.Initialize("Right"); // タスク3
m_knight_script.Initialize("Center", 1); // タスク3
m_bishop_script.Initialize("Center", 2); // タスク3
}
private void Update() {
m_time_count += Time.deltaTime;
if (m_time_count > 1.0f) {
BaseCheck();
ActiveObject();
}
}
private void BaseCheck() {
if (!m_left_base[m_left_state].activeSelf) {
m_left_state -= 1;
if (m_left_state < 0) m_left_state = 0;
switch (m_left_state) {
case 1:
m_pop_point[0].MoveObject(new Vector3(50.0f, 0.5f, 90.0f));
break;
case 0:
m_pop_point[0].MoveObject(new Vector3(50.0f, 0.5f, 65.0f));
break;
}
for (int l_loop = 0; l_loop < m_mob_count / 3; l_loop += 1) {
m_mob_prefab_script[l_loop].BaseChange(m_left_state);
}
m_hero_script.BaseChange(m_left_state); // タスク3
if (m_attacker1_position == Number.Left) m_knight_script.BaseChange(m_left_state); // タスク3
if (m_attacker2_position == Number.Left) m_bishop_script.BaseChange(m_left_state); // タスク3
}
if (!m_center_base[m_center_state].activeSelf) {
m_center_state -= 1;
if (m_center_state < 0) m_center_state = 0;
switch (m_center_state) {
case 1:
m_pop_point[1].MoveObject(new Vector3(75.0f, 0.5f, 90.0f));
break;
case 0:
m_pop_point[1].MoveObject(new Vector3(75.0f, 0.5f, 65.0f));
break;
}
for (int l_loop = m_mob_count / 3; l_loop < m_mob_count - (m_mob_count / 3); l_loop += 1) {
m_mob_prefab_script[l_loop].BaseChange(m_center_state);
}
m_mage_script.BaseChange(m_center_state); // タスク3
if (m_attacker1_position == Number.Center) m_knight_script.BaseChange(m_center_state); // タスク3
if (m_attacker2_position == Number.Center) m_bishop_script.BaseChange(m_center_state); // タスク3
}
if (!m_right_base[m_right_state].activeSelf) {
m_right_state -= 1;
if (m_right_state < 0) m_right_state = 0;
switch (m_right_state) {
case 1:
m_pop_point[2].MoveObject(new Vector3(110.0f, 0.5f, 90.0f));
break;
case 0:
m_pop_point[2].MoveObject(new Vector3(110.0f, 0.5f, 65.0f));
break;
}
for (int l_loop = m_mob_count - (m_mob_count / 3); l_loop < m_mob_count; l_loop += 1) {
m_mob_prefab_script[l_loop].BaseChange(m_right_state);
}
m_dragoon_script.BaseChange(m_right_state); // タスク3
if (m_attacker1_position == Number.Right) m_knight_script.BaseChange(m_right_state); // タスク3
if (m_attacker2_position == Number.Right) m_bishop_script.BaseChange(m_right_state); // タスク3
}
}
private void ActiveObject() {
m_time_count = 0.0f;
for (int l_loop = 0; l_loop < m_mob_count / 3; l_loop += 1) {
if (m_mob_prefab_list[l_loop].activeSelf == false) {
m_mob_prefab_list[l_loop].SetActive(true);
m_mob_prefab_script[l_loop].MobState(true);
break;
}
}
for (int l_loop = m_mob_count / 3; l_loop < m_mob_count - (m_mob_count / 3); l_loop += 1) {
if (m_mob_prefab_list[l_loop].activeSelf == false) {
m_mob_prefab_list[l_loop].SetActive(true);
m_mob_prefab_script[l_loop].MobState(true);
break;
}
}
for (int l_loop = m_mob_count - (m_mob_count / 3); l_loop < m_mob_count; l_loop += 1) {
if (m_mob_prefab_list[l_loop].activeSelf == false) {
m_mob_prefab_list[l_loop].SetActive(true);
m_mob_prefab_script[l_loop].MobState(true);
break;
}
}
}
}
/*
[規則]
p_ 外部アクセス
m_ メンバー変数
l_ ローカル変数
a_ 引数
[説明]
[バージョン]
2020-12-12 指定数生成及びプーリング呼び出し
2021-03-12 管理拠点を配列化し拠点管理を行いモブに伝える
2021-03-17 センターライン指揮官キャラの生成と名前変更(旧CenterMobManagerEnemy)
2021-03-25 戦闘ラインを1から3ラインに変更
2021-04-05 敵キャラクター生成位置を真ん中へ(左下では生成時にナビエリアが遠くエラーが出る)
2021-05-19 プレイヤーからのライン変更指示を遊撃キャラクターに反映させる
[タスク]
(済)タスク1 プレイヤーキャラのステータスを反映させる
(済)タスク2 臨時防衛拠点数
タスク3 メニュー画面から各ラインのキャラクターを受け取るようにする
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class KnightPlayer : MonoBehaviour {
[SerializeField]
private GameObject m_defense_player;
private List<GameObject> m_defense_prefab_list = new List<GameObject>();
private List<DefensePlayer> m_defense_prefab_script = new List<DefensePlayer>();
private Transform[] m_serch_object = new Transform[5];
private NavMeshAgent m_navmesh_agent;
private Animator m_animator;
private int m_base_state = 2;
private int m_attacker = 0;
enum STATE {
SET,
SERCH,
}; STATE e_state;
public void BaseChange(int a_number) {
m_base_state = a_number;
if(m_attacker == 0) {
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
m_defense_prefab_script[l_loop].BaseChange(m_base_state);
}
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
e_state = STATE.SET;
}
public void LineChange(string a_line, int a_number) {
m_base_state = a_number;
for (int l_loop = 0; l_loop <= a_number; l_loop += 1) {
GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPositionA{0}", m_attacker));
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
e_state = STATE.SET;
}
public void Initialize(string a_line, int a_attacker) {
m_navmesh_agent = GetComponent<NavMeshAgent>();
m_animator = GetComponent<Animator>();
m_attacker = a_attacker;
if (m_attacker == 0) {
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
var l_instance = Instantiate(m_defense_player, this.transform);
m_defense_prefab_list.Add(l_instance);
m_defense_prefab_script.Add(l_instance.GetComponent<DefensePlayer>());
m_defense_prefab_script[l_loop].Initialize(a_line, l_loop);
}
}
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
if (m_attacker == 0) {
m_serch_object[l_loop] = l_object.transform.Find("SetPositionL");
} else {
m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPositionA{0}", m_attacker));
}
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
}
private void FixedUpdate() {
switch (e_state) {
case STATE.SET:
if ((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude < 1.0f) {
m_animator.SetBool("animation_move", false);
e_state = STATE.SERCH;
}
break;
case STATE.SERCH:
break;
}
}
}
/*
[規則]
p_ 外部アクセス
m_ メンバー変数
l_ ローカル変数
a_ 引数
[説明]
[バージョン]
2021-05-19 指揮官基本動作に加えて指示によるライン変更
[タスク]
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class BishopPlayer : MonoBehaviour {
[SerializeField]
private GameObject m_defense_player;
private List<GameObject> m_defense_prefab_list = new List<GameObject>();
private List<DefensePlayer> m_defense_prefab_script = new List<DefensePlayer>();
private Transform[] m_serch_object = new Transform[5];
private NavMeshAgent m_navmesh_agent;
private Animator m_animator;
private int m_base_state = 2;
private int m_attacker = 0;
enum STATE {
SET,
SERCH,
}; STATE e_state;
public void BaseChange(int a_number) {
m_base_state = a_number;
if (m_attacker == 0) {
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
m_defense_prefab_script[l_loop].BaseChange(m_base_state);
}
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
e_state = STATE.SET;
}
public void LineChange(string a_line, int a_number) {
m_base_state = a_number;
for (int l_loop = 0; l_loop <= a_number; l_loop += 1) {
GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPositionA{0}", m_attacker));
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
e_state = STATE.SET;
}
public void Initialize(string a_line, int a_attacker) {
m_navmesh_agent = GetComponent<NavMeshAgent>();
m_animator = GetComponent<Animator>();
m_attacker = a_attacker;
if (m_attacker == 0) {
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
var l_instance = Instantiate(m_defense_player, this.transform);
m_defense_prefab_list.Add(l_instance);
m_defense_prefab_script.Add(l_instance.GetComponent<DefensePlayer>());
m_defense_prefab_script[l_loop].Initialize(a_line, l_loop);
}
}
for (int l_loop = 0; l_loop < 3; l_loop += 1) {
GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
if (m_attacker == 0) {
m_serch_object[l_loop] = l_object.transform.Find("SetPositionL");
} else {
m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPositionA{0}", m_attacker));
}
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
}
private void FixedUpdate() {
switch (e_state) {
case STATE.SET:
if ((this.transform.position - m_serch_object[m_base_state].transform.position).sqrMagnitude < 1.0f) {
m_animator.SetBool("animation_move", false);
e_state = STATE.SERCH;
}
break;
case STATE.SERCH:
break;
}
}
}
/*
[規則]
p_ 外部アクセス
m_ メンバー変数
l_ ローカル変数
a_ 引数
[説明]
[バージョン]
2021-05-19 指揮官基本動作に加えて指示によるライン変更
[タスク]
*/
現在は【PlayerController】から入力をおこない【CharacterManager】経由で【遊撃部隊のキャラクター】へ指示が伝わるようにしています。
このあたりのスマートな実装方法が存在しないか模索中です🐷
if (Input.GetKeyDown("1")) m_character_script.AttackerCommand(1, Number.Left); // タスク6
if (Input.GetKeyDown("2")) m_character_script.AttackerCommand(1, Number.Center); // タスク6
if (Input.GetKeyDown("3")) m_character_script.AttackerCommand(1, Number.Right); // タスク6
if (Input.GetKeyDown("8")) m_character_script.AttackerCommand(2, Number.Left); // タスク6
if (Input.GetKeyDown("9")) m_character_script.AttackerCommand(2, Number.Center); // タスク6
if (Input.GetKeyDown("0")) m_character_script.AttackerCommand(2, Number.Right); // タスク6
【PlayerController】からの入力により【CharacterManager】の関数である【AttackerCommand】を呼び出しています。
public void AttackerCommand(int a_attacker, int a_position) {
switch (a_position) {
case Number.Left:
if (a_attacker == 1) {
m_attacker1_position = Number.Left;
m_knight_script.LineChange("Left", m_left_state); // タスク3
} else {
m_attacker2_position = Number.Left;
m_bishop_script.LineChange("Left", m_left_state); // タスク3
}
break;
case Number.Center:
if (a_attacker == 1) {
m_attacker1_position = Number.Center;
m_knight_script.LineChange("Center", m_center_state); // タスク3
} else {
m_attacker2_position = Number.Center;
m_bishop_script.LineChange("Center", m_center_state); // タスク3
}
break;
case Number.Right:
if (a_attacker == 1) {
m_attacker1_position = Number.Right;
m_knight_script.LineChange("Right", m_right_state); // タスク3
} else {
m_attacker2_position = Number.Right;
m_bishop_script.LineChange("Right", m_right_state); // タスク3
}
break;
}
}
【AttackerCommand】では引数1の数値により遊撃部隊1か2を指定するようにして引数2で守らせるレーンの数値を受け取り遊撃部隊に付いている関数【LineChange】を呼び出すようにしています。
public void LineChange(string a_line, int a_number) {
m_base_state = a_number;
for (int l_loop = 0; l_loop <= a_number; l_loop += 1) {
GameObject l_object = GameObject.Find(string.Format("{0}Base{1}", a_line, l_loop));
m_serch_object[l_loop] = l_object.transform.Find(string.Format("SetPositionA{0}", m_attacker));
}
m_navmesh_agent.SetDestination(m_serch_object[m_base_state].transform.position);
m_animator.SetBool("animation_move", true);
e_state = STATE.SET;
}
【LineChange】関数では守るべき拠点が入っている配列内容を書き換えています。
書き換えた後に変更先の一番前線の拠点へ向かっていくようにしています。
今回作成した【Knight】と【Bishop】については現在各レーンを防衛している指揮官キャラクターと同様のプログラムに加えて初期化関数でレーン防衛キャラクターか遊撃キャラクターか指定可能なシステムにしています。
次回以降に他の指揮官キャラクターにも反映してキャラクターの入れ替えに対応する予定です。