【Unity2D】Linecastを使って掴み処理を実装する

Unity2D

Unity2D ARPG

こんにちは。なおキーヌです。

ブログ毎日更新は326日目になります。

前回「【Unity2D】Linecastを使って前方の判定を取る」にて掴むための判定をとれるようにしました。

手探り状態でやっていますので、少々遠回りしてしまいましたがやっと掴む処理の実装に入ります。

判定はもう取ることができるようになりましたので、
後はその判定を元にアニメーションを切り替えて掴み状態にします。

それでは早速掴み処理を実装していきましょう。

判定が取れたら掴みモードに処理を切り替えよう

プレイヤーの状態を管理する変数はnowAnimで使いまわせそうですね。

本来はプレイヤーの状態を管理するプレイヤークラスとかを作った方がいいのですが、
最初のうちから細かく分けていると制作が全然進まないので一旦そのまま1つのスクリプトファイル内に収めておきましょう。

掴みモードに入る条件は

  • 掴める壁の前に居る
  • Gキーを押し続けている

ですね。

どちらも条件が外れてしまえば通常の歩行モードになるようにしましょう。

現時点ではFixedUpdateのなかに書いてしまっていたので、
十字キーと同じくしてGを押したときにキー配列に組み込むようにしてみます。

そしてLinqを使ってキー配列にGが入っていたら掴み処理を呼び出すといった感じになります。

    void FixedUpdate() {
        this.transform.Translate(vx/50, vy/50, 0);

        switch(nowAnim - nowAnim%10) {
            case 0:
                // 離れた距離は1fで十分だった
                castPositionDistance = transform.up * 1f;
                // Vector3dの.Set関数はVector3を入れると値がセットされる
                castPosition.Set(transform.position.x,(transform.position.y-5.5f) - 10.5f,0f);
                break;
            case 10:
                castPositionDistance = transform.up * -1f;
                castPosition.Set(transform.position.x,(transform.position.y-5.5f) + 10.5f,0f);
                break;
            case 20:
                castPositionDistance = transform.right * 1f;
                castPosition.Set(transform.position.x-8f,(transform.position.y-5.5f),0f);
                break;
            case 30:
                castPositionDistance = transform.right * -1f;
                castPosition.Set(transform.position.x+8f,(transform.position.y-5.5f),0f);
                break;
            default:
                break;
        }
    }

    private void CharaMove() {
        // キー押チェック
        KeyCheck();

        // キー状態を見て移動
        PlayerMove();
        PlayerAttack();
        if (PushedArrowKeyList.Contains(KeyCode.G)) {
            PlayerGrab();
        }
    }

    private void KeyCheck() {
        // 十字(上下左右)
        KeyAddRemove(KeyCode.LeftArrow);
        KeyAddRemove(KeyCode.RightArrow);
        KeyAddRemove(KeyCode.UpArrow);
        KeyAddRemove(KeyCode.DownArrow);

        // 掴み(G)
        KeyAddRemove(KeyCode.G);
    }

FixedUpdateからGキー処理を削除し、KeyCheck関数にキー配列にGを組み込めるようにしました。

そしてCharaMoveの中でキー配列にGがないか調べるようにしました。

もしGが押されていたらPlayerGrab関数を呼び出します。

それではPlayerGrab関数を作りましょう。

    private void PlayerGrab() {
        Debug.DrawLine(
                castPosition,
                castPosition - castPositionDistance,
                Color.red
        );
        is_lcast = Physics2D.Linecast(
            castPosition,
            castPosition - castPositionDistance,
            layerMask
        );

        if (is_lcast.collider.tag == "GrabOK") {
            // 向いてる方向の掴みにする
            if ( nowAnim != (nowAnim-nowAnim%10) + 5) {
                nowAnim = (nowAnim-nowAnim%10) + 5;
                animator.SetInteger("AnimIdx",nowAnim);
            }

            Debug.Log(nowAnim);
        }
    }

前回作ったDrawLineとLinecastをそのまま持ってきました。

後はLinecastで取得した判定情報の「collider.tag」で設定したタグを取得できますので
それをstring型で条件をとってやるといいでしょう。

