github傳送門:https://github.com/dongzizhu/unity3DLearning
視頻傳送門:https://space.bilibili.com/472759319
遊戲智能
所謂遊戲智能,其實是一種根據狀態的決策模型。這種模型小到可以是根據規則下井字棋、玩貪喫蛇,大到可以利用強化學習訓練處一個決策模型。
因爲電子遊戲可以方便地與代碼交互,所以用電子遊戲來訓練人工智能或者啓發人工智能的研究,成爲了一個常用的手段。這裏所謂“交互”,其實就是決策-->環境給出反饋-->狀態發生變化-->決策如此循環地一個過程,我們可以簡化地將遊戲過程看作是一個狀態機轉化的過程,然後可以用人爲給定的方式或者強化學習自主訓練的方式來得到一個決策模型。
這裏用之前完成的牧師與魔鬼遊戲來簡單地實現一個遊戲智能。
牧師與魔鬼
根據遊戲規則,我們可以列出狀態轉移圖(這裏我借鑑了前輩的博客中畫的圖,只做了一點點小的改動)。
上圖中採用fromCoast上面的人物個數以及船的位置來表示狀態(比如3P3DB代表fromCoast上有3個牧師和3個魔鬼,船也在fromCoast這邊),狀態轉移則用兩個或一個字母來表示穿上承載的人;省略號所在的方框代表遊戲輸了,左下角0P0D的方框代表遊戲勝利;紅線標出的路徑就是正確的路徑,當程序每次做決策的時候只要沿着路徑的方向走就好了。可以看到在3P3DB以及0P1D的位置有兩個分叉,我們可以用一個隨機函數來實現隨機性。
首先我們在baseCode中加入我們需要的新的結構和函數。
如Boataction結構來幫助我們記錄當前船要進行的操作。
public enum Boataction { P, D, PP, DD, PD }
或者在coastController中加入獲取指定類型的人物的函數。
public MyCharacterController getP(){
for(int i = 0; i < passengerPlaner.Length; i++){
if(passengerPlaner[i]==null)
continue;
if(passengerPlaner[i].getType()==0)
return passengerPlaner[i];
}
return null;
}
public MyCharacterController getD(){
for(int i = 0; i < passengerPlaner.Length; i++){
if(passengerPlaner[i]==null)
continue;
if(passengerPlaner[i].getType()==1)
return passengerPlaner[i];
}
return null;
}
完成以上準備工作後,我們就可以在firstController中實現所需要的功能了。
注意這裏的autoNext調用StartCoroutine函數開啓了一個協程,這樣做的目的是通過調用WaitForSeconds來讓進程暫停。因爲我們要保證人物移動到穿上之後再開船,所以這裏要等待個0.5秒。
IEnumerator moveWithPause() {
Boataction nextAction = getNext();
autoMoveCharacterOnBoat(nextAction);
yield return new WaitForSeconds(0.5f);
moveBoat();
yield return new WaitForSeconds(0.5f);
autoMoveCharacterOffBoat();
yield return new WaitForSeconds(0.5f);
}
public void autoNext(){
if(userGUI.status != 0)
return;
StartCoroutine(moveWithPause());
}
至於其中每一步調用的函數就沒有什麼可太贅述得了。就是單純的根據fromCoast的狀態決定下一步的操作,然後移動相應的人到船上,等人在船上後開船,最後再將人物移動上岸。
private int randomValue() {
float num = Random.Range(0f, 1f);
if (num <= 0.5f) return 1;
else return 2;
}
public Boataction getNext(){
int pNum = fromCoast.getPNum();
int dNum = fromCoast.getDNum();
// to->-1; from->1
if(pNum == 3 && dNum == 3 && boat.get_to_or_from() == 1){
return randomValue()==1 ? Boataction.DD : Boataction.PD;
}
else if(pNum == 0 && dNum == 2 && boat.get_to_or_from() == 1){
return Boataction.DD;
}
else if(pNum == 1 && dNum == 1 && boat.get_to_or_from() == 1){
return Boataction.PD;
}
else if(pNum == 0 && dNum == 1 && boat.get_to_or_from() == -1){
return randomValue()==1 ? Boataction.D : Boataction.P;
}
else if(pNum == 2 && dNum == 1 && boat.get_to_or_from() == 1){
return Boataction.PP;
}
else if(pNum == 0 && dNum == 3 && boat.get_to_or_from() == 1){
return Boataction.DD;
}
else if(pNum == 0 && dNum == 2 && boat.get_to_or_from() == -1){
return Boataction.D;
}
else if(pNum == 2 && dNum == 2 && boat.get_to_or_from() == 1){
return Boataction.PP;
}
else if(pNum == 1 && dNum == 1 && boat.get_to_or_from() == -1){
return Boataction.PD;
}
else if(pNum == 3 && dNum == 1 && boat.get_to_or_from() == 1){
return Boataction.PP;
}
else if(pNum == 3 && dNum == 0 && boat.get_to_or_from() == -1){
return Boataction.D;
}
else if(pNum == 3 && dNum == 2 && boat.get_to_or_from() == 1){
return Boataction.DD;
}
else if(pNum == 3 && dNum == 1 && boat.get_to_or_from() == -1){
return Boataction.D;
}
else if(pNum == 2 && dNum == 2 && boat.get_to_or_from() == -1){
return Boataction.P;
}
else if(pNum == 3 && dNum == 2 && boat.get_to_or_from() == -1){
return Boataction.D;
}
Debug.Log("out of control!");
return Boataction.D;
}
public void autoMoveCharacterOnBoat(Boataction nextAction){
if(nextAction==Boataction.P){
if(boat.get_to_or_from()==toCoast.get_to_or_from())
characterIsClicked(toCoast.getP());
else
characterIsClicked(fromCoast.getP());
}
else if(nextAction==Boataction.D){
if(boat.get_to_or_from()==toCoast.get_to_or_from())
characterIsClicked(toCoast.getD());
else
characterIsClicked(fromCoast.getD());
}
else if(nextAction==Boataction.DD){
if(boat.get_to_or_from()==toCoast.get_to_or_from()){
characterIsClicked(toCoast.getD());
characterIsClicked(toCoast.getD());
}
else{
characterIsClicked(fromCoast.getD());
characterIsClicked(fromCoast.getD());
}
}
else if(nextAction==Boataction.PD){
if(boat.get_to_or_from()==toCoast.get_to_or_from()){
characterIsClicked(toCoast.getP());
characterIsClicked(toCoast.getD());
}
else{
characterIsClicked(fromCoast.getP());
characterIsClicked(fromCoast.getD());
}
}
else{
if(boat.get_to_or_from()==toCoast.get_to_or_from()){
characterIsClicked(toCoast.getP());
characterIsClicked(toCoast.getP());
}
else{
characterIsClicked(fromCoast.getP());
characterIsClicked(fromCoast.getP());
}
}
}
public void autoMoveCharacterOffBoat(){
for(int i = 0; i < boat.getCharacters().Length; i++){
if(boat.getCharacters()[i]==null)
return;
characterIsClicked(boat.getCharacters()[i]);
}
}
除了決策外,操作的時候調用之前已經寫好的函數即可。
完整項目請見github。