* 完整代码传送门

此次用到的Unity插件地址Unity3dAsyncAwaitUtil GitHub,如果使用async-await时仍然报错,请把.net standard 2.0转换为.net 4.x。
       这里的报错其实很玄学,如果不转换的话只有编译器是报错的,Unity里实际运行一点问题都没有,为了好看还是换了吧,拓展库更多避免以后的问题,比如之前做到的自定义属性面板就必须得转换,不然命名空间类名都找不到。

       本来是个2D的项目但是懒得新建工程,正好手头有个打开的3D项目随手就做了,因为里面还有别的代码和模型,所以这次就不发工程了。话不多说直接就开始吧。
       用UGUI做的话需要先放置一个Panel在场景里,毕竟UI都是以Panel为单位的,这里自动生成的Canvas,因为场景只有这一个Canvas,所以直接把渲染模式设置为渲染相机,调整分辨率只调整相机就足够了。在Panel下创建一个背景层,再创建一个SumArea存放所有的Cell,然后就可以做Area的预制体了。
       这里我为了方便计算做好的宽高都是100,自己可以根据情况来调整,因为没有合适的图片所以直接偷懒把原生的BackGround拿来用了,导致了后面分辨率太高有锯齿的情况没法解决,所以尽量找个背景拿来用吧,网上的白底好扣图,PS魔棒一扣就出来了,或者色调分离也可以完美解决,扯远了。首先背景解决了,数字的话想做的美观点可以用TextMeshPro,我就用的Text,Text设置为Area的子对象,size和pos的话尽量离背景的圈远一点,不然有时候会重叠。左右上下都设为居中,勾上best fit选项可以自动调整大小,可以随便打几个数字试下,做完这些保存预制体即可。


       然后就是游戏结束和分数的UI,这几个也用Text做,和上面的大同小异,分数的话水平轴上可以设置为overflow,这样就不用担心大小不够而不显示的问题了,其他的也没什么需要注意的地方,锚点设置好就行,记得都要放在Panel的下面好管理。

上面的Main可以忽视掉,因为我用到了我其他脚本的代码需要初始化。

接下来就可以写脚本了,上面已经确定好当前的网格大小是100*100的单位,然后再确定棋盘格的长宽,用个100*100的Image比量一下就好。

     //直接定义好三个变量显示在面板上设置好[SerializeField] private Vector2 upperRight;[SerializeField] private Vector2 lowerLeft;[SerializeField] private float areaSize;

首先思索一下游戏逻辑,移动时分为行变换和列变换,所以需要两个列表来存引用,x方向的列表变换的话y方向也会跟着变换,所以列表内存引用类型的变量会变得简单一点,创建一个CellData类,使用元组来存储,或者拆分两个列表都行。同时再定义长宽能容纳几个Cell的变量。

    public sealed class Viewer : MonoBehaviour{[SerializeField] private Vector2 upperRight;[SerializeField] private Vector2 lowerLeft;[SerializeField] private float areaSize;private (List<CellData> x, List<CellData> y) _allPos;private ushort _xSize;private ushort _ySize;}public sealed class CellData{}

