「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」前端
摘要:玩游戏也能学习知识?还记得高中时的化学元素常见金属活动性属性表吗?一块儿来看看化学元素和游戏之间发生的碰撞吧~「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」算法
玩游戏也能学习知识?还记得高中时的化学元素常见金属活动性属性表吗?一块儿来复习一下:钾K,钙Ca,钠Na,镁Mg,铝Al,锌Zn,铁Fe, 锡Ni,铅Sn,氢(H),铜Cu,汞Hg,银Ag,铂Pt,金Au。 一股很熟悉的味道有没有?一块儿来看看化学元素和游戏之间发生的碰撞吧~后端
2048 一款益智小游戏,游戏的规则十分简单,简单易上手的数字小游戏,闲来无事,本身制做一个,却怎么也到没有成功到2048。数组
老规矩,先看下按照此博文一步步操做完成的效果吧~markdown
打开Unity Hub 点击“新建”,在弹窗中输入 --> "项目名称" --> 选择"项目位置" --> "项目版本控制系统" 选不选都行,不须要在云端备份的话就不要选(我通常不选) ,选了没用过的话,会自动帮你安装PlasticSCM到本地。最后点击建立便可。dom
将提早准备好的素材(图片和声音)导入工程。(文末会提供下载地址)oop
导入项目后工程目录以下: post
设置分辨率:点击Game视图分辨率选框,设置为(1080x1920),没有的话点击加号自行添加一个,或者使用一个竖屏的就能够。学习
建立背景:右键UI --> Image 建立后,重命名为BG,将其锚点设置为铺满,源文件指定为素材"play_bg_forest_dark":测试
而后在“MapBg”下再次建立一个Image,重命名为“gezi”,将其宽高设置为(400,388),调整其颜色为(22,0,192,30),仍是同样调整到本身喜欢的颜色便可。而后“Ctrl + D” 复制15个格子出来:
最后在“MapBg”上添加"Grid Layout Group",属性设置以下图,将格子铺满到地图背景上:
场景终于搭建完了,下面开始编写脚本吧,完整代码在四中给出,这里只讲解实现思路,查看核心逻辑。
在Project下建立Scripts文件夹,而后建立"Manager.cs" 和 "Number.cs" 脚本。
基础逻辑就是:默认生成两个数字,接收用户输入,管理器触发移动逻辑,数字移动并校验合并,移动后校验是否游戏结束,若结束处理游戏结束摞,若未结束在自动生成一个数字:
graph TD 默认生成数字 --> 管理器接收用户输入 --> 管理器触发数字移动逻辑 --> 数字移动并校验合并 --> 移动后校验游戏结束 --结束--> 处理游戏结束逻辑 移动后校验游戏结束 --继续--> 默认生成数字
此时全部的游戏逻辑就都完成,运行游戏就能够玩耍了~
音效是这一个游戏的很重要的部分,一个好的音效可让用户获得更好的反馈,因此再简单的游戏也要有背景音和音效给用户一个好的交互体验。
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<Number> isMovingNum = new List<Number>();
public bool hasMove = false; //是否有数字发生了移动
public GameObject UIFinsh; //游戏结束页面
void Awake()
{
_isnstance = this;
}
void Start()
{
numPrefab = Resources.Load<GameObject>("Num");
// 开始游戏
ReStartBtn();
// 游戏结束面板按钮监听,从新开始
UIFinsh.GetComponentInChildren<Button>().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 游戏逻辑
/// <summary>
/// 数字移动方法
/// </summary>
/// <param name="directionX"></param>
/// <param name="directionY"></param>
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);
}
}
}
}
}
/// <summary>
/// 判断是不是空格的方法
/// </summary>
/// <param name="x">数组索引X</param>
/// <param name="y">数组索引Y</param>
/// <returns></returns>
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;
}
/// <summary>
/// 判断游戏是否结束
/// </summary>
/// <returns>返回true则游戏结束</returns>
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
/// <summary>
/// 游戏结束
/// </summary>
/// <param name="isSuccess">false:输,true:赢</param>
public void ShowUIFinsh(bool isSuccess)
{
UIFinsh.SetActive(true);
if (isSuccess)
{
UIFinsh.GetComponentInChildren<Text>().text = "游戏成功";
}
else
{
UIFinsh.GetComponentInChildren<Text>().text = "游戏失败";
}
}
}
复制代码
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<Image>().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 游戏核心移动算法
/// <summary>
/// 核心,移动方法(有空格,有物体是否同样)
/// </summary>
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
/// <summary>
/// 动画结束,标志改成false
/// </summary>
public void MoveOver()
{
isMoving = false;
//若碰到了相同的数字 销毁本身,和改变另外一个图片(数字)
if (toDestroy)
{
Destroy(this.gameObject);
value = Manager._isnstance.numbers[posX, posY].value;
Manager._isnstance.numbers[posX, posY].GetComponent<Image>().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);
}
/// <summary>
/// 根据数字加载对应图片
/// </summary>
/// <returns></returns>
Sprite LoadSprite()
{
return Resources.Load<Sprite>(value.ToString());
}
}
复制代码
本文重新建项目开始一步一步带你完成全部步骤,还在等什么呢?三连支持一下吧~
须要素材或者源码的小伙伴能够评论区留言哦~