1. 程式人生 > >用 Unity 編寫象棋遊戲

用 Unity 編寫象棋遊戲

原文:How to Make a Chess Game with Unity
作者:Brian Broom
譯者:kmyhy

並不是所有成功的遊戲都包括打外星人或拯救世界。棋盤遊戲,尤其是國際象棋,有著數千年的歷史。它們不僅玩起來很有趣,而且將它們從現實生活中轉變成視訊遊戲也很有趣。

在本教程中,你將用 Unity 編寫一個 3D 象棋遊戲。在這個過程中,你將學習:

  • 選擇要移動的棋子
  • 判斷移動是否合法
  • 切換玩家
  • 判斷輸贏

當您完成本教程時,你將建立一個富有功能的棋類遊戲,你可以將它作為其他棋盤遊戲的起點。

注意:你應該具有 Unity 和 C# 語言基礎。如果你需要學習 C#,那麼

Unity C# 初階屏播系列很適合初學者。

開始

請下載本教程的開始專案。你可以在本文頂部或底部找到下載連結。用 Unity 開啟開始專案。

象棋通常會做成簡單的 2D 遊戲。但是,本 3D 版本模擬了你和朋友坐在桌子旁邊下棋。此外…… 3D 比較棒了。

開啟 Scenes 資料夾中的 Main 場景。你會看到一個表示棋盤的 Board 物件和一個 GameManager 物件。這些物件都已經綁定了指令碼。

  • Prefabs: 包含了棋盤、 棋子、和移動過程中的指示方塊。
  • Materials: 包含了棋盤、棋子和瓦片的材質。
  • Scripts: 包含了在結構檢視中已經繫結到物件中的元件。
  • Board: 記錄棋子的視覺化狀態。這個元件還會處理每顆棋子的高亮狀態。
  • Geometry.cs: 工具類,負責處理行列轉換和 Vector3 點。
  • Player.cs: 記錄玩家的棋子,玩家手執的棋子。儲存玩家棋子移動的方向,比如小兵。
  • Piece.cs: 一個基類,定義了所有例項化的棋子物件的列舉。它還包含確定遊戲中有效移動的邏輯。
  • GameManager.cs: 儲存遊戲邏輯,比如允許的移動,遊戲一開始時棋子的位置等。它是一個單例物件,所以其他類很容易呼叫它。

GameManager 的 pieces 儲存了一個 2D 陣列,記錄了棋子在棋盤上的位置。可以看一下 AddPiece、PieceAtGrid 和 GridForPiece 的邏輯。

進入試玩模式,你會看到一個棋盤,棋子準備好後就可以下棋了。

移動棋子

首先需要找出要移動哪枚棋子。

射線查詢可用於找出使用者滑鼠正在經過哪一塊瓦片。如果你不熟悉 Unity 的射線查詢,請檢視我們的 Unity 指令碼教程入門或者我們的熱門教程炸彈超人

一旦玩家選擇了一個棋子,你需要在棋子可以移動的地方生成有效的瓦片。然後,你選擇其中一個瓦片。你將新增兩個新指令碼來實現這個功能。TileSelector 用於將選擇移動的棋子,MoveSelector 用於選擇目的地。

兩個元件的基本方法是類似的:

  • Start: 進行一次性的設定。
  • EnterState: 進行本次動作的設定。
  • Update: 當滑鼠移動時,執行射線查詢。
  • ExitState: 清除本次狀態,呼叫下一狀態的 EnterState。

這實現了一個基本的狀態機。如果你有更多狀態,可以寫得更規範點,當然程式碼也會更復雜。

選擇瓦片

在結構檢視中選擇棋盤。然後在檢視器視窗,點選 Add Component 按鈕。然後在文字框中輸入 TileSelector 並點選 New Script。最後,點選 Create and Add,繫結指令碼。

注:建立新指令碼的時候,記得將它們移動到正確的目錄。保持 Assets 資料夾的合理有序。

高亮選中瓦片

雙擊 TileSelector.cs,開啟檔案,新增變數:

public GameObject tileHighlightPrefab;

private GameObject tileHighlight;

這兩個變數構成了一個透明遮罩,用於凸顯你所選中的瓦片。預製件將在編輯模式下被賦值,而元件負責跟蹤和移動高亮狀態。