CellData代表着场景中每个方块单元,所以共有的属性就是位置和当前位置是否有图片这两个,是否有图片则需要图片的引用,所以图片也写进来。

 public sealed class CellData{internal Image Image { get; set; }//每个位置都是固定的所以在构造方法里赋值就行internal readonly Vector3 cellPos;internal bool IsHaveImage => Image != null;internal CellData(Vector3 cellPos){this.cellPos = cellPos;}}

这些做完以后可以开始做初始化列表了。

        /// <summary>/// 初始化地图且生成x,y正方向的元组/// </summary>private void InitializeMap(){//设置一个临时位置便于修改后储存,不需要每次都newvar tempPos = Vector2.zero;_xSize = Convert.ToUInt16((upperRight.x - lowerLeft.x) / areaSize + 1);_ySize = Convert.ToUInt16((upperRight.y - lowerLeft.y) / areaSize + 1);var xDic = new List<CellData>(Convert.ToInt32(_xSize*_ySize) + 1);//向元组的x列表中添加引用for (var i = lowerLeft.y; i <= upperRight.y; i += areaSize){for (var j = lowerLeft.x; j <= upperRight.x; j += areaSize){tempPos.Set(j, i);xDic.Add(new CellData(tempPos));}}//y列表使用x列表中的引用,方便后面代码编写,需要修改cellData的属性字段时xy只改一个就行var yDic = new List<CellData>(Convert.ToInt32(_xSize*_ySize) + 1);var count = 0;for (var i = 0; i < xDic.Count + _xSize; i += _ySize){if (i >= xDic.Count){count++;i = i - xDic.Count + 1;}if (count == 7){break;}yDic.Add(xDic[i]);}_allPos = (xDic, yDic);}

确保添加的引用全部正确以后就可以在开始游戏时添加数字了,接下来看注释就行,没什么难的地方。

    public sealed class Viewer : MonoBehaviour{.../// <summary>/// 为场景中添加新数字/// </summary>/// <param name="count">要添加几个数字</param>private void AddNumber2Map(sbyte count){var i = 0;while (i < count){//找出没有图片的单元并成组var emptyCellData = _allPos.x.Where(cellData => !cellData.IsHaveImage).ToList();//如果没有空闲的地方则不再生成if (emptyCellData.Count==0){break;}//随机选取空闲单元var randomCell = emptyCellData[Random.Range(0, emptyCellData.Count)];//在空闲单元实例化新数字var newArea = Instantiate(area, randomCell.cellPos, Quaternion.identity, sumArea.transform);//随机生成2或4var num = (Random.value > 0.7f ? 4 : 2).ToString();//调整该单元的参数randomCell.SetValue(this, randomCell.cellPos, newArea, num);i++;}}/// <summary>/// 根据位置查找到存储当前位置的cellData/// </summary>/// <param name="nowPos">给定的位置</param>/// <returns></returns>public CellData this[Vector2 nowPos] =>_allPos.x.Find(cellData => Vector2.Distance(cellData.cellPos, nowPos) < 0.05f);}public sealed class CellData{...internal Text Text => Image.GetComponentInChildren<Text>();internal RectTransform Transform => Image.GetComponent<RectTransform>();internal Vector3 AnchoredVector3{set => Image.GetComponent<RectTransform>().anchoredPosition3D = value;get => Image.GetComponent<RectTransform>().anchoredPosition3D;}/// <summary>/// 调整单元内的参数,在生成的时候调用/// </summary>/// <param name="viewer">使用CellData的viewer脚本实例</param>/// <param name="anchoredPos">游戏物体的位置</param>/// <param name="newImage">新创建的image</param>/// <param name="num">图片的数字</param>internal void SetValue(Viewer viewer, Vector3 anchoredPos, Image newImage, string num){var xCell = viewer[cellPos];xCell.Image = newImage;xCell.AnchoredVector3 = anchoredPos;xCell.Text.text = num;}}

直接在Viewer里Awake调用两个方法测试。

       开始的话都好办,没有太复杂的逻辑,现在可以说下移动的方法了。
       移动需要接受外界输入,可以直接拿Input.GetAxisRaw()直接用,也可以写Input.GetKey(),前面的代码少就用前面的来做。

    public sealed class Viewer : MonoBehaviour{...private void Update(){var h = Input.GetAxisRaw("Horizontal");var v = Input.GetAxisRaw("Vertical");//简单的单向移动if (h!=0){v = 0;}else{h = 0;}//不同的方向使用不同的准备方法switch (h, v){case (0, 1):MovePreparation(MoveDirection2D.Y);break;case (0, -1):MovePreparation(MoveDirection2D.OppositeY);break;case (1, 0):MovePreparation(MoveDirection2D.X);break;case (-1, 0):MovePreparation(MoveDirection2D.OppositeX);break;}}private async void MovePreparation(MoveDirection2D direction2D){}}

逻辑大概就是这样的逻辑,接下来继续写准备的方法。
       比如向x正方向移动,那就需要判断每一行,所以先把行从allPos.x中分离开来。

for (var i = 0; i < _allPos.x.Count; i += _xSize)
{//获取x正方向的每一行的cellDatavar xLineCellData = _allPos.x.GetRange(i, _xSize);//因为沿x负方向判断所以反转数组xLineCellData.Reverse();
}

现在已经获取了x方向的所有的CellData引用,要移动图片所以要获取这一行当中有图片的cellData。

 for (var i = 0; i < _allPos.x.Count; i += _xSize){//获取x正方向的每一行的cellDatavar xLineCellData = _allPos.x.GetRange(i, _xSize);//因为沿x负方向判断所以反转数组xLineCellData.Reverse();//获取当前行所有有图片的cellDatavar imageList = FindCellDataWithImage(xLineCellData);}/// <summary>/// 找给定列表中带有图片的cellData/// </summary>/// <param name="cellDataList"></param>/// <returns></returns>private static List<CellData> FindCellDataWithImage(List<CellData> cellDataList) =>cellDataList.Where(cellData => cellData.IsHaveImage).ToList();

两个参数都知道了就可以移动了,先把方法写出来,参数传递进去。

for (var i = 0; i < _allPos.x.Count; i += _xSize)
{//获取x正方向的每一行的cellDatavar xLineCellData = _allPos.x.GetRange(i, _xSize);//因为沿x负方向判断所以反转数组xLineCellData.Reverse();//获取当前行有图片的cellDatavar imageList = FindCellDataWithImage(xLineCellData);//移动的逻辑,必须要等待,不等待会导致线程不安全,或者使用bool变量控制亦可await MoveImage(imageList, xLineCellData);
}private async Task MoveImage(List<CellData> imageList, List<CellData> lineList)
{}

这个方法就差不多成型了,其他方向以此类推。

        /// <summary>/// 沿不同方向移动的数据处理/// </summary>/// <param name="direction2D">移动方向</param>private async void MovePreparation(MoveDirection2D direction2D){switch (direction2D){case MoveDirection2D.X://遍历沿x正方向的每一行for (var i = 0; i < _allPos.x.Count; i += _xSize){//获取x正方向的每一行的cellDatavar xLineCellData = _allPos.x.GetRange(i, _xSize);//因为沿x负方向判断所以反转数组xLineCellData.Reverse();//获取当前行有图片的cellDatavar imageList = FindCellDataWithImage(xLineCellData);//移动的逻辑,必须要等待,不等待会导致线程不安全,或者使用bool变量控制亦可await MoveImage(imageList, xLineCellData);}break;case MoveDirection2D.Y://遍历y正方向for (var i = 0; i < _allPos.y.Count; i+=_ySize){var yLineCellData = _allPos.y.GetRange(i, _ySize);yLineCellData.Reverse();var imageList = FindCellDataWithImage(yLineCellData);await MoveImage(imageList,yLineCellData);}break;case MoveDirection2D.OppositeX:for (var i = 0; i < _allPos.x.Count; i+=_xSize){var xLineCellData = _allPos.x.GetRange(i, _xSize);var imageList = FindCellDataWithImage(xLineCellData);await MoveImage(imageList, xLineCellData);}break;case MoveDirection2D.OppositeY:for (var i = 0; i < _allPos.y.Count; i += _ySize){var yLineCellData = _allPos.y.GetRange(i, _ySize);var imageList = FindCellDataWithImage(yLineCellData);await MoveImage(imageList, yLineCellData);}break;default:throw new NullReferenceException("移动方向错误");}}

到现在方向的逻辑已经完毕,传入的列表均为一致,可以拆开单行来分析了。
       如果当前行内没有目标,直接返回即可;如果有一个图片,那么也直接移动就好;如果有两张图片,那么需要判断两个图片是否可以相加,不能相加则直接移动,能相加则移动后销毁一张图片;如果有三张的话会更复杂一点,如果开始的两张图片不可以相加,那么移动即可,如果后两张图片又可以相加,那么后两张图片先移动到下一个目标位置后再相加并且销毁,大概是这么个思路,但是写代码会有点不一样,先完善没有图片和只有一张图片的情况。

        private async Task MoveImage(List<CellData> imageList, List<CellData> lineList){switch (imageList.Count){case 0://没有图片则不移动return;case 1://一个图片时直接移动,移动后更正image在cellData的新位置var imageCellData = imageList[0];var emptyCellData = lineList[0];await Move(imageCellData, emptyCellData.cellPos);ExchangeGridData(ref imageCellData, ref emptyCellData);return;}}

Move方法也一样,可以先放着,写完一个方法再来完善。交换数据也只是交换了image的引用,并没有改变其他的东西。至于上面为什么要等待,因为等待移动完才可以交换image,不然会丢失image的引用或者其他奇怪的错误。

        /// <summary>/// 交换数据/// </summary>/// <param name="cellData1"></param>/// <param name="cellData2"></param>private static void ExchangeGridData(ref CellData cellData1, ref CellData cellData2){(cellData1.Image, cellData2.Image) = (cellData2.Image, cellData1.Image);}

两个以上的图片移动其实差不多,边写代码边说吧,遇事不决先写个for

for (var i = 0; i < imageList.Count; i++)
{}

都是两个两个判断的,就先把两个引用拉出来溜溜,由于next使用i+1会越界,所以写在try里。

for (var i = 0; i < imageList.Count; i++)
{var lastCell = imageList[i];CellData nextCell;//可能在下面被销毁,所以需要先判断空,双重保险if (lastCell.Image is null){continue;}try{//当索引越界时则表示lastCell是最后一个有图片的nextCell = imageList[i + 1];}catch(ArgumentOutOfRangeException){//因为只剩lastCell所以直接移动lastCell且返回即可await Move(lastCell, lineList[j].cellPos);var correctCellData = this[lineList[j].cellPos];ExchangeGridData(ref correctCellData, ref lastCell);lastCell.HasBeenPlus = false;return;}
}

由于移动会等待,所以多次移动也没问题,不会出现速度变快的毛病,管他最后一个移动过没有,直接让他再移动一次。
       到下面已经不用担心越界的问题了,所以就直接比较值就好,还有如果原本加过的值本次移动就不可以再次相加,所以CellData里再加入一个布尔属性。

internal bool HasBeenPlus { get; set; }

接下来直接写就行。

//j是lineList的索引,用于确定下一次移动的位置
var j = 0;
//遍历当前行有图片的索引
for (var i = 0; i < imageList.Count; i++)
{var lastCell = imageList[i];CellData nextCell;//可能在下面被销毁,所以需要先判断空,双重保险if (lastCell.Image is null){continue;}try{//当索引越界时则表示lastCell是最后一个有图片的nextCell = imageList[i + 1];}catch (ArgumentOutOfRangeException){//因为只剩lastCell所以直接移动lastCell且返回即可await Move(lastCell, lineList[j].cellPos);var correctCellData = this[lineList[j].cellPos];ExchangeGridData(ref correctCellData, ref lastCell);lastCell.HasBeenPlus = false;return;}//如果可以相加且上一个没被加过则相加if (lastCell.ImageNumber == nextCell.ImageNumber&&!lastCell.HasBeenPlus){//移动这两个图片到指定位置await Move(lastCell, lineList[j].cellPos);await Move(nextCell, lineList[j].cellPos);//先在有图片的列表中移除lastCell的引用再销毁,否则也会报错imageList.Remove(lastCell);//销毁上一个图片,image引用变为空Destroy(lastCell.Image.gameObject);//查找到当前应移动到哪个cell,因为list不同所以需要查找x中的var correctCellData = this[lineList[j].cellPos];//交换两cell的图片索引ExchangeGridData(ref correctCellData, ref nextCell);//数字合并correctCellData.ImageNumber *= 2;nextCell.HasBeenPlus = true;//索引变为下一位置j++;}//其他多种情况也只处理上一张图片else{await Move(lastCell, lineList[j].cellPos);var correctCellData = this[lineList[j].cellPos];ExchangeGridData(ref correctCellData, ref lastCell);lastCell.HasBeenPlus = false;j++;}
}

完善一下Move方法,可以选择协程或者async都行。

        /// <summary>/// 移动方法/// </summary>/// <param name="cellData">要移动的cellData</param>/// <param name="move2Pos">目标位置</param>/// <returns></returns>private IEnumerator Move(CellData cellData, Vector3 move2Pos){var rectTransform = cellData.Transform;var nowPos = rectTransform.anchoredPosition3D;for (; Vector3.Distance(move2Pos, nowPos) > 0.01f;){nowPos = Vector3.MoveTowards(nowPos, move2Pos,flySpeed);rectTransform.anchoredPosition3D = nowPos;yield return null;}}

剩余的都是一些小功能,比如游戏结束,计分板什么的,不再带着做了,直接贴代码。美中不足的一点就是,由于采用了async-await的模式,导致移动行列的时候不会同步,总是先执行的先移动,后面的会等待前面的移动完再移动,这一点还可以优化下。还有代码中的findXXX什么的请手动改为Unity的查找方法,我用的自己的UI模块的查找,复制粘贴肯定报错,继承改为MonoBehaviour,其他的应该没什么地方了,贴代码了。

完整代码及参数

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Random = UnityEngine.Random;namespace UI.View
{public enum MoveDirection2D{X,Y,OppositeY,OppositeX}public sealed class Viewer : MonoBehaviour{[SerializeField] private Image area;[SerializeField] private GameObject sumArea;[SerializeField] private Vector2 upperRight;[SerializeField] private Vector2 lowerLeft;[SerializeField] private float areaSize;[SerializeField] private float flySpeed;[SerializeField] [Range(1, 4)]private int spawnNum;private (List<CellData> x, List<CellData> y) _allPos;private Text gameOver;private Text nowScore;private Text bestScore;private ushort _xSize;private ushort _ySize;private uint _nowScore;private bool _isMoving;protected override void Awake(){gameOver = FindGameObjectInList("GameOver", null).GetComponent<Text>();nowScore = FindGameObjectInList("Score", "NowScore").GetComponent<Text>();bestScore = FindGameObjectInList("Score", "BestScore").GetComponent<Text>();bestScore.text = PlayerPrefs.GetInt("BestScore",0).ToString();InitializeMap();AddNumber2Map(8);}private void Update(){var h = Input.GetAxisRaw("Horizontal");var v = Input.GetAxisRaw("Vertical");if (h!=0){v = 0;}else{h = 0;}if (_isMoving)return;switch (h, v){case (0, 1):MovePreparation(MoveDirection2D.Y);break;case (0, -1):MovePreparation(MoveDirection2D.OppositeY);break;case (1, 0):MovePreparation(MoveDirection2D.X);break;case (-1, 0):MovePreparation(MoveDirection2D.OppositeX);break;}}/// <summary>/// 初始化地图且生成x,y正方向的元组/// </summary>private void InitializeMap(){//设置一个临时位置便于修改后储存,不需要每次都newvar tempPos = Vector2.zero;_xSize = Convert.ToUInt16((upperRight.x - lowerLeft.x) / areaSize + 1);_ySize = Convert.ToUInt16((upperRight.y - lowerLeft.y) / areaSize + 1);var xDic = new List<CellData>(Convert.ToInt32(_xSize*_ySize) + 1);//向元组的x列表中添加引用for (var i = lowerLeft.y; i <= upperRight.y; i += areaSize){for (var j = lowerLeft.x; j <= upperRight.x; j += areaSize){tempPos.Set(j, i);xDic.Add(new CellData(tempPos));}}//y列表使用x列表中的引用,方便后面代码编写,需要修改cellData的属性字段时只改一个就行var yDic = new List<CellData>(Convert.ToInt32(_xSize*_ySize) + 1);var count = 0;for (var i = 0; i < xDic.Count + _xSize; i += _ySize){if (i >= xDic.Count){count++;i = i - xDic.Count + 1;}if (count == 7){break;}yDic.Add(xDic[i]);}_allPos = (xDic, yDic);}/// <summary>/// 沿不同方向移动的数据处理/// </summary>/// <param name="direction2D">移动方向</param>private async void MovePreparation(MoveDirection2D direction2D){_isMoving = true;switch (direction2D){case MoveDirection2D.X://遍历沿x正方向的每一行for (var i = 0; i < _allPos.x.Count; i += _xSize){//获取x正方向的每一行的cellDatavar xLineCellData = _allPos.x.GetRange(i, _xSize);//因为沿x负方向判断所以反转数组xLineCellData.Reverse();//获取当前行有图片的cellDatavar imageList = FindCellDataWithImage(xLineCellData);//移动的逻辑,必须要等待,不等待会导致线程不安全await MoveImage(imageList, xLineCellData);}break;case MoveDirection2D.Y://遍历y正方向for (var i = 0; i < _allPos.y.Count; i+=_ySize){var yLineCellData = _allPos.y.GetRange(i, _ySize);yLineCellData.Reverse();var imageList = FindCellDataWithImage(yLineCellData);await MoveImage(imageList,yLineCellData);}break;case MoveDirection2D.OppositeX:for (var i = 0; i < _allPos.x.Count; i+=_xSize){var xLineCellData = _allPos.x.GetRange(i, _xSize);var imageList = FindCellDataWithImage(xLineCellData);await MoveImage(imageList, xLineCellData);}break;case MoveDirection2D.OppositeY:for (var i = 0; i < _allPos.y.Count; i += _ySize){var yLineCellData = _allPos.y.GetRange(i, _ySize);var imageList = FindCellDataWithImage(yLineCellData);await MoveImage(imageList, yLineCellData);}break;default:throw new NullReferenceException("移动方向错误");}AddNumber2Map((sbyte)spawnNum);_isMoving = false;}/// <summary>/// 计算并移动/// </summary>/// <param name="imageList">有图片的列表</param>/// <param name="lineList">当前行所有的cellData,默认倒序</param>private async Task MoveImage(List<CellData> imageList, List<CellData> lineList){switch (imageList.Count){case 0://没有图片则不移动return;case 1://一个图片时直接移动,移动后更正image在cellData的新位置var imageCellData = imageList[0];var emptyCellData = lineList[0];await Move(imageCellData, emptyCellData.cellPos);ExchangeGridData(ref imageCellData, ref emptyCellData);return;}//j是lineList的索引,用于确定下一次移动的位置var j = 0;//遍历当前行有图片的索引for (var i = 0; i < imageList.Count; i++){var lastCell = imageList[i];CellData nextCell;//可能在下面被销毁,所以需要先判断空,双重保险if (lastCell.Image is null){continue;}try{//当索引越界时则表示lastCell是最后一个有图片的nextCell = imageList[i + 1];}catch (ArgumentOutOfRangeException){//因为只剩lastCell所以直接移动lastCell且返回即可await Move(lastCell, lineList[j].cellPos);var correctCellData = this[lineList[j].cellPos];ExchangeGridData(ref correctCellData, ref lastCell);lastCell.HasBeenPlus = false;return;}//如果可以相加且上一个没被加过则相加if (lastCell.ImageNumber == nextCell.ImageNumber&&!lastCell.HasBeenPlus){//移动这两个图片到指定位置await Move(lastCell, lineList[j].cellPos);await Move(nextCell, lineList[j].cellPos);//先在有图片的列表中移除lastCell的引用再销毁,否则也会报错imageList.Remove(lastCell);//销毁上一个图片,image引用变为空Destroy(lastCell.Image.gameObject);//查找到当前应移动到哪个cellvar correctCellData = this[lineList[j].cellPos];//交换两cell的图片索引ExchangeGridData(ref correctCellData, ref nextCell);//数字合并correctCellData.ImageNumber *= 2;_nowScore += correctCellData.ImageNumber;nowScore.text = _nowScore.ToString();nextCell.HasBeenPlus = true;//索引变为下一位置j++;}//其他多种情况也只处理上一张图片else{await Move(lastCell, lineList[j].cellPos);var correctCellData = this[lineList[j].cellPos];ExchangeGridData(ref correctCellData, ref lastCell);lastCell.HasBeenPlus = false;j++;}}}/// <summary>/// 移动方法/// </summary>/// <param name="cellData">要移动的cellData</param>/// <param name="move2Pos">目标位置</param>/// <returns></returns>private IEnumerator Move(CellData cellData, Vector3 move2Pos){var rectTransform = cellData.Transform;var nowPos = rectTransform.anchoredPosition3D;for (; Vector3.Distance(move2Pos, nowPos) > 0.01f;){nowPos = Vector3.MoveTowards(nowPos, move2Pos,flySpeed);rectTransform.anchoredPosition3D = nowPos;yield return null;}}/// <summary>/// 找给定列表中带有图片的cellData/// </summary>/// <param name="cellDataList"></param>/// <returns></returns>private static List<CellData> FindCellDataWithImage(List<CellData> cellDataList) =>cellDataList.Where(cellData => cellData.IsHaveImage).ToList();/// <summary>/// 交换数据/// </summary>/// <param name="cellData1"></param>/// <param name="cellData2"></param>private static void ExchangeGridData(ref CellData cellData1, ref CellData cellData2){(cellData1.Image, cellData2.Image) = (cellData2.Image, cellData1.Image);}/// <summary>/// 为场景中添加新数字/// </summary>/// <param name="count">要添加几个数字</param>private void AddNumber2Map(sbyte count){var i = 0;var hasFreeCell = true;while (i < count){//找出没有图片的单元并成组var emptyCellData = _allPos.x.Where(cellData => !cellData.IsHaveImage).ToList();//如果没有空闲的地方则不再生成if (emptyCellData.Count==0){hasFreeCell = false;break;}//随机选取空闲单元var randomCell = emptyCellData[Random.Range(0, emptyCellData.Count)];//在空闲单元实例化新数字var newArea = Instantiate(area, randomCell.cellPos, Quaternion.identity, sumArea.transform);//随机生成2或4var num = (Random.value > 0.7f ? 4 : 2).ToString();//调整该单元的参数randomCell.SetValue(this, randomCell.cellPos, newArea, num);i++;}if (hasFreeCell)return;var canBeMoved= JudgeCanBeMoved();if (!canBeMoved){StartCoroutine(GameOver());}}/// <summary>/// 游戏结束且重新开始/// </summary>/// <returns></returns>private IEnumerator GameOver(){//如果最好的分数小于目前分数则重新设置if (PlayerPrefs.GetInt("BestScore")<_nowScore){PlayerPrefs.SetInt("BestScore", (int)_nowScore);}gameOver.gameObject.SetActive(true);yield return new WaitForSeconds(3f);SceneManager.LoadScene("2048",LoadSceneMode.Single);}/// <summary>/// 判断是否还可以移动/// </summary>/// <returns></returns>private bool JudgeCanBeMoved(){//即判断相邻的任意两个数字是否可以相加,可以则返回truereturn _allPos.x.Where((t, i) => i % _xSize != _xSize - 1 && t.ImageNumber == _allPos.x[i + 1].ImageNumber).Any() || _allPos.y.Where((t, i) =>i % _ySize != _ySize - 1 && t.ImageNumber == _allPos.y[i + 1].ImageNumber).Any();}/// <summary>/// 根据位置查找到存储当前位置的cellData/// </summary>/// <param name="nowPos">给定的位置</param>/// <returns></returns>public CellData this[Vector2 nowPos] =>_allPos.x.Find(cellData => Vector2.Distance(cellData.cellPos, nowPos) < 0.05f);}public sealed class CellData{internal Image Image { get; set; }internal readonly Vector3 cellPos;internal Text Text => Image.GetComponentInChildren<Text>();internal RectTransform Transform => Image.GetComponent<RectTransform>();internal Vector3 AnchoredVector3{set => Image.GetComponent<RectTransform>().anchoredPosition3D = value;get => Image.GetComponent<RectTransform>().anchoredPosition3D;}internal uint ImageNumber{get =>uint.TryParse(Text.text, out var res) ? res : throw new NullReferenceException("uint转换失败");set => Text.text = Convert.ToString(value);}internal bool IsHaveImage => Image != null;internal bool HasBeenPlus { get; set; }internal CellData(Vector3 cellPos){this.cellPos = cellPos;}/// <summary>/// 调整单元内的参数,在生成的时候调用/// </summary>/// <param name="viewer">使用CellData的viewer脚本实例</param>/// <param name="anchoredPos">游戏物体的位置</param>/// <param name="newImage">新创建的image</param>/// <param name="num">图片的数字</param>internal void SetValue(Viewer viewer, Vector3 anchoredPos, Image newImage, string num){var xCell = viewer[cellPos];xCell.Image = newImage;xCell.AnchoredVector3 = anchoredPos;xCell.Text.text = num;}}
}

Unity初级教程2048附带源码及插件(400行代码1个脚本UI实现)相关推荐

  1. Python爬取腾讯动漫全站漫画详细教程(附带源码)

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:merlin& PS:如有需要Python学习资料的小伙伴可 ...

  2. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(二)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 上一篇 点此阅读 简要介绍了FlashToAnimation的功能,也就是将flash动画无缝导入到Android ...

  3. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(一)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 项目介绍 项目名称:FlashAnimationToMobile 源码. 使用方法点这里. 这是一个把flash中 ...

  4. onclick 源码_仿照React源码流程打造90行代码的Hooks

    作者:苏畅 转发链接:https://mp.weixin.qq.com/s/YLSD4IojDWTPlov_RQtVAA 前言 你可能已经看过其它简易的Hooks实现.那么本文和其它实现有什么区别呢? ...

  5. Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android iOS cocos2dx(二)

    [注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 上一篇 点此阅读 简要介绍了FlashToAnimation的功能,也就是将flash动画无缝导入到Android ...

  6. 微信/易信公共平台开发(一):开发了一个简单易用的PHP类(提供源码),十几行代码搞定一个公众号

    这两天学习了一下微信公共平台和易信公共平台的开发 (易信与微信协议是基本一样的)(PS:没听说过易信?你out了?) 公共平台开发原理这里就不讲了,如果是初次接触的同学,建议先看 David_Tang ...

  7. AI 实战篇 |基于 AI开放平台实现 人脸识别对比 功能,超详细教程【附带源码】

  8. 猜猜乐游戏php源码,C/C++百行代码实现热门游戏消消乐功能的示例代码

    游戏设计 首先我们需要使用第三方框架,这里我使用的是sfml,不会使用sfml在我的上几篇文章当中-扫雷(上)有详细的开发环境搭建介绍 首先准备图片资源 一张背景图片,一张宝石图片 窗口初始化加载图片 ...

  9. jsp mysql视频_手把手教你做jsp servlet mysql实现的图书管理系统附带视频开发教程和完整源码...

    上一个教程我们做的是对数据库框架底层原理的讲解,然后教大家做了一个自己的数据库框架,这次我们做的这个图书管理系统就是用我们上个教程自己写的数据库框架,整个项目做完框架运行的很稳定,没有出现任何问题.如 ...

最新文章

  1. 协程和任务 异步IO 重点
  2. 【Netty】NIO 简介 ( NIO 模型 | NIO 三大组件 | 选择器 Selector | 通道 Channel | 缓冲区 Buffer | NIO 组件分配 | 缓冲区示例 )
  3. [云炬创业基础笔记]第九章企业的法律形态测试8
  4. Create new SAP DDL view and click finish in wizard
  5. Android开发之ApiCloud模块开发的注意事项
  6. electron 读取文件夹内容_如何使用Electron Framework选择,读取,保存,删除或创建文件...
  7. SpringBoot+VUE 前端加密算法 RSA+DES
  8. Spring5 新增的两大功能,吹一波这个框架!
  9. Failed to execute goal com.github.wvengen:proguard-maven-plugin:2.0.10:proguard (default) on project
  10. ZooKeeper分布式过程协同技术详解2——了解ZooKeeper
  11. 为什么Go比Java快这么多?看完这个例子就懂!
  12. postgresql14编译安装参考手册(centos)
  13. Multisim基础 调出面包板
  14. 光继电器一体化控制实训系统
  15. 迟到的Meltdown/Spectre分析
  16. 笔记:《深入浅出统计学》第十六章-附录:分布剖析、样本方差与协方差、决定系数、非线性关系、连续随机变量的期望与方差
  17. Python摇色子游戏(两种模式)
  18. Kinect开发(如何连接电脑)
  19. Snipaste 免费截图贴图工具(良心推荐)
  20. OpenCV色彩空间类型

热门文章

  1. 电脑进入pe时蓝屏_电脑进入pe时出现蓝屏0x000000a5应该如何解决?
  2. get和post的差别
  3. qt程序在Linux下字体乱了,QT5程序字体渲染的问题
  4. 大数据周会-本周学习内容总结014
  5. Python数据分析学习系列 十一 时间序列
  6. word 公式编辑器 右对齐 + 自动编号
  7. 几种Ajax框架的比较
  8. USBCAN 系列便携式二路 CAN 分析仪的产品选型
  9. 编写一个简单的汇编程序(DOSBox 0.74和MASM 6.15)
  10. 企业文件数据泄露防护(DLP)