前言
玩游戏也能学习知识?还记得高中时的化学元素常见金属活动性属性表吗?一起来复习一下:钾K,钙Ca,钠Na,镁Mg,铝Al,锌Zn,铁Fe, 锡Ni,铅Sn,氢(H),铜Cu,汞Hg,银Ag,铂Pt,金Au。 一股很熟悉的味道有没有?一起来看看化学元素和游戏之间发生的碰撞吧~
一,游戏介绍和效果展示
2048 一款益智小游戏,游戏的规则十分简单,简单易上手的数字小游戏,闲来无事,自己制作一个,却怎么也到没有成功到2048。
老规矩,先看下按照此博文一步步操作完成的效果吧~
二,开发前的准备工作
2.1 创建工程
打开Unity Hub 点击“新建”,在弹窗中输入 --> “项目名称” --> 选择"项目位置" --> “项目版本控制系统” 选不选都行,不需要在云端备份的话就不要选(我一般不选)
,选了没用过的话,会自动帮你安装PlasticSCM到本地。最后点击创建即可。
2.2 导入素材
将提前准备好的素材(图片和声音)导入工程。(文末会提供-)
方式一: 在"Project"面板右键 --> “Improt Package” --> “Custom Package…” --> 选择自己下载下来的UnityPackage --> 最后点击"Import" 导入。
方式二: 直接将下载还的UnityPackage,拖拽到项目中,然后点击"Import" 导入:
导入项目后工程目录如下:
三,游戏开发进行中
3.1 游戏场景搭建
设置分辨率:点击Game视图分辨率选框,设置为(1080x1920),没有的话点击加号自行添加一个,或者使用一个竖屏的就可以。
创建背景:右键UI --> Image 创建后,重命名为BG,将其锚点设置为铺满,源文件指定为素材"play_bg_forest_dark":
修改Canvas:将自动创建出来的Canvas的Canvas Scaler 属性设置如下:
创建格子: 在“Canvas”下创建Image,重命名为“MapBg”,将其宽高设置为(1720,1720),然后修改其颜色为浅蓝色,透明度相应调低,效果如下:(自己觉得好看就可以了)
然后在“MapBg”下再次创建一个Image,重命名为“gezi”,将其宽高设置为(400,388),调整其颜色为(22,0,192,30),还是一样调整到自己喜欢的颜色即可。然后“Ctrl + D” 复制15个格子出来:
最后在“MapBg”上添加"Grid Layout Group",属性设置如下图,将格子铺满到地图背景上:
创建数字池: 右键创建一个空物体作为加载数字的父物体,并命名为“NumPool”,所有属性默认即可。
创建数字预制体: 右键Image重命名为Num,宽高调整为和格子一样大(400,380),然后将其拽到Resources文件夹下,作为预制体,然后右键删除场景中Num即可:
游戏结束面板:创建Image命名为“UIFinsh”,铺满背景。在“UIFinsh”下在创建一个Text和一个Button,作为游戏结束显示文本和重新开始按钮:
3.2 核心代码编写
场景终于搭建完了,下面开始编写脚本吧,完整代码在四中给出,这里只讲解实现思路,查看核心逻辑。
在Project下创建Scripts文件夹,然后创建"Manager.cs" 和 “Number.cs” 脚本。
基础逻辑就是:默认生成两个数字,接收用户输入,管理器触发移动逻辑,数字移动并校验合并,移动后校验是否游戏结束,若结束处理游戏结束摞,若未结束在自动生成一个数字:
结束
继续
默认生成数字
管理器接收用户输入
管理器触发数字移动逻辑
数字移动并校验合并
移动后校验游戏结束
处理游戏结束逻辑
Manager中接收用户输入的方式一种是在移动端的滑动,一种是在PC端的按下剪头或WASD上下左右移动:
Manager收到用户输入后的控制游戏数字移动逻辑:
Num类收到Manager的移动,合并逻辑处理:
最后将Manager挂载到“BG”,并对公有变量’PoolManager’和’UIFinsh’赋值,如下图即可:
将Number脚本挂载到上面制作的预制体“Num”上即可。
此时所有的游戏逻辑就都完成,运行游戏就可以玩耍了~
3.3 游戏音效处理
音效是这一个游戏的很重要的部分,一个好的音效可以让用户得到更好的反馈,所以再简单的游戏也要有背景音和音效给用户一个好的交互体验。
背景音乐:在“BG”上,添加组件“Audio Source”,将音频文件“bg_1”赋值给AudioClip并勾选Loop,即可完成自动循环播放背景音乐:
数字音效:同理在“Num”预制体上添加组件“Audio Source”,将音频文件“num”赋值给AudioClip,其他属性默认即可。
四,游戏完成源码分享
4.1 Manager游戏管理类
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Manager : MonoBehaviour { public static Manager _isnstance; //单例模式的引用 public Transform poolManager; //生成数字的池子 private GameObject numPrefab; //数字的预制体 public Number[,] numbers = new Number[4, 4]; //保存方格中的数组 //正在移动中的Num public List isMovingNum = new List(); public bool hasMove = false; //是否有数字发生了移动 public GameObject UIFinsh; //游戏结束页面 void Awake() { _isnstance = this; } void Start() { numPrefab = Resources.Load("Num"); // 开始游戏 ReStartBtn(); // 游戏结束面板按钮监听,重新开始 UIFinsh.GetComponentInChildren().onClick.AddListener(ReStartBtn); } // 重新开始 void ReStartBtn() { isMovingNum.Clear(); numbers = new Number[4, 4]; for (int i = poolManager.childCount - 1; i >= 0; i--) { Destroy(poolManager.GetChild(i).gameObject); } hasMove = false; //游戏开始生成两个数字 CreateNun(); CreateNun(); UIFinsh.SetActive(false); } void CreateNun() { GameObject go = Instantiate(numPrefab); go.transform.parent = poolManager; go.transform.localScale = Vector3.one; } #region 检测键盘和触摸输入 void Update() { //--------- 移动端检测逻辑 --------- //有触摸点,且滑动 if (isMovingNum.Count == 0) { if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved) { int dieX = 0; int dieY = 0; //获取滑动的距离 Vector2 touchDelPos = Input.GetTouch(0).deltaPosition; if (Mathf.Abs(touchDelPos.x) > Mathf.Abs(touchDelPos.y)) { //滑动距离 if (touchDelPos.x > 10) { dieX = 1; } else if (touchDelPos.x < -10) { dieX = -1; } } else { if (touchDelPos.y > 10) { dieY = 1; } else if (touchDelPos.y < -10) { dieY = -1; } } MoveNum(dieX, dieY); } } //--------- PC端检测逻辑 --------- if (isMovingNum.Count == 0) { int dieX = 0; int dieY = 0; if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow)) { dieX = -1; } else if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow)) { dieX = 1; } else if (Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow)) { dieY = 1; } else if (Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow)) { dieY = -1; } MoveNum(dieX, dieY); } if (hasMove && isMovingNum.Count == 0) //生成新的数字 { CreateNun(); hasMove = false; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (numbers[i, j] != null) { numbers[i, j].OneMove = false; } } } } } #endregion #region 游戏逻辑 /// /// 数字移动方法 /// /// /// public void MoveNum(int directionX, int directionY) { if (directionX == 1) //向右移动 { //首先将空格填满 最右侧列不需做判断 for (int j = 0; j < 4; j++) { for (int i = 2; i >= 0; i--) { if (numbers[i, j] != null) //格子中有物体(数字),,调用移动方法 { numbers[i, j].Move(directionX, directionY); } } } } else //===========向左移动================== if (directionX == -1) { for (int j = 0; j < 4; j++) { for (int i = 1; i < 4; i++) { //最左侧的一列 [0,0] [0,1] [0,2] [0,3] if (numbers[i, j] != null) { numbers[i, j].Move(directionX, directionY); } } } } else //===========向上移动================== if (directionY == 1) { for (int i = 0; i < 4; i++) { for (int j = 2; j >= 0; j--) { if (numbers[i, j] != null) { numbers[i, j].Move(directionX, directionY); } } } } else //===========向下移动================== if (directionY == -1) { for (int i = 3; i >= 0; i--) { for (int j = 0; j < 4; j++) { if (numbers[i, j] != null) //有物体(数字)就移动 { numbers[i, j].Move(directionX, directionY); } } } } } /// /// 判断是否是空格的方法 /// /// 数组索引X /// 数组索引Y /// public bool isEmpty(int x, int y) { if (x < 0 || x > 3 || y < 0 || y > 3) { return false; } else if (numbers[x, y] != null) { return false; } return true; } /// /// 判断游戏是否结束 /// /// 返回true则游戏结束 public bool isDead() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (numbers[i, j] == null) { return false; } } } for (int j = 0; j < 4; j++) { for (int i = 0; i < 3; i++) { if (numbers[i, j].value == numbers[i + 1, j].value) { return false; } } } for (int i = 0; i < 4; i++) { for (int j = 0; j < 3; j++) { if (numbers[i, j].value == numbers[i, j + 1].value) { return false; } } } return true; } #endregion /// /// 游戏结束 /// /// false:输,true:赢 public void ShowUIFinsh(bool isSuccess) { UIFinsh.SetActive(true); if (isSuccess) { UIFinsh.GetComponentInChildren().text = "游戏成功"; } else { UIFinsh.GetComponentInChildren().text = "游戏失败"; } } }
4.2 Number数字处理类
using System.Collections; using System.Collections.Generic; using System.Numerics; using UnityEngine; using UnityEngine.UI; using Vector3 = UnityEngine.Vector3; public class Number : MonoBehaviour { //在二维数组中的位置X,Y public int posX; public int posY; private int offsetX = -620; //显示偏移,Y,,, private int offsetY = -620; private int space = 420; // 间距 private bool isMoving = false; //动画是否播放过的计数 public int value; //产生数字是几 private bool toDestroy; //判断数字是否销毁 public bool OneMove = false; //标识数字是否合并过一次 // Use this for initialization void Start() { // 80%成2的概率,更改本身的Sprite名字,以更换图片 value = Random.value > 0.2f ? 2 : 4; this.GetComponent ().sprite = LoadSprite(); do { posX = Random.Range(0, 4); posY = Random.Range(0, 4); } while (Manager._isnstance.numbers[posX, posY] != null); transform.localPosition = GetLocalPos(); // 存放数字本身到数组中,表示此位置有数字不能生成新的数字 Manager._isnstance.numbers[posX, posY] = this; if (Manager._isnstance.isDead()) { // 游戏失败 Manager._isnstance.ShowUIFinsh(false); } } // Update is called once per frame void Update() { //播放一次动画 if (!isMoving) { if (transform.localPosition != GetLocalPos()) { isMoving = true; StartCoroutine(MoveAni()); } } } // 移动动画 IEnumerator MoveAni() { Debug.Log("移动动画..."); float t = 0; for (int i = 0; i < 10; i++) { transform.localPosition = Vector3.Lerp(transform.localPosition, GetLocalPos(), t); t += 0.1f; yield return new WaitForEndOfFrame(); } // 移动结束的回调 MoveOver(); } #region 游戏核心移动算法 ///
/// 核心,移动方法(有空格,有物体是否一样) /// public void Move(int directionX, int directionY) { //Debug.Log("测试"); //==========向右移动================== if (directionX == 1) { int index = 1; // 空格标志 while (Manager._isnstance.isEmpty(posX+index,posY)) { index++; } // 有空格的移动 if (index>1) { if (!Manager._isnstance.isMovingNum.Contains(this)) { // 保证不会重复添加物体(数字)到列表, Manager._isnstance.isMovingNum.Add(this); } //移动一次,就生成两个数字的标志符 Manager._isnstance.hasMove = true; //向空格位置移动 Manager._isnstance.numbers[posX, posY] = null; posX = posX + index - 1; Manager._isnstance.numbers[posX, posY] = this; } //有相同数字的移动 if (posX < 3 && value == Manager._isnstance.numbers[posX+1,posY].value && !Manager._isnstance.numbers[posX+1,posY].OneMove) { // 只合并一次的标志 Manager._isnstance.numbers[posX + 1, posY].OneMove = true; // 移动的标志,(生成新的物体(数字)) Manager._isnstance.hasMove = true; // 动画播放的限定(有数字在列表中就不会重复播放第二次动画) // 不会重复添加物体(数字)到列表, if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } // 碰到一样的数字,讲位置设为空 并销毁本身标识(true), // 再将其位置上的值变为2倍,(更换成新的数字) toDestroy = true; Manager._isnstance.numbers[posX, posY] = null; Manager._isnstance.numbers[posX + 1, posY].value *= 2; posX += 1; } }else //===========向左移动================== if (directionX == -1) { int index = 1; while (Manager._isnstance.isEmpty(posX - index, posY)) { index++; } //有空格的移动 if (index > 1) { Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } Manager._isnstance.numbers[posX, posY] = null; posX = posX - index + 1; Manager._isnstance.numbers[posX, posY] = this; } //碰到相同数字的移动 if (posX > 0 && value == Manager._isnstance.numbers[posX - 1, posY].value && !Manager._isnstance.numbers[posX - 1, posY].OneMove) { Manager._isnstance.numbers[posX - 1, posY].OneMove = true; Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } toDestroy = true; Manager._isnstance.numbers[posX, posY] = null; Manager._isnstance.numbers[posX - 1, posY].value *= 2; posX -= 1; } }else //===========向上移动================== if (directionY == 1) { int index = 1; //空格标志 while (Manager._isnstance.isEmpty(posX , posY + index)) { index++; } //有空格的移动 if (index > 1) { Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } Manager._isnstance.numbers[posX, posY] = null; posY = posY + index - 1; Manager._isnstance.numbers[posX, posY] = this; } //有相同位置的移动 if (posY < 3 && value == Manager._isnstance.numbers[posX , posY + 1].value && !Manager._isnstance.numbers[posX, posY + 1].OneMove) { Manager._isnstance.numbers[posX , posY + 1].OneMove = true; Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } toDestroy = true; Manager._isnstance.numbers[posX, posY] = null; Manager._isnstance.numbers[posX , posY + 1].value *= 2; posY += 1; } }else //===========向下移动================== if (directionY == -1) { int index = 1; //空格标志位 while (Manager._isnstance.isEmpty(posX, posY - index)) { index++; } //有空格的移动 if (index > 1) { Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } Manager._isnstance.numbers[posX, posY] = null; posY = posY - index + 1; Manager._isnstance.numbers[posX, posY] = this; } //有相同数字的移动 if (posY > 0 && value == Manager._isnstance.numbers[posX, posY - 1].value && !Manager._isnstance.numbers[posX, posY - 1].OneMove) { Manager._isnstance.numbers[posX, posY -1].OneMove = true; Manager._isnstance.hasMove = true; if (!Manager._isnstance.isMovingNum.Contains(this)) { Manager._isnstance.isMovingNum.Add(this); } toDestroy = true; Manager._isnstance.numbers[posX, posY] = null; Manager._isnstance.numbers[posX, posY - 1].value *= 2; posY -= 1; } } } #endregion /// /// 动画结束,标志改为false /// public void MoveOver() { isMoving = false; //若碰到了相同的数字 销毁自己,和改变另一个图片(数字) if (toDestroy) { Destroy(this.gameObject); value = Manager._isnstance.numbers[posX, posY].value; Manager._isnstance.numbers[posX, posY].GetComponent ().sprite = LoadSprite(); //游戏成功 if (value == 4096) { Manager._isnstance.ShowUIFinsh(true); } } Manager._isnstance.isMovingNum.Remove(this); } Vector3 GetLocalPos() { return new Vector3(offsetX + posX * space, offsetY + posY * space, 0); } /// /// 根据数字加载对应图片 /// /// Sprite LoadSprite() { return Resources.Load(value.ToString()); } }
结语
本文从新建项目开始一步一步带你完成所有步骤,还在等什么呢?三连支持一下吧~
需要素材的小伙伴点击链接下载即可。没有积分的同学,关注我的VX公众号,回复“2048”即可。
5G游戏 unity
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。