天天看點

Unity中國象棋(四)——悔棋、判斷勝負的實作,以及動畫特效和代碼的優化

悔棋功能的實作:

基本思路就是建立一個List,儲存每一步所移動的棋子ID,移動前的位置A的坐标,移動後的位置B的坐标,以及吃掉的棋子的ID(若沒有吃掉棋子則ID為-1)

附上相關代碼:

結構體的List

public struct step
    {
        public int moveId;
        public int killId;

        public float xFrom;
        public float yFrom;
        public float xTo;
        public float yTo;

        public step(int _moveId, int _killId, float _xFrom, float _yFrom, float _xTo, float _yTo)
        {
            moveId = _moveId;
            killId = _killId;
            xFrom = _xFrom;
            yFrom = _yFrom;
            xTo = _xTo;
            yTo = _yTo;
        }
    }
    public List<step> _steps=new List<step>();
           

儲存移動的每一步,在每一次移動棋子前就應調用一次

void SaveStep(int moveId, int killId, float bx, float by)
    {
        step tmpStep = new step();

        float ax = StoneManager.s[moveId]._x;
        float ay = StoneManager.s[moveId]._y;

        tmpStep.moveId = moveId;
        tmpStep.killId = killId;
        tmpStep.xFrom = ax;
        tmpStep.yFrom = ay;
        tmpStep.xTo = bx;
        tmpStep.yTo = by;

        _steps.Add(tmpStep);
    }
           

那麼,悔棋即可以通過複活被吃掉的棋子(若有)和通過儲存的Step移動棋子來實作

判斷勝負則不需要多說,對方顔色的将被吃了,則我方就獲勝;我方的将被吃了則失敗。

在寫完了之後發現代碼不夠清晰,應當遵循一個函數執行一個功能的單一原則,于是對GameManger腳本進行了代碼的優化,使代碼看起來更加的簡單易懂

public class GameManager : MonoBehaviour
{

    #region 變量
    /// <summary>
    /// 被選中的棋子的ID,若沒有被選中的棋子,則ID為-1
    /// </summary>
    public int _selectedId=-1;

    /// <summary>
    /// 是否輪到紅子的回合
    /// </summary>
    public bool _beRedTurn=true;

    /// <summary>
    /// 儲存每一步走棋
    /// </summary>
    public struct step
    {
        public int moveId;
        public int killId;

        public float xFrom;
        public float yFrom;
        public float xTo;
        public float yTo;

        public step(int _moveId, int _killId, float _xFrom, float _yFrom, float _xTo, float _yTo)
        {
            moveId = _moveId;
            killId = _killId;
            xFrom = _xFrom;
            yFrom = _yFrom;
            xTo = _xTo;
            yTo = _yTo;
        }
    }
    public List<step> _steps=new List<step>();
    #endregion

    #region 遊戲物體
    /// <summary>
    /// 正常狀态下的棋子的圖檔資源
    /// </summary>
    public Object[] normalChess;

    /// <summary>
    /// 被選中狀态下的棋子的圖檔資源
    /// </summary>
    public Object[] seletcedChess;

    /// <summary>
    /// 選框的GameObject
    /// </summary>
    public GameObject Selected;

    /// <summary>
    /// 路徑的GameObject
    /// </summary>
    public GameObject Path;

    /// <summary>
    /// 勝利界面
    /// </summary>
    public GameObject WinPlane;

    /// <summary>
    /// 失敗界面
    /// </summary>
    public GameObject LosePlane;
    #endregion

    #region 音樂檔案
    /// <summary>
    /// 放置棋子的音效
    /// </summary>
    public AudioSource clickMusic;

    /// <summary>
    /// 勝利的音效
    /// </summary>
    public AudioSource winMusic;

    /// <summary>
    /// 失敗的音效
    /// </summary>
    public AudioSource loseMusic;
    #endregion

    void Awake()
    {
        //加載資源
        normalChess = Resources.LoadAll("chessman2");
        seletcedChess = Resources.LoadAll("chessman3");
    }
	
	void Update () 
    {
        MainProcess();
    }