然後在 Start 方法中新增下列程式碼:

Vector2Int gridPoint = Geometry.GridPoint(0, 0);
Vector3 point = Geometry.PointFromGrid(gridPoint);
tileHighlight = Instantiate(tileHighlightPrefab, point, Quaternion.identity, gameObject.transform);
tileHighlight.SetActive(false);

Start 方法初始化高亮瓦片的行和列,將其轉換為 point,並通過預製件建立一個遊戲物件。這個物件一開始是未啟用的,所以在需要時才會顯示。

注:以行列引用座標是很有用的,它是一個 Vector2Int 型別,即一個 GridPoint。Vector2Int 有兩個整數值:x和y。當你需要在遊戲場景中放入一個物件時,你需要用 Vector3 座標。Vector3 座標包含三個浮點值:x、y 和 z。

在 Geometry.cs 有進行兩者間轉換的實用方法:
* GridPoint(int col, int row): gives you a GridPoint for a given column and row.
* PointFromGrid(Vector2Int gridPoint): turns a GridPoint into a Vector3 actual point in the scene.
* GridFromPoint(Vector3 point): gives the GridPoint for the x and z value of that 3D point, and the y value is ignored.

然後是 EnterState 方法:

public void EnterState()
{
    enabled = true;
}

當選擇另一顆棋子時,重新啟用元件。

然後是 Update 方法:

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
    Vector3 point = hit.point;
    Vector2Int gridPoint = Geometry.GridFromPoint(point);

    tileHighlight.SetActive(true);
    tileHighlight.transform.position =
        Geometry.PointFromGrid(gridPoint);
}
else
{
    tileHighlight.SetActive(false);
}

這裡,你從鏡頭中建立了一束射線,經過滑鼠點,射向無限遠!

Physics.Raycast 會檢測射線是否和系統中的物理碰撞體發生相交。由於棋盤是唯一擁有碰撞體的物件,你不必擔心棋子互相遮擋。

如果射線和碰撞體相交,RaycastHit 中會包含交點資料。你將交點轉換成 GridPoint(使用助手方法),然後設定高亮瓦片的位置。

由於滑鼠指標位於棋盤上方,你可以啟用高光瓦片,以便讓它顯示。

最後,在結構檢視中選擇 Board 然後在專案視窗中單擊 Prefabs。然後,將 Selection-Yellow 預製件拖到棋盤的 Tile Selector 元件的 Tile Hightlight Prefab 方框中。

進入遊戲試玩模式,當滑鼠指標移動時會有一個黃色的高亮瓦片跟隨。

選擇棋子

要選中某顆棋子,你需要判斷按下的滑鼠按鈕是哪一顆。在啟用完瓦片的 hightlight之後,用一個 if 語句中新增一個判斷:

if (Input.GetMouseButtonDown(0))
{
    GameObject selectedPiece = 
        GameManager.instance.PieceAtGrid(gridPoint);
    if(GameManager.instance.DoesPieceBelongToCurrentPlayer(selectedPiece))
    {
        GameManager.instance.SelectPiece(selectedPiece);
    // Reference Point 1: add ExitState call here later
    }
}

如果滑鼠按鈕被按下,GameManager 就會為你獲取該位置的棋子。你還必須確保這個棋子是屬於當前玩家的,因為玩家不允許移動對手的棋子。

注:在一個複雜的遊戲中,最好為元件清晰地劃分職責。棋盤負責顯示和凸顯棋子。GameManager 負責保棋子的 GridPoint。助手方法負責告訴棋子在哪裡以及它們屬於哪個玩家。

進入試玩模式,選擇一枚棋子。

現在你手上已經有棋子了,把它移動到別的地方吧。

選擇移動目標

現在,TileSelector 已經完。接下來是另一個元件:MoveSelector。

這個元件和 TileSelector 類似。和之前一樣,在結構檢視中選中 Board 物件,新增一個新元件,名為 MoveSelector。

傳遞控制

第一件事情是將控制從 TileSelector 傳遞給 MoveSelector。這樣我們就需要用到 ExitState 了。在 TileSelector.cs 中,新增一個方法:

private void ExitState(GameObject movingPiece)
{
    this.enabled = false;
    tileHighlight.SetActive(false);
    MoveSelector move = GetComponent<MoveSelector>();
    move.EnterState(movingPiece);
}

