【Unity2D】引く・押す処理の作り込み

Unity2D

Unity2D ARPG

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

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

前回「【Unity2D】防御と回避の実装」にてやりたいアクションの実装が完了しました。

今のままではクオリティが低くゲームとして公開するわけにはいきません。

作った仕組みをある程度までクオリティアップしていきましょう。

ガッツリと作り込むのはあとでいいので、一旦基本的な品質を確保します。

引く処理の後方チェック

衝突判定が用意されていたら自動的にUnityが進行を止めてくれるかなと思ったのですがめり込みました(笑)

なので引っ張っている時に後ろにもLinecastを飛ばしてオブジェクトのチェックをしようと思います。

Unityでなければ毎フレーム後方にあるオブジェクトにぶつかるかどうかを調べるのですが、
Unityの場合は後方にLinecastを飛ばしてぶつかっていれば引っ張り処理を止めるという作り方が可能です。

判定を飛ばせるって便利ですね。

それでは早速作ってみましょう。

確認のために動かせる壁をコピーして適当な位置に配置します。

    // update側でnewしたくない為
    Vector3 is_lcastBack;

    // 衝突判定が取れたかどうかを格納する変数
    RaycastHit2D is_lcastFront;
    RaycastHit2D is_lcastBack;

    Start() {
        // その他省略

        // 仮代入するようでここでnewしておく
        DummyVector3 = new Vector3(0,0,0);
    }

    // 掴み処理
    private void PlayerGrab() {


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

        // 前方Linecast
        Debug.DrawLine(
                castPositionFront,
                castPositionFront - castPositionDistance,
                Color.red
        );
        is_lcastFront = Physics2D.Linecast(
            castPositionFront,
            castPositionFront - castPositionDistance,
            layerMask
        );
        Debug.Log(is_lcastFront.collider);
        // 後方Linecast
        Debug.DrawLine(
                castPositionBack,
                castPositionBack - castPositionDistance,
                Color.red
        );
        is_lcastBack = Physics2D.Linecast(
            castPositionBack,
            castPositionBack - castPositionDistance,
            layerMask
        );

        if (is_lcastFront.collider != null) {
            if (is_lcastFront.collider.tag == "GrabOK") {
                // 向いてる方向の掴みにする
                if ( nowAnim != (nowAnim-nowAnim%10) + 5) {
                    nowAnim = (nowAnim-nowAnim%10) + 5;
                    animator.SetInteger("AnimIdx",nowAnim);
                } else {
                    // 引っ張るか押す処理
                    PlayerPullPush();
                }
            }
        }
    }

Linecastの計算がFixedUpdateに入っていたので、Grab関数に持ってきました。

実際にLinecastの判定を取っている部分は関数化してキレイにできそうですね。

とりあえず1つの関数にまとめてみましょう。

    private RaycastHit2D getLineCast2Object(int direct, bool DebugFlag = false) {
        Vector3 castPosition = DummyVector3;
        RaycastHit2D isLcast;
        switch(direct) {
            case 0:
                castPosition = castPositionFront;
                break;
            case 1:
                castPosition = castPositionBack;
                break;
            default:
                break;
        }
        // 前方Linecast
        if (DebugFlag) {
            Debug.DrawLine(
                    castPosition,
                    castPosition - castPositionDistance,
                    Color.red
            );
        }
        isLcast = Physics2D.Linecast(
            castPosition,
            castPosition - castPositionDistance,
            layerMask
        );

        return isLcast;
    }

関数の返り値はvoidじゃなくてRaycastHIT2Dになっていることをに注意してください。

最後に結果の判定したオブジェクトを返す為です。

「getLineCast2Object」の引数は2つあります。

1つ目は取得する方向ですね。

基本的に今は向いてる方向の前後がとれたらOKです。

そのうち機能拡張で左右を取りたい場合はswitch分岐を増やせば対応できます。

2つ目はデバッグ表示を使用するかどうか。

ぶっちゃけ使ってても実際のゲーム画面には反映されないのでなくてもいいっちゃいいんですが……

