Unity中国象棋(四)——悔棋、判断胜负的实现,以及动画特效和代码的优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_24994943/article/details/54895270

悔棋功能的实现:

基本思路就是创建一个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脚本进行了代码的优化,使代码看起来更加的简单易懂

扫描二维码关注公众号,回复: 3301393 查看本文章
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
}

明天开始研究单人游戏,即中国象棋的人工智能方面



猜你喜欢

转载自blog.csdn.net/sinat_24994943/article/details/54895270