俄罗斯方块
程序员文章站
2023-12-23 19:51:21
...
原理思想
(1)对象池(提前准备对象进行显示隐藏操作来完成逻辑)
(2)矩阵思想(方方正正的地图游戏)、笛卡尔坐标系思想(利用笛卡尔坐标系计算逻辑数据)
(3)转换思想 数据转换为对象状态来进行游戏状态切换 数据为0(可移动区域) 1(落地的方块) 2(地图边界) 3(可移动方块)
(4)策略思想 大局观 细节剖析 整合逻辑 进行策略
细节总结
- (1)产生地图 => 0(可移动区域) 1(落地的方块) 2(地图边界) 3(可移动方块)
- (2)随机方块 => 一个存放方块的索引值->随机索引->方块的局部坐标偏移值和基础点进行数学运算显示
- (3)方块移动 => 左、右、下->先判断移动后的数据如果是可以移动进行方块移动(交换数据刷新地图)
- (4)方块旋转 => 先旋转后的数据 可以旋转 交换数据刷新地图 注意:此功能存在相对多的细节问题
- (5)方块消除 => 遍历矩阵判断每一行是否填满1 如果是进行消除操作注意多行消除判断
原理图
矩阵原理图
坐标关系原理图
方块局部坐标原理图
旋转原理图
C#代码
// An highlighted block
#region 数据模块(结构体枚举等放在最上头)
//列举出方块的移动方向
public enum MoveDir
{
left = -1, //左移
right = 1, //右移
up, //旋转
down, //加速和正常向下移动
}
public MoveDir MyMoveDir;
//生成方块:I L J Z S O T 七种 每种类型都有属于自己的 平面坐标系 零点为方块的中心点(0,0)
//方块基础点 x=20/2 y=30-4 (10,26)
//----方块局部坐标---- 对应七个方块图形每个图形对应四个局部位置
struct CubeLocalPos
{
public CubeLocalPos(int _X, int _Y)
{
X = _X;
Y = _Y;
}
//X偏移值
public int X;
//Y偏移值
public int Y;
}
//七种图形 每种图形对应四个局部坐标 将来我们会拿局部坐标跟全局坐标进行转换
CubeLocalPos[,] MyCubeLocalPos = new CubeLocalPos[7, 4]
{
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(0,2),new CubeLocalPos(1,0)}, //L
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(0,2),new CubeLocalPos(-1,0)}, //J
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(-1,1),new CubeLocalPos(1,0)}, //Z
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(1,1),new CubeLocalPos(-1,0)}, //S
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(-1,0),new CubeLocalPos(1,0)}, //T
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(0,2),new CubeLocalPos(0,-1)}, //I
{new CubeLocalPos(0,0),new CubeLocalPos(0,1),new CubeLocalPos(1,1),new CubeLocalPos(1,0)}, //O
};
//地图
GameObject[,] MyCubes = new GameObject[Height, Width];
//地图数据
int[,] MyCubeValue = new int[Height, Width];
CubeLocalPos[] T_BeforePos = new CubeLocalPos[4];
//高
const int Height = 30;
//宽
const int Width = 20;
//限制我们初始化数据
bool IsOnnceRota = true;
//全局基础点
int X_Gold = 10;
int Y_Gold = 26;
//随机的方块类型
int Ran_IndexType = 0;
float StartTime;
float Down_Speed = 0.3f;
#endregion
#region 逻辑模块
#region 脚本生命周期
private void Start()
{
//生成地图
TetrisMap();
//随机方块类型
MyCubeType();
//移动协程(Y轴方向)
StartCoroutine(MoveY());
}
private void Update()
{
CtrlMove();
}
#endregion
#region 换值 地图刷新 恢复数据
//换值(方块跟地图数据换值)
void ExchangeValue(int _ExchangeValue)
{
//四个元素
for (int i = 0; i < 4; i++)
{
//我们要交换的全局坐标的X Y 全局基础点加上方块的局部坐标点=>方块转换为全局坐标后的坐标点=>坐标点换值
//加上我们随机产生的方块的局部坐标点的X
int X = X_Gold + MyCubeLocalPos[Ran_IndexType, i].X;
//加上我们随机产生的方块的局部坐标点的Y
int Y = Y_Gold + MyCubeLocalPos[Ran_IndexType, i].Y;
//刷新值
MyCubeValue[Y, X] = _ExchangeValue;
}
}
//地图刷新 0隐藏其他数字显示
void MapFresh()
{
//遍历矩阵
for (int Y = 0; Y < Height; Y++)
{
for (int X = 0; X < Width; X++)
{
//如果矩阵的值为0就隐藏
if (MyCubeValue[Y, X] == 0)
{
MyCubes[Y, X].SetActive(false);
}
else
{
//否则显示
MyCubes[Y, X].SetActive(true);
}
}
}
}
//恢复数据
void IniCubeTypePos()
{
//旋转之后才可以收集初始化数据
if (IsOnnceRota == false)
{
//遍历方块恢复数据
for (int i = 0; i < 4; i++)
{
MyCubeLocalPos[Ran_IndexType, i] = T_BeforePos[i];
}
}
}
#endregion
#region 协程
IEnumerator MoveY()
{
yield return new WaitForSeconds(0.02f);//保险
while (true)
{
//如果可以移动说明方块没有停止 否则那就要产生新的方块
if (IsCanMove(MoveDir.down))
{
//原先位置的数据归0
ExchangeValue(0);
//基础点Y_Gold-1
Y_Gold--;
//移动后的位置数据变为3
ExchangeValue(3);
}
else
{
//不能移动的位置数据变为1
ExchangeValue(1);
//判断消除
Eliminate();
//初始化方块偏移值
IniCubeTypePos();
//是否是第一次旋转变为true
IsOnnceRota = true;
//更新方块
MyCubeType();
}
//刷新
MapFresh();
yield return new WaitForSeconds(Down_Speed);
}
}
#endregion
#region 俄罗斯方块地图生成
void TetrisMap()
{
//生成地图矩阵 单个元素是Cube
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
//创建cube盒子 存在MyCubes里面
MyCubes[y, x] = GameObject.CreatePrimitive(PrimitiveType.Cube);
//给盒子添加颜色 随机颜色
float r = Random.Range(0, 1f);
float g = Random.Range(0, 1f);
float b = Random.Range(0, 1f);
//中间变量存我们的随机颜色
Color T_Color = new Color(r, g, b);
//边界赋值2 x=0 x=Width-1 y=0 y= Height-1
if (x == 0 || x == Width - 1 || y == 0 || y == Height - 1)
{
//2代表边界
MyCubeValue[y, x] = 2;
MyCubes[y, x].GetComponent<MeshRenderer>().material.color = Color.yellow;
}
else
{
//0代表地图
MyCubeValue[y, x] = 0;
MyCubes[y, x].GetComponent<MeshRenderer>().material.color = T_Color;
MyCubes[y, x].SetActive(false);
}
//给盒子一个位置
MyCubes[y, x].transform.position = new Vector3(x, y);
}
}
}
#endregion
#region 俄罗斯方块地图生成
void TetrisMap()
{
//生成地图矩阵 单个元素是Cube
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
//创建cube盒子 存在MyCubes里面
MyCubes[y, x] = GameObject.CreatePrimitive(PrimitiveType.Cube);
//给盒子添加颜色 随机颜色
float r = Random.Range(0, 1f);
float g = Random.Range(0, 1f);
float b = Random.Range(0, 1f);
//中间变量存我们的随机颜色
Color T_Color = new Color(r, g, b);
//边界赋值2 x=0 x=Width-1 y=0 y= Height-1
if (x == 0 || x == Width - 1 || y == 0 || y == Height - 1)
{
//2代表边界
MyCubeValue[y, x] = 2;
MyCubes[y, x].GetComponent<MeshRenderer>().material.color = Color.yellow;
}
else
{
//0代表地图
MyCubeValue[y, x] = 0;
MyCubes[y, x].GetComponent<MeshRenderer>().material.color = T_Color;
MyCubes[y, x].SetActive(false);
}
//给盒子一个位置
MyCubes[y, x].transform.position = new Vector3(x, y);
}
}
}
#endregion
#region 随机方块
//产生的方块值为3 矩阵原先位置的值为0 先获取矩阵全局坐标的位置上的0值=>把我们随机方块后方块四个元素对应的矩阵坐标对应的0值变为3值
void MyCubeType()
{
//初始化基础点
X_Gold = 10; Y_Gold = 26;
//产生随机方块
Ran_IndexType = Random.Range(0, 7);
//给方块的位置赋值3
ExchangeValue(3);
}
#endregion
#region 方块移动(先判断后移动)
//移动=>判断是否是需要判断的方块元素=>是:我们假设移动进行判断 否:不需要判断=>如果可以移动我们交换数据进行地图刷新
bool IsCanMove(MoveDir JudgeDir)
{
bool T_IsCanMove = true;
//依次判断
for (int i = 0; i < 4; i++) //方快的四个元素遍历
{
#region 假设移动 准备工作 准备我们假设移动后的坐标值
//加上我们随机产生的方块的局部坐标点的X
int X = X_Gold + MyCubeLocalPos[Ran_IndexType, i].X; //10+偏移值X
//加上我们随机产生的方块的局部坐标点的Y
int Y = Y_Gold + MyCubeLocalPos[Ran_IndexType, i].Y; //26+偏移值Y
switch (JudgeDir)
{
case MoveDir.down:
Y = Y - 1;
break;
case MoveDir.left:
X = X - 1;
break;
case MoveDir.right:
X = X + 1;
break;
}
#endregion
bool IsNeedJudgeElement = true;
//每个元素需要跟除了自身以外的三个元素判断 判断是否是需要判断的方块
for (int j = 0; j < 4; j++)
{
if (j != i)
{
//加上我们随机产生的方块的局部坐标点的X
int J_X = X_Gold + MyCubeLocalPos[Ran_IndexType, j].X;
//加上我们随机产生的方块的局部坐标点的Y
int J_Y = Y_Gold + MyCubeLocalPos[Ran_IndexType, j].Y;
//假设移动后的操作要跟我们移动后的方块位置的数据进行比较
//之前的元素的值是3 之后的值需要跟之前的值判断如果一样说明移动后的位置的值是自身
if (MyCubeValue[Y, X] == MyCubeValue[J_Y, J_X])
{
IsNeedJudgeElement = IsNeedJudgeElement && false;
break;
}
}
}
if (IsNeedJudgeElement)
{
//数据移动后的位置是1 或者是2 则不可移动
if (MyCubeValue[Y, X] == 1 || MyCubeValue[Y, X] == 2)
{
T_IsCanMove = T_IsCanMove && false;
}
}
}
return T_IsCanMove;
}
void CtrlMove()
{
if (Input.GetKey(KeyCode.A)) { MoveX(MoveDir.left, 0.06f); }
if (Input.GetKey(KeyCode.D)) { MoveX(MoveDir.right, 0.06f); }
if (Input.GetKey(KeyCode.S))
{
Down_Speed = 0.01f;
}
else
{
Down_Speed = 0.3f;
}
if (Input.GetKeyDown(KeyCode.W) && Ran_IndexType != 6)
{
Rota();
}
}
//X方向左右移动
void MoveX(MoveDir moveDir, float Move_Time)
{
StartTime += Time.deltaTime;
if (StartTime >= Move_Time)
{
//是否可以移动
if (IsCanMove(moveDir))
{
//时间归零
StartTime = 0;
//全局坐标X_Gold变化来移动
ExchangeValue(0);
//数据移动
X_Gold = X_Gold + (int)moveDir;
//移动后的数据变为3
ExchangeValue(3);
//刷新地图
MapFresh();
}
}
}
#endregion
#region 方块旋转(先判断后旋转)
/// <summary>
/// 旋转需要注意的细节
/// 1. O形方块不需要旋转 则需要加以限制
/// 2. 旋转需要注意边界会出现数组越界
/// 3. 旋转需要记录第一次旋转之前的初始数据,以便我们下次生成方块进行初始化
/// 4. 收集初始化数据需要在旋转后作为前提收集否则收集的数据为0则会出现一个方块的bug
/// </summary>
//坐标转换 Y之后 = -X之前 X之后 = Y之前
CubeLocalPos GetRotaPos(int X, int Y)
{
int T = Y; Y = -X; X = T;
CubeLocalPos T_CubeLocalPos = new CubeLocalPos();
T_CubeLocalPos.X = X;
T_CubeLocalPos.Y = Y;
return T_CubeLocalPos;
}
//判断是否可以旋转
bool IsCanRota()
{
bool T_IsCanRota = true;
//处理过程
for (int i = 0; i < 4; i++)
{
//获取旋转之后的X Y
int X = X_Gold + GetRotaPos(MyCubeLocalPos[Ran_IndexType, i].X, MyCubeLocalPos[Ran_IndexType, i].Y).X;
int Y = Y_Gold + GetRotaPos(MyCubeLocalPos[Ran_IndexType, i].X, MyCubeLocalPos[Ran_IndexType, i].Y).Y;
//判断X Y代表的数组索引是否越界 越界就不能旋转返回false
if (Y < 0 || X > Width - 1 || X < 0)
{
T_IsCanRota = T_IsCanRota && false;
return T_IsCanRota;
}
//判断旋转后的数据对应的索引值是否是1或者是2 是返回false
if (MyCubeValue[Y, X] == 1 || MyCubeValue[Y, X] == 2)
{
T_IsCanRota = T_IsCanRota && false;
}
}
return T_IsCanRota;
}
//旋转
void Rota()
{
if (IsCanRota())
{
//原始位置的数据变为0
ExchangeValue(0);
//处理过程
for (int i = 0; i < 4; i++)
{
//收集初始数据
if (IsOnnceRota) { T_BeforePos[i] = MyCubeLocalPos[Ran_IndexType, i]; }
//把原来的位置变为旋转后的位置
MyCubeLocalPos[Ran_IndexType, i] = GetRotaPos(MyCubeLocalPos[Ran_IndexType, i].X, MyCubeLocalPos[Ran_IndexType, i].Y);
}
IsOnnceRota = false;
//数据变为3
ExchangeValue(3);
//更新地图
MapFresh();
}
}
#endregion
#region 方块消除
//消除方块
void Eliminate()
{
//遍历矩阵 判断每一行的数据 如果都是1则判定为可消除行
for (int Y = 1; Y < Height - 1 - 1; Y++)
{
bool IsCanEliminate = true;
for (int X = 1; X < Width - 1 - 1; X++)
{
if (MyCubeValue[Y, X] != 1)
{
IsCanEliminate = IsCanEliminate && false;
}
}
if (IsCanEliminate)
{
//把可消除行变为0
for (int x = 0; x < Width - 1 - 1; x++)
{
MyCubeValue[Y, x] = 0;
}
//填补处理
for (int y = Y; y < Height - 1 - 1; y++)
{
for (int x = 0; x < Width - 1 - 1; x++)
{
MyCubeValue[y, x] = MyCubeValue[y + 1, x];
}
}
//多行消除判断
Y--;
continue;
}
}
}
#endregion
优化方向
- (1)方块可以添加材质或者shader方面主要是添加边框
- (2)可以添加UI进行得分展示
- (3)细节操作体验优化
- (4)代码优化 分逻辑整合优化