早速この関数を使って書き替えてみましょう。

    // 掴み処理
    private void PlayerGrab() {
        switch(nowAnim - nowAnim%10) {
            case 0:
                castPositionDistance = transform.up * 1f;
                castPositionFront.Set(transform.position.x,(transform.position.y-5.5f) - 10.5f,0f);
                castPositionBack.Set(transform.position.x,(transform.position.y-5.5f) + 10.5f,0f);
                break;
            case 10:
                castPositionDistance = transform.up * -1f;
                castPositionFront.Set(transform.position.x,(transform.position.y-5.5f) + 10.5f,0f);
                castPositionBack.Set(transform.position.x,(transform.position.y-5.5f) - 10.5f,0f);
                break;
            case 20:
                castPositionDistance = transform.right * +1f;
                castPositionFront.Set(transform.position.x-8f,(transform.position.y-5.5f),0f);
                castPositionBack.Set(transform.position.x+8f,(transform.position.y-5.5f),0f);
                break;
            case 30:
                castPositionDistance = transform.right * -1f;
                castPositionFront.Set(transform.position.x+8f,(transform.position.y-5.5f),0f);
                castPositionBack.Set(transform.position.x-8f,(transform.position.y-5.5f),0f);
                break;
            default:
                break;
        }

        // Linecast実行
        is_lcastFront = getLineCast2Object(0,true); // 前方
        is_lcastBack  = getLineCast2Object(1,true); // 後方

        if (is_lcastFront.collider != null) {
            if (is_lcastFront.collider.tag == "GrabOK") {
                // 向いてる方向の掴みにする
                if ( nowAnim != (nowAnim-nowAnim%10) + 5) {
                    nowAnim = (nowAnim-nowAnim%10) + 5;
                    animator.SetInteger("AnimIdx",nowAnim);
                } else {
                    // 引っ張るか押す処理
                    PlayerPullPush();
                }
            }
        }
    }

スッキリしましたね。

あからさまに関数化しても問題ないところはこうやって関数化していきます。

微妙な所は一旦保留と言った感じですね。

後方チェックをする

このままだと後ろをチェックしているだけで特に何もしていないので、
後ろキーを押したときに判定をくわえてあげましょう。

判定はすでに取れてるので、後ろのLinecastにオブジェクトがあった場合
移動処理を無視するように組んでみましょう。

    // 引く処理
    private void PlayerPull(int direct) {
        Debug.Log(is_lcastBack.collider);
        if (is_lcastBack.collider == null) {
            switch(direct) {
                case 0:
                    vy = -(speed/4);
                    break;
                case 1:
                    vy = speed/4;
                    break;
                case 2:
                    vx   = -(speed/4);
                    break;
                case 3:
                    vx = speed/4;
                    break;
            }
            is_lcastFront.transform.Translate(vx/50, vy/50, 0);
        }
    }

変更点は「is_lcastBack.collider == null」の部分ですね。

Linecastは何も触れていなくてもRaycastHIT2Dオブジェクトではありますが、
nullではありません。しかしnull判定をしたいのでその場合は掴んだ時と同じようにcolliderプロパティで比較してやればOKです。

これで後ろに何もなければ引きずることが出来て、何かあればそこで止まるという仕組みになりました。

押す処理も殆ど同じ

あとは押す処理も制限を付けてやる必要があります。

注意してほしい点は、判定は掴んでいるブロックの後ろ側だということです。

この場合どうしたらいいのかというと、実はもうオブジェクトの情報をつかんだときに確保できているので
プレイヤーと同じように座標計算をしてLinecastでオブジェクトの後ろにとばしてやればOKです。

