接上文.

已经实现了方块下落之后,可以意识到,平移变得非常简单.因为我们已经实现了边界检查的函数,只要把移动目标坐标放进去判断就可以了.

在UPDATE中增加如下代码

if (Input.GetKeyDown(KeyCode.RightArrow) && !CheckCollision(nowControlBlock, nowBlockPos + new Vector2(1, 0)))
{MoveBlockTo(nowControlBlock, nowBlockPos + new Vector2(1, 0));
}
if (Input.GetKeyDown(KeyCode.LeftArrow) && !CheckCollision(nowControlBlock, nowBlockPos + new Vector2(-1, 0)))
{MoveBlockTo(nowControlBlock, nowBlockPos + new Vector2(-1, 0));
}

就可以实现方块的自由左右移动.

在此基础上我们还可以添加快速下落的控制代码

if (Input.GetKeyDown(KeyCode.DownArrow))
{DoFall();
}

重构DoFall,用返回的布尔值判断DoFall后是否直接落地

public bool DoFall()
{Vector2 targetPos = nowBlockPos + new Vector2(0, 1);if(!CheckCollision(nowControlBlock,targetPos)){MoveBlockTo(nowControlBlock, targetPos);return true;}else{FallGround(nowControlBlock);NextBlock();return false;}
}

然后在update中添加直接落地的按键控制代码

if (Input.GetKeyDown(KeyCode.LeftControl))
{while (DoFall()){}
}

至此,旋转之外的控制已经完全结束.

需要注意的是,用Input.GetKeyDown,每次按下键盘案件只响应一次操作,而用GetKey则会每帧响应一次,若要实现固定时间响应一次操作,可以用GetKey结合timeCount的实现方式.因此内容是仅针对操作进行优化的过程,故而暂不在此进行讨论.

在我们想要制作旋转的时候,一般有两种思路.其一是根据我们现有的数据,用代码来计算每次旋转之后squareCoord的值.这种方式比较简单,顺时针旋转时,只要将每一个正方形坐标的x和y对调并让x乘以-1就能实现(逆时针则是y乘以-1),但是用这种方法进行旋转时,因为我们最小的单位是1个正方形,而方块的中心点可能不是整数,无法进行完美旋转,会使游戏进行中出现一些与玩家认知不协调的情况.处起来十分复杂(也就是所谓的Super Rotation System - SRS旋转系统)

在这里,我们不对方块的旋转方法进行更深入的讨论(可以搜到很多不同的旋转规则)

先用有现有实例的Arika Rotation System(ARS旋转系统)来实现