条件に合致したら現在のアニメーションの向き情報(2桁目)だけをとり、掴み状態である1桁目の5を足します。

1桁目が5にしているのは3が防御で4がシャベル使用状態にしたいからです。

この値は各自好きにしてもらって大丈夫です。

押されてる時に毎回代入されてしまっては少し処理が重くなるので、
掴んだときにアニメーションが切り替わっていなければ掴みアニメに切り替えるというやり方にしました。

これで1回だけ発動するようになったので毎フレーム代入処理は行われなくなりました。

しかしこのままだと掴んでしまったら動けなくなるので、
Gキーが離されたら掴み処理を解除するようにする「PlayerGrabRelease」関数を作ります。

    private void CharaMove() {
        // キー押チェック
        KeyCheck();

        // キー状態を見て移動
        PlayerMove();
        PlayerAttack();
        if (PushedArrowKeyList.Contains(KeyCode.G)) {
            PlayerGrab();
        } else {
            PlayerGrabRelease();
        }

    }
    private void PlayerGrabRelease() {
        if ( nowAnim == (nowAnim-nowAnim%10) + 5) {
            nowAnim = nowAnim-nowAnim%10;
            animator.SetInteger("AnimIdx",nowAnim);
        }
    }

仕組みは単純で、キー配列にGが含まれていなければPlayerGrabReleaseを呼び出します。

処理の内容は先ほどの1回だけ判定をするやつを流用して
掴みアニメーションであれば向いている方向のスタンド状態に戻す。

にしてあります。

これで掴み解除は完成ですね。

後はAnimatorで掴みアニメーションを作成してnowAnimの1桁目が5の時に遷移するようにすれば完了です。

ここまでで掴み処理は完成しました。

後は引っ張るか押すかの分岐処理ですね。

一旦ここまでのコードを載せておきます。

ちょっと整理していないので乱雑なコードになっていますが、現状私が動いているので動くはずです。

デバッグ用に色々入れていますが気にしないでください(笑)

それでは。

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

// Enumを使う為
using System;

public class Controller : MonoBehaviour
{
    GameObject hammerObj;
    Animator animator;
    Animator animator_hammer;
    Animation animation;
    Animation animation_hammer;
    Vector3 castPosition = new Vector3();
    Vector3 castPositionDistance = new Vector3();
    // 衝突判定が取れたかどうかを格納する変数
    RaycastHit2D is_lcast;
    [SerializeField]
    private LayerMask layerMask;

    public float speed = 30;

    // キー入力順番配列
    private List<KeyCode> PushedKeyList = new List<KeyCode>();
    private List<KeyCode> PushedArrowKeyList = new List<KeyCode>();

    float vx = 0;
    float vy = 0;

    int nowAnim = 0;

    bool isAttack = false;

    string walkAnimation = "DownStand@Player";

    int AttackPressCount = 0;

    void Start() {
        animator = GetComponent<Animator>();
        // ハンマー取得        
        hammerObj = GameObject.Find("hammer");
        animator_hammer = hammerObj.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        vx = 0;
        vy = 0;

        // 向いてる静止アニメを設定
        StaticDirectSetting();

        // if (GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).shortNameHash.Equals(Animator.StringToHash("DownAttack@Player")));
        // {
        // //animation_nameはアニメーション中です
        // Debug.Log("攻撃中");
        // }

        // キャラクター移動
        CharaMove();
    }

    void FixedUpdate() {
        this.transform.Translate(vx/50, vy/50, 0);
        // Debug.Log(this.transform.position);
        // this.GetComponent<Animator>().Play(walkAnimation);
        ShowListContentsInTheDebugLog(PushedArrowKeyList);

        switch(nowAnim - nowAnim%10) {
            case 0:
                castPositionDistance = transform.up * 1f;
                castPosition.Set(transform.position.x,(transform.position.y-5.5f) - 10.5f,0f);
                break;
            case 10:
                castPositionDistance = transform.up * -1f;
                castPosition.Set(transform.position.x,(transform.position.y-5.5f) + 10.5f,0f);
                break;
            case 20:
                castPositionDistance = transform.right * 1f;
                castPosition.Set(transform.position.x-8f,(transform.position.y-5.5f),0f);
                break;
            case 30:
                castPositionDistance = transform.right * -1f;
                castPosition.Set(transform.position.x+8f,(transform.position.y-5.5f),0f);
                break;
            default:
                break;
        }
    }

