初めに
敵を作っていきます。そのなかで、敵を動かす、敵に当たった時に残機を減らすなどの処理をします。
敵を作る上で必要な処理を確認する
敵を作るうえで、どのような動きをし、どのような処理が必要か初めに考えておきましょう。
今回は、次のような挙動をする敵を作っていきます。
- 敵は同じ場所を徘徊する
- 画面内に入ると動き出す
- プレイヤーにぶつかると、残機を減らす
本記事では、次のドット絵を動かしていきます。
敵に当たり判定をつける
敵に当たり判定をつけるために、Collider2DとRigidbody2Dをアタッチしましょう。これらについての説明は下の記事をご覧ください。
fineworks-fine.hatenablog.com
fineworks-fine.hatenablog.com
Colliderは敵のサイズに合わせておきましょう。
画面内に入っているか判定する
オブジェクトが画面内に表示されているかどうかを判定するために、RendererコンポーネントのisVisibleを使います。
Renderer.isVisible | 挙動 |
---|---|
オブジェクトが画面内にある | trueを返す |
オブジェクトが画面内にない | falseを返す |
敵につけるスクリプトを書いていきます。次のスクリプトを敵にアタッチしましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyMove : MonoBehaviour { private SpriteRenderer sr = null; void Start() { //SpriteRendererのコンポーネントを取得 sr = GetComponent<SpriteRenderer>(); } void Update() { if (sr.isVisible) //見えているかを判定する { Debug.Log("画面に見えている"); } }
実行してみると、ちゃんと画面に敵が映ったときに、ログが現れていることがわかります。
敵の動きを作る
敵の動きを作っていきます。先ほど、敵につけたスクリプトに追記していきます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyMove : MonoBehaviour { //右を向いていることを判定 private bool isRight = false; //何秒ごとに向きを変えるか public float turnTime = 5.0f; //動くスピード public float speed = 0.02f; //EnemyTurnを呼び出し始めたか判定する private bool beginningTurn = false; private SpriteRenderer sr = null; void Start() { sr = GetComponent<SpriteRenderer>(); } void Update() { if (sr.isVisible) { Debug.Log("画面に見えている"); if(beginningTurn == false) { beginningTurn = true; InvokeRepeating("EnemyTurn", turnTime, turnTime); } if(isRight) //右を向いていたら右に動く { this.transform.position += new Vector3(speed, 0, 0); } else //右を向いていなければ左に動く { this.transform.position -= new Vector3(speed, 0, 0); } } } //向きを変える関数 void EnemyTurn() { Debug.Log("向きを変えるね"); //右を向くときはisRightをtrueに、左を向くときはfalseに if(isRight == false) { isRight = true; } else { isRight = false; } //localScaleのx成分に-1を掛けて向きを変える Vector3 localScale = this.transform.localScale; localScale.x *= -1; this.transform.localScale = localScale; } }
実行すると次のようになります。
スクリプトで追記した部分について、2か所ほどピックアップして説明します。
publicとprivateの使い分け
変数を定義するとき、publicで定義しているものとprivateで定義しているものがあります。
//右を向いていることを判定 private bool isRight = false; //何秒ごとに向きを変えるか public float turnTime = 5.0f; //動くスピード public float speed = 0.02f; //EnemyTurnを呼び出し始めたか判定する private bool beginningTurn = false;
他のスクリプトから呼び出す必要があるものは、publicで定義する必要がありますが、今回のturnTimeとspeedについては、そのためにpublicにしているわけではありません。publicにし、Inspectorから数値を変更できるようにしておくことで、実行したあとすぐに変更できるようにしています。
逆に、isRightのように変更する必要のないものは、privateにしておきましょう。
数秒ごとに向きを変える
数秒ごとに向きを変えるために、向きを変える関数をInvokeRepeatingを使い、数秒ごとに呼び出しています。
//x秒後に呼び出し、そこからy秒ごとに繰り返し呼び出す InvokeRepeating("呼び出したい関数", x, y);
beginningTurnというbool変数を使い、一回しかInvokeRepeatingが動作しないようにしています。そうしなければ、毎フレームごとにInvokeRepeatingを呼び出し、毎フレームごとに向きを変えてしまいます。
if(beginningTurn == false) { beginningTurn = true; InvokeRepeating("EnemyTurn", turnTime, turnTime); }
敵のz軸回転を止める
このままでは、敵がなにかに当たった時に、敵も回転して動いてしまいます。そこで、敵のRigidbody2DにあるFreeze Rotationにチェックをいれましょう。
プレイヤーに衝突したら残機を減らす、プレイヤーは止める
プレイヤーのスクリプト、ゲームマネージャーのスクリプトを次のように変更します。
PlayerScript
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerScript : MonoBehaviour { //GameManager, 追加部分 private GameObject _gameManagerObject; GameManager GMScript; //プレイヤーがdownしたかどうか, 追加部分 private bool down = false; //Animatorを入れる変数 private Animator animator; //Rigidbory2D private Rigidbody2D rb; public float JumpForce = 200f; //接地判定 private bool isGround = false; void Start() { //GameManagerを取得, 追加部分 _gameManagerObject = GameObject.Find("GameManager"); GMScript = _gameManagerObject.GetComponent<GameManager>(); //Animatorを取得 animator = GetComponent<Animator>(); //Rigidbody2Dを取得 rb = this.GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { //downしてないときだけキー入力を受け付ける, 追加部分 if(down == false) { if (Input.GetAxis("Horizontal") > 0) //HorizontalのPositive Buttonが押されたら { this.transform.position += new Vector3(0.1f, 0.0f, 0.0f); //右を向くためにScaleのx成分を変更 this.transform.localScale = new Vector3(-0.5f, 0.5f, 0.5f); //AnimatorパラメーターのWalkをtrueに animator.SetBool("Walk", true); } else if(Input.GetAxis("Horizontal") < 0) //HorizontalのNegative Buttonが押されたら { this.transform.position += new Vector3(-0.1f, 0.0f, 0.0f); //左を向くためにScaleのx成分を変更 this.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); //AnimatorパラメーターのWalkをtrueに animator.SetBool("Walk", true); } else { //入力されていないときはWalkをfalseに animator.SetBool("Walk", false); } if(Input.GetAxis("Jump") > 0) //JumpのPositive Buttonが押されたら { if(isGround) { Vector2 force = new Vector2(0, JumpForce); rb.AddForce(force); //AnimatorパラメーターのJumpをtrueに animator.SetBool("Jump", true); } } else { //AnimatorパラメーターのJumpをfalseに animator.SetBool("Jump", false); } } else { //AnimatorパラメーターのJumpをfalseに animator.SetBool("Jump", false); } } //Enemyに触れたら呼ばれる関数 void OnCollisionEnter2D(Collision2D collision) { if(collision.collider.CompareTag("Enemy")) { GMScript.PlayerFailed(); down = true; } } void OnCollisionStay2D(Collision2D collision) { if(collision.collider.CompareTag("Ground")) { isGround = true; } } void OnCollisionExit2D(Collision2D collision) { if(collision.collider.CompareTag("Ground")) { isGround = false; } } }
GameManager
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; //Textを使うため追加 using UnityEngine.SceneManagement; //Sceneを切り替えるために追加 public class GameManager : SingletonMonoBehaviour<GameManager> { //現在のステージ public int stageNum; //StageClaer時に生成するCanvas public GameObject StageClearCanvas; //残機 private int remain = 2; public void Awake() { if (this != Instance) { Destroy(gameObject); return; } DontDestroyOnLoad(gameObject); } //以下に処理を書く void Start() { Debug.Log("始まったよ"); } //ステージクリア時に呼び出される関数 public void StageClaer() { //表示されるTextの取得 Text stageclearText = StageClearCanvas.transform.Find("Text").GetComponent<Text>(); //表示されるTextを入力 stageclearText.text = "Stage" + stageNum + "\nClear"; //Canvasごとクリア時のTextを生成する GameObject _clearcanvas = Instantiate(StageClearCanvas, new Vector3(0, 0, 0), Quaternion.identity); //stageNumを1増やす stageNum++; Invoke(nameof(ChangeScene), 2.0f); } //残機が減ったときに呼び出される関数, 追加部分 public void PlayerFailed() { remain--; if(remain < 0) { //ゲームオーバーの処理を書く //Title画面を作ったらに戻らせるなど } //1秒後にシーンをロードする Invoke(nameof(ChangeScene), 1.0f); } public void ChangeScene() { SceneManager.LoadScene("Stage" + stageNum); } }
プレイヤーのスクリプトでは、敵に触れたらゲームマネージャーに書かれている残機を減らす関数を呼び出しています。また、downしたかbool変数を使って判定しています。
ゲームマネージャーには、"PlayerFailed" という残機を減らす関数を新たに作っています。残機がマイナスになったら、タイトル画面に戻るなどの処理を記述する必要がありますが、タイトル画面を作るまで保留にしておきます。また、いきなりシーンをロードしなおすと違和感があるため、Invokeを使っています。
実行結果
最後に
敵に当たった時にプレイヤーを点滅させるなど追加する場合は、敵に当たった時に関数として呼び出せばできます。また、敵の動きも今回は簡単なものですが、工夫次第でいろいろな動きをさせることができます。