這裡我們隱藏 overlay 瓦片,然後禁用 TileSelector 元件。在 Unity 中,在禁用元件上你無法呼叫 Update 方法。因為你想呼叫另外一個元件的 Update 方法,所以就禁用原元件,防止干擾。

在 Update 方法的 Referenct point 1 之後呼叫這個方法。

ExitState(selectedPiece);

然後開啟 MoveSelector 新增一個例項變數:

public GameObject moveLocationPrefab;
public GameObject tileHighlightPrefab;
public GameObject attackLocationPrefab;

private GameObject tileHighlight;
private GameObject movingPiece;

這些變數用於儲存滑鼠高亮、移動點和攻擊點的瓦片圖層,以及原先的高亮瓦片以及前面選中的棋子。

然後,在 Start 方法中加入:

this.enabled = false;
tileHighlight = Instantiate(tileHighlightPrefab, Geometry.PointFromGrid(new Vector2Int(0, 0)),
    Quaternion.identity, gameObject.transform);
tileHighlight.SetActive(false);

這個元件一開始必須 disabled,因為首先要執行 TitleSelector。然後,載入高亮圖層。

移動棋子

然後新增 EnterState 方法:

public void EnterState(GameObject piece)
{
    movingPiece = piece;
    this.enabled = true;
}

當這個方法呼叫時,它會儲存被移動的棋子,然後禁用元件自身。

在 MoveSelector 的 Update 方法中,新增程式碼:

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
    Vector3 point = hit.point;
    Vector2Int gridPoint = Geometry.GridFromPoint(point);

    tileHighlight.SetActive(true);
    tileHighlight.transform.position = Geometry.PointFromGrid(gridPoint);
    if (Input.GetMouseButtonDown(0))
    {
        // Reference Point 2: check for valid move location
        if (GameManager.instance.PieceAtGrid(gridPoint) == null)
        {
            GameManager.instance.Move(movingPiece, gridPoint);
        }
        // Reference Point 3: capture enemy piece here later
        ExitState();
    }
}
else
{
    tileHighlight.SetActive(false);
}

這個 Update 方法和 TileSelector 的 Update 方法類似,同樣使用射線檢查滑鼠正在點選那個瓦片。當滑鼠鍵被按下,呼叫 GameManager 移動棋子到新瓦片上。

最後是 ExitState 方法,做一些清理工作併為下次移動做好準備:

private void ExitState()
{
    this.enabled = false;
    tileHighlight.SetActive(false);
    GameManager.instance.DeselectPiece(movingPiece);
    movingPiece = null;
    TileSelector selector = GetComponent<TileSelector>();
    selector.EnterState();
}

這裡禁用了元件,將高亮瓦片隱藏。因為棋子已經移動完成,你可以清空它,讓 Gamemanager 取消棋子的高亮狀態。然後,呼叫 TileSelector 的 EnterState,以便再次開始整個過程。

回到編輯器,選中 Board 物件,從 prefab 資料夾將 tile overlay 預製件拖到 MoveSelector 的這些地方:

  • Move Location Prefab 的值應該是 Selection-Blue
  • Tile Highlight Prefab 的值應該是 Selection-Yellow.
  • Attack Location Prefab 的值應該是 Selection-Red

你可以通過修改材質來調整它們的顏色。

開啟試玩模式,移動棋子試試。

你會發現,你可以將棋子移動到任何空格子上。這在象棋中完全是不合理的。接下來應該讓棋子的移動符合遊戲規則。

算出合法的移動

在國際象棋中,每個棋子能夠做出的動作是不一樣的。有的棋子能夠往任意方向移動,有的棋子可以移動任意個空格,有的棋子只能在某個方向上進行移動。你如何記住這些規則?

一種方法是用一個抽象的基類來表示所有棋子,然後由子類來實現具體的方法,以計算移動的位置。

另一個問題是:這些動作要在哪裡生成?

一個選擇是在 MoveSelector 的 EnterStat 方法中。這是你將棋子可以移動的位置顯示給使用者的方法,因此這是一個合理的選擇 。

計算有效目標的集合

