Unity3d 2048 核心算法講解

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ù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浑吟,隨后出現(xiàn)的幾起案子笙纤,更是在濱河造成了極大的恐慌,老刑警劉巖组力,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件省容,死亡現(xiàn)場離奇詭異,居然都是意外死亡燎字,警方通過查閱死者的電腦和手機腥椒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門阿宅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笼蛛,你說我怎么就攤上這事洒放。” “怎么了滨砍?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵往湿,是天一觀的道長。 經(jīng)常有香客問我惋戏,道長领追,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任响逢,我火速辦了婚禮绒窑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舔亭。我一直安慰自己些膨,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布分歇。 她就那樣靜靜地躺著傀蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪职抡。 梳的紋絲不亂的頭發(fā)上葬燎,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音缚甩,去河邊找鬼谱净。 笑死,一個胖子當著我的面吹牛擅威,可吹牛的內(nèi)容都是我干的壕探。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼郊丛,長吁一口氣:“原來是場噩夢啊……” “哼李请!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厉熟,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤导盅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后揍瑟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體白翻,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年绢片,在試婚紗的時候發(fā)現(xiàn)自己被綠了滤馍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岛琼。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巢株,靈堂內(nèi)的尸體忽然破棺而出槐瑞,到底是詐尸還是另有隱情,我是刑警寧澤纯续,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布随珠,位于F島的核電站,受9級特大地震影響猬错,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茸歧,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一倦炒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧软瞎,春花似錦逢唤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至只锭,卻和暖如春著恩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜻展。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工喉誊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纵顾。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓伍茄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親施逾。 傳聞我的和親對象是個殘疾皇子敷矫,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽?zāi)J的外補...
    _Yfling閱讀 13,754評論 1 92
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,104評論 1 32
  • 曾經(jīng)汉额,我有藍田日暖玉生煙 曾經(jīng)曹仗,我有滄海月明鮫人淚 曾經(jīng),我有嬉笑怒罵皆達意 也曾寄愁心與明月 也奈何花落不相識 ...
    聽雨煮酒閱讀 289評論 0 1
  • 事業(yè)分為三個階段: 生存期 發(fā)展期 自我實現(xiàn)期 人生亦如是闷愤,家族的發(fā)展亦如是 生存期整葡,根據(jù)財務(wù)指標指導(dǎo)行動,掙扎在...
    笑顏_5888閱讀 2,617評論 0 0