前言

中山大学数据科学与计算机学院3D游戏课程学习记录博客。
游戏代码:gitee 游戏视频:bilibili

游戏要求

  • 游戏内容:井字棋
  • 技术限制:仅使用IMGUI构建UI
  • 作业目的:
  • 了解 OnGUI() 事件,提升 debug 能力
  • 提升阅读 API 文档能力

规则分析

  • 每个位置只能有一个棋子
  • 两人交替下棋,每次下一枚棋子
  • 当三个同种棋子连成一线,游戏结束
  • 棋盘大小为3x3

游戏实现

游戏实现大概分为两步:制作UI和规则代码化。

制作UI:
  • 先选取合适的位置,合适的大小画出棋盘:
int w=Screen.width/2;           
    GUI.Box(new Rect(w-150,50,300,400),"井字棋");
  • 再画出目前的游戏状态,比如play(游戏中),O win(玩家1胜利)等:
GUI.Box(new Rect(w-50,80,100,35),"Play!");
  • 然后画出重置按钮,便于重新开始游戏:
if(GUI.Button(new Rect(w-50,415,100,35),"Reset")) 
	reset();
当reset被点击时需要重新开始游戏,所以需要判断是否被点击。
  • 最后用border来表示各个位置的值,画出棋盘上的每个位置:
private int[][] border=new int[3][]{new int[3],new int[3],new int[3]};
    for(int i=0;i<3;i++){            
    	for(int j=0;j<3;j++){                
    	    if(border[i][j]==0){                    
    		GUI.Button(new Rect(w-150+i*100,115+j*100,100,100),"O");                
    	    }else if(border[i][j]==1){
    		GUI.Button(new Rect(w-150+i*100,115+j*100,100,100),"X");                
    	    }else{                    
    	    	if(GUI.Button(new Rect(w-150+i*100,115+j*100,100,100),"")){                        
    	    	    //空按钮被点击的操作
    	   	}
    	    }
    	}
   }
border是一个2维数组,其中储存的值代表棋盘上的值。
-1代表该位置没有值,0代表该位置为"O",1代表该位置为"X"。
由于只有当位置为空(border值为-1)时才允许点击放置新值,所以需要判断是否被点击。

至此,完成了对UI的设计。接下来只需要用代码实现井字棋的逻辑即可。

规则代码化:

其实可以发现主要的操作分为两部分,第一部分为点击空按钮是发生的事件,第二部分为点击reset按钮是发生的事件。

点击reset只需要重置所有变量即可,因此把重点放在实现空按钮点击即可。

空按钮点击时发生的事件:

  • 判断当前游戏是否已经分出胜负,若分出胜负则不做任何操作:
private int end=0;
    if(点击空按钮){
        if(end==1) break;
    }
使用变量end来标记游戏是否结束,0代表未结束,1代表结束。
  • 改变当前位置的值为当前玩家所代表的值:
private int player=0;
    if(点击空按钮){
	border[i][j]=player;
    }
使用变量player来标记当前玩家,0代表"O"玩家,1代表"X"玩家。
  • 判断这步之后是否分出胜负:
private int res=-1;
    if(点击空按钮){
    	if(border[0][j]==border[1][j] && border[0][j]==border[2][j]) 
    	     res=player;                        
    	else if(border[i][0]==border[i][1] && border[i][0]==border[i][2]) 
    	     res=player;                        
    	else if(i==j && border[0][0]==border[1][1] && border[0][0]==border[2][2]) 
    	     res=player;                        
    	else if(i+j==2 && border[0][2]==border[1][1] && border[0][2]==border[2][0]) 
    	     res=player;
    }
使用res来标记当前游戏的状态,-1代表游戏正在进行,而0或者1代表决出胜负。
四行if:
    第一行if用来判断同一列是否连成线;
    第二行if用来判断同一行是否连成线;
    第三四两行if用来判断对角线是否连成线。
  • 判断是否平局:
private int cnt=0;
     if(点击空按钮){
     	cnt++;
     	if(res!=-1 && cnt==9) res=2;
     	if(res!=-1) end=1;
     }
使用变量cnt来表示已经点击过的空按钮数量,假如达到9而且未分出胜负,则平局。
res为2代表平局。
如果res不为-1,则代表游戏已经结束,改变end。
  • 根据胜负改变"Play!"位置的值:
void printRes(int res){        
     int w=Screen.width/2;        
     if(res==0){            
     	GUI.Box(new Rect(w-50,80,100,35),"O win!");
     }else if(res==1){            
     	GUI.Box(new Rect(w-50,80,100,35),"X win!");       
     }else if(res==2){           
      	GUI.Box(new Rect(w-50,80,100,35),"Draw!");        
     }else{            
      	GUI.Box(new Rect(w-50,80,100,35),"Play!");        
     }    
 }
使用函数printRes来根据res改变游戏状态值。
  • 该表代表当前玩家的值:
player=player^1;
使用位运算。

点击reset按钮发生的事件:

void reset(){
     player=0;        
     cnt=0;        
     end=0;        
     res=-1;        
     for(int i=0;i<3;i++){            
         for(int j=0;j<3;j++){                
     	     border[i][j]=-1;            
     	 }       
     }        
}
使用reset函数初始化刚刚用过的所有变量。

至此完成了规则代码化。

对于游戏的完整实现代码和游戏视频,请访问前言中的链接。