    // 最後に向いてた方向の静止アニメーションに固定
    private void StaticDirectSetting() {
        switch (walkAnimation) {
            case "LeftWalk@Player":
                walkAnimation = "LeftStand@Player";
                break;
            case "RightWalk@Player":
                walkAnimation = "RightStand@Player";
                break;
            case "UpWalk@Player":
                walkAnimation = "UpStand@Player";
                break;
            case "DownWalk@Player":
                walkAnimation = "DownStand@Player";
                break;
        }
    }

    // 移動処理
    private void CharaMove() {
        // キー押チェック
        KeyCheck();

        // キー状態を見て移動
        PlayerMove();
        PlayerAttack();
        if (PushedArrowKeyList.Contains(KeyCode.G)) {
            PlayerGrab();
        } else {
            PlayerGrabRelease();
        }

    }

    // 掴み処理
    private void PlayerGrab() {
        Debug.DrawLine(
                castPosition,
                castPosition - castPositionDistance,
                Color.red
        );
        is_lcast = Physics2D.Linecast(
            castPosition,
            castPosition - castPositionDistance,
            layerMask
        );

        if (is_lcast.collider.tag == "GrabOK") {
            // 向いてる方向の掴みにする
            if ( nowAnim != (nowAnim-nowAnim%10) + 5) {
                nowAnim = (nowAnim-nowAnim%10) + 5;
                animator.SetInteger("AnimIdx",nowAnim);
            }

            Debug.Log(nowAnim);
        }
    }
    private void PlayerGrabRelease() {
        if ( nowAnim == (nowAnim-nowAnim%10) + 5) {
            nowAnim = nowAnim-nowAnim%10;
            animator.SetInteger("AnimIdx",nowAnim);
        }
    }