常見的策略是選中一顆棋子,然後讓 GameMananger 返回一個有效目標(比如移動)的集合。GameManager 使用該棋子的子類計算可能的目標集合。然後,過濾掉其中已經被佔的或者已經離開棋盤的位置。

過濾後的集合傳回給 MoveSelector,將合理的移動高亮顯示,等待玩家做出選擇。

小兵的移動最為簡單,因此我們從它開始。

開啟 Pieces 下面的 Pawn.cs,修改 MoveLocation 方法:

public override List MoveLocations(Vector2Int gridPoint) 
{
    var locations = new List<Vector2Int>();

    int forwardDirection = GameManager.instance.currentPlayer.forward;
    Vector2Int forward = new Vector2Int(gridPoint.x, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forward) == false)
    {
        locations.Add(forward);
    }

    Vector2Int forwardRight = new Vector2Int(gridPoint.x + 1, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forwardRight))
    {
        locations.Add(forwardRight);
    }

    Vector2Int forwardLeft = new Vector2Int(gridPoint.x - 1, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forwardLeft))
    {
        locations.Add(forwardLeft);
    }

    return locations;
}

這個方法做了以下事情:

  1. 這段程式碼首先建立一個空的 list 用於儲存位置。然後,建立了一個 location 用於表示前方的一個空格。
  2. 因為白子和黑子移動方向相反,玩家物件有一個值表示了小兵可以移動的方向。對於第一個玩家這個值是 +1,第二個玩家這個值是 -1。
  3. 小兵有一個特殊的移動方式和幾點特殊規則。它雖然可以往前移動一步,但它卻不能吃那個格子中的對方棋子,而是吃前方對角線上的棋子。在把前面這個格子標記為有效移動位置之前,首先要判斷這個地方有沒有被其它棋子佔據。如果沒有,你可以將這個瓦片放到 list 中。
  4. 對於吃子,你必須檢查那個位置是否有棋子。如果有,才能吃子。

現在還不需要關心需要判斷是自己的棋子還是對方的棋子——這個稍後再說。

在 GameManager.cs 中,在 Move 方法後新增一個方法:

public List MovesForPiece(GameObject pieceObject)
{
    Piece piece = pieceObject.GetComponent();
    Vector2Int gridPoint = GridForPiece(pieceObject);
    var locations = piece.MoveLocations(gridPoint);

    // filter out offboard locations
    locations.RemoveAll(tile => tile.x < 0 || tile.x > 7
        || tile.y < 0 || tile.y > 7);

    // filter out locations with friendly piece
    locations.RemoveAll(tile => FriendlyPieceAt(tile));

    return locations;
}

這裡,你從 GameOject 中獲取一個 Piece 元件,以及它的當前位置。

然後,要求 GameManager 返回一個該棋子的 location 集合,並過濾掉其中無效的值。

RemoveAll 方法使用一個回撥 lamda 表示式作為引數。這個方法遍歷 list 中所有值,把它傳給表示式中的 tile 變數。如果表示式返回 ture,這個值就會從 list 中移除。

第一個表示式移除所有 x、y 值超出棋盤以外的 location。第二個表示式移除所有己方棋子的位置。

在 MoveSelector.cs 中,新增一個例項變數:

private List<Vector2Int> moveLocations;
private List<GameObject> locationHighlights;

第一個變數儲存了一個移動位置的 GridPoint 陣列;第二個變數儲存了玩家是否可以移動到那個地方的 overlay 瓦片陣列。

在 EnterState 方法最後新增:

moveLocations = GameManager.instance.MovesForPiece(movingPiece);
locationHighlights = new List<GameObject>();

foreach (Vector2Int loc in moveLocations)
{
    GameObject highlight;
    if (GameManager.instance.PieceAtGrid(loc))
    {
        highlight = Instantiate(attackLocationPrefab, Geometry.PointFromGrid(loc),
            Quaternion.identity, gameObject.transform);
    } 
    else 
    {
        highlight = Instantiate(moveLocationPrefab, Geometry.PointFromGrid(loc),
            Quaternion.identity, gameObject.transform);
    }
    locationHighlights.Add(highlight);
}

這段程式碼做了這幾件事情:

首先,它從 GameManager 獲取有效 location 陣列,構造一個空陣列用於儲存 overlay 瓦片物件。然後對 lcoation 陣列進行遍歷,如果在某個位置已經有棋子,那麼必定是對方棋子,因為己方棋子已經被過濾掉了。