もう1つLinecast用の変数を作って組んでいきましょう。

    // クラス変数
    RaycastHit2D is_lcastObjBack;
    Vector3 castPositionObjBack;

    // これはStart関数にいれてください
    castPositionObjBack = new Vector3(0,0,0);


    private RaycastHit2D getLineCast2Object(int direct, bool DebugFlag = false) {
        Vector3 castPosition = DummyVector3;
        Vector3 distance = DummyVector3;
        RaycastHit2D isLcast;
        switch(direct) {
            case 0:
                castPosition = castPositionFront;
                distance = castPositionDistance;
                break;
            case 1:
                castPosition = castPositionBack;
                distance = castPositionDistance;
                break;
            case 2:
                // オブジェクト側
                switch(nowAnim - nowAnim%10) {
                    case 0:
                        castPositionObjBack.Set(is_lcastFront.collider.transform.position.x,is_lcastFront.collider.transform.position.y-8.1f,0f);
                        distance = castPositionDistance = transform.up * 0.1f;
                        break;
                    case 10:
                        castPositionObjBack.Set(is_lcastFront.collider.transform.position.x,is_lcastFront.collider.transform.position.x+8.1f,0f);
                        distance = castPositionDistance = transform.up * -0.1f;
                        break;
                    case 20:
                        castPositionObjBack.Set(is_lcastFront.collider.transform.position.x-8.1f,is_lcastFront.collider.transform.position.y,0f);
                        distance = castPositionDistance = transform.right * 0.1f;
                        break;
                    case 30:
                        castPositionObjBack.Set(is_lcastFront.collider.transform.position.x+8.1f,is_lcastFront.collider.transform.position.y,0f);
                        distance = castPositionDistance = transform.right * -0.1f;
                        break;
                    default:
                        break;
                }
                castPosition = castPositionObjBack;
                break;
            default:
                break;
        }
        // 前方Linecast
        if (DebugFlag) {
            Debug.DrawLine(
                    castPosition,
                    castPosition - distance,
                    Color.red
            );
        }
        isLcast = Physics2D.Linecast(
            castPosition,
            castPosition - distance,
            layerMask
        );

        return isLcast;
    }

    private void PlayerPush(int direct) {
        // 掴んだオブジェクトの後方にLinecast発動
        is_lcastObjBack = getLineCast2Object(2,true);

        if (is_lcastObjBack.collider == null) {
            switch(direct) {
                case 0:
                    vy = -(speed/4);
                    break;
                case 1:
                    vy = speed/4;
                    break;
                case 2:
                    vx   = -(speed/4);
                    break;
                case 3:
                    vx = speed/4;
                    break;
            }
            is_lcastFront.transform.Translate(vx/50, vy/50, 0);
        }
    }

getLineCast2Objectの関数に1つ処理を増やしました。

2は掴んだオブジェクトの処理になります。

呼び出すところはPlayerPushされた時にしました。

それ以外で呼び出していても仕方ないので押したときだけ呼び出しています。

後、getLineCast2Objectのどれだけ先の判定を取るかの距離の部分(distance変数)は
プレイヤーとオブジェクトでは変化させたかったのでローカル変数で処理しています。

同じにするとオブジェクトとオブジェクトの間に隙間が出て止まって不自然にみえちゃいます。

なので0.1fにしたら丁度良い感じに隣接してくれました。

これで押す引く両方の制御処理が完了しました。

問題点が出てきた

完成した……ようにみえたのですが、頭の良い人ならもうお気づきかと思います。

そう、判定は線なのですが、進行方向にせんを伸ばしているので実質点でとっているような状態です。

つまりその判定外のところだとめりこんでしまうわけですね。

どういうことかというと、半分ブロックをずらして別のブロックをめり込ませると普通にめり込みます。

当然、真ん中が何もないので判定をとれないためです。

これを直すためにはLinecastよりもBoxcastの方がしっくりくるような気がします。

しかしこれを突き詰めてしまうとたぶん微調整微調整の繰り返しで終わらない気がするんですよね……

そこで考えたのが、1回動かすと1ブロック分動かす。

こうするとパズルを作るときも仕組みを考えやすいですし、なにより判定はこのままで問題なくなりそうです。

なので次回は1回押したり引いたりしたら一定距離動くようにしてみましょう。

それでは。