    // キープッシュリストによる移動
    private void PlayerMove() {
        // スタンドか歩行時のみ
        if (nowAnim%10 == 0 || nowAnim%10 == 1 ) {

            // まず一番最初に押された
            if (PushedArrowKeyList.Count > 0) {
                if (PushedArrowKeyList[0] == KeyCode.LeftArrow) {
                    vx = -speed;
                    WalkSwitch("LeftWalk");
                    // walkAnimation = "LeftWalk@Player";
                    if (PushedArrowKeyList.Count > 1) {
                        if (PushedArrowKeyList[1] == KeyCode.UpArrow) vy = speed;
                        if (PushedArrowKeyList[1] == KeyCode.DownArrow) vy = -speed;
                    }
                } else if (PushedArrowKeyList[0] == KeyCode.RightArrow) {
                    vx = speed;
                    WalkSwitch("RightWalk");
                    // walkAnimation = "RightWalk@Player";
                    if (PushedArrowKeyList.Count > 1) {
                        if (PushedArrowKeyList[1] == KeyCode.UpArrow) vy = speed;
                        if (PushedArrowKeyList[1] == KeyCode.DownArrow) vy = -speed;
                    }
                } else if (PushedArrowKeyList[0] == KeyCode.UpArrow) {
                    vy = speed;
                    WalkSwitch("UpWalk");
                    // walkAnimation = "UpWalk@Player";
                    if (PushedArrowKeyList.Count > 1) {
                        if (PushedArrowKeyList[1] == KeyCode.LeftArrow) vx = -speed;
                        if (PushedArrowKeyList[1] == KeyCode.RightArrow) vx = speed;
                    }
                } else if (PushedArrowKeyList[0] == KeyCode.DownArrow) {
                    vy = -speed;
                    WalkSwitch("DownWalk");
                    // walkAnimation = "DownWalk@Player";
                    if (PushedArrowKeyList.Count > 1) {
                        if (PushedArrowKeyList[1] == KeyCode.LeftArrow) vx = -speed;
                        if (PushedArrowKeyList[1] == KeyCode.RightArrow) vx = speed;
                    }
                }
            } else {
                WalkSwitch("Stand");
            }
        }
    }
    //アニメーション終了を判定するコルーチン
    private IEnumerator WaitAnimationEnd(string animatorName) {
        bool finish = false;
        while (!finish) {
            AnimatorStateInfo nowState = animator.GetCurrentAnimatorStateInfo(0);
            if (nowState.IsName (animatorName)) {
                finish = true;
                Debug.Log("おわったぞ");
            } else {
                Debug.Log("まだおわらんよ");
                yield return new WaitForSeconds (0.1f);
            }
        }
    }
    private void WalkSwitch(string str) {

        switch (str) {
            case "DownWalk" :
                nowAnim = 1;
                break;
            case "UpWalk" :
                nowAnim = 11;
                break;
            case "LeftWalk" :
                nowAnim = 21;
                break;
            case "RightWalk" :
                nowAnim = 31;
                break;
            case "Stand" :
                if (nowAnim == 1 || nowAnim == 0) {
                    nowAnim = 0;
                } else if (nowAnim == 11 || nowAnim == 10) {
                    nowAnim = 10;
                } else if (nowAnim == 21 || nowAnim == 20) {
                    nowAnim = 20;
                } else if (nowAnim == 31 || nowAnim == 30) {
                    nowAnim = 30;
                }
                break;
            default:
                break;
        }
        animator.SetInteger("AnimIdx",nowAnim);
        animator_hammer.SetInteger("AnimIdx",nowAnim);


        // if (str == "Stand") {
        //     if (animator.GetBool("DownWalk")) {
        //         animator.SetBool("DownStand",true);
        //     }
        // } else {
        //     animator.SetBool("DownStand",false);
        // }

        // animator.SetBool("LeftWalk",false);
        // animator.SetBool("RightWalk",false);
        // animator.SetBool("UpWalk",false);
        // animator.SetBool("DownWalk",false);

        // switch (str) {
        //     case "LeftWalk" :
        //         animator.SetBool("LeftWalk",true);
        //         break;
        //     case "RightWalk" :
        //         animator.SetBool("RightWalk",true);
        //         break;
        //     case "UpWalk" :
        //         animator.SetBool("UpWalk",true);
        //         break;
        //     case "DownWalk" :
        //         animator.SetBool("DownWalk",true);
        //         break;
        //     default:
        //         break;
        // }

    }
    private void PlayerAttack() {
        // 攻撃中かどうかを見る
        if (nowAnim%10 == 2) {
            // 攻撃モーションが終わっているかどうか
            if(animator.GetCurrentAnimatorStateInfo(0).normalizedTime>1) {
                // 初回攻撃モーションかつボタンカウントが5以上か調べる
                if(!isAttack && AttackPressCount > 5) {
                    // Trueなら次の攻撃モーションに切り替え
                    isAttack = true;
                    animator.SetInteger("AnimIdx",nowAnim);
                } else {
                    // アニメーションが終わったら後処理
                    //---------------------------------------------
                    // 1桁目を取り出してそれを引くことで向いてる方向のスタンド状態にする
                    nowAnim = nowAnim - nowAnim%10;
                    // アタックカウントをリセット
                    AttackPressCount = 0;
                    // アタック中フラグを解除
                    isAttack = false;
                    // それぞれのアニメーションをセット
                    animator.SetInteger("AnimIdx",nowAnim);
                    animator_hammer.SetInteger("AnimIdx",nowAnim);
                }
            }
            // 攻撃モーションが終わってなければZキー監視
            if(Input.GetKey(KeyCode.Z)) {
                // 攻撃ボタンカウントを溜める
                AttackPressCount++;
            }

        } else {
            // スタンドか移動中の場合
            if (nowAnim%10 == 0 || nowAnim%10 == 1 ) {
                // 攻撃中じゃない状態でZキーが1回押されたら
                if(Input.GetKeyDown(KeyCode.Z)) {
                    // 向いてる方向に対して攻撃モーションを適用
                    //---------------------------------------------
                    // 初回攻撃にモーション変更
                    nowAnim = (nowAnim - nowAnim%10) + 2;
                    // アニメーションのセット
                    animator.SetInteger("AnimIdx",nowAnim);
                    // ハンマーも動かす
                    animator_hammer.SetInteger("AnimIdx",nowAnim);
                }
            }
        }

    }

