悔棋功能的實作:
基本思路就是建立一個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
}
明天開始研究單人遊戲,即中國象棋的人工智能方面