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

       

* 完整代码传送门

       此次用到的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()
        {
   
            //设置一个临时位置便于修改后储存,不需要每次都new
            var 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或4
                var 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.
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dark9spring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值