頑張ってゲーム作ることにする #4.5 プレイヤーの死亡

「敵キャラに当たった場合に死亡する」を実装したいと思います。

単純に敵キャラにEnemyというTagを設定し、Collisonした物体についているTagがEnemyだった場合にGameOverとします。

void GameOver()
    {
        CircleCollider2D circleCollider = GetComponent<CircleCollider2D>();
        Destroy(circleCollider);


        Sequence sequence = DOTween.Sequence();
        sequence.Append(transform.DOLocalMoveY(5.0f, 0.3f));
        sequence.Append(transform.DOLocalMoveY(-20.0f, 2f));

        sequence.Play();
        Destroy(this.gameObject,2.3f);
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Enemy")
        {
            GameOver();
        }
    }

DOTweenを使用して死亡した際には上に上がって下に落ちていくというマリオみたいな死に方をするようにしました。

次回はゴールの作成をします。

頑張ってゲーム作ることにする #4 敵キャラとプレイヤーの死亡

まずは敵キャラの画像を作成します。
プレイヤーの目を白に、体全体を黒塗りにしただけです。
f:id:Neuman-I:20210330000641p:plain

敵キャラを動くようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    private Rigidbody2D rbody;

    public float moveSpeed = 1;

    public LayerMask blockLayer; // ブロックレイヤー

    public enum MOVE_DIR
    {
        LEFT,
        RIGHT,
    }

    private MOVE_DIR moveDirection = MOVE_DIR.RIGHT;

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void FixedUpdate()
    {
        bool isBlock; // 進行方向にブロックがあるかどうか

        switch (moveDirection)
        {
            case MOVE_DIR.RIGHT:
                rbody.velocity = new Vector2(moveSpeed, rbody.velocity.y);
                transform.localScale = new Vector2(1, 1);

                isBlock = Physics2D.Linecast(
                    new Vector2(transform.position.x, transform.position.y),
                    new Vector2(transform.position.x + 0.65f, transform.position.y),
                    blockLayer);

                if (isBlock)
                {
                    Debug.Log("Block!!!");
                    moveDirection = MOVE_DIR.LEFT;
                }
                    
                break;

            case MOVE_DIR.LEFT:
                rbody.velocity = new Vector2(moveSpeed*-1, rbody.velocity.y);
                transform.localScale = new Vector2(-1, 1);

                isBlock = Physics2D.Linecast(
                    new Vector2(transform.position.x, transform.position.y ),
                    new Vector2(transform.position.x - 0.65f, transform.position.y ),
                    blockLayer);

                if (isBlock)
                {
                    moveDirection = MOVE_DIR.RIGHT;
                }

                break;
        }
        Debug.DrawLine(
            new Vector3(transform.position.x, transform.position.y, 0),
            new Vector3(transform.position.x + 0.75f, transform.position.y, 0),
            Color.red
            ) ;

    }
}

こちらになります。
列挙型のMOVE_DIRによって現在のオブジェクトの動く方向を決定しています。
例えばmoveDirectionにMOVEDIR.LEFTが入っている状態にあるときには左向きに動きます。逆もまた然りです。

敵キャラは自動で動きます。そのため壁にぶつかったときに壁に進み続けてしまうことになります。
それを解消するためにphysics2D.Linecastというメソッドを使用します。
これは第一引数の座標から第二引数の座標に向かって衝突判定を行う見えない線を発生させます。
そして衝突したLayerが第三引数に指定されているものの場合Trueを返すようになっています。
(厳密には違うかもしれませんがなんとなくのニュアンスはあっていると思います。)

f:id:Neuman-I:20210330004023p:plain
赤線がDebug.DrawLineメソッドによって表示させているLineCastの線になります。GameSceneには表示されていません。

この線にblockLayer(第三引数に設定しているLayer)が接触すると反転して動くようなスクリプトにしています。

おいおい落下しないような挙動をする敵キャラなんかも実装したいと思います

プレイヤーの死亡は次回になります。

頑張ってゲーム作ることにする #3.5 キャラクターのジャンプを実装する2

前回なぜかジャンプボタン(スペース)が反応したりしなかったりしました。
それを何とか改良してみたところ、無事毎回確実に反応するようになりました。

反応しなかったのは自分がスペースを押したときにうまくフレームがかみ合っていないからだと考えました。

特に

FixedUpdate()

Update()

の違いについて理解する必要があると思われます。

