【Unity2D】押すオブジェクトの仕掛けを作る

Unity2D

Unity2D ARPG

初期エリアの基本的な仕組みが完成(宝箱イベントは後回し)したので次に進みます。

押す引く処理はすでに作っているのですが、このままだと無限に動かせてしまうので制限が必要です。

2Dゼルダの伝説の場合、基本的にオブジェクトは押すのみなんですよね。

引っ張るのはレバーありのものだったと思います。

3Dになってから引っ張ることもできましたが、一旦は押す処理だけに絞ってみましょう。

コントローラースクリプトの修正

久々に押す処理を動かそうとしたらスクリプト変更の弊害があってうまく動きませんでした。

以下のようにController_PlayerのKeyAddRemove関数を修正しましょう。

    // キーのリスト入れ替え関数
    private void KeyAddRemove(KeyCode keycode)
    {
        if (Input.GetKey(keycode))
        {
            // Linqの機能:リストに指定物が中身になければリストに加える

            // 全キー監視
            if (!(PushedKeyList.Contains(keycode)))
            {
                PushedKeyList.Add(keycode);
            }

            // 矢印キーだけ監視
            if (!(PushedArrowKeyList.Contains(keycode)))
            {
                setPushedArrowKeyList(keycode);
            }
        }
        else
        {
            // Linqの機能:リストに指定物が中身にあればリストにから外す

            // 全キー監視
            if (PushedKeyList.Contains(keycode))
            {
                // あれば削除
                PushedKeyList.Remove(keycode);
                // キーカウントリセット
                selectPushedKeyCountReset(keycode);
            }

            // 矢印キーのみ監視
            if (PushedArrowKeyList.Contains(keycode))
            {
                // 方向キーのみ
                removePushedArrowKeyList(keycode);
                // キーカウントリセット
                selectPushedKeyCountReset(keycode);
            }
        }
    }

前のままだと矢印キーオンリーの配列にもほかのキーが入る状態になってたので、
それぞれに入ったり消えたりするように条件分岐を増やしました。

もっとスリムにかけそうな気もしますが、最適化は後回しです。

1回動かしたら動けなくしたい

何回も動かせるとバグの元になりそうなので、2Dゼルダにならって動かせるのは1回までにしましょう。

ここで動かした判定をどうするかに悩んでいました。

間違えたらエリアを切り替えてやり直すというのが基本にしたいです。

オブジェクト側で動いたということを記憶してエリア移動するときに、
そのフラグを消して初期座標に戻るという仕組みにしたいです。

まずはオブジェクトステータスに動いた後か動く前かのフラグを設けましょう。

_ObjectStateGeneralに追記します。

using System.Collections;
using UnityEngine;

public class _ObjectStateGeneral : MonoBehaviour
{
    // 押しオブジェクトの移動状態
    public bool isMoved_pushObj = false;

    void Start()
    {
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.name)
        {
            case "hammer":
                this.GetComponent<SpriteRenderer>().color = new Color(0, 0, 0, 0);
                transform.position = Vector3.zero;
                break;
        }
    }
}

このスクリプトをプレハブの元となっているオブジェクトにアタッチします。

その後、この移動状態変数を操作するために掴んだ時のグリッド移動関数の最後に、
変数を真にすることで動かせなくします。

Move_Playerを変更します。

    // 割り切れたら位置補正
    private void fixedGridPosition(State_Player state, RaycastHit2D castFront, GameObject player)
    {
        // ~~~ 省略 ~~~

            // ステートを強制的に0に戻す
            state.endGridMove();
            // オブジェクトの移動完了をONにして動かせなくする ※これを追加
            castFront.collider.GetComponent<_ObjectStateGeneral>().isMoved_pushObj = true;
        }

最後の行を追加してください。

アタッチしてるコンポーネントの変数をtrueにしています。

一応これがtrueの時にグリッド移動が作動しないようにしておきます。

さらにMove_Playerを変更します。

    // 掴みグリッド移動関数
    protected override void grabGridMove()
    {
        // オブジェクトが取得できていたら
        if (_Action.IsLcastFront.collider != null)
        {
            // オブジェクトが移動完了してなければ
            if (!_Action.IsLcastFront.collider.GetComponent<_ObjectStateGeneral>().isMoved_pushObj) {
                float vx = 0, vy = 0;
                int speed = _State.ActState == 106 ? _State.MoveSpeed : -(_State.MoveSpeed);
                switch (_State.Direction)
                {
                    case 0:
                        vy = speed / 4;
                        break;
                    case 1:
                        vy = -(speed / 4);
                        break;
                    case 2:
                        vx = speed / 4;
                        break;
                    case 3:
                        vx = -(speed / 4);
                        break;
                }
                _Action.IsLcastFront.transform.Translate(vx / 50, vy / 50, 0);
                _Player.transform.Translate(vx / 50, vy / 50, 0);

                // 割り切れたら位置補正
                fixedGridPosition(_State, _Action.IsLcastFront, _Player);

            }
        }
    }