    #region 遊戲流程的相關函數,包括:遊戲的主流程、判斷遊戲結果(勝利或失敗)、重新開始遊戲

    /// <summary>
    /// 象棋的主要流程
    /// </summary>
    void MainProcess()
    {
        //當滑鼠點選時
        if (Input.GetMouseButtonDown(0))
        {
            //錄影機到點選位置的射線
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            //若點選的位置在棋盤内
            if (Physics.Raycast(ray, out hit) && InsideChessbord(hit))
            {
                //擷取點選的中心點
                Vector3 clickCenter = Center(hit.point);

                Click(hit);              
            }
        }
    }


    /// <summary>
    /// 判斷勝負
    /// </summary>
    void JudgeVictory()
    {
        if (StoneManager.s[4]._dead == true)
        {
            //獲勝
            WinPlane.SetActive(true);
            PlayMusic_Win();
        }

        if (StoneManager.s[20]._dead == true)
        {
            //失敗
            LosePlane.SetActive(true);
            PlayMusic_Lose();
        }
    }


    /// <summary>
    /// 重新開始遊戲
    /// </summary>
    public void Restart()
    {
        SceneManager.LoadScene("Main");
    }
    #endregion


    #region 移動棋子相關動畫特效函數,包括:改變棋子圖檔、顯示/隐藏棋子移動路徑、移動錯誤的動畫特效

    /// <summary>
    /// 當滑鼠選擇棋子時,改變棋子的Sprite
    /// </summary>
    void ChangeSpriteToSelect(int moveId)
    {
        GameObject Stone = GameObject.Find(moveId.ToString());
        SpriteRenderer spr = Stone.GetComponent<SpriteRenderer>();
        int i=1;
        if (StoneManager.s[moveId]._red)
        {
            switch (StoneManager.s[moveId]._type)
            {
                case StoneManager.Stone.TYPE.JIANG: i = 8;
                    break;
                case StoneManager.Stone.TYPE.SHI: i = 9;
                    break;
                case StoneManager.Stone.TYPE.XIANG: i = 10;
                    break;
                case StoneManager.Stone.TYPE.MA: i = 11;
                    break;
                case StoneManager.Stone.TYPE.CHE: i = 12;
                    break;
                case StoneManager.Stone.TYPE.PAO: i = 13;
                    break;
                case StoneManager.Stone.TYPE.BING: i = 14;
                    break;
            }
        }
        else
        {
            switch (StoneManager.s[moveId]._type)
            {
                case StoneManager.Stone.TYPE.JIANG: i = 1;
                    break;
                case StoneManager.Stone.TYPE.SHI: i = 2;
                    break;
                case StoneManager.Stone.TYPE.XIANG: i = 3;
                    break;
                case StoneManager.Stone.TYPE.MA: i = 4;
                    break;
                case StoneManager.Stone.TYPE.CHE: i = 5;
                    break;
                case StoneManager.Stone.TYPE.PAO: i = 6;
                    break;
                case StoneManager.Stone.TYPE.BING: i = 7;
                    break;
            }
        }
        spr.sprite = seletcedChess[i] as Sprite;   
    }


    /// <summary>
    /// 當滑鼠取消選擇時,再次改變其Sprite
    /// </summary>
    void ChangeSpriteToNormal(int moveId)
    {
        GameObject Stone = GameObject.Find(moveId.ToString());
        SpriteRenderer spr = Stone.GetComponent<SpriteRenderer>();
        int i = 1;
        if (StoneManager.s[moveId]._red)
        {
            switch (StoneManager.s[moveId]._type)
            {
                case StoneManager.Stone.TYPE.JIANG: i = 8;
                    break;
                case StoneManager.Stone.TYPE.SHI: i = 9;
                    break;
                case StoneManager.Stone.TYPE.XIANG: i = 10;
                    break;
                case StoneManager.Stone.TYPE.MA: i = 11;
                    break;
                case StoneManager.Stone.TYPE.CHE: i = 12;
                    break;
                case StoneManager.Stone.TYPE.PAO: i = 13;
                    break;
                case StoneManager.Stone.TYPE.BING: i = 14;
                    break;
            }
        }
        else
        {
            switch (StoneManager.s[moveId]._type)
            {
                case StoneManager.Stone.TYPE.JIANG: i = 1;
                    break;
                case StoneManager.Stone.TYPE.SHI: i = 2;
                    break;
                case StoneManager.Stone.TYPE.XIANG: i = 3;
                    break;
                case StoneManager.Stone.TYPE.MA: i = 4;
                    break;
                case StoneManager.Stone.TYPE.CHE: i = 5;
                    break;
                case StoneManager.Stone.TYPE.PAO: i = 6;
                    break;
                case StoneManager.Stone.TYPE.BING: i = 7;
                    break;
            }
        }
        spr.sprite = normalChess[i] as Sprite;
    }


