这篇文章将为大家详细讲解有关Unity制作俄罗斯方块游戏的方法,文章内容质量较高,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
一、使用SpriteRenderer作为小方块图片,创建7种由若干个小方块图片组成的方块,如下图:
Shape-1是一个空物体,其子物体Block、Block(1)、Block(2)、Block(3)是小方块,Pivot是锚点(空物体),锚点用作于旋转中心点,方块旋转是以它为中心进行旋转的。旋转方块的代码如下:
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数组后,就可以管理全部小方块,实现判断整行满并消除行,方块是否可以下落一行,方块是否可以变形,方块是否可以水平移动等功能,下面贴出相关代码。
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(); } }
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; } }
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是处理游戏结束之类的逻辑。
以上就是Unity制作俄罗斯方块游戏的方法,看完之后是否有所收获呢?如果想了解更多相关内容,欢迎关注亿速云行业资讯,感谢各位的阅读。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。