こちらが改良したコードになります。

 void Update()
    {
        if (Input.GetKeyDown("space") && groundCheck.IsGround())
        {
            goJump = true;
        }
    }

    void FixedUpdate()
    {
        float x = Input.GetAxis("Horizontal");
        isGround = groundCheck.IsGround();

        if (x > 0)// move right
        {
            transform.localScale = new Vector2(1, 1);
            moveSpeed = MOVE_SPEED;
            Debug.Log("1");
        }
        else if (x < 0) // move left
        {
            transform.localScale = new Vector2(-1, 1);
            moveSpeed = -1 * MOVE_SPEED;
            Debug.Log("-1");
        }
        else
        {
            moveSpeed = 0;
            Debug.Log("0");
        }

        rb.velocity = new Vector2(moveSpeed, rb.velocity.y);

        if (goJump)
        {
            rb.AddForce(Vector2.up * JUMP_POWER);
            goJump = false;
        }

    }

着目してほしいところが、

if (Input.GetKeyDown("space") && groundCheck.IsGround())
        {
            goJump = true;
        }

この条件分岐の部分をUpdate関数の中に入れているところです。

物理演算というのは時間の概念が大事(移動距離の計算などがあるので)なので、フレームという概念の中にいません。フレームはフレームレートによって時間が変動してしまうので当てにならないのです。

その為、UpdateとFixedUpdateは呼ばれ方が違います。ここにちょっと問題があります。

Updateは入力と画面の更新に関係し、FixedUpdateは物理的な移動に関係がある事に注目してください。

物理演算というのは時間の概念が大事(移動距離の計算などがあるので)なので、フレームという概念の中にいません。フレームはフレームレートによって時間が変動してしまうので当てにならないのです。

その為、UpdateとFixedUpdateは呼ばれ方が違います。ここにちょっと問題があります。

Updateは入力と画面の更新に関係し、FixedUpdateは物理的な移動に関係がある事に注目してください。

https://dkrevel.com/makegame-beginner/make-2d-action-ground/

URLにある説明がまさに躓いたポイントでした。

次回は敵キャラを作るのとプレイヤーの死亡を実装していこうと思います。

頑張ってゲーム作ることにする #3キャラクターのジャンプを実装する

前回は左右の移動のみを実装したので、今回はサクッとジャンプ動作を実装したいと思います。
→地面に設置しているかどうかを判別して、空中ジャンプはとりあえずなしにしようと思うので、あまりサクッと実装はできなさそう

とりあえず、タイルマップにgroundという名前のタグを新たに付け地面だと認識できるようにします。
f:id:Neuman-I:20210325181249p:plain

つぎに、地面に設置しているかどうかを確かめるためのboxCollider2DをPlayerの足元に子オブジェクトとして作成しました。
f:id:Neuman-I:20210325181714p:plain

このGroundCheckオブジェクトにGroundCheckという名前のスクリプトをアタッチします。
このスクリプトには地面に設置している状態であるかどうかが分かるようにします。

計画では、PlayerオブジェクトはこのGroundCheckオブジェクト内のbool型の設置確認変数をジャンプボタンが押されるたびに見に行きます。
そして、変数が設置を表している場合のみジャンプすることにします。

groudcheck

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GroundCheck : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    //if canJump is True, you can Jump!!
    public bool canJump = false;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag=="Ground")
        {
            canJump = true;
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.tag=="Ground")
        {
            canJump = false;
        }
    }

    private void OnTriggerStay2D(Collider2D collision)
    {
        if (collision.tag == "Ground")
        {
            canJump = true;
        }
    }

    public bool IsGround()
    {
        return canJump;
    }
}
>|| 

PlayerController
>|cs|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // Player move speed
    const float MOVE_SPEED = 3;

    // Player jump power
    public float JUMP_POWER = 100;

    float moveSpeed;
    // Rigidbody2d setting
    private Rigidbody2D rb;

    //groundCheck
    public GroundCheck groundCheck;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void FixedUpdate()
    {
        float x = Input.GetAxis("Horizontal");

        if (x > 0)// move right
        {
            transform.localScale = new Vector2(1, 1);
            moveSpeed = MOVE_SPEED;
            Debug.Log("1");
        }
        else if (x < 0) // move left
        {
            transform.localScale = new Vector2(-1, 1);
            moveSpeed = -1 * MOVE_SPEED;
            Debug.Log("-1");
        }
        else 
        {
            moveSpeed = 0;
            Debug.Log("0");
        }

        rb.velocity = new Vector2(moveSpeed, rb.velocity.y);

        if (Input.GetKeyDown("space") && groundCheck.canJump)
        {
            rb.velocity = new Vector2(rb.velocity.x, JUMP_POWER);
        }
    }
}
>||

