2048是一款非常好玩的游戲唱较,經(jīng)常讓人花費好幾個小時來玩它。 游戲目標是將相同值的格子“合并”在一起召川,合并到一起后原來格子的值翻倍南缓。 當玩家在她想要的方向上滑動時,格子被移動到一邊并且生成新的格子荧呐。 如果玩家達到了2048汉形,那么就贏了游戲。 這篇文檔主要用來介紹如何在Unity中來制作這個游戲倍阐。
輸入系統(tǒng)
游戲?qū)崿F(xiàn)了通過鍵盤的箭頭鍵來控制格子移動的方法, 我們通過一個枚舉來獲取用戶的輸入和一個必須由我們想要使用的每個輸入方法實現(xiàn)的接口IInputDetector概疆。
public enum InputDirection
{
Left, Right, Top, Bottom
}
public interface IInputDetector
{
InputDirection? DetectInputDirection();
}
DetectInputDirection方法的返回值已經(jīng)實現(xiàn)為Nullable類型,因為用戶可能根本沒有輸入峰搪。 現(xiàn)在岔冀,讓我們訪問第一個通過鍵盤輸入的輸入法。 如下所示概耻,代碼非常簡單明了
public class ArrowKeysDetector : MonoBehaviour, IInputDetector
{
public InputDirection? DetectInputDirection()
{
if (Input.GetKeyUp(KeyCode.UpArrow))
return InputDirection.Top;
else if (Input.GetKeyUp(KeyCode.DownArrow))
return InputDirection.Bottom;
else if (Input.GetKeyUp(KeyCode.RightArrow))
return InputDirection.Right;
else if (Input.GetKeyUp(KeyCode.LeftArrow))
return InputDirection.Left;
else
return null;
}
}
全局變量
Globals類包含有關(guān)Rows使套,Columns和AnimationDuration的靜態(tài)變量。
public static class Globals
{
public readonly static int Rows = 4;
public readonly static int Columns = 4;
public static readonly float AnimationDuration = 0.05f;
}
格子相關(guān)數(shù)據(jù)結(jié)構(gòu)
ItemMovementDetails類用于攜帶有關(guān)即將移動和/或復(fù)制的對象的詳細信息鞠柄。 NewRow / NewColumn屬性包含項目在數(shù)組中的位置信息侦高,而GOToAnimateScale和GOToAnimatePosition屬性包含有關(guān)即將移動和/或擴展的游戲?qū)ο蟮男畔ⅰ?正常的過程是移動一個項目(改變它的位置),但如果這個項目將與另一個項目合并厌杜,那么這也將改變它的大蟹钋骸(然后消失)。
public class ItemMovementDetails
{
public GameObject GOToAnimateScale { get; set; }
public GameObject GOToAnimatePosition { get; set; }
public int NewRow { get; set; }
public int NewColumn { get; set; }
public ItemMovementDetails(int newRow, int newColumn, GameObject goToAnimatePosition, GameObject goToAnimateScale)
{
NewRow = newRow;
NewColumn = newColumn;
GOToAnimatePosition = goToAnimatePosition;
GOToAnimateScale = goToAnimateScale;
}
}
Item類很簡單
Value屬性包含項目的值(例如2,4,8,16等)
row和column屬性包含此項目所屬的數(shù)組的相應(yīng)行和列值
GO屬性包含對此Item引用的Unity GameObject的引用
WasJustDuplicated值是指此項目在此移動中是否重復(fù)
public class Item
{
public int Value { get; set; }
public int Row { get; set; }
public int Column { get; set; }
public GameObject GO { get; set; }
public bool WasJustDuplicated { get; set; }
}
ItemArray類包含一個私有成員,一個名為matrix的二維項目數(shù)組瞧壮。 它還公開了一個索引器登馒,以提供對此數(shù)組的訪問。 如果項占據(jù)數(shù)組中的位置馁痴,則矩陣[row谊娇,column]項包含對它的引用。 否則罗晕,matrix [row济欢,column]為null。
public class ItemArray
{
private Item[,] matrix = new Item[Globals.Rows, Globals.Columns];
public Item this[int row, int column]
{
get
{
return matrix[row, column];
}
set
{
matrix[row, column] = value;
}
}
此方法獲取數(shù)組中的非null項小渊。 它用于在每次移動后創(chuàng)建新格子法褥。
public void GetRandomRowColumn(out int row, out int column)
{
do
{
row = random.Next(0, Globals.Rows);
column = random.Next(0, Globals.Columns);
} while (matrix[row, column] != null);
}
每次滑動后都會調(diào)用此方法,并將所有WasJustDuplicated值設(shè)置為false酬屉。
private void ResetWasJustDuplicatedValues()
{
for (int row = 0; row < Globals.Rows; row++)
for (int column = 0; column < Globals.Columns; column++)
{
if (matrix[row, column] != null && matrix[row, column].WasJustDuplicated)
matrix[row, column].WasJustDuplicated = false;
}
}
此方法檢查作為參數(shù)傳遞的兩個項(通過其列/行索引)是否具有相同的值半等。首先,它檢查傳遞的索引是否超出范圍呐萨。然后它檢查此數(shù)組位置中的項是否為空杀饵,以及它是否只是重復(fù)(即在當前滑動后它沒有重復(fù))。如果所有這些檢查都是真的谬擦,那么
我們復(fù)制第一個項目值并將WasJustDuplicated字段設(shè)置為true
我們在保持對它的引用之后從數(shù)組中刪除第二個項目切距,以便為它設(shè)置動畫
我們返回一個ItemMovementDetails類的新實例,它攜帶項目的信息以使其位置具有動畫效果惨远,并使項目的比例為動畫(并最終消失)谜悟。
關(guān)于根據(jù)用戶的滑動項目的移動,我們有各種場景我們必須涵蓋北秽。請記住葡幸,數(shù)組中的空項表示空格。
因此贺氓,我們假設(shè)X是空列蔚叨,2是值為“2”的列。我們還假設(shè)左滑動掠归∶宓可能發(fā)生的一些情況如下,以及滑動后的相應(yīng)項目移動虏冻。
a)2 | 2 | X | X => 4 | X | X | X.
b)2 | X | 2 | X => 4 | X | X | X.
c)2 | 2 | X | 2 => 4 | 2 | X | X. //前兩個'2'將合并,第三個將移動到第二列
d)X | 2 | 2 | 2 => 4 | 2 | X | X. //與先前選項相同的情況弹囚。前兩個'2'合并厨相,移動到第一列,第三個'2'移動到第二列。
e)4 | 2 | 2 | X => 4 | 4 | X | X.
private ItemMovementDetails AreTheseTwoItemsSame(
int originalRow, int originalColumn, int toCheckRow, int toCheckColumn)
{
if (toCheckRow < 0 || toCheckColumn < 0 || toCheckRow >= Globals.Rows || toCheckColumn >= Globals.Columns)
return null;
if (matrix[originalRow, originalColumn] != null && matrix[toCheckRow, toCheckColumn] != null
&& matrix[originalRow, originalColumn].Value == matrix[toCheckRow, toCheckColumn].Value
&& !matrix[toCheckRow, toCheckColumn].WasJustDuplicated)
{
matrix[toCheckRow, toCheckColumn].Value *= 2;
matrix[toCheckRow, toCheckColumn].WasJustDuplicated = true;
var GOToAnimateScaleCopy = matrix[originalRow, originalColumn].GO;
matrix[originalRow, originalColumn] = null;
return new ItemMovementDetails(toCheckRow, toCheckColumn, matrix[toCheckRow, toCheckColumn].GO, GOToAnimateScaleCopy);
}
else
{
return null;
}
}
此方法將項目移動到應(yīng)該去的位置(基于值檢查)蛮穿。它將項目分配給新位置并“取消”舊項目庶骄。此外,它檢查它旁邊的項目是否具有相同的值践磅。如果是這種情況单刁,我們會返回此信息,而如果它們不同府适,我們只返回已移動項目的詳細信息羔飞。
private ItemMovementDetails MoveItemToNullPositionAndCheckIfSameWithNextOne
(int oldRow, int newRow, int itemToCheckRow, int oldColumn, int newColumn, int itemToCheckColumn)
{
matrix[newRow, newColumn] = matrix[oldRow, oldColumn];
matrix[oldRow, oldColumn] = null;
ItemMovementDetails imd2 = AreTheseTwoItemsSame(newRow, newColumn, itemToCheckRow,
itemToCheckColumn);
if (imd2 != null)
{
return imd2;
}
else
{
return new ItemMovementDetails(newRow, newColumn, matrix[newRow, newColumn].GO, null);
}
}
此方法將項目移動到應(yīng)該去的位置(基于值檢查)。它將項目分配給新位置并“取消”舊項目檐春。此外逻淌,它檢查它旁邊的項目是否具有相同的值。如果是這種情況疟暖,我們會返回此信息卡儒,而如果它們不同,我們只返回已移動項目的詳細信息俐巴。
我們有兩種移動項目的方法骨望。滑動是水平時調(diào)用的一個欣舵,垂直滑動調(diào)用的一個擎鸠。在編寫代碼時,我首先創(chuàng)建了一個“MoveLeft”方法邻遏。經(jīng)過多次測試糠亩,修復(fù)等,我創(chuàng)建了“MoveRight”准验。然后赎线,我很清楚它們可以合并為一個方法,所以我創(chuàng)建了MoveHorizontal方法糊饱。再次垂寥,經(jīng)過多次測試和修復(fù)后,對方法進行了轉(zhuǎn)換和調(diào)整另锋,以創(chuàng)建MoveVertical方法滞项。這些方法有很多共同點,它們當然可以合并為一個“Move”方法夭坪。但是文判,我強烈認為這會使本教程復(fù)雜化。因此室梅,我決定將它們保留原樣∠凡郑現(xiàn)在疚宇,它們在功能上非常相似,所以我們只描述“MoveHorizontal”赏殃。
public List<ItemMovementDetails> MoveHorizontal(HorizontalMovement horizontalMovement)
{
ResetWasJustDuplicatedValues();
var movementDetails = new List<ItemMovementDetails>();
int relativeColumn = horizontalMovement == HorizontalMovement.Left ? -1 : 1;
var columnNumbers = Enumerable.Range(0, Globals.Columns);
if (horizontalMovement == HorizontalMovement.Right)
{
columnNumbers = columnNumbers.Reverse();
}
}
方法從重置所有WasJustDuplicated值開始敷待。 然后,根據(jù)運動是左還是右仁热,我們得到-1或1.這將有助于確定要比較的項目榜揖。 如果向左滑動察署,我們將剩下的所有項目移動葛账,因此我們需要將每個項目與前一項目(-1一項)進行比較鼠锈,以便測試相似性寒砖。 轉(zhuǎn)移佛嬉,我們使用Enumerable.Range方法來獲取列索引蛉迹。 此方法將返回包含[0,1,2,3稠诲,...舵匾,Globals.Columns-1]的列表诬乞。 如果滑動是正確的册赛,那么我們顛倒columnNumbers列表的順序。 這是因為我們需要以正確的方向循環(huán)colums震嫉。 如果向左滑動森瘪,我們將首先檢查第一列是否為null,然后是第二列等票堵。這就是為什么因為我們要將第一個非空項目從左側(cè)開始移動到第一個空位置扼睬。 如果我們有正確的滑動,我們需要在相反的方向上執(zhí)行此操作悴势。 這就是我們反轉(zhuǎn)columnNumbers列表的原因窗宇。
for (int row = Globals.Rows - 1; row >= 0; row--)
{
foreach (int column in columnNumbers)
{
if (matrix[row, column] == null) continue;
ItemMovementDetails imd = AreTheseTwoItemsSame(row, column, row, column + relativeColumn);
if (imd != null)
{
movementDetails.Add(imd);
continue;
}
}
}
在這里,我們開始循環(huán)特纤。 當然军俊,我們會檢查所有行。 然后捧存,我們遍歷所有列粪躬,從columnNumbers列表中獲取索引。 在遍歷每一行時昔穴,我們首先檢查每個項目是否為null镰官。 如果它為null,我們繼續(xù)檢查下一個項目(通過檢查下一列 - 下一個意味著-1或1吗货,具體取決于滑動泳唠。當我們到達非空列時,我們檢查此列是否與 再次宙搬,“旁邊”表示-1或1警检,具體取決于滑動是左還是右孙援。如果這些項是相同的害淤,那么我們將此信息添加到movingDetails列表并繼續(xù)循環(huán) 下一欄扇雕。
int columnFirstNullItem = -1;
int numberOfItemsToTake = horizontalMovement == HorizontalMovement.Left? column : Globals.Columns – column;
bool emptyItemFound = false;
columnFirstNullItem++)
foreach (var tempColumnFirstNullItem in columnNumbers.Take(numberOfItemsToTake))
{
columnFirstNullItem = tempColumnFirstNullItem;
if (matrix[row, columnFirstNullItem] == null)
{
emptyItemFound = true;
break;
}
}
如果這些項不相同,那么我們必須在當前的第一個空位置移動我們當前引用的項窥摄。 對于左側(cè)滑動镶奉,如果項目是[row,column]崭放,那么唯一可能的位置是從[row哨苛,0]到[row,column-1]币砂,因此我們需要columnNumbers列表中的第一個列項建峭。 對于右滑動,唯一可能的位置是從[row决摧,Globals.Columns-1]到[row亿蒸,column + 1],因此我們需要第一個Globals.Columns - 來自reverse columnNumbers列表的列項掌桩。 我們在這些列中執(zhí)行循環(huán)(使用Take LINQ方法)保持對每個列號的引用(通過columnFirstNullItem變量)并檢查每個項是否為null边锁。 如果我們找到一個,我們退出循環(huán)波岛。
if (!emptyItemFound)
{
continue;
}
ItemMovementDetails newImd =MoveItemToNullPositionAndCheckIfSameWithNextOne
(row, row, row, column, columnFirstNullItem, columnFirstNullItem + relativeColumn);
movementDetails.Add(newImd);
}
}
return movementDetails;
}
如果我們沒有找到空項茅坛,則當前引用的項位于正確的位置,因此我們保持原樣则拷。 如果我們這樣做贡蓖,那么我們將當前引用的項移動到null位置,并創(chuàng)建ItemMovementDetails類的實例煌茬,以便攜帶動畫信息斥铺。 在MoveHorizontal方法的末尾,我們返回movementDetails列表宣旱,其中包含必須執(zhí)行的所有動畫的信息仅父。
游戲結(jié)束判定
public bool CheckGameOver()
{
for (int row = 0; row < Globals.Rows; row++)
{
for (int column = 0; column < Globals.Columns; column++)
{
if (matrix[row, column] == null)
{
return false;
}
}
}
for (int x = 0; x < Globals.Rows; x++)
{
for (int y = 0; y < Globals.Columns - 1; y++)
{
if (matrix[x, y].Value == matrix[x, y + 1].Value)
{
return false;
}
}
}
for (int y = 0; y < Globals.Columns; y++)
{
for (int x = 0; x < Globals.Rows - 1; x++)
{
if (matrix[x, y].Value == matrix[x + 1, y].Value)
{
return false;
}
}
}
return true;
}
結(jié)束判定很簡單,遍歷看矩陣中有沒有空 或 有沒有兩個相鄰且相等的數(shù)