ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Unity - 2D:敵の作り方, 残機の処理 -

初めに

 敵を作っていきます。そのなかで、敵を動かす、敵に当たった時に残機を減らすなどの処理をします。


敵を作る上で必要な処理を確認する

 敵を作るうえで、どのような動きをし、どのような処理が必要か初めに考えておきましょう。
 今回は、次のような挙動をする敵を作っていきます。

  • 敵は同じ場所を徘徊する
  • 画面内に入ると動き出す
  • プレイヤーにぶつかると、残機を減らす

 本記事では、次のドット絵を動かしていきます。


敵に当たり判定をつける

 敵に当たり判定をつけるために、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を使っています。

実行結果

最後に

 敵に当たった時にプレイヤーを点滅させるなど追加する場合は、敵に当たった時に関数として呼び出せばできます。また、敵の動きも今回は簡単なものですが、工夫次第でいろいろな動きをさせることができます。