ジャンプはするし、2段ジャンプもしない
でもジャンプが反応するときと反応しないときがある...

頑張ってゲーム作ることにする #2 キャラクターが動くようにする

今回はキャラクターが動くようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // Player move speed
    const float MOVE_SPEED = 3;
    float moveSpeed;
    // Rigidbody2d setting
    private Rigidbody2D rb;


    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void FixedUpdate()
    {
        float x = Input.GetAxis("Horizontal");

        if (x > 0)// move right
        {
            transform.localScale = new Vector2(1, 1);
            moveSpeed = MOVE_SPEED;
            Debug.Log("1");
        }
        else if (x < 0) // move left
        {
            transform.localScale = new Vector2(-1, 1);
            moveSpeed = -1 * MOVE_SPEED;
            Debug.Log("-1");
        }
        else 
        {
            moveSpeed = 0;
            Debug.Log("0");
        }

        rb.velocity = new Vector2(moveSpeed, rb.velocity.y);
    }
}

このスクリプトをPlayerにアタッチします。
FixedUpdate内の条件分岐に関してなのですが、xが0から分岐を設定しているとどうやらダメらしかったので、そこだけ注意します。

頑張ってゲーム作ることにする #1 テストマップ作製

頑張ってゲームを作ります。

Unityのタイルマップ機能を使用して簡単なマップを作成しました。
このマップを使用してテストを行っていこうと思っています。
f:id:Neuman-I:20210324221550p:plain

マップタイルにはAssetStoreからよさそうなものを選んできました。

assetstore.unity.com

定番のこちらになります。

baba-s.hatenablog.com
こちらの記事を参考にしながらタイルマップを作成しました。

注意点としては画像をスライスする際にPixles Per Unitの値を16にしておかなければいけないという点です。
使う素材によってこの値を確実に設定しておかなければいけません。
f:id:Neuman-I:20210324222140p:plain

キャラクター画像には私の好きなすずぬーとさんのキャラクターを参考パクにさせていただいております。

Unityの寺子屋 定番スマホゲーム開発入門 

→タッチ判定の対象外になる 

  • public で変数を定義することでインスペクターを使ってゲームオブジェクトを取得することができる
//オブジェクト参照
    public GameObject orbPrefabs;

スクリプトだけでもゲームオブジェクトの取得は可能だが、エディタ上で対象のゲームオブジェクトやプレハブを変更することができる

  • Instantiate(インスタンシエイト)メソッド
GameObject orb = (GameObject)Instantiate(orbPrefab);

PrefabをGameObjectとして出現させるときに使うと考えられる...

  • クリックを検出する一連の流れ

P.64参照
EventTriggerコンポーネントを追加する

  • ゲームオブジェクトの画像を切り替える
GetComponent<Image> ().sprite = hoge;

このスクリプトはゲームオブジェクト(Image)にアタッチされていなければいけない

  • 地面にプレイヤーが設置しているかどうかの判定を行う

Linecastメソッドを使用する。このメソッドでは与えた線分上に特定のレイヤーに属するColliderが存在する場合にTrueを返す

 canJump = Physics2D.Linecast(transform.position - (transform.right * 0.3f),
                        transform.position - (transform.up * 0.1f), blockLayer) ||
                  Physics2D.Linecast(transform.position + (transform.right * 0.3f),
                        transform.position - (transform.up * 0.1f), blockLayer);

プレイヤーのスプライトの下端中央を軸と設定している。そのため上記のスクリプトで表される線分は
画像の赤線部分となる
f:id:Neuman-I:20210313154851j:plain

  • 敵を踏んだ時に少し跳ね上がるような物理挙動を実装する
 // 踏んだ
                rbody.velocity = new Vector2(rbody.velocity.x, 0);
                rbody.AddForce(Vector2.up * jumpPower);

rbody.velocity = new Vector2(rbody.velocity.x, 0); というコードの意味が分からなかった
→ AddForceでは物理的(現実的)な上方向への力をゲームオブジェクトへ加えるメソッドになっている。
 そのため落下中のplayerは下方向への加速度が働いているので通常のジャンプの時と同様の力を上方向に加えても、通常のジャンプほど跳ね上がることはない。そこで敵を踏んだ瞬間にy軸方向への加速度(速度)を0にすることで、通常時のジャンプと同様のはね方を実装することができる。

アニメーション関係

  • アニメーターコントローラーの現在の状態を取得する
AnimatorStateInfo stateInfo = imageMokugyo.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0);

GetCurrentAnimatorStateInfoメソッドでは現在のステートの情報をAnimatorStateInfo型で返してくれる。
引数はレイヤーのインデックス