Unity笔记-15-2048小游戏
搭建UI框架
首先创建一个Canvas
画布,作为UI的承载体
创建一个Panel
取名为PanelMain
,作为游戏的主要运行画面
在PanelMain
中创建一个Image
作为游戏主要画面的背景,给予Grid Layout Group
组件,设置对应的参数,以达到让它的Child-Image
对象能按行列准确排列即可,如下图
然后依次制作出完成UI
,如下图
GameOver
会有事件去控制,这里先做好UI
即可
脚本部分
主要分三大类:1.游戏控制类;2.精灵(图片)功能类;3.游戏核心;4.资源读取类;
游戏控制类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using Core2048;
//设置枚举类型为Core2048里的枚举,防止枚举类型冲突
using MoveDirection = Core2048.MoveDirection;
public class GameControllerDemo : MonoBehaviour,IPointerDownHandler,IDragHandler
{
/// <summary>
/// 游戏核心算法
/// </summary>
private GameCoreDemo core;
/// <summary>
/// 每个位置的精灵脚本引用
/// </summary>
private NumberSetDemo[,] spriteAction;
/// <summary>
/// 开始
/// </summary>
private GameObject gameOver;
private void Start()
{
gameOver = transform.parent.transform.Find("GameOver").gameObject;
gameOver.SetActive(false);
spriteAction = new NumberSetDemo[4, 4];
core = new GameCoreDemo();
Init();
CreateNumber();
CreateNumber();
}
/// <summary>
/// 初始化地图
/// </summary>
private void Init()
{
for(int r = 0; r < 4; r++)
{
for(int c = 0; c < 4; c++)
{
CreateSprite(r,c);
}
}
}
/// <summary>
/// 创建精灵
/// </summary>
/// <param name="r"></param>
/// <param name="c"></param>
private void CreateSprite(int r,int c)
{
GameObject go = new GameObject(r.ToString()+c.ToString());
go.AddComponent<Image>();
NumberSetDemo set= go.AddComponent<NumberSetDemo>();
spriteAction[r, c] = set;//储存该位置的精灵脚本引用
set.SetNumber(0);
go.transform.SetParent(this.transform,false);
}
/// <summary>
/// 创建新数字
/// </summary>
private void CreateNumber()
{
LocationDemo loc;
int number;
core.GenerateNumber(out loc, out number);
spriteAction[loc.RIndex, loc.CIndex].SetNumber(number);
spriteAction[loc.RIndex, loc.CIndex].CreateEffect();
}
private void Update()
{
if (core.IsChange)
{
UpdateMap();
CreateNumber();
core.IsChange = false;
AllMergeEffect();
AllMoveEffect();
ResetAll();
UpdateScore();
Invoke("IsGameOver", 0.3f);
}
}
private void ResetAll()
{
//重置前位置地图
core.ReProData(1);
//core.ReProData(2);
//重置合并集合
core.mergeList.Clear();
}
/// <summary>
/// 所有精灵的移动动画
/// </summary>
private void AllMoveEffect()
{
for (int r = 0; r < 4; r++)
{
for (int c = 0; c < 4; c++)
{
if (core.preMap[r, c] != null)
{
spriteAction[r, c].MoveEffect(spriteAction[core.preMap[r, c].RIndex, core.preMap[r, c].CIndex].transform.position);
}
}
}
}
/// <summary>
/// 精灵的合并动画
/// </summary>
private void AllMergeEffect()
{
foreach (var item in core.mergeList)
{
LocationDemo loc = item as LocationDemo;
spriteAction[loc.RIndex, loc.CIndex].MergeEffect();
}
}
/// <summary>
/// 更新精灵
/// </summary>
private void UpdateMap()
{
for(int r = 0; r < 4; r++)
{
for(int c = 0; c < 4; c++)
{
spriteAction[r, c].SetNumber(core.Map[r,c]);
}
}
}
/// <summary>
/// 更新分数
/// </summary>
private void UpdateScore()
{
transform.parent.transform.Find("ScoreText").GetComponent<Text>().text=core.score.ToString();
}
/// <summary>
/// 鼠标开始位置
/// </summary>
private Vector2 startPos;
/// <summary>
/// 鼠标是否按下
/// </summary>
private bool isDown=false;
/// <summary>
/// 鼠标按下事件
/// </summary>
/// <param name="eventData"></param>
public void OnPointerDown(PointerEventData eventData)
{
startPos = eventData.position;
isDown = true;
}
/// <summary>
/// 鼠标拖动事件
/// </summary>
/// <param name="eventData"></param>
public void OnDrag(PointerEventData eventData)
{
if (isDown==false)
{
return;
}
Vector2 offset = eventData.position-startPos;
float x = Mathf.Abs(offset.x);
float y = Mathf.Abs(offset.y);
MoveDirection dir;
if (x >= y)//水平
{
dir = offset.x > 0 ? MoveDirection.Right : MoveDirection.Left;
}
else//垂直
{
dir = offset.y > 0 ? MoveDirection.Up : MoveDirection.Down;
}
core.Move(dir);
isDown = false;
}
/// <summary>
/// 重新开始
/// </summary>
public void ReStart()
{
for (int r = 0; r < 4; r++)
{
for (int c = 0; c < 4; c++)
{
spriteAction[r, c].SetNumber(0);
}
}
gameOver.SetActive(false);
core = new GameCoreDemo();
UpdateScore();
CreateNumber();
CreateNumber();
}
/// <summary>
/// 游戏结束
/// </summary>
private void IsGameOver()
{
if (core.IsOver())
{
gameOver.SetActive(true);
}
}
/// <summary>
/// 退出游戏
/// </summary>
public void Exit()
{
// UnityEditor.EditorApplication.isPlaying = false;
Application.Quit();
}
}
精灵功能类
public class NumberSetDemo : MonoBehaviour
{
/// <summary>
/// 图片组件引用
/// </summary>
private Image img;
private void Awake()
{
img = GetComponent<Image>();
}
/// <summary>
/// 设置精灵数字
/// </summary>
/// <param name="number"></param>
public void SetNumber(int number)
{
img.sprite = ResourcesLoadDemo.SpriteLoad(number);
}
/// <summary>
/// 精灵创建动画
/// </summary>
public void CreateEffect()//创建动画
{
iTween.ScaleFrom(this.gameObject,Vector3.zero,0.3f);
}
/// <summary>
/// 精灵合并动画
/// </summary>
public void MergeEffect()//合并动画
{
iTween.ScaleFrom(this.gameObject,Vector3.one*1.2F,0.3f);
}
/// <summary>
/// 精灵移动动画
/// </summary>
/// <param name="target"></param>
public void MoveEffect(Vector3 target)
{
iTween.MoveFrom(this.gameObject,iTween.Hash(
"position",target,
"speed",100,
"easetype",iTween.EaseType.easeOutElastic
));
}
}
游戏核心类
提供,游戏核心算法功能的类,相对独立
namespace Core2048
{
/// <summary>
/// 2048核心算法类
/// </summary>
public class GameCoreDemo
{
/// <summary>
/// 得分
/// </summary>
public int score;
/// <summary>
/// 当前地图布局
/// </summary>
private int[,] map;
/// <summary>
/// 本次变换前的地图布局,以下简称前地图
/// </summary>
private int[,] originalMap;
/// <summary>
/// 移动行/列的单独提取数组,以下简称单组
/// </summary>
private int[] mergeArray;
/// <summary>
/// 后移所有零的提取数组,以下简称去零单组
/// </summary>
private int[] removeZeroArray;
/// <summary>
/// 随机数
/// </summary>
private System.Random random;
/// <summary>
/// 空位置布局
/// </summary>
private List<LocationDemo> emptyLOC;
/// <summary>
/// 合并位置的集合
/// </summary>
public List<LocationDemo> mergeList;
/// <summary>
/// 移动行/列的单独提取合并位置的数组,以下简称合并单组
/// </summary>
public int[] mergePoint;//初始值为负表示空
/// <summary>
/// 上述数组的索引
/// </summary>
public int mergeIndex;
/// <summary>
/// 当前地图每个位置移动前的位置,以下简称前位置地图
/// </summary>
public LocationDemo[,] preMap;
/// <summary>
/// 移动行/列的单独提取移动前的位置数组,以下简称位置单组
/// </summary>
public int[] prePoint;
/// <summary>
/// 上述数组的索引
/// </summary>
public int preIndex = 0;
/// <summary>
/// 移动行/列的单独提取数组的非零数字
/// </summary>
public int preLength;
/// <summary>
/// 当前地图属性
/// </summary>
public int[,] Map
{
get
{ return map; }
}
/// <summary>
/// 核心算法构造函数-初始化
/// </summary>
public GameCoreDemo()
{
//初始化得分
score = 0;
//初始化前位置地图
preMap = new LocationDemo[4, 4];
//初始化位置单组
prePoint = new int[4];
//初始化合并单组
mergePoint = new int[] { -1, -1 };//注意:由于行列固定为4,因此单组合并的次数最大为2
//mergeIndex = 0;
//初始化地图
map = new int[4, 4];
//初始化单组
mergeArray = new int[map.GetLength(0)];
//初始化去零单组
removeZeroArray = new int[4];
//初始化空位置布局
emptyLOC = new List<LocationDemo>(16);
//初始化随机数
random = new System.Random();
//初始化前地图
originalMap = new int[4, 4];
//初始化合并位置集合
mergeList = new List<LocationDemo>(8);
}
/// <summary>
/// 合并单组重置
/// </summary>
public void MergePointReStart()
{
mergePoint[0] = -1;
mergePoint[1] = -1;
mergeIndex = 0;
}
/// <summary>
/// 位置单组以及前位置地图重置
/// </summary>
/// <param name="number"></param>
public void ReProData(int number)
{
switch (number)
{
case 1://重置前位置地图
preMap = new LocationDemo[4, 4];
break;
case 2://重置位置单组
preIndex = 0;
preLength = 0;
Array.Clear(prePoint, 0, prePoint.Length);
break;
}
}
/// <summary>
/// 后移单组的所有零,简称去零
/// </summary>
private void RemoveZero()
{
Array.Clear(removeZeroArray, 0, 4);
int index = 0;
for (int i = 0; i < mergeArray.Length; i++)
{
if (mergeArray[i] != 0)
{
removeZeroArray[index++] = mergeArray[i];//1
if (i == mergePoint[mergeIndex])//如果当前的i与合并位置相同,则赋给新去零后的合并位置
{
mergePoint[mergeIndex++] = index - 1;
}
}
}
mergeIndex = 0;
removeZeroArray.CopyTo(mergeArray, 0);
}
/// <summary>
/// 合并
/// </summary>
private void Merge()
{
//单组合并前,首先调用去零
RemoveZero();
//判断前后位置是否相同,有则合并
for (int i = 0; i < mergeArray.Length - 1; i++)
{
if (mergeArray[i] != 0 && mergeArray[i] == mergeArray[i + 1]&&mergeArray[i]!=2048)
{
mergeArray[i] += mergeArray[i + 1];
score += mergeArray[i];
mergeArray[i + 1] = 0;
//记录合并位置,合并单组索引自增
mergePoint[mergeIndex++] = i;
}
}
//合并完成,索引归零,因为下一行/列会更新单组
mergeIndex = 0;
//调用去零
RemoveZero();
}
/// <summary>
/// 记录单组变化前的位置
/// </summary>
private void RecordPrePoint()
{
for(int i = 0; i < mergeArray.Length; i++)
{
if (mergeArray[i] != 0)
{
prePoint[preIndex] = i;//按照数字顺序拿到之前的位置
preIndex++;
}
}
//记录有效数字
preLength = preIndex;
//位置单组索引归零
preIndex = 0;
}
/// <summary>
/// 向上移动
/// </summary>
private void MoveUp()
{
for (int c = 0; c < map.GetLength(1); c++)
{
//首先提取每行/列到单组
for (int r = 0; r < map.GetLength(0); r++)
mergeArray[r] = map[r, c];
//记录原位置
RecordPrePoint();
//合并
Merge();
//单组回归地图
for (int r = 0; r < map.GetLength(0); r++)
{
map[r, c] = mergeArray[r];
//把原位置记录到集合
if (preIndex<preLength&&mergeArray[r]!=0)
{
preMap[r, c] = new LocationDemo(prePoint[preIndex], c);
preIndex++;
}
//把合并位置记录到集合
if (mergeIndex < 2)
{
if (r == mergePoint[mergeIndex])
{
mergeList.Add(new LocationDemo(r, c));
}
}
}
//重置位置单组
ReProData(2);
//重置合并单组
MergePointReStart();
}
}
/// <summary>
/// 向下移动
/// </summary>
private void MoveDown()
{
for (int c = 0; c < map.GetLength(1); c++)
{
for (int r = map.GetLength(0) - 1; r >= 0; r--)
{
mergeArray[3 - r] = map[r, c];
}
RecordPrePoint();
Merge();
for (int r = map.GetLength(0) - 1; r >= 0; r--)
{
map[r, c] = mergeArray[3 - r];
if (preIndex < preLength && mergeArray[3-r] != 0)
{
preMap[r, c] = new LocationDemo(3-prePoint[preIndex], c);
preIndex++;
}
if (mergeIndex < 2)
{
if ((3 - r) == mergePoint[mergeIndex])
{
mergeList.Add(new LocationDemo(r, c));
mergeIndex++;
}
}
}
ReProData(2);
MergePointReStart();
}
}
/// <summary>
/// 向左移动
/// </summary>
private void MoveLeft()
{
for (int r = 0; r < 4; r++)
{
for (int c = 0; c < 4; c++)
mergeArray[c] = map[r, c];
RecordPrePoint();
Merge();
for (int c = 0; c < 4; c++)
{
map[r, c] = mergeArray[c];
if (preIndex < preLength && mergeArray[c] != 0)
{
preMap[r, c] = new LocationDemo(r,prePoint[preIndex]);
preIndex++;
}
if (mergeIndex < 2)
{
if (c == mergePoint[mergeIndex])
{
mergeList.Add(new LocationDemo(r, c));
mergeIndex++;
}
}
}
ReProData(2);
MergePointReStart();
}
}
/// <summary>
/// 向右移动
/// </summary>
private void MoveRight()
{
for (int r = 0; r < 4; r++)
{
for (int c = 3; c >= 0; c--)
mergeArray[3 - c] = map[r, c];
RecordPrePoint();
Merge();
for (int c = 3; c >= 0; c--)
{
map[r, c] = mergeArray[3 - c];
if (preIndex < preLength && mergeArray[3-c] != 0)
{
preMap[r, c] = new LocationDemo(r,3-prePoint[preIndex]);
preIndex++;
}
if (mergeIndex < 2)
{
if ((3 - c) == mergePoint[mergeIndex])
{
mergeList.Add(new LocationDemo(r, c));
mergeIndex++;
}
}
}
ReProData(2);
MergePointReStart();
}
}
/// <summary>
/// 地图改变与否
/// </summary>
public bool IsChange { get; set; }
/// <summary>
/// 移动主调用方法
/// </summary>
/// <param name="direction"></param>
public void Move(MoveDirection direction)
{
//移动前记录Map
Array.Copy(map, originalMap, map.Length);
IsChange = false;//假设没有发生改变
switch (direction)
{
case MoveDirection.Up: MoveUp(); break;
case MoveDirection.Down: MoveDown(); break;
case MoveDirection.Left: MoveLeft(); break;
case MoveDirection.Right: MoveRight(); break;
}
//移动后对比 重构 --> 提取方法
//MergePointReStart();
//检查地图是否改变
CheckMapChange();
}
/// <summary>
/// 检查地图是否改变
/// </summary>
private void CheckMapChange()
{
for (int r = 0; r < map.GetLength(0); r++)
{
for (int c = 0; c < map.GetLength(1); c++)
{
if (map[r, c] != originalMap[r, c])
{
IsChange = true;//发生改变
return;
}
}
}
}
/// <summary>
/// 计算空位置,并将空位置添加到空位置集合
/// </summary>
private void CalculateEmpty()
{
emptyLOC.Clear();
for (int r = 0; r < map.GetLength(0); r++)
{
for (int c = 0; c < map.GetLength(1); c++)
{
if (map[r, c] == 0)
{
emptyLOC.Add(new LocationDemo(r, c));
}
}
}
}
/// <summary>
/// 创建新数字
/// </summary>
/// <param name="loc"></param>
/// <param name="newNumber"></param>
public void GenerateNumber(out LocationDemo loc, out int newNumber)
{
CalculateEmpty();
if (emptyLOC.Count > 0)
{
int emptyLocIndex = random.Next(0, emptyLOC.Count);//0,15
loc = emptyLOC[emptyLocIndex];//有空位置的list 3
newNumber = map[loc.RIndex, loc.CIndex] = (random.Next(0, 10) == 1) ? 4 : 2;
//将该位置清除
emptyLOC.RemoveAt(emptyLocIndex);
}
else
{
//注意:int? a加了问好之后就会从值类型变成引用类型,被成为可空值类型,不再是原来的值类型,如果要获取值类型数据,要用a.Value但这是只读的
newNumber = -1;
loc = new LocationDemo(-1, -1);
}
}
/// <summary>
/// 游戏是否结束
/// </summary>
public bool IsOver()
{
if (emptyLOC.Count > 0) return false;
#region 省略
//水平
//for (int r = 0; r < 4; r++)
//{
// for (int c = 0; c < 3; c++)
// {
// if (map[r, c] == map[r, c + 1])
// return false;
// }
//}
垂直
//for (int c = 0; c < 4; c++)
//{
// for (int r = 0; r < 3; r++)
// {
// if (map[r, c] == map[r + 1, c])
// return false;
// }
//}
#endregion
for (int r = 0; r < 4; r++)
{
for (int c = 0; c < 3; c++)
{
//检查是否有合并空间
if (map[r, c] == map[r, c + 1] || map[c, r] == map[c + 1, r])
return false;
}
}
return true;//游戏结束
}
}
}
namespace Core2048
{
public enum MoveDirection
{
//******定义值*******
Up,
Down,
Left,
Right
}
}
namespace Core2048
{
public class LocationDemo
{
/// <summary>
/// 行索引
/// </summary>
public int RIndex { get; set; }
/// <summary>
/// 列索引
/// </summary>
public int CIndex { get; set; }
public LocationDemo(int r, int c)
{
this.RIndex = r;
this.CIndex = c;
}
}
}
资源读取类
public class ResourcesLoadDemo
{
/// <summary>
/// 资源字典
/// </summary>
private static Dictionary<int, Sprite> spriteNumber;
/// <summary>
/// 静态构造初始化资源
/// </summary>
static ResourcesLoadDemo()
{
spriteNumber = new Dictionary<int, Sprite>();
var allSprite = Resources.LoadAll<Sprite>("2048Demo/2048");
foreach (var item in allSprite)
{
spriteNumber.Add(int.Parse(item.name),item);
}
}
/// <summary>
/// 读取资源
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
public static Sprite SpriteLoad(int number)
{
return spriteNumber[number];
}
}