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

来源:互联网 发布:淘宝查卖家电话 编辑:程序博客网 时间:2024/09/21 06:33

接上文.

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

在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))     {         //GameOver         runFlag = 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和游戏流程控制

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

(五) 方块平移和旋转

(六) 方块消除


阅读全文
0 0