これが呼び出される前に止まるようにするのですが、何らかの理由でここが動いてしまうとバグるので念のための制御です。

これで1回移動させたら動かせなくなるのですが、このままだと2回目動かそうとするとプレイヤーがグリッド移動状態になるけど
オブジェクトが動かないので移動が完了できずプレイヤーが動けなくなってしまいます。

押したときに動かせない状態だと掴み状態に戻すようにします。

Action_Playerを変更しましょう

    // 掴み処理
    protected override void Grab()
    {
        if (_State.ActState >= 5 && _State.ActState <= 7 || _State.ActState == 106 || _State.ActState == 107)
        {
            // 衝突判定をしたオブジェクトのcolliderがnullでなければ
            _isLcastFront = getLineCast2Object(_State.Direction, _Player, _layerMaskGrab, true);
            if (_isLcastFront.collider != null)
            {
                if (!_isLcastFront.collider.GetComponent<_ObjectStateGeneral>().isMoved_pushObj)
                {
                    switch (_State.ActState)
                    {
                        case 106:
                            Pull();
                            break;
                        case 107:
                            Push();
                            break;
                        default:
                            break;
                    }

                }
                else
                {
                    // 動かせなければステートを5にして押す引く状態を停止させる
                    _State.ActState = 5;
                }

            }
        }
    }

これで1回だけ押したり引いたりできるようになりました。

オブジェクトの初期値を記録するために変数を設けましょう。

using System.Collections;
using UnityEngine;

public class _ObjectStateGeneral : MonoBehaviour
{
    // 押しオブジェクトの移動状態
    public bool isMoved_pushObj = false;

    // オブジェクトの初期位置
    public Vector3 initPos;

    // 動かす正解の位置
    public Vector3 truePos;


    void Start()
    {
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        switch (other.name)
        {
            case "hammer":
                this.GetComponent<SpriteRenderer>().color = new Color(0, 0, 0, 0);
                transform.position = Vector3.zero;
                break;
        }
    }
}

これで初期値と動かしたときの正解の位置を記録できるようになりました。

値は一ヶ所のデータベースに集めておいてそこから値を呼び出して
ゲーム開始時に保持しておくといったほうがいいかもしれません。

体験版は一旦はオブジェクトに持たせておきます。

正解の場所に動かしたときに仕掛けを動かす

動かし完了した時にオブジェクトの座標とオブジェクトの持つ正解の位置を比べて一致すれば
仕掛けが解けるといった感じの仕組みを作りましょう。

とりあえず右のオブジェクトは東にずらして顔を完成させたらOKという感じですね。

位置チェックはMove_Player側でやっておきましょう。

Move_Managerに下記の関数を作ってください。

    // 動かせるオブジェクトの正解位置チェック
    protected bool checkGrabObjTruePos(RaycastHit2D obj)
    {
        float cx = obj.transform.localPosition.x;
        float tx = obj.collider.GetComponent<_ObjectStateGeneral>().truePos.x;

        float cy = obj.transform.localPosition.y;
        float ty = obj.collider.GetComponent<_ObjectStateGeneral>().truePos.y;

        if (cx == tx && cy == ty)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

Vector3で条件一致させようとしたらなぜか自分自身のZ軸が-1になって
条件一致しなかったのでxとyをローカル変数に取り出して2つをチェックするようにしました。

これをMove_PlayerのfixedGridPosition関数の一番下に付け加えてください。

// ステートを強制的に0に戻す
state.endGridMove();
// オブジェクトの移動完了をONにして動かせなくする
castFront.collider.GetComponent<_ObjectStateGeneral>().isMoved_pushObj = true;
// オブジェクトの正解位置チェック
if (checkGrabObjTruePos(castFront))
{
    Debug.Log("正解");
}
else
{
    Debug.Log("不正解");
}

これで右に動かしたら正解と出てそれ以外に動かすと不正解とでるはずです。

最後に、現状だとハンマーがあたったらブロックが消えてしまうので、
壊せる岩の条件分岐を変更します。

using System.Collections;
using UnityEngine;

public class _ObjectStateGeneral : MonoBehaviour
{
    // 押しオブジェクトの移動状態
    public bool isMoved_pushObj = false;

    // オブジェクトの初期位置
    public Vector3 initPos;

    // 動かす正解の位置
    public Vector3 truePos;


    void Start()
    {
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        // 壊せる岩の判定
        if (other.name == "hammer" && this.tag == "brokenBlock")
        {
            this.GetComponent<SpriteRenderer>().color = new Color(0, 0, 0, 0);
            transform.position = Vector3.zero;
        }
    }
}

こんな感じですね。

壊せる岩のタグには「brokenBlock」を付けてやるとそれがハンマーに当たると消えます。

それでは!