ぬるーむ

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

Unityでテトリスを作ってみた 1


スポンサードリンク

基本部分

基本的な部分は下記のサイトなどを参考に作成。

落下を滑らかにする

落下距離をピクセル単位にする

落下距離を常に一定(1ブロック分)に保ち、落下する間隔を変化させる方法だと動きがややカクカクしているように見える。
なのでフレーム毎の落下距離をピクセル単位で調整できるようにする。 (ただし、1フレームで落下する距離はブロックの大きさを超えない範囲にする)

//落下速度。1つのブロックの高さ = 1
var float speed = 0.05f;

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

移動判定の修正

移動判定は現在のテトリミノを構成している一つ一つのブロックの座標と、フィールドに固定されているブロックらの座標(2次元配列)を使って行っている。
しかし、落下距離が整数でないので、テトリミノの座標が小数になり配列で扱えない。したがって、整数にする処理が必要になるのだが、切り上げや四捨五入ではうまくいかないので切捨てを行う(下図参照)。

f:id:Nullsuke:20200927102409p:plain
移動判定時の端数処理
また、Unityの仕様なのか-1.000321e-7のような非常に小さい端数がでることがある。これをそのままにして端数切捨てをすると、大きな誤差になることがある。
よって、この微小な端数をなくすため適当な位で四捨五入をしなければならない。

//ほとんど0に近い値
var n = -1.000321e-7f;

//そのまま端数切捨て
var a = Mathf.FloorToInt(n);
Debug.Log(a); // -1

//小数第4位で四捨五入してから
var b = (float)(Math.Round(n * 1000, MidpointRounding.AwayFromZero)) / 1000;
//端数切捨て
var c = Mathf.FloorToInt(b);
Debug.Log(c); // 0

つまり、移動判定時には先に四捨五入して微小な端数をなくしてから端数切捨てを行う必要がある。
ただし、このやり方だと落下距離が1を超えるとうまくいかくなる。上記ので落下距離は1以下という理由はこれである。

public bool CanMove()
{
    foreach (Transform c in transform)
    {
        var x = Mathf.RoundToInt(c.transform.position.x);
        //先に四捨五入してから
        var ny = Round4thDecimalPlace(c.transform.position.y);
        //端数切捨て
        var y = Mathf.FloorToInt(ny);

        if (x < 0 || x >= width || y < 0 || y >= height)
        {
            return false;
        }

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

    return true;
}

//小数第4位で四捨五入
private float Round4thDecimalPlace(float n)
{
    return (float)(Math.Round(n * 1000, MidpointRounding.AwayFromZero)) / 1000;
}

横移動の修正

移動判定時に端数切捨てを行うせいで、横移動の時ブロックにのめり込んでしまうことがあるので修正する。

f:id:Nullsuke:20200927223500p:plain
横移動の失敗例
手順は以下の通り。

  • 移動判定が真のとき座標を上にずらして(端数切り上げ)、再度移動判定する。
  • 上にずらした後の移動判定が偽なら座標を下にずらす(端数切捨て)。
  • 横に移動。
    f:id:Nullsuke:20200928220405p:plain
    横移動時の修正処理
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;
    }
}

//上にずらして移動できるかどうか判定
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);
}

移動判定後、テトリミノを戻す処理の修正

移動判定が偽の時は、当然テトリミノは壁や他のテトリミノにのめり込んでいる状態である。
なので判定後正常な位置に戻す必要がある。つまり、y座標を整数にしなければならない。
落下距離が常に1ならば単純に落下した分足せばいいのだが、落下距離が一定でない場合移動判定と同様のめり込む量が一定ではないので
y座標に1を加算して、端数を切り捨てる。
という処理を行う。

f:id:Nullsuke:20200927102527p:plain
位置固定時の端数処理

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

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

Unityでテトリスを作ってみた 2