    /// <summary>
    /// 設定上一步棋子走過的路徑,即将上一步行動的棋子的位置留下辨別,并辨別該棋子
    /// </summary>
    void ShowPath(Vector3 oldPosition, Vector3 newPosition)
    {
        Selected.transform.position = newPosition;
        Selected.SetActive(true);

        Path.transform.position = oldPosition;
        Path.SetActive(true);
    }


    /// <summary>
    /// 隐藏路徑
    /// </summary>
    void HidePath()
    {
        Selected.SetActive(false);
        Path.SetActive(false);
    }


    /// <summary>
    /// 播放移動錯誤的動畫特效
    /// </summary>
    void MoveError(int moveId,Vector3 position)
    {
        GameObject Stone = GameObject.Find(moveId.ToString());
        Vector3 oldPosition = new Vector3(StoneManager.s[moveId]._x, StoneManager.s[moveId]._y,0);
        Vector3[] paths = new Vector3[3];
        paths[0] = oldPosition;
        paths[1] = position;
        paths[2] = oldPosition;
        Stone.transform.DOPath(paths,0.8f); 
    }

    #endregion


    #region 播放音效的相關函數,包括:放置棋子音效、勝利音效、失敗音效

    /// <summary>
    /// 播放放置棋子時的音效
    /// </summary>
    void PlayMusic_Move()
    {
        if (!clickMusic.isPlaying)
        {
            clickMusic.Play();
        }
    }


    /// <summary>
    /// 播放勝利時的音效
    /// </summary>
    void PlayMusic_Win()
    {
        if (!winMusic.isPlaying)
        {
            winMusic.Play();
        }
    }


    /// <summary>
    /// 播放失敗時的音效
    /// </summary>
    void PlayMusic_Lose()
    {
        if (!loseMusic.isPlaying)
        {
            loseMusic.Play();
        }
    }
    #endregion 


    #region 幫助函數

    bool IsRed(int id)
    {
        return StoneManager.s[id]._red;
    }

    bool IsDead(int id)
    {
        if (id == -1) return true;
        return StoneManager.s[id]._dead;
    }

    bool SameColor(int id1, int id2)
    {
        if (id1 == -1 || id2 == -1) return false;

        return IsRed(id1) == IsRed(id2);
    }

    /// <summary>
    /// 設定棋子死亡
    /// </summary>
    /// <param name="id"></param>
    void KillStone(int id)
    {
        if (id == -1) return;

        StoneManager.s[id]._dead = true;
        GameObject Stone = GameObject.Find(id.ToString());
        Stone.SetActive(false);     
    }

    /// <summary>
    /// 複活棋子
    /// </summary>
    /// <param name="id"></param>
    void ReliveChess(int id)
    {
        if (id == -1) return;

        //因GameObject.Find();函數不能找到active==false的物體,故先找到其父物體,再找到其子物體才可以找到active==false的物體
        StoneManager.s[id]._dead = false;
        GameObject Background = GameObject.Find("Background");
        GameObject Stone = Background.transform.Find(id.ToString()).gameObject;
        Stone.SetActive(true);           
    }

