ぬるーむ

Unity初心者が誰もが知っているゲームの模倣をしています。個人的な備忘録ですが、入門書を読み終えたばかりの初心者の方は「こんなへなちょこでもいいのか!」「俺の方がうまく作れる」と作成意欲がわいたりするかもしれません。

Unityでぷよぷよを作ってみた 1


スポンサードリンク

基本部分

2次元配列を用いて処理を行うなど基本的な考え方はテトリスとほほ同じ。
2つのぷよ(組ぷよ)に4つの状態(Normal, FreeFall, Vanish, Wait)をもたせ、これらを遷移させることで移動やぷよの自由落下、連鎖などを行う。
大まかな流れは以下の通り。

f:id:Nullsuke:20201031133957p:plain
大まかな流れ

組ぷよの落下、移動、回転

落下

毎フレーム、状態がNormalのとき入力受付、落下、固定処理を行う。
固定後に状態を自由落下に遷移させる。

private static readonly float defaultSpeed = 0.05f;
private static readonly float maxSpeed = 0.5f;
private static readonly int delayLimit = 30;
private static List<Puyo> droppablePuyoes = new List<Puyo>();
private static float levelSpeed = defaultSpeed;
private static float speed = levelSpeed;
private enum State { Normal, FreeFall, Vanish, Wait }
private List<Puyo> puyoes;
private Field field;
private PairMover mover;
private State state;
private bool isDelaying;
private int delayCount;

private void Update()
{
    switch (state)
    {
        case State.Normal:
            CheckInput();
            mover.Fall(speed);

            //落下できなくなったら
            if (!mover.CanMove())
            {
                isDelaying = true;
                delayCount--;

                //上へずらす
                mover.AdjustToAbove();

                if (delayCount < 0)
                {
                    //ぷよの固定
                    field.Fix();

                    //落下可能なぷよを取得
                    droppablePuyoes = field.GetDroppablePuyoes();

                    //FreeFallへ遷移
                    state = State.FreeFall;
                }
            }
            else
            {
                isDelaying = false;
            }

            break;

        case State.FreeFall:
            //自由落下処理
            break;

        case State.Vanish:
            //ぷよ消去処理
            break;

        case State.Wait:
            //待機中
            break;
    }
}

//入力受付
private void CheckInput()
{
    //長押し処理できるようにする。テトリスのものと同じ。
    KeyInputReceiver.Update();

    if (KeyInputReceiver.GetKeyLongDown(KeyCode.LeftArrow))
    {
        mover.MoveLeft();
    }
    else if (KeyInputReceiver.GetKeyLongDown(KeyCode.RightArrow))
    {
        mover.MoveRight();
    }
    else if (Input.GetKeyDown(KeyCode.DownArrow))
    {
        if (isDelaying) delayCount = -1;
        else speed = maxSpeed;
    }
    else if (Input.GetKeyDown(KeyCode.UpArrow))
    {
        if (!isDelaying) mover.HardDrop();

        delayCount = -1;
    }
    else if (Input.GetKeyDown(KeyCode.Z))
    {
        mover.TurnLeft(RotatePosition);
    }
    else if (Input.GetKeyDown(KeyCode.X))
    {
        mover.TurnRight(RotatePosition);
    }
}

private void Awake()
{
    field = GetComponent<Field>();

    mover = GetComponent<PairMover>();
    mover.Setup(field);

    delayCount = delayLimit;
    isDelaying = false;
    speed = levelSpeed;
}

移動、回転

BaseMoverを継承したPairMoverで移動、回転の処理を行う。

public abstract class BaseMover : MonoBehaviour
{
    protected static Field field;
    protected static int width;
    protected static int height;
    private static readonly float dropSpeed = 0.9f;

    //PairMoverとSingleMoverで処理が違う
    public abstract bool CanMove();

    public void Setup(Field field)
    {
        BaseMover.field = field;
        width = field.Width;
        height = field.Height;
    }

    public void Fall(float speed)
    {
        transform.position += new Vector3(0, -speed, 0);
    }

    public void HardDrop()
    {
        var vec = new Vector3(0, -dropSpeed);

        do
        {
            transform.position += vec;
        } while (CanMove());

        AdjustToAbove();
    }

    //上へずらす
    public void AdjustToAbove()
    {
        var p = transform.position;
        int y = Mathf.FloorToInt(p.y + 1);

        transform.position = new Vector3(p.x, y);
    }
}
public class PairMover : BaseMover
{
    //移動判定
    public override bool CanMove()
    {
        foreach (Puyo p in transform.GetComponentsInChildren<Puyo>())
        {
            if (!p.CanMove()) return false;
        }

        return true;
    }

    public void MoveLeft()
    {
        transform.position += Vector3.left;

        if (!CanMove())
        {
            transform.position -= Vector3.left;
        }
        else if (!CanMoveAboveSpace())
        {
            transform.position -= Vector3.left;
            AdjustToBelow();
            transform.position += Vector3.left;
        }
    }

    public void MoveRight()
    {
        transform.position += Vector3.right;

        if (!CanMove())
        {
            transform.position -= Vector3.right;
        }
        else if (!CanMoveAboveSpace())
        {
            transform.position -= Vector3.right;
            AdjustToBelow();
            transform.position += Vector3.right;
        }
    }

    public void TurnLeft(Vector3 rotatePosition)
    {
        transform.RotateAround(transform.TransformPoint(rotatePosition),
                new Vector3(0, 0, 1), 90);

        if (!CanMove())
        {
            transform.RotateAround(transform.TransformPoint(rotatePosition),
                new Vector3(0, 0, 1), -90);
        }
    }

    public void TurnRight(Vector3 rotatePosition)
    {
        transform.RotateAround(transform.TransformPoint(rotatePosition),
                new Vector3(0, 0, 1), -90);

        if (!CanMove())
        {
            transform.RotateAround(transform.TransformPoint(rotatePosition),
                new Vector3(0, 0, 1), 90);
        }
    }

    //一つ上なら移動できるか判定
    private bool CanMoveAboveSpace()
    {
        foreach (Transform c in transform)
        {
            var x = Mathf.RoundToInt(c.transform.position.x);
            var y = Mathf.CeilToInt(c.transform.position.y);

            if (field[x, y] != null) return false;
        }

        return true;
    }

    //下へずらす
    private void AdjustToBelow()
    {
        var p = transform.position;
        int y = Mathf.FloorToInt(p.y);

        transform.position = new Vector3(p.x, y);
    }
}