悔棋功能的实现:
基本思路就是创建一个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
}
明天开始研究单人游戏,即中国象棋的人工智能方面