    // キーのリスト入れ替え関数
    private void KeyAddRemove(KeyCode keycode) {
        if (Input.GetKey(keycode)) {
            // Linqの機能:リストに指定物が中身になければリストに加える
            if (!(PushedArrowKeyList.Contains(keycode))) {
                // あれば削除
                PushedKeyList.Add(keycode);
                // 方向キーのみ
                PushedArrowKeyList.Add(keycode);
            }
        } else {
            // Linqの機能:リストに指定物が中身にあればリストにから外す
            if (PushedArrowKeyList.Contains(keycode)) {
                // あれば削除
                PushedKeyList.Remove(keycode);
                // 方向キーのみ
                PushedArrowKeyList.Remove(keycode);
            }
        }
    }



    // キーチェック関数
    private void KeyCheck() {
        // 十字(上下左右)
        KeyAddRemove(KeyCode.LeftArrow);
        KeyAddRemove(KeyCode.RightArrow);
        KeyAddRemove(KeyCode.UpArrow);
        KeyAddRemove(KeyCode.DownArrow);

        // 掴み(G)
        KeyAddRemove(KeyCode.G);


        // 




        // // 何かキーが押されたら
        // if (Input.anyKeyDown) {
        //     // キー状態で回す
        //     foreach (KeyCode code in Enum.GetValues(typeof(KeyCode))) {
        //         // 回したアイテムと押されたキーだったら
        //         if (Input.GetKeyDown (code) ) {
        //             // Linqの機能:リストに指定物が中身にあるかどうか
        //             if (!(PushedKeyList.Contains(code))) {
        //                 // 中身が無ければキープッシュリストに追加する
        //                 PushedKeyList.Add(code);
        //                 // 方向キーのみ格納する
        //                 if (code == KeyCode.LeftArrow || code == KeyCode.RightArrow || code == KeyCode.UpArrow || code == KeyCode.DownArrow ) {
        //                     PushedArrowKeyList.Add(code);
        //                 }
        //                 // リストの中身を表示(デバッグ)
        //                 ShowListContentsInTheDebugLog(PushedArrowKeyList);
        //             }
        //             // 1回でも動けばループを抜ける
        //             break;
        //         }
        //     }
        // }
    }
    // キーが離されたかチェック
    private void UpKeyCheck() {
        // // キーリストを監視
        // foreach(var key in PushedKeyList.Select((val) => new {val}))
        // {
        //     // 回しながら離されたキーと一致したら
        //     if (Input.GetKeyUp(key.val)) {
        //         // Linqの機能:リストに指定物が中身にあるかどうか
        //         if (PushedKeyList.Contains(key.val)) {
        //             // 中身にあればキープッシュリストから消す
        //             PushedKeyList.Remove(key.val);
        //             // 方向キーのみ取り出す
        //             if (key.val == KeyCode.LeftArrow || key.val == KeyCode.RightArrow || key.val == KeyCode.UpArrow || key.val == KeyCode.DownArrow ) {
        //                 PushedArrowKeyList.Remove(key.val);
        //             }
        //             // リストの中身を表示(デバッグ)
        //                 ShowListContentsInTheDebugLog(PushedArrowKeyList);
        //         }
        //         // 1回でも動けばループを抜ける
        //         break;
        //     }
        // }
    }

    // Listの中身出力
    public void ShowListContentsInTheDebugLog<T>(List<T> list)
    {
        string log = "";

        foreach(var content in list.Select((val, idx) => new {val, idx}))
        {
            if (content.idx == list.Count - 1)
                log += content.val.ToString();
            else
                log += content.val.ToString() + ", ";
        }
    }

    // 衝突判定
    /// <summary>
    /// Sent when an incoming collider makes contact with this object's
    /// collider (2D physics only).
    /// </summary>
    /// <param name="other">The Collision2D data associated with this collision.</param>
    void OnCollisionEnter2D(Collision2D other)
    {
        Debug.Log("ぶつかり!");
    }
    private void OnTriggerStay2D(Collider2D other) {
        Debug.Log("ぶつかっとーで!");
    }

    private void OnTriggerExit2D(Collider2D other) {
        Debug.Log("はなれたで!");
    }

}