對方棋子加上攻擊蒙層,而其它棋子則新增移動蒙層。

執行動作

在 Reference Point 2 處新增程式碼,就在判斷滑鼠按鈕的 if 語句中:

if (!moveLocations.Contains(gridPoint))
{
    return;
}

如果玩家玩家點選到無效的瓦片,退出函式。

最後,在 MoveSelector.cs 的 ExitState 中新增程式碼:

foreach (GameObject highlight in locationHighlights)
{
    Destroy(highlight);
}

這時,玩家已經選擇了一個落點,你可以移除 overlay 物件了。

哇!改了這麼多程式碼,僅僅是讓小兵動一步而已。現在你已經完成了最艱鉅的工作,其它棋子的移動就簡單了。

下一個玩家

只有一邊可以動的遊戲並不多見。該解決下這個問題了!

為了讓兩邊都能玩,你必須知道如何切換玩家以及在哪裡新增程式碼。

因為 GameManager 負責所有遊戲規則,切換玩家的程式碼應該放在這裡。

實際上,切換玩家十分簡單。在 GameManager 中定義有當前玩家和其它玩家的變數,你只需要交換二者即可。

更麻煩一點的問題是:在哪裡呼叫切換玩家的方法?

當玩家移動了一顆棋子後,這個玩家的回合就結束了。MoveSelector 的 ExitState 方法在棋子被移動之後都會呼叫,因此這裡就是進行切換的好地方。

在 GameManager.cs 最後新增這個方法:

public void NextPlayer()
{
    Player tempPlayer = currentPlayer;
    currentPlayer = otherPlayer;
    otherPlayer = tempPlayer;
}

交換需要使用臨時變數;否則在拷貝一個值之前會導致原來的值被覆蓋。

回到 MoveSelector.cs,在 ExitState 方法中,呼叫 EnterState 之前新增程式碼:

GameManager.instance.NextPlayer();

這就可以了!ExitState 和 EnterState 會進行清理工作。

進入試玩模式,你可以移動雙方棋子了。距離真正的遊戲不遠嘍!

吃子

吃子是棋類遊戲中的重要內容。俗話說得好,“在真正失去騎士之前,一切都不過是遊戲”。

因為遊戲規則由 GameManager 負責,所以開啟它增加一個方法:

public void CapturePieceAt(Vector2Int gridPoint)
{
    GameObject pieceToCapture = PieceAtGrid(gridPoint);
    currentPlayer.capturedPieces.Add(pieceToCapture);
    pieces[gridPoint.x, gridPoint.y] = null;
    Destroy(pieceToCapture);
}

這裡,GameManager 查詢位於目標位置的棋子。這顆棋子被新增到當前玩家的“吃掉的棋子”陣列。然後,從 GameManager 的棋盤貼片中刪除該棋子的記錄,然後銷燬 GameObject,導致從場景中移除該棋子。

要吃掉一顆棋子,你需要移動到其位置並點選。因此應當在 MoveSelector.cs 中呼叫這個方法。

在 Update 方法中,找到註釋 Reference Point 3 的地方,編寫程式碼:

else
{
    GameManager.instance.CapturePieceAt(gridPoint);
    GameManager.instance.Move(movingPiece, gridPoint);
}

之前 if 語句是判斷目標位置是否有棋子。因為之前的移動已經過濾掉了己方棋子,那麼如果有棋子則肯定是敵方棋子。

將敵方棋子移除後,就可以將手持的棋子放進去了。

點選 play,移動小兵,吃掉一顆棋子。

結束遊戲

當玩家殺死對方的王之後,遊戲就結束了。在你吃子時,需要判斷對方是否是王。如果是,遊戲結束。

當怎樣才能結束遊戲呢?一種方法是刪除棋盤上的 TileSelector 和 MoveSelector 指令碼。

在 GameManager.cs 的 CapturePieceAt 中,在銷燬被殺死的棋子之前,新增程式碼:

if (pieceToCapture.GetComponent<Piece>().type == PieceType.King)
{
    Debug.Log(currentPlayer.name + " wins!");
    Destroy(board.GetComponent<TileSelector>());
    Destroy(board.GetComponent<MoveSelector>());
}

