Unity|Tetris|俄罗斯方块复刻2|核心代码(C#)

Unity|Tetris|俄罗斯方块复刻2|核心代码(C#)_第1张图片

Board.cs
using UnityEngine;
using UnityEngine.Tilemaps;

[DefaultExecutionOrder(-1)] // 确保这个脚本在其他脚本之前执行
public class Board : MonoBehaviour
{
    public Tilemap tilemap { get; private set; } // 游戏面板的Tilemap组件
    public Piece activePiece { get; private set; } // 当前活动的方块

    public TetrominoData[] tetrominoes; // 存储各种方块形状的数据数组
    public Vector2Int boardSize = new Vector2Int(10, 20); // 游戏面板的尺寸
    public Vector3Int spawnPosition = new Vector3Int(-1, 8, 0); // 新方块生成的初始位置

    public RectInt Bounds // 游戏面板的边界区域
    {
        get
        {
            Vector2Int position = new Vector2Int(-boardSize.x / 2, -boardSize.y / 2);
            return new RectInt(position, boardSize);
        }
    }

    private void Awake()
    {
        // 获取子对象中的Tilemap组件
        tilemap = GetComponentInChildren<Tilemap>();
        // 获取子对象中的Piece组件
        activePiece = GetComponentInChildren<Piece>();

        // 初始化所有方块形状的数据
        for (int i = 0; i < tetrominoes.Length; i++)
        {
            tetrominoes[i].Initialize();
        }
    }

    private void Start()
    {
        // 游戏开始时生成第一个方块
        SpawnPiece();
    }

    public void SpawnPiece()
    {
        // 随机选择一个方块形状
        int random = Random.Range(0, tetrominoes.Length);
        TetrominoData data = tetrominoes[random];

        // 初始化并生成新的方块
        activePiece.Initialize(this, spawnPosition, data);

        // 判断生成位置是否有效
        if (IsValidPosition(activePiece, spawnPosition))
        {
            // 将方块绘制到Tilemap上
            Set(activePiece);
        } 
        else 
        {
            // 生成位置无效则游戏结束
            GameOver();
        }
    }

    public void GameOver()
    {
        // 清空所有方块
        tilemap.ClearAllTiles();

        // 可以在这里添加其他游戏结束的逻辑
    }

    public void Set(Piece piece)
    {
        // 将方块的每个单元格绘制到Tilemap上
        for (int i = 0; i < piece.cells.Length; i++)
        {
            Vector3Int tilePosition = piece.cells[i] + piece.position;
            tilemap.SetTile(tilePosition, piece.data.tile);
        }
    }

    public void Clear(Piece piece)
    {
        // 从Tilemap上清除方块的每个单元格
        for (int i = 0; i < piece.cells.Length; i++)
        {
            Vector3Int tilePosition = piece.cells[i] + piece.position;
            tilemap.SetTile(tilePosition, null);
        }
    }

    public bool IsValidPosition(Piece piece, Vector3Int position)
    {
        // 获取游戏面板的边界区域
        RectInt bounds = Bounds;

        // 判断方块的每个单元格是否都在有效位置
        for (int i = 0; i < piece.cells.Length; i++)
        {
            Vector3Int tilePosition = piece.cells[i] + position;

            // 如果单元格超出边界或已有方块占据该位置,则返回false
            if (!bounds.Contains((Vector2Int)tilePosition) || tilemap.HasTile(tilePosition)) 
            {
                return false;
            }
        }

        // 所有单元格都有效,返回true
        return true;
    }

    public void ClearLines()
    {
        // 获取游戏面板的边界区域
        RectInt bounds = Bounds;
        // 从底部开始检查
        int row = bounds.yMin;

        // 从底部到顶部逐行检查并消除已填满的行
        while (row < bounds.yMax)
        {
            if (IsLineFull(row)) // 如果当前行已填满
            {
                LineClear(row); // 清除当前行
            } 
            else 
            {
                row++; // 否则检查下一行
            }
        }
    }

    public bool IsLineFull(int row)
    {
        // 获取游戏面板的边界区域
        RectInt bounds = Bounds;

        // 检查当前行的每个单元格是否都有方块
        for (int col = bounds.xMin; col < bounds.xMax; col++)
        {
            Vector3Int position = new Vector3Int(col, row, 0);

            // 如果当前行有空单元格,则返回false
            if (!tilemap.HasTile(position)) 
            {
                return false;
            }
        }

        // 当前行已填满,返回true
        return true;
    }

    public void LineClear(int row)
    {
        // 获取游戏面板的边界区域
        RectInt bounds = Bounds;

        // 清除当前行的所有方块
        for (int col = bounds.xMin; col < bounds.xMax; col++)
        {
            Vector3Int position = new Vector3Int(col, row, 0);
            tilemap.SetTile(position, null);
        }

        // 将上方的所有行下移一行
        while (row < bounds.yMax)
        {
            for (int col = bounds.xMin; col < bounds.xMax; col++)
            {
                Vector3Int position = new Vector3Int(col, row + 1, 0);
                TileBase above = tilemap.GetTile(position);

                position = new Vector3Int(col, row, 0);
                tilemap.SetTile(position, above);
            }

            row++;
        }
    }
}
Ghost
using UnityEngine;
using UnityEngine.Tilemaps;

public class Ghost : MonoBehaviour
{
    public Tile tile; // 用于绘制“幽灵”方块的Tile
    public Board mainBoard; // 游戏的核心管理类Board的实例
    public Piece trackingPiece; // 当前正在跟踪的活动方块

    public Tilemap tilemap { get; private set; } // 用于绘制“幽灵”方块的Tilemap组件
    public Vector3Int[] cells { get; private set; } // 存储“幽灵”方块的各个单元格位置
    public Vector3Int position { get; private set; } // “幽灵”方块的位置

    private void Awake()
    {
        // 获取子对象中的Tilemap组件
        tilemap = GetComponentInChildren<Tilemap>();
        // 初始化cells数组
        cells = new Vector3Int[4];
    }

    private void LateUpdate()
    {
        // 清除上一帧的“幽灵”方块
        Clear();
        // 复制活动方块的cells数据
        Copy();
        // 模拟方块自由下落到底部的过程
        Drop();
        // 绘制新的“幽灵”方块
        Set();
    }

    private void Clear()
    {
        // 清除“幽灵”方块的每个单元格
        for (int i = 0; i < cells.Length; i++)
        {
            Vector3Int tilePosition = cells[i] + position;
            tilemap.SetTile(tilePosition, null);
        }
    }

    private void Copy()
    {
        // 复制活动方块的cells数据
        for (int i = 0; i < cells.Length; i++)
        {
            cells[i] = trackingPiece.cells[i];
        }
    }

    private void Drop()
    {
        // 获取活动方块的位置
        Vector3Int position = trackingPiece.position;
        // 当前方块的y坐标
        int current = position.y;
        // 游戏面板底部的y坐标
        int bottom = -mainBoard.boardSize.y / 2 - 1;

        // 清除活动方块
        mainBoard.Clear(trackingPiece);

        // 从当前行到底部逐行检查
        for (int row = current; row >= bottom; row--)
        {
            position.y = row; // 更新位置的y坐标

            // 判断新位置是否有效
            if (mainBoard.IsValidPosition(trackingPiece, position))
            {
                this.position = position; // 更新“幽灵”方块的位置
            } 
            else 
            {
                break; // 无效位置则停止下落
            }
        }

        // 重新绘制活动方块
        mainBoard.Set(trackingPiece);
    }

    private void Set()
    {
        // 绘制“幽灵”方块的每个单元格
        for (int i = 0; i < cells.Length; i++)
        {
            Vector3Int tilePosition = cells[i] + position;
            tilemap.SetTile(tilePosition, tile);
        }
    }
}
Piece
using UnityEngine;

public class Piece : MonoBehaviour
{
    public Board board { get; private set; } // 所属的游戏核心管理类Board的实例
    public TetrominoData data { get; private set; } // 存储方块形状等数据
    public Vector3Int[] cells { get; private set; } // 存储方块的各个单元格位置
    public Vector3Int position { get; private set; } // 方块的位置
    public int rotationIndex { get; private set; } // 方块的旋转索引

    public float stepDelay = 1f; // 方块自动下落的时间间隔
    public float moveDelay = 0.1f; // 移动操作的时间间隔
    public float lockDelay = 0.5f; // 方块锁定的时间延迟

    private float stepTime; // 下一次自动下落的时间
    private float moveTime; // 下一次移动操作的时间
    private float lockTime; // 方块锁定的时间

    public void Initialize(Board board, Vector3Int position, TetrominoData data)
    {
        // 初始化方块的数据
        this.data = data;
        this.board = board;
        this.position = position;

        rotationIndex = 0; // 初始化旋转索引
        stepTime = Time.time + stepDelay; // 初始化下一次自动下落的时间
        moveTime = Time.time + moveDelay; // 初始化下一次移动操作的时间
        lockTime = 0f; // 初始化方块锁定的时间

        if (cells == null) // 如果cells数组为空,则初始化
        {
            cells = new Vector3Int[data.cells.Length];
        }

        for (int i = 0; i < cells.Length; i++) // 复制方块的初始单元格位置
        {
            cells[i] = (Vector3Int)data.cells[i];
        }
    }

    private void Update()
    {
        // 每帧清除方块,以便重新绘制
        board.Clear(this);

        // 使用计时器允许玩家在方块锁定前进行调整
        lockTime += Time.deltaTime;

        // 处理旋转操作
        if (Input.GetKeyDown(KeyCode.Q)) 
        {
            Rotate(-1); // 逆时针旋转
        } 
        else if (Input.GetKeyDown(KeyCode.E)) 
        {
            Rotate(1); // 顺时针旋转
        }

        // 处理硬下落操作
        if (Input.GetKeyDown(KeyCode.Space)) 
        {
            HardDrop();
        }

        // 处理移动操作,但只有在moveTime时间后才允许移动
        if (Time.time > moveTime) 
        {
            HandleMoveInputs();
        }

        // 每隔stepDelay时间自动下落一行
        if (Time.time > stepTime) 
        {
            Step();
        }

        // 重新绘制方块
        board.Set(this);
    }

    private void HandleMoveInputs()
    {
        // 处理软下落操作
        if (Input.GetKey(KeyCode.S)) 
        {
            if (Move(Vector2Int.down)) 
            {
                // 更新stepTime以防止双倍移动
                stepTime = Time.time + stepDelay;
            }
        }

        // 处理左右移动操作
        if (Input.GetKey(KeyCode.A)) 
        {
            Move(Vector2Int.left);
        } 
        else if (Input.GetKey(KeyCode.D)) 
        {
            Move(Vector2Int.right);
        }
    }

    private void Step()
    {
        // 更新下一次自动下落的时间
        stepTime = Time.time + stepDelay;

        // 下落一行
        Move(Vector2Int.down);

        // 如果方块锁定时间超过lockDelay,则锁定方块
        if (lockTime >= lockDelay) 
        {
            Lock();
        }
    }

    private void HardDrop()
    {
        // 快速下落到最底部
        while (Move(Vector2Int.down)) 
        {
            continue;
        }

        // 锁定方块
        Lock();
    }

    private void Lock()
    {
        // 将方块绘制到Tilemap上
        board.Set(this);
        // 消除已填满的行
        board.ClearLines();
        // 生成新的方块
        board.SpawnPiece();
    }

    private bool Move(Vector2Int translation)
    {
        // 计算新位置
        Vector3Int newPosition = position;
        newPosition.x += translation.x;
        newPosition.y += translation.y;

        // 判断新位置是否有效
        bool valid = board.IsValidPosition(this, newPosition);

        // 如果新位置有效,则更新方块位置
        if (valid) 
        {
            position = newPosition;
            // 更新下一次移动操作的时间
            moveTime = Time.time + moveDelay;
            // 重置锁定时间
            lockTime = 0f;
        }

        return valid; // 返回新位置是否有效
    }

    private void Rotate(int direction)
    {
        // 保存当前旋转索引,以便在旋转失败时恢复
        int originalRotation = rotationIndex;

        // 更新旋转索引并应用旋转矩阵
        rotationIndex = Wrap(rotationIndex + direction, 0, 4);
        ApplyRotationMatrix(direction);

        // 如果墙踢测试失败,则恢复原来的旋转状态
        if (!TestWallKicks(rotationIndex, direction)) 
        {
            rotationIndex = originalRotation;
            ApplyRotationMatrix(-direction);
        }
    }

    private void ApplyRotationMatrix(int direction)
    {
        // 获取旋转矩阵
        float[] matrix = Data.RotationMatrix;

        // 使用旋转矩阵对方块的每个单元格位置进行旋转计算
        for (int i = 0; i < cells.Length; i++) 
        {
            Vector3 cell = cells[i];

            int x, y;

            switch (data.tetromino) 
            {
                case Tetromino.I:
                case Tetromino.O:
                    // “I”和“O”型方块的旋转中心有偏移
                    cell.x
					cell.y -= 0.5f;
                    x = Mathf.CeilToInt((cell.x * matrix[0] * direction) + (cell.y * matrix[1] * direction));
                    y = Mathf.CeilToInt((cell.x * matrix[2] * direction) + (cell.y * matrix[3] * direction));
                    break;

                default:
                    x = Mathf.RoundToInt((cell.x * matrix[0] * direction) + (cell.y * matrix[1] * direction));
                    y = Mathf.RoundToInt((cell.x * matrix[2] * direction) + (cell.y * matrix[3] * direction));
                    break;
            }

            cells[i] = new Vector3Int(x, y, 0);
        }
    }

    private bool TestWallKicks(int rotationIndex, int rotationDirection)
    {
        // 获取墙踢数据的索引
        int wallKickIndex = GetWallKickIndex(rotationIndex, rotationDirection);

        // 尝试每个墙踢位置
        for (int i = 0; i < data.wallKicks.GetLength(1); i++)
        {
            Vector2Int translation = data.wallKicks[wallKickIndex, i];

            // 如果移动到墙踢位置有效,则返回true
            if (Move(translation)) 
            {
                return true;
            }
        }

        // 所有墙踢位置都无效,返回false
        return false;
    }

    private int GetWallKickIndex(int rotationIndex, int rotationDirection)
    {
        // 计算墙踢数据的索引
        int wallKickIndex = rotationIndex * 2;

        if (rotationDirection < 0) 
        {
            wallKickIndex--;
        }

        // 确保索引在有效范围内
        return Wrap(wallKickIndex, 0, data.wallKicks.GetLength(0));
    }

    private int Wrap(int input, int min, int max)
    {
        // 循环取值,确保输入值在指定范围内
        if (input < min) 
        {
            return max - (min - input) % (max - min);
        } 
        else 
        {
            return min + (input - min) % (max - min);
        }
    }
}
Data
using System.Collections.Generic;
using UnityEngine;

public static class Data
{
    // 旋转矩阵的cos值,用于计算旋转
    // cos(90度) = 0,用于旋转矩阵的计算
    public static readonly float cos = Mathf.Cos(Mathf.PI / 2f);

    // 旋转矩阵的sin值,用于计算旋转
    // sin(90度) = 1,用于旋转矩阵的计算
    public static readonly float sin = Mathf.Sin(Mathf.PI / 2f);

    // 旋转矩阵,用于将方块旋转90度
    // 旋转矩阵的形式为 [cos, sin, -sin, cos]
    // 例如,对于一个点 (x, y),旋转90度后的坐标为 (y, -x)
    public static readonly float[] RotationMatrix = new float[] { cos, sin, -sin, cos };

    // 各种方块形状的初始位置数据
    // 这些数据定义了每种方块在初始状态下的各个单元格位置
    public static readonly Dictionary<Tetromino, Vector2Int[]> Cells = new Dictionary<Tetromino, Vector2Int[]>()
    {
        // I型方块的初始位置
        { Tetromino.I, new Vector2Int[] { new Vector2Int(-1, 1), new Vector2Int(0, 1), new Vector2Int(1, 1), new Vector2Int(2, 1) } },
        // J型方块的初始位置
        { Tetromino.J, new Vector2Int[] { new Vector2Int(-1, 1), new Vector2Int(-1, 0), new Vector2Int(0, 0), new Vector2Int(1, 0) } },
        // L型方块的初始位置
        { Tetromino.L, new Vector2Int[] { new Vector2Int(1, 1), new Vector2Int(-1, 0), new Vector2Int(0, 0), new Vector2Int(1, 0) } },
        // O型方块的初始位置
        { Tetromino.O, new Vector2Int[] { new Vector2Int(0, 1), new Vector2Int(1, 1), new Vector2Int(0, 0), new Vector2Int(1, 0) } },
        // S型方块的初始位置
        { Tetromino.S, new Vector2Int[] { new Vector2Int(0, 1), new Vector2Int(1, 1), new Vector2Int(-1, 0), new Vector2Int(0, 0) } },
        // T型方块的初始位置
        { Tetromino.T, new Vector2Int[] { new Vector2Int(0, 1), new Vector2Int(-1, 0), new Vector2Int(0, 0), new Vector2Int(1, 0) } },
        // Z型方块的初始位置
        { Tetromino.Z, new Vector2Int[] { new Vector2Int(-1, 1), new Vector2Int(0, 1), new Vector2Int(0, 0), new Vector2Int(1, 0) } },
    };

    // “I”型方块的墙踢数据
    // 墙踢数据用于在方块旋转时,如果旋转后的位置无效,尝试移动到其他位置
    private static readonly Vector2Int[,] WallKicksI = new Vector2Int[,]
    {
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-2, 0), new Vector2Int(1, 0), new Vector2Int(-2, -1), new Vector2Int(1, 2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(2, 0), new Vector2Int(-1, 0), new Vector2Int(2, 1), new Vector2Int(-1, -2) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(2, 0), new Vector2Int(-1, 2), new Vector2Int(2, -1) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(-2, 0), new Vector2Int(1, -2), new Vector2Int(-2, 1) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(2, 0), new Vector2Int(-1, 0), new Vector2Int(2, 1), new Vector2Int(-1, -2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-2, 0), new Vector2Int(1, 0), new Vector2Int(-2, -1), new Vector2Int(1, 2) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(-2, 0), new Vector2Int(1, -2), new Vector2Int(-2, 1) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(2, 0), new Vector2Int(-1, 2), new Vector2Int(2, -1) },
    };

    // 其他类型方块的墙踢数据
    // 这些数据用于在方块旋转时,如果旋转后的位置无效,尝试移动到其他位置
    private static readonly Vector2Int[,] WallKicksJLOSTZ = new Vector2Int[,]
    {
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(-1, 1), new Vector2Int(0, -2), new Vector2Int(-1, -2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(1, -1), new Vector2Int(0, 2), new Vector2Int(1, 2) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(1, -1), new Vector2Int(0, 2), new Vector2Int(1, 2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(-1, 1), new Vector2Int(0, -2), new Vector2Int(-1, -2) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(1, 1), new Vector2Int(0, -2), new Vector2Int(1, -2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(-1, -1), new Vector2Int(0, 2), new Vector2Int(-1, 2) },
        // 旋转方向为逆时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(-1, 0), new Vector2Int(-1, -1), new Vector2Int(0, 2), new Vector2Int(-1, 2) },
        // 旋转方向为顺时针时的墙踢数据
        { new Vector2Int(0, 0), new Vector2Int(1, 0), new Vector2Int(1, 1), new Vector2Int(0, -2), new Vector2Int(1, -2) },
    };

    // 墙踢数据字典,存储每种方块的墙踢数据
    // 这个字典用于在方块旋转时,如果旋转后的位置无效,尝试移动到其他位置
    public static readonly Dictionary<Tetromino, Vector2Int[,]> WallKicks = new Dictionary<Tetromino, Vector2Int[,]>
    {
        // I型方块的墙踢数据
        { Tetromino.I, WallKicksI },
        // J型方块的墙踢数据
        { Tetromino.J, WallKicksJLOSTZ },
        // L型方块的墙踢数据
        { Tetromino.L, WallKicksJLOSTZ },
        // O型方块的墙踢数据
        { Tetromino.O, WallKicksJLOSTZ },
        // S型方块的墙踢数据
        { Tetromino.S, WallKicksJLOSTZ },
        // T型方块的墙踢数据
        { Tetromino.T, WallKicksJLOSTZ },
        // Z型方块的墙踢数据
        { Tetromino.Z, WallKicksJLOSTZ },
    };
}
Tetromino
using UnityEngine;
using UnityEngine.Tilemaps;

// 定义方块形状的枚举
// 这个枚举定义了游戏中可能出现的七种方块形状
public enum Tetromino
{
    I, // I型方块
    J, // J型方块
    L, // L型方块
    O, // O型方块
    S, // S型方块
    T, // T型方块
    Z  // Z型方块
}

// 定义方块数据的结构体
// 这个结构体用于存储方块的相关数据,包括方块的Tile、形状类型、单元格位置数据和墙踢数据
[System.Serializable]
public struct TetrominoData
{
    // 用于绘制方块的Tile
    // 这个Tile定义了方块的外观,包括颜色和纹理
    public Tile tile;

    // 方块的形状类型
    // 这个枚举值定义了方块的形状,例如I型、J型等
    public Tetromino tetromino;

    // 方块的单元格位置数据
    // 这个数组定义了方块在初始状态下的各个单元格位置
    public Vector2Int[] cells { get; private set; }

    // 方块的墙踢数据
    // 这个二维数组定义了方块在旋转时的墙踢数据
    public Vector2Int[,] wallKicks { get; private set; }

    // 初始化方块数据
    // 这个方法用于初始化方块的单元格位置数据和墙踢数据,从Data类中获取
    public void Initialize()
    {
        // 从Data类中获取对应形状的单元格位置数据
        cells = Data.Cells[tetromino];
        // 从Data类中获取对应形状的墙踢数据
        wallKicks = Data.WallKicks[tetromino];
    }
}

你可能感兴趣的:(Unity,unity,c#,游戏引擎)