Unity实现俄罗斯方块

作者:两水先木示 时间:2021-05-28 13:37:08 

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

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

Unity实现俄罗斯方块

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是处理游戏结束之类的逻辑。

更多俄罗斯方块精彩文章请点击专题:俄罗斯方块游戏集合 进行学习。

更多有趣的经典小游戏实现专题,分享给大家:

C++经典小游戏汇总

python经典小游戏汇总

python俄罗斯方块游戏集合

JavaScript经典游戏 玩不停

javascript经典小游戏汇总

来源:https://blog.csdn.net/qq_39574690/article/details/89221233

标签:Unity,俄罗斯方块
0
投稿

猜你喜欢

  • ClassLoader类加载源码解析

    2023-11-25 18:17:09
  • springBoot加入thymeleaf模板的方式

    2023-11-25 14:31:23
  • Java实现显示指定类型的文件

    2021-10-26 11:30:37
  • java 图片验证码的实现代码

    2023-11-09 13:33:52
  • Flutter瀑布流仿写原生的复用机制详解

    2023-06-20 17:02:08
  • flutter中的资源和图片加载示例详解

    2023-08-24 13:19:39
  • Java 中解决Unsupported major.minor version 51.0的问题

    2022-07-22 03:53:08
  • 使用Springboot根据配置文件动态注入接口实现类

    2022-11-18 06:56:45
  • Java中StringUtils与CollectionUtils和ObjectUtil概念讲解

    2023-11-29 07:45:38
  • javac final变量未赋值检测案例讲解

    2023-09-29 04:25:17
  • Android 实现可任意拖动的悬浮窗功能(类似悬浮球)

    2023-08-07 10:19:05
  • java集合与数组的相同点和不同点

    2022-07-19 03:13:53
  • java与JSON数据的转换实例详解

    2022-07-03 22:48:25
  • Android利用传感器实现微信摇一摇功能

    2023-07-12 05:05:18
  • SpringBoot系列教程之防重放与操作幂等

    2021-12-07 11:51:12
  • Java 中责任链模式实现的三种方式

    2023-11-08 14:32:31
  • 学习Java设计模式之观察者模式

    2023-07-03 05:17:46
  • Springboot Thymeleaf模板文件调用Java类静态方法

    2023-11-25 05:34:47
  • idea输入sout无法自动补全System.out.println()的问题

    2023-11-28 21:34:03
  • java服务器的简单实现过程记录

    2023-01-18 06:44:34
  • asp之家 软件编程 m.aspxhome.com