前言
此前特别喜欢玩2048,于是所幸就想了想便复刻了一个。还能扩展原先的游戏玩法性。
PS:玩到2048实在太简单了(😉)
游戏规则(游戏需求)
1.界面是有4x4个方格
2.开始随机两个方格生成数字
3.生成的数字10%是4,90%是2(可自定义)
4.玩家可以上下左右四个方向进行操作
5.玩家进行的每个方向的操作,如果相邻是同样的数字则会合并(空方格可以忽略,即两个相同的数字,如果中间有空方格,是能够合并的)
6.玩家每完成一次操作,并且有方块进行了移动或合并,则会在空方格中随机一个方格生成数字
胜利条件:方格中出现有2048数字
失败条件:所有方格都被占满,且上下左右操作都不能再合并数字。
分解需求
- 产生4x4的矩阵 int[,] map 并初始化InitGame()
- 生成数字的规则 GenerateNum()
- 挑选空方格生成数字
- 统计矩阵中空方格位置 CalEmpty struct MapPos List
emptyList - 随机一个位置生成数字 GenerateNumInEmpty()
- 统计矩阵中空方格位置 CalEmpty struct MapPos List
- 玩家的操作
- PlayerOp:Up,Down,Left,Right
- Move LeftMove RightMove UpMove DownMove
- 数字合并,变化则生成新数字
- 分为四个方向的合并,主要以Left为例,然后其余情况可以调整输入数字即可完成
- Merge()
- 移除空格子 RemoveEmpty()
- 相邻合并
- 移除空格子
- 回填
- 合并或移动前后是否有变化,有变化则生成新数字
- 胜利判断方法
- 失败判断方法
创建项目(Unity3d版本:2018.4.18f1)
- 项目命名:2048MiniGame
- 创建完成后在Assets目录创建GameRes,Resources,Scripts目录
- 导入相关资源与DoTween插件
2048资源和DoTween插件
将2048资源解压后放到GameRes中,DoTween插件解压后放到Assets目录下,然后进行安装即可。
完成后目录结构如下:
Scenes目录是创建项目后自动创建的目录,其中有一个SampleScene场景文件。
编写(游戏核心) GameCore.cs
根据分解的需求,一部一部完成核心逻辑
using System;
using System.Collections;
using System.Collections.Generic;
//存储4x4方格位置的结构体
public struct MapPos
{
public int x;
public int y;
public MapPos(int i,int j)
{
x = i;
y = j;
}
}
//玩家操作方向的枚举
public enum PlayerOp
{
Up,
Down,
Left,
Right
}
public class GameCore
{
private int[,] map;
private Random random;
private int row;
private int column;
private List<MapPos> emptyList;
private int[,] moveBeforeMap;
public GameCore()
{
row = 4;
column = 4;
map = new int[row, column];
moveBeforeMap = new int[row, column];
random = new Random();
emptyList = new List<MapPos>();
InitGame();
}
private void InitGame()
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < column; j++)
map[i, j] = 0;
}
}
/// <summary>
/// 90%生成2,10%生成4
/// </summary>
/// <returns></returns>
private int GenerateNum()
{
int result = random.Next(0, 10);
if (result < 9)
{
return 2;
}
else
{
return 4;
}
}
/// <summary>
/// 计算空格子
/// </summary>
private void CalEmpty()
{
emptyList.Clear();
for (int i = 0; i < row; i++)
{
for (int j = 0; j < column; j++)
{
if (map[i, j] == 0)
{
emptyList.Add(new MapPos(i, j));
}
}
}
}
/// <summary>
/// 在空格子处生成数字
/// </summary>
private void GenerateNumInEmpty()
{
CalEmpty();
int pos = random.Next(0, emptyList.Count);
MapPos mapPos = emptyList[pos];
map[mapPos.x, mapPos.y] = GenerateNum();
}
public void StartGame()
{
//随机寻找两个空白格子生成两个数字
GenerateNumInEmpty();
GenerateNumInEmpty();
}
public bool IsChange()
{
bool isChanage = false;
for(int i=0;i<row;i++)
{
for(int j=0;j<column;j++)
{
if(map[i,j]!=moveBeforeMap[i,j])
{
isChanage = true;
return isChanage;
}
}
}
return isChanage;
}
public void Move(PlayerOp op)
{
Array.Copy(map, moveBeforeMap, map.Length);
switch(op)
{
case PlayerOp.Left:
LeftMove();
break;
case PlayerOp.Right:
RightMove();
break;
case PlayerOp.Up:
UpMove();
break;
case PlayerOp.Down:
DownMove();
break;
}
if(IsChange())
{
GenerateNumInEmpty();
}
}
private void LeftMove()
{
for(int i=0;i<row;i++)
{
int[] tempArray = new int[column];
for(int j=0;j<column;j++)
{
tempArray[j] = map[i, j];
}
Merge(tempArray);
for(int j=0;j<column;j++)
{
map[i, j] = tempArray[j];
}
}
}
private void RightMove()
{
for (int i = 0; i < row; i++)
{
int[] tempArray = new int[column];
for (int j = 0; j < column; j++)
{
tempArray[column- 1-j] = map[i, j];
}
Merge(tempArray);
for (int j = 0; j < column; j++)
{
map[i, j] = tempArray[column - 1 - j];
}
}
}
private void UpMove()
{
for (int i = 0; i < column; i++)
{
int[] tempArray = new int[row];
for (int j = 0; j < row; j++)
{
tempArray[j] = map[j, i];
}
Merge(tempArray);
for (int j = 0; j < row; j++)
{
map[j, i] = tempArray[j];
}
}
}
private void DownMove()
{
for (int i = 0; i < column; i++)
{
int[] tempArray = new int[row];
for (int j = 0; j < row; j++)
{
tempArray[row-1-j] = map[j, i];
}
Merge(tempArray);
for (int j = 0; j < row; j++)
{
map[j, i] = tempArray[row - 1 - j];
}
}
}
private void Merge(int[] array)
{
//移除空格
RemoveEmpty(array);
//合并
for(int i=0;i<array.Length-1;i++)
{
if(array[i]!=0 && array[i]==array[i+1])
{
array[i] *= 2;
array[i + 1] = 0;
}
}
//移除空格
RemoveEmpty(array);
}
private void RemoveEmpty(int[] array)
{
int curEmptyIndex = 0;
for (int i = 0; i < array.Length; i++)
{
if (array[i] != 0)
{
if (curEmptyIndex != i)
{
array[curEmptyIndex] = array[i];
array[i] = 0;
}
curEmptyIndex++;
}
}
}
public string PrintMap()
{
string str = "";
for(int i=0;i<row;i++)
{
for(int j=0;j<column;j++)
{
str += map[i, j].ToString() + " ";
}
str += "\n";
}
return str;
}
}
玩家控制
- 创建GameController.cs
using UnityEngine.UI;
using UnityEngine.EventSystems; - 完成玩家的操作控制 (键盘和滑动)
滑动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class GameController : MonoBehaviour, IPointerDownHandler, IDragHandler
{
private Vector2 downPoint;
private bool isDown = false;
public void OnPointerDown(PointerEventData eventData)
{
downPoint = eventData.position;
isDown = true;
}
public void OnDrag(PointerEventData eventData)
{
if (!isDown)
return;
Vector2 offset = eventData.position - downPoint;
float x = Mathf.Abs(offset.x);
float y = Mathf.Abs(offset.y);
PlayerOp? dir = null;
if(x>y &&x>50)
{
dir = offset.x > 0 ? PlayerOp.Right : PlayerOp.Left;
}
else if(x<y && y>50)
{
dir = offset.y > 0 ? PlayerOp.Up : PlayerOp.Down;
}
if(dir!=null)
{
//游戏核心类完成相应的移动
//core.Move(dir);
}
isDown = false;
}
}
键盘控制
private float time = 0.0f;
private void Update()
{
time += Time.deltaTime;
if(time>0.2f)
{
KeyBordControl();
}
}
private void KeyBordControl()
{
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
time = 0.0f;
if (x>0)
{
core.Move(PlayerOp.Right);
Debug.Log(core.PrintMap());
}
else if(x<0)
{
core.Move(PlayerOp.Left);
Debug.Log(core.PrintMap());
}
else if(y>0)
{
core.Move(PlayerOp.Up);
Debug.Log(core.PrintMap());
}
else if(y<0)
{
core.Move(PlayerOp.Down);
Debug.Log(core.PrintMap());
}
}
总结
- 本篇主要讲述了项目的创建
- 分析需求,并分解需求,然后在书写游戏核心类
- 加入玩家的控制(包括键盘输入和手势滑动)
- 基本完成游戏能够在控制台游玩
扩展:如何创建出游戏相应的UI?
如何使用DoTween给UI加上动画,丰富游戏感受?