    /// <summary>
    /// 移動棋子到目标位置
    /// </summary>
    /// <param name="point"></param>
    void MoveStone(int moveId, Vector3 point)
    {
        GameObject Stone = GameObject.Find(moveId.ToString());
        Stone.transform.DOMove(point, 0.5f);
        StoneManager.s[moveId]._x = point.x;
        StoneManager.s[moveId]._y = point.y;

        _beRedTurn = !_beRedTurn;
    }

    /// <summary>
    /// 判斷點選的位置是否在棋盤内
    /// </summary>
    /// <param name="hit"></param>
    /// <returns></returns>
    bool InsideChessbord(RaycastHit hit)
    {
        if ((hit.point.x > -2.29 && hit.point.x < 2.29) && ((hit.point.y > -2.6 && hit.point.y < -0.06) || (hit.point.y > -0.04 && hit.point.y < 2.5)))
            return true;
        else
            return false;
    }

    /// <summary>
    /// 通過滑鼠點選的位置,擷取距離目前坐标點最近的中心點的位置
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    Vector3 Center(Vector3 point)
    {
        //x,y,z為要傳回的三維坐标
        //将象棋分為9列(i)和10行(j)
        //計算距離滑鼠所指坐标點的最近的行列的序号(tpmi、tmpj)
        //通過行列的序号算出位于該行該列的中心點的坐标位置并傳回

        float x, y, z = 0;
        int i, tmpi = 1, j, tmpj = 1;
        float min = 51;

        for (i = 0; i < 9; ++i)
        {
            if (System.Math.Abs(point.x * 100 - ToolManager.colToX(i) * 100) < min)
            {
                min = System.Math.Abs(point.x * 100 - ToolManager.colToX(i) * 100);
                tmpi = i;
            }
        }
        x = ToolManager.colToX(tmpi);


        min = 51;
        for (j = 0; j < 10; ++j)
        {
            if (System.Math.Abs(point.y * 100 - ToolManager.rowToY(j) * 100) < min)
            {
                min = System.Math.Abs(point.y * 100 - ToolManager.rowToY(j) * 100);
                tmpj = j;
            }
        }
        y = ToolManager.rowToY(tmpj);

        return new Vector3(x, y, z);
    }

    #endregion


    #region 移動棋子

    /// <summary>
    /// 擷取點選的中心位置,若該位置上有棋子,則擷取該棋子ID,否則id為-1
    /// </summary>
    /// <param name="hit"></param>
    void Click(RaycastHit hit)
    {
        float x = Center(hit.point).x;
        float y = Center(hit.point).y;
        int id = ToolManager.GetStoneId(x,y);
        Click(id,x,y);
    }

    /// <summary>
    /// 若目前沒有選中棋子,則嘗試選中點選的棋子;若目前已有選中的棋子,則嘗試移動棋子
    /// </summary>
    /// <param name="id"></param>
    /// <param name="x"></param>
    /// <param name="y"></param>
    void Click(int id, float x, float y)
    {
        if (_selectedId == -1)
        {
            TrySelectStone(id);
        }
        else
        {
            TryMoveStone(id, x, y);
        }
    }

    /// <summary>
    /// 嘗試選擇棋子;若id=-1或者不是處于移動回合的棋子,則傳回;否則,将該棋子設為選中的棋子,并更新圖檔
    /// </summary>
    /// <param name="id"></param>
    void TrySelectStone(int id)
    {
        if (id == -1) return;

        if (!CanSelect(id)) return;

        _selectedId = id;

        ChangeSpriteToSelect(id);
    }

    /// <summary>
    /// 嘗試移動棋子
    /// 若要移動的目标位置有棋子(kiillId)且和目前選中的棋子同色,則換選擇
    /// 若可以移動,則移動;若不能移動,則播放移動錯誤的提示動畫
    /// </summary>
    /// <param name="killId"></param>
    /// <param name="x"></param>
    /// <param name="y"></param>
    void TryMoveStone(int killId, float x, float y)
    {
        if (killId != -1 && SameColor(killId, _selectedId))
        {
            ChangeSpriteToNormal(_selectedId);
            TrySelectStone(killId);
            return;
        }

        bool ret = CanMove(_selectedId, killId, new Vector3(x, y, 0));

        if (ret)
        {
            MoveStone(_selectedId, killId, new Vector3(x, y, 0));
            _selectedId = -1;
        }
        else
        {
            MoveError(_selectedId, new Vector3(x, y, 0));          
        }
    }

