服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - Unity实现俄罗斯方块

Unity实现俄罗斯方块

2022-09-16 13:42两水先木示 C#

这篇文章主要为大家详细介绍了Unity实现俄罗斯方块,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了Unity实现俄罗斯方块的具体代码,供大家参考,具体内容如下

一、使用SpriteRenderer作为小方块图片,创建7种由若干个小方块图片组成的方块,如下图:

Unity实现俄罗斯方块

Shape-1是一个空物体,其子物体Block、Block(1)、Block(2)、Block(3)是小方块,Pivot是锚点(空物体),锚点用作于旋转中心点,方块旋转是以它为中心进行旋转的。旋转方块的代码如下:

?
1
transform.RotateAround(pivot.position, Vector3.forward, -90);

二、通过测试划分出一个俄罗斯方块操作区域(游戏区域),在z轴相同 的xy平面上的 每个坐标作为二维数组map的索引,如:map[1,0]保存(1,0,z)坐标上的小方块物体的Transform组件,游戏区域上x是横轴、y是纵轴,左下角的小方块坐标(0,0),右上角是的小方块坐标(x-1,y-1)。这样,将游戏区域划分成 一个map数组后,就可以管理全部小方块,实现判断整行满并消除行,方块是否可以下落一行,方块是否可以变形,方块是否可以水平移动等功能,下面贴出相关代码。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Model : MonoBehaviour
{
 public const int NORMAL_ROWS = 20;//最大行数(这个也是用于判断游戏结束逻辑)
 public const int MAX_ROWS = 23;//最大行数+3(用于判断游戏结束逻辑)
 public const int MAX_COLUMNS = 10;//最大列数
 private Transform[,] map = new Transform[MAX_COLUMNS, MAX_ROWS];//地图数组map
 //下面这些不用管,是UI的一些参数
 private int score = 0;
 private int highScore = 0;
 private int numbersGame = 0;
 
 public int Score { get { return score; } }
 public int HighScore { get { return highScore; } }
 public int NumbersGame { get { return numbersGame; } }
 
 public bool isDataUpdate = false;
 
 private void Awake()
 {
 LoadData();
 }
 //检查一个方块(如Sharp-1)的位置是否合理,参数t是方块的Transform
 public bool IsValidMapPosition(Transform t)
 {
 foreach (Transform child in t)
 {
 //子物体带"Block"字符的标签tag都是"Block",它们就是小方块
 if (child.tag != "Block") continue;
 //如果遍历到的子物体是小方块的话进行下面的逻辑判断
 Vector2 pos = child.position.Round();//Round是我扩展的方法,==贴出来.
 //若不在地图范围内,则不可用
 if (IsInsideMap(pos) == false) return false;
 //有其他图形占用着,则不可用
 if (map[(int)pos.x, (int)pos.y] != null)
 {
 return false;//不可用
 }
 }
 return true;
 }
 //不在地图范围内返回false,在返回true
 private bool IsInsideMap(Vector2 pos)
 {
 return pos.x >= 0 && pos.x < MAX_COLUMNS && pos.y >= 0;
 }
 /// <summary>
 /// 在方块进行检查位置不合理后,会固定自身位置 放入map[,]保存小方块Block的Transform组件
 /// </summary>
 /// <param name="t">shape的父物体transform</param>
 public bool PlaceShape(Transform t)
 {
 foreach (Transform child in t)
 {
 if (child.tag != "Block") continue;
 Vector2 pos = child.position.Round();
 map[(int)pos.x, (int)pos.y] = child;
 }
 //放进map后需要检查整张地图是否需要消行
 return CheckMap();
 }
 /// <summary>
 /// 检查地图 是否需要消除行
 /// </summary>
 private bool CheckMap()
 {
 int count = 0;//消行数
 for (int i = 0; i < MAX_ROWS; i++)
 {
 //若第i行填满小方块
 if (CheckIsRowFull(i))
 {
 count++;
 DeleteRow(i);//消除第i行
 MoveDownRowsAbove(i + 1);//第i+1行及其上方的所有行向下移动一行(从下到上移动)
 i--; //因为上面的那一行已经移动下来了,这里将i--,目的是为了继续检查上面那一行是否满,因为for会将i++,这样就抵消了i++,让i继续保持原样
 }
 }
 //下面不用管,是UI部分的逻辑
 if (count > 0)
 {
 score += count * 100;
 if(score>highScore)
 {
 highScore = score;
 }
 isDataUpdate = true;
 return true;
 }
 else
 {
 return false;
 }
 
 }
 //检查第row行是否满
 private bool CheckIsRowFull(int row)
 {
 for (int i = 0; i < MAX_COLUMNS; i++)
 {
 if (map[i, row] == null)
 {
 return false;
 }
 }
 return true;
 }
 private void DeleteRow(int row)
 {
 for (int i = 0; i < MAX_COLUMNS; i++)
 {
 Destroy(map[i, row].gameObject);
 map[i, row] = null;
 }
 }
 //将索引row行至最上面的行都往下移动一行(从下开始移动)
 public void MoveDownRowsAbove(int row)
 {
 for (int i = row; i < MAX_ROWS; i++)
 {
 MoveDownRow(i);
 }
 }
 //将row行 下移一行,处理了逻辑位置 和 物体位置
 private void MoveDownRow(int row)
 {
 for (int i = 0; i < MAX_COLUMNS; i++)
 {
 if (map[i, row] != null)
 {
 map[i, row - 1] = map[i, row];//这里是逻辑上的移动
 map[i, row] = null;
 map[i, row - 1].position += new Vector3(0, -1, 0);//物理上的移动
 }
 }
 }
 //判断游戏结束逻辑
 public bool isGameOver()
 {
 for(int i= NORMAL_ROWS; i<MAX_ROWS;i++)
 {
 for(int j=0;j<MAX_COLUMNS;j++)
 {
 if (map[j, i] != null)
 {
  numbersGame++;
  SaveData();
  return true;
 }
 }
 }
 return false;
 }
 public void Restart()
 {
 for(int i=0;i<MAX_COLUMNS;i++)
 {
 for(int j=0;j<MAX_ROWS;j++)
 {
 if(map[i,j])
 {
  Destroy(map[i, j].gameObject);
  map[i, j] = null;
 }
 }
 }
 score = 0;
 }
 private void LoadData()
 {
 highScore= PlayerPrefs.GetInt("HighScore", 0);
 numbersGame = PlayerPrefs.GetInt("NumbersGame",0);
 }
 private void SaveData()
 {
 PlayerPrefs.SetInt("HighScore", highScore);
 PlayerPrefs.SetInt("NumbersGame", numbersGame);
 }
 public void ClearData()
 {
 score = 0;
 highScore = 0;
 numbersGame = 0;
 SaveData();
 }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Shape : MonoBehaviour {
 
 private GameManager gameManager;
 private Ctrl ctrl;
 private bool isPause = false;
 private bool isSpeedUp = false;
 private float timer = 0;
 private float stepTime = 0.8f;//0.8s方块下落一行
 private Transform pivot;//锚点(旋转中心点)
 private int multiple = 15;
 
 private void Awake()
 {
 pivot = transform.Find("Pivot");
 }
 
 private void Update()
 {
 if (isPause) return;
 timer += Time.deltaTime;
 if(timer>stepTime)
 {
 timer = 0;
 Fall();
 }
 InputControl();
 }
 /// <summary>
 /// 方块下落一行逻辑
 /// </summary>
 private void Fall()
 {
 //先尝试将方块下移一行
 Vector3 pos = transform.position;
 pos.y -= 1;
 transform.position = pos;
 //使用Model的方法检查方块位置是否合理,若不合理返回false进入if
 if(ctrl.model.IsValidMapPosition(this.transform)==false)
 {
 pos.y += 1;//因为当前位置是不可用的,而上一个位置肯定是可用的,所以这里移动回到上一个位置处
 transform.position = pos;
 isPause = true;//暂停当前shape的下移
 bool isLineclear = ctrl.model.PlaceShape(this.transform);//使用Model的方法固定shape,即放入map数组保存小方块信息,并且检查地图消除行
 if (isLineclear) ctrl.audioManager.PlayLineClear();//isLineclear是true代表消除行了,播放声音
 gameManager.FallDown();//设置GameManager中的currentShape=null,这样就会实例化下一个shape方块,并且更新分数UI
 return;
 }
 ctrl.audioManager.PlayDrop();//方块固定的声音
 }
 //输入控制
 private void InputControl()
 {
 // if (isSpeedUp) return;
 float h=0;
 if(Input.GetKeyDown(KeyCode.LeftArrow))
 {
 h = -1;
 }
 else if(Input.GetKeyDown(KeyCode.RightArrow))
 {
 h = 1;
 }
 if(h!=0)
 {
 //这里和Fall下移逻辑处理手法一样,不再阐述!
 Vector3 pos = transform.position;
 pos.x += h;
 transform.position = pos;
 if (ctrl.model.IsValidMapPosition(this.transform) == false)
 {
 pos.x -= h;
 transform.position = pos;
 }
 else
 {
 ctrl.audioManager.PlayControl();
 }
 }
 
 //按键盘↑对方块进行旋转(即变形)
 if(Input.GetKeyDown(KeyCode.UpArrow))
 {
 //以pivot位置为旋转中心,绕z轴旋转-90°
 transform.RotateAround(pivot.position, Vector3.forward, -90);
 //旋转后自然也要检查下位置是否合理
 if (ctrl.model.IsValidMapPosition(this.transform) == false)
 {
 //不合理,旋转90° 变回之前那样子(注意:这个过程是瞬间发生的,玩家不会看到这个变化过程!)
 transform.RotateAround(pivot.position, Vector3.forward, 90);
 }
 else
 {
 ctrl.audioManager.PlayControl();
 }
 }
 //按键盘↓会进行加速方块下移
 if(Input.GetKeyDown(KeyCode.DownArrow))
 {
 isSpeedUp = true;
 stepTime /= multiple;
 }
 }
 public void Init(Color color,Ctrl ctrl,GameManager gameManager)
 {
 this.gameManager = gameManager;
 this.ctrl = ctrl;
 foreach(Transform t in transform)
 {
 if(t.tag=="Block")
 {
 t.GetComponent<SpriteRenderer>().color = color;
 }
 }
 }
 public void Pause()
 {
 isPause = true;
 }
 public void Resume()
 {
 isPause = false;
 }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class GameManager : MonoBehaviour {
 
 private Ctrl ctrl;
 private bool isPause = true;//是否暂停状态
 private Shape currentShape = null;
 public Shape[] shapes;//shape prefabs
 public Color[] colors;//shape colors
 private Transform blockHolder;
 private void Awake()
 {
 ctrl = GetComponent<Ctrl>();
 blockHolder = transform.Find("BlockHolder");
 }
 
 void Update () {
 if(isPause)
 {
 return;
 }
 //如果当前方块为空,生成一个方块(这个currentShape是由Shape
 if (currentShape == null)
 {
 SpawnShape();
 }
 }
 /// <summary>
 /// 删除已经没有小方块Block的sharp方块物体,即childCount小于等于1的
 /// 并且判断游戏结束逻辑
 /// </summary>
 public void FallDown()
 {
 currentShape = null;//将当前方块设置为空,这一步是为了生成新的方块
 //更新UI逻辑
 if(ctrl.model.isDataUpdate)
 {
 ctrl.view.UpdateGameUI(ctrl.model.Score, ctrl.model.HighScore);
 }
 //删除已经没有小方块的sharp物体
 foreach(Transform t in blockHolder)
 {
 if(t.childCount<=1)
 {
 Destroy(t.gameObject);
 }
 }
 //判断游戏结束
 if(ctrl.model.isGameOver())
 {
 PauseGame();
 ctrl.view.ShowGameOverUI(ctrl.model.Score);
 }
 }
 public void StartGame()
 {
 isPause = false;
 if (currentShape != null)
 {
 currentShape.Resume();
 }
 }
 public void PauseGame()
 {
 isPause = true;
 if(currentShape!=null)
 {
 currentShape.Pause();
 }
 }
 /// <summary>
 /// 生成一个方块sharp
 /// </summary>
 void SpawnShape()
 {
 //随机方块类型
 int index = Random.Range(0, shapes.Length);//random create a shape
 //随机颜色
 int indexColor = Random.Range(0, colors.Length);//random shape color
 //实例化方块
 currentShape = GameObject.Instantiate(shapes[index]);
 //放入blockholder物体下
 currentShape.transform.parent = blockHolder;
 //初始化其方块颜色等工作
 currentShape.Init(colors[indexColor],ctrl,this);
 
 }
 /// <summary>
 /// 删除当前正在下落的方块
 /// </summary>
 public void ClearShape()
 {
 if(currentShape!=null)
 {
 Destroy(currentShape.gameObject);
 currentShape = null;
 }
 }
}

大致上,俄罗斯方块的核心逻辑都总结在这3个脚本,Sharp是处理了方块的移动逻辑,Model是真正的核心处理方块的位置是否合理、消行、消行后将上方方块整体下移一行等逻辑,GameManager是处理游戏结束之类的逻辑。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_39574690/article/details/89221233

延伸 · 阅读

精彩推荐