(图片引用自https://tieba.baidu.com/p/1380321204)

从这个系统来看,我们不需要找出方块的旋转规律用程序进行计算,而是把方块旋转的顺序,用不同的坐标list固化在blockBase中即可.

这种方式的好处是,游戏开发者可以较为自由的用我们自定义的编辑器来把握方块旋转的规则,减少程序修改计算公式的麻烦.

我们先把原来保存正方形坐标的List打包成另外一个类

※这里使用这种方法是为了让Unity能够识别这种序列化方式,如果用List<List<Vector2>>则无法用ScriptableObject来保存这个字段.

[System.Serializable]
public class dmBlockForm
{public List<Vector2> squareCoordList = new List<Vector2>();
}

然后重构dmBlockBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;[CreateAssetMenu(fileName = "NewBlock",menuName = "TetrisDemo/BlockBase")]
public class dmBlockBase : ScriptableObject {public Vector2 blockSize;public List<dmBlockForm> blockFormList ;public List<Vector2> GetSquareCoordList(int index){if(index>=0 && index < blockFormList.Count){return blockFormList[index].squareCoordList;}return null;}public dmBlockBase(){blockFormList = new List<dmBlockForm>();}
}

这样我们就有了新的,能够保存方块不同方向的数据基础类.

根据IDE中的报错,逐个修正变更带来的错误.

BlockBuilder中的函数:

    public void BuildRandomBlock(){if(nowBlock!=null)DestroySquares(nowBlock);dmBlockBase inBuildingBlock = blockBaseList[Random.Range(0, blockBaseList.Count)];nowBlock = new dmBlock();nowBlock.InitBlock(inBuildingBlock);foreach (Vector2 vec in inBuildingBlock.GetSquareCoordList(0)){GameObject newSquare = Instantiate(squarePrefab);newSquare.transform.SetParent(transform);newSquare.transform.localPosition = new Vector3(squareSize.x * vec.x, -squareSize.x * vec.y);newSquare.GetComponent<RectTransform>().sizeDelta = squareSize;nowBlock.squareList.Add(newSquare);}}

在GameArea中声明用来保存当前控制的方块的方向index的变量,并修改碰撞检查函数

    int nowBlockCoordIndex = 0;public bool CheckCollision(dmBlock block,Vector2 targetPos){        foreach(Vector2 squareCoord in block.bindBase.GetSquareCoordList(nowBlockCoordIndex)){Vector2 squarePos = squareCoord + targetPos;if(squarePos.y >= areaSize.y || squarePos.x<0 || squarePos.x >= areaSize.x){return true;}if (squareMap.ContainsKey(squarePos)) return true;       }return false;}

方块落地函数

public void FallGround(dmBlock block)
{for(int i = 0; i < onMoveBlock.bindBase.blockFormList[nowBlockCoordIndex].squareCoordList.Count;i++){squareMap.Add(nowBlockPos + block.bindBase.GetSquareCoordList(nowBlockCoordIndex)[i], block.squareList[i]);block.squareList[i].GetComponentInChildren<Image>().color = Color.gray;}
}

方块移动函数

 public void MoveBlockTo(dmBlock onMoveBlock,Vector2 targetPos){if (CheckCollision(onMoveBlock, targetPos)){//GameOverrunFlag = false;return;}for (int i = 0; i < onMoveBlock.bindBase.blockFormList[nowBlockCoordIndex].squareCoordList.Count; i++){Vector2 squareCoord = targetPos + onMoveBlock.bindBase.GetSquareCoordList(nowBlockCoordIndex)[i];onMoveBlock.squareList[i].transform.localPosition = AreaPos2Local(squareCoord);onMoveBlock.squareList[i].SetActive(squareCoord.y >= 0);}nowBlockPos = targetPos;}

上述改动是因为我们原来没有预留多个正方形坐标的存储空间,认为一个方块只有一种数据,现在要改成从多个数据中选择当前方向的数据.

然后来修改Editor编辑器,让我们能够快速编辑每个方块的数据

首先把尺寸调整时候的遍历修正

if(tempSize!= targetBlock.blockSize) //当方块尺寸变更时,删除超出方块尺寸范围的数据
{foreach(dmBlockForm blockForm in targetBlock.blockFormList){List<Vector2> tempList = new List<Vector2>();tempList.AddRange(blockForm.squareCoordList);foreach (Vector2 vec in tempList){if (vec.x >= targetBlock.blockSize.x || vec.y >= targetBlock.blockSize.y){blockForm.squareCoordList.Remove(vec);}}}}

然后编写形状编辑功能.

ReorderableList blockFormReorderableList;
private void OnEnable()
{Color lastColor = GUI.backgroundColor;blockFormReorderableList = new ReorderableList(targetBlock.blockFormList, typeof(dmBlockForm));blockFormReorderableList.drawHeaderCallback = (Rect rect) => { GUI.Label(rect,"方块形状数据(按顺时针排序):"); };blockFormReorderableList.elementHeightCallback = (int index) => { return 5f + targetBlock.blockSize.y * EditorGUIUtility.singleLineHeight; };blockFormReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>{for (int y = 0; y < targetBlock.blockSize.y; y++){for (int x = 0; x < targetBlock.blockSize.x; x++){Vector2 nVec = new Vector2(x, y);GUI.backgroundColor = targetBlock.blockFormList[index].squareCoordList.Contains(nVec) ? Color.green : Color.gray;if (GUI.Button(new Rect(rect.x + x * EditorGUIUtility.singleLineHeight, rect.y + y * EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight),GUIContent.none)){if (targetBlock.blockFormList[index].squareCoordList.Contains(nVec)){targetBlock.blockFormList[index].squareCoordList.Remove(nVec);}else{targetBlock.blockFormList[index].squareCoordList.Add(nVec);}EditorUtility.SetDirty(target);}}}GUI.backgroundColor = lastColor;};
}

关于控件Reorderablelist请参考其他文档.

代码编写完成后,我们就可以按照ARS的图片重新录入方块数据

等等7个方块全部设置完毕.

然后修改GameArea的update函数,增加旋转的控制

if (Input.GetKeyDown(KeyCode.UpArrow))
{ClockwiseTurnBlock(nowControlBlock);
}

编写顺时针旋转函数

public void ClockwiseTurnBlock(dmBlock block)
{int targetIndex = nowBlockCoordIndex + 1;if (targetIndex >= block.bindBase.blockFormList.Count) targetIndex = 0;if (!CheckCollision(block, nowBlockPos, targetIndex)){nowBlockCoordIndex = targetIndex;for (int i = 0; i < block.bindBase.blockFormList[nowBlockCoordIndex].squareCoordList.Count; i++){Vector2 squareCoord = nowBlockPos + block.bindBase.GetSquareCoordList(nowBlockCoordIndex)[i];block.squareList[i].transform.localPosition = AreaPos2Local(squareCoord);block.squareList[i].SetActive(squareCoord.y >= 0);}}
}

最后,千万别忘记取新的方块时,将当前方块方向变量初始化

public void NextBlock()
{nowBlockCoordIndex = 0;PutInBlock(blockBuilder.nowBlock);blockBuilder.BuildRandomBlock();
}

大功告成!快点PLAY测试一下吧!

目录

(一) 定义方块

(二) 搭建方块UI和生成方块.

(三) 搭建场地UI和游戏流程控制

(四) 方块下落和落地判定

(五) 方块平移和旋转

(六) 方块消除

UNITY 开发日记/教程 俄罗斯方块 (五) 方块平移和旋转相关推荐

  1. unity开发日记之火箭发射

    2020-06-25 夜晚,思考人生的意义,乔布斯在斯坦福的演讲说到,你的热爱就是你的事业,你的事业就是你的热爱,我想了想,什么是自己的热爱呢,这么多年,陪伴在自己身边的也就是充满乐趣的游戏吧.自己不 ...

  2. Unity开发日记【第一天】——素材的导入及地图的建立

    前言:本人由于兴趣和未来的工作原因开始尝试接触和学习Unity开发,为了避免忘记一些东西所以写下这些博客算是一个记录. 目录 一.软件的安装与配置 二.如何编辑素材以及Tilemap的使用 三.图层和 ...

  3. Pico Neo3使用Unity开发简明教程

    前言 项目开发需要,最近开始整Pico VR,Pico被字节收编了,入局元宇宙,估计会有不错的应用和发展.简单使用了下,与Quest2对比,最大便利自然是网络环境.其他的,就不说了.这里重点是开发流程 ...

  4. C# + Unity开发日记(二) 利用宏命令实现简单的代码开关

    unity提供的宏命令在开发中可以非常方便的对不同的代码进行区分,例如UNITY_EDITOR 就可以定义代码只在编辑器里运行,这样就可以在下面写一些编辑器专用的代码,而不用担心打包Android或者 ...

  5. Unity开发日记【第七天】——怪物的移动和动画及类的实现

    目录 一.怪物的移动 二.怪物的动画 三.实现消灭动画.类的初探. 一.怪物的移动 之前我们向游戏中添加了敌人,现在要努力让敌人动起来,首先我们之前使用的是方形的碰撞器,为了防止怪物卡住我们改用圆形的 ...

  6. 《最详细的docker+php开发环境教程》(五) 搭建开发环境概要

    环境部署方式的优缺点 环境部署需要很多东西,也是开发过程与项目上线必须要考虑的一个重要环节,随着项目的迭代更新,系统会越来越庞杂,其所依赖的环境也会需要经常的变动更新,一个基本的项目就至少需要ngin ...

  7. Unity开发日记【第六天】——敌人、受伤和运动手感提升

    目录 一.敌人的实现 二.受伤效果的实现 三.代码优化及二段跳实现 一.敌人的实现 今天试图将敌人添加进来让游戏变得更有趣 首先按照之前的步骤,使用素材制作三种敌人的动画 这里面需要添加的组件(敌人首 ...

  8. Unity开发日记【第八天】——音效与对话框

    目录 一.音效的实现 二.对话框的实现 一.音效的实现 声音是一个游戏必不可少的元素,现在开始尝试给游戏加入一些音效,这里面设计三个组件,分别是Audio Listener(接收).Audio Sou ...

  9. 【Arduino】开发入门教程【一】什么是Arduino

    Arduino Arduino 是一款便捷灵活.方便上手的开源电子原型平台,包含硬件(各种型号的arduino板)和软件(arduino IDE).它适用于艺术家.设计师.爱好者和对于"互动 ...

最新文章

  1. 【Groovy】MOP 元对象协议与元编程 ( Groovy 类内部和外部分别获取 metaClass | 分析获取 metaClass 操作的字节码 | HandleMetaClass 注入方法 )
  2. 《Objective-c》-(description方法)
  3. NSDictionary所有API的学习。
  4. Win:Win系统下安装Linux的Ubuntu系统的简介、安装之详细攻略
  5. 记录之pytorch中文文档
  6. 1.Consul 简介和环境搭建
  7. Vue执行动画(transition)
  8. 1.HTML,CSS知识点
  9. 报时功能_拥有自鸣等20种复杂功能的百达翡丽6300G 连日期都可以用打簧报时听出来...
  10. android okhttp+解析json( okhttp 工具类)
  11. Shell脚本编程知识点总结及范例
  12. Struts2之命名空间与Action的三种创建方式
  13. @Autowired与@Resource的差别
  14. Leawo Video Converter(狸窝视频转换器)V8.1.0 下载安装和激活和常见操作说明
  15. 计蒜客 青出于蓝胜于蓝
  16. 如何安装HDDM,无法使用HDDM
  17. lisp方格网法计算土方量_CASS方格网法如何计算土方量
  18. Python写的Web spider(网络爬虫)
  19. Java8 拉姆达与集合中对象处理方式记录
  20. git cherry-pick 多分支的代码库,将代码从一个分支转移到另一个分支

热门文章

  1. oracle begin exception end,Oracle Exception异常处理
  2. 安装PHP7的图形用户界面(GUI) 扩展
  3. 一点一滴解读网狐的加解密
  4. MySQL加密存储,解密读取
  5. 为何我工作十年,内心仍无比恐慌(腾讯产品总监曹菲)
  6. spring boot如何引入XML文件
  7. sharepoint摘记
  8. 什么标签用于在表单中构建复选框_目前艾滋病主要的传播方式是(  )。
  9. 自适应学习率算法.基于阿米霍步长准则的线性回溯搜索算法
  10. Cytoscape.js_关系图API_文档