光是禁用這些元件還不夠。因為下一次呼叫 ExitState 和 EnterState 時會重新 enable 它們,那樣遊戲又可以玩了。

Destroy 方法不僅僅可用於 GameObject 類,還可以刪除這個物件上繫結的元件。

點選 play。移動小兵,吃掉對方的王。你會看到 Unity 控制檯打印出“勝利”的字樣。

你可以挑戰一下自己,新增顯示 Game Over 和跳轉回主選單畫面的 UI。

接下來祭出我們的大殺器,移動威力更強的棋子!

特殊移動

Piece 及其子類是封裝特殊移動的好地方。

你可以使用和小兵一樣的方法為其它棋子新增特殊移動。能夠向不同方向移動一個空格的棋子,比如國王和騎士,都可以用同樣的方式建立。試試看你能不能實現這些移動規則。

如果需要幫助,請閱讀最終的專案程式碼。

多格移動

能夠向某一方向移動多格的棋子要難一點。比如象、車和王后。為了簡便起見,我們以象為例。

開啟 Bishop.cs, 將 MoveLocations 替換為:

public override List<Vector2Int> MoveLocations(Vector2Int gridPoint)
{
    List<Vector2Int> locations = new List<Vector2Int>();

    foreach (Vector2Int dir in BishopDirections)
    {
        for (int i = 1; i < 8; i++)
        {
            Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y);
            locations.Add(nextGridPoint);
            if (GameManager.instance.PieceAtGrid(nextGridPoint))
            {
                break;
            }
        }
    }

    return locations;
}

foreach 迴圈每個方向。對於每一個方向,再次對棋子可以移動的位置進行迴圈。因為棋盤以外的位置會被過濾,所以你只需保證格子足夠多不會遺漏任何瓦片即可。

在每一步裡,建立一個 GridPoint 網點並新增到 list 裡。然後判斷當前位置是否有棋子。如果有,中斷內層迴圈進入下一個方向。

因為如果已經有棋子的話會阻斷棋子的移動,因此必須 break。同時,在後面會過濾掉己方棋子的位置,所以在這裡你不需要關心這個問題。

注:如果你需要區分前後的方向,或者左右方向,那麼你需要考慮黑白棋子在移動方向上的區別。

對於國際象棋,只有小兵才需要考慮這個問題,但如果是其它遊戲則也可能需要進行這種區別。

好了!點選 play 試玩一下。

移動王后

王后是最強大的棋子,因此把它放到最後。

王后的移動是象和車的結合;在基類中,有一個數組用來儲存每個棋子的移動方向。你可以用這個陣列將兩者結合。

將 Queen.cs 的 MoveLocations 修改為:

public override List<Vector2Int> MoveLocations(Vector2Int gridPoint)
{
    List<Vector2Int> locations = new List<Vector2Int>();
    List<Vector2Int> directions = new List<Vector2Int>(BishopDirections);
    directions.AddRange(RookDirections);

    foreach (Vector2Int dir in directions)
    {
        for (int i = 1; i < 8; i++)
        {
            Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y);
            locations.Add(nextGridPoint);
            if (GameManager.instance.PieceAtGrid(nextGridPoint))
            {
                break;
            }
        }
    }

    return locations;
}

唯一不同的地方是,你把方向陣列轉變成 List。

List 的特點是可以將其他陣列中的方向新增進來,把所有方向都新增到這個 List。該方法的其餘部分和 Bishop 類相同。

點選 play,把小兵移開,檢查一下效果是否實現。

接下來去哪裡?

還有一些內容需要你完成,比如實現王、騎士和車的移動。如果做不錯來,請參考下載下來的專案資原始碼。

還有一些特殊規則有待實現,比如允許兵第一步可以移動兩格而不是一格,王車易位等。

一般的模式是向 GameManager 新增變數和方法,以記錄這些情況,並檢查它們在移動時是否可用。如果可用,則在 MoveLocations 中新增相應的位置。

還可以在視覺方面進行改進。例如,棋子平滑移動到目標位置,或者可以旋轉鏡頭以表示其它玩家在進行回合時的檢視。

有任何問題和建議,或者想秀一下你的 3D 象棋遊戲,請在下面留言。

Download Materials