    /// <summary>
    /// 走棋并吃棋
    /// </summary>
    /// <param name="hit"></param>
    void MoveStone(int moveId, int killId, Vector3 position)
    {
        // 1.若移動到的位置上有棋子,将其吃掉
        // 2.将移動棋子的路徑顯示出來
        // 3.将棋子移動到目标位置
        // 4.播放音效
        // 5.改變精靈的渲染圖檔
        // 6.判斷是否符合勝利或者失敗的條件

        SaveStep(moveId, killId, position.x, position.y);

        KillStone(killId);

        ShowPath(new Vector3(StoneManager.s[moveId]._x, StoneManager.s[moveId]._y, 0), position);

        MoveStone(moveId, position);

        PlayMusic_Move();

        ChangeSpriteToNormal(moveId);

        JudgeVictory();
    }

    /// <summary>
    /// 将移動的棋子ID、吃掉的棋子ID以及棋子從A點的坐标移動到B點的坐标都記錄下來
    /// </summary>
    /// <param name="moveId"></param>
    /// <param name="killId"></param>
    /// <param name="bx"></param>
    /// <param name="by"></param>
    void SaveStep(int moveId, int killId, float bx, float by)
    {
        step tmpStep = new step();

        float ax = StoneManager.s[moveId]._x;
        float ay = StoneManager.s[moveId]._y;

        tmpStep.moveId = moveId;
        tmpStep.killId = killId;
        tmpStep.xFrom = ax;
        tmpStep.yFrom = ay;
        tmpStep.xTo = bx;
        tmpStep.yTo = by;

        _steps.Add(tmpStep);
    }

    /// <summary>
    /// 通過記錄的步驟結構體來傳回上一步
    /// </summary>
    /// <param name="_step"></param>
    void Back(step _step)
    {
        ReliveChess(_step.killId);
        MoveStone(_step.moveId, new Vector3(_step.xFrom, _step.yFrom, 0));
        HidePath();
        if (_selectedId != -1)
        {
            ChangeSpriteToNormal(_selectedId);
            _selectedId = -1;
        }
    }

    /// <summary>
    /// 悔棋,退回一步
    /// </summary>
    public void BackOne()
    {
        if (_steps.Count == 0) return;

        step tmpStep = _steps[_steps.Count - 1];
        _steps.RemoveAt(_steps.Count - 1);
        Back(tmpStep);
    }

    #endregion


    #region 規則

    /// <summary>
    /// 判斷走棋是否符合走棋的規則
    /// </summary>
    /// <param name="selectedId"></param>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    bool CanMove(int moveId, int killId, Vector3 clickPoint)
    {
        if (SameColor(moveId, killId)) return false;

        int col = ToolManager.xToCol(clickPoint.x);
        int row = ToolManager.yToRow(clickPoint.y);

        switch (StoneManager.s[moveId]._type)
        {
            case StoneManager.Stone.TYPE.JIANG:
                return RuleManager.moveJiang(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.SHI:
                return RuleManager.moveShi(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.XIANG:
                return RuleManager.moveXiang(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.CHE:
                return RuleManager.moveChe(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.MA:
                return RuleManager.moveMa(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.PAO:
                return RuleManager.movePao(moveId, row, col, killId);
            case StoneManager.Stone.TYPE.BING:
                return RuleManager.moveBing(moveId, row, col, killId);
        }

        return true;
    }

    /// <summary>
    /// 判斷點選的棋子是否可以被選中,即點選的棋子是否在它可以移動的回合
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    bool CanSelect(int id)
    {
        return _beRedTurn == StoneManager.s[id]._red;
    }

    #endregion
}
           

明天開始研究單人遊戲,即中國象棋的人工智能方面