Unity制作井字棋小游戏

Unity制作井字棋小游戏

前言

成果展示

项目地址
演示视频

Unity官网对ECS的定义

  • The Entity Component System (ECS) is the core of the Unity Data-Oriented Tech Stack. As the name indicates, ECS has three principal parts:

    • Entities — the entities, or things, that populate your game or program
    • Components — the data associated with your entities, but organized by the data itself rather than by entity. (This difference in organization is one of the key differences between an object-oriented and a data-oriented design.)
    • Systems — the logic that transforms the component data from its current state to its next state— for example, a system might update the positions of all moving entities by their velocity times the time interval since the previous frame.

    请添加图片描述

项目说明

  • 游戏内容: 井字棋
  • 技术限制:
    • 仅允许使用 IMGUI 构建 UI
    • 要求做到实体(数据),部件(逻辑),系统(交互与呈现)三者分离
  • 作业目的:
    • 了解 OnGUI() 事件,提升 debug 能力
    • 提升阅读 API 文档能力

玩法说明

  • 游戏规则为先实现一行或一列或一条对角线上全为己方棋子的玩家获胜,若棋盘下满仍无人获胜则为平局
  • 点击左侧RESET按键可让游戏重新开始
  • 直接点击棋盘空位为玩家落子,点击左侧AI plays 1 step为AI落子,落子后自动切换轮次
  • 棋盘上方显示游戏结果

项目配置

  • 首先新建项目

    请添加图片描述

  • 点击上方工具栏GameObject,选择Greate Empty,并将空的游戏对象命名为init。

  • 将代码文件TicTacToe.cs(在code文件夹下)和背景图片background.jpg(在picture文件夹中)拖入Assets栏。

  • 将Assets栏的TicTacToe.cs拖到左侧init上,点击init,将Assets栏的background.jpg拖到右侧TicTakToe组件的Background属性上

    请添加图片描述

  • 编译运行启动游戏

项目实现

  • 整体设计思路:按项目要求实现实体(数据),部件(逻辑),系统(交互与呈现)三者分离,实现游戏状态随与玩家的交互更新、游戏界面随游戏状态更新、轮次自动切换、AI自动落子、游戏结局的判定的功能。
  • 实体(数据):数据结构如下
        private int turn = 1;    //1 - Player 1, 0 - Player 2 
        private int[,] board = new int[3,3];   //标记玩家落子
        private int result = 0;  //0代表未完成,1 - Player 1 wins,2 - Player 2 wins,3-平局
        //背景
        public Texture2D background;  
        //字体
        private GUIStyle fontStyle;
        private GUIStyle fontStyle2;
        private GUIStyle fontStyle3;
        private GUIStyle fontStyle4;  
    
  • 部件(逻辑):
    • 游戏基础界面初始化
          void Init(){
              //标题字体设置
              fontStyle = new GUIStyle();  
              fontStyle.normal.background = null;   
              fontStyle.normal.textColor= new Color(1, 0, 0);    
              fontStyle.fontSize = 30;   
      
              //游戏状态字体设置
              fontStyle2 = new GUIStyle();  
              fontStyle2.normal.background = null;   
              fontStyle2.normal.textColor= new Color(1, 0, 1);    
              fontStyle2.fontSize = 20;   
      
              //player&turn字体设置
              fontStyle3 = new GUIStyle();  
              fontStyle3.normal.background = null;   
              fontStyle3.normal.textColor= new Color(0, 0, 1);    
              fontStyle3.fontSize = 20;   
      
              //dot字体设置
              fontStyle4 = new GUIStyle();  
              fontStyle4.normal.background = null;   
              fontStyle4.normal.textColor= new Color(0, 0, 1);    
              fontStyle4.fontSize = 100;   
      
              //背景图片设置
              GUI.Label (new Rect (0, 0, 1000,500), background);
              //显示标题、玩家轮次提示
              GUI.Label (new Rect (180, 15, 200, 100), "Let's  play  TicTacToe  !",fontStyle);  
              GUI.Label (new Rect (560, 200, 100, 50), "Player1",fontStyle3); 
              GUI.Label (new Rect (560, 270, 100, 50), "Player2",fontStyle3);
              GUI.Label (new Rect (510, 135, 100, 50), "Turns:", fontStyle3);  
          }
      
    • 重置棋盘
          void Reset() { 
              result = 0;
              turn = 1;    
              for (int i=0; i<3; ++i) {    
                  for (int j=0; j<3; ++j) {    
                      board[i,j] = 0;    
                  }    
              }  
          }   
      
    • 若游戏未结束,当玩家点击棋盘上未落子位置时添加标记,记录下棋者
          void mark(int i,int j){
              if (result == 0 && board [i, j] == 0) {    
                  if (turn == 1)    
                      board [i, j] = 1;  
                  else    
                      board [i, j] = 2;    
                  turn = 1-turn;    
              }  
          }
      
    • 随机下一步棋:先随机选取一个棋盘上的位置,然后从该位置开始从左往右、从上往下依次扫描棋盘上的9个位置,直到找到一个空位置或全部位置扫描完为止,若找到空位则标记下棋者并交换轮次
          void AiPlay(){
              //随机选取一个棋盘上的位置
              int i = (int)Random.Range (0, 2);
              int j = (int)Random.Range (0, 2);
              int cnt = 0;
              //从该位置开始从左往右、从上往下依次扫描棋盘上的9个位置,直到找到一个空位置或全部位置扫描完为止
              while (board [i, j] != 0) {
                  i++;
                  j=(j + i / 3)%3;
                  i%=3;
                  cnt++;
                  if (cnt == 9)
                      break;
              }
              //检测是否找到空位置
              int flag = 0;
              if (board [i, j] == 0)
                  flag = 1;
              else return ;
              //若找到空位则标记下棋者
              if (turn == 1 && flag == 1)    
                  board [i, j] = 1;  
              else if(turn == 0 && flag == 1)   
                  board [i, j] = 2;    
              //交换轮次
              if (flag == 1)
                  turn = 1-turn;    
          }
      
    • 检查游戏是否结束,返回游戏状态:0代表未完成,1代表Player 1 获胜,2代表Player 2 获胜,3代表平局
          int check() {    
              //检查是否存在一行下满三颗同种棋
              for (int i=0; i<3; ++i) {    
                  if (board[i,0]!=0 && board[i,0]==board[i,1] && board[i,1]==board[i,2]) {    
                      return board[i,0];    
                  }    
              }    
              //检查是否存在一列下满三颗同种棋
              for (int j=0; j<3; ++j) {    
                  if (board[0,j]!=0 && board[0,j]==board[1,j] && board[1,j]==board[2,j]) {    
                      return board[0,j];    
                  }    
              }    
              //检查是否存在一条对角线下满三颗同种棋
              if (board[1,1]!=0 && board[0,0]==board[1,1] && board[1,1]==board[2,2] || board[0,2]==board[1,1] && board[1,1]==board[2,0]) {    
                  return board[1,1];    
              }    
              //检查棋盘是否下满
              int count = 0;
              for (int i = 0; i < 3; i++) {
                  for (int j = 0; j < 3; j++) {
                      if (board [i, j] != 0)
                          count++;
                  }
              }
              //若棋盘下满且无人获胜则平局
              if (count == 9)
                  return 3;
              //若棋盘未下满且无人获胜则游戏继续
              return 0;    
          }   
      
  • 系统(交互与呈现):
    • OnGUI每帧刷新,每次调用InitShowTurnShowBoardShowResult四个辅助函数保证游戏界面随游戏状态而更新,若用户点击RESET按钮,则调用Reset函数重置游戏,若用户点击AI plays 1 step按钮,则调用AiPlay函数使计算机随机落一子
          void OnGUI() {   
              //游戏基础界面初始化
              Init();
              //显示轮次
              ShowTurn();
              //设置重置游戏按钮
              if (GUI.Button (new Rect (50, 180, 100, 50), "RESET"))  
                  Reset ();  
              //显示棋盘状态
              ShowBoard();
              //显示游戏状态
              ShowResult();
              //AI plays 1 step
              if (GUI.Button (new Rect (50, 250, 100, 50), "AI plays 1 step") && result == 0) {
                  AiPlay();
              }
          }  
      
    • 显示轮次
          void ShowTurn(){
              if (turn == 1) {
                  GUI.Label (new Rect (510, 155, 50, 50), "·", fontStyle4);
              } else {
                  GUI.Label (new Rect (510, 225, 50, 50), "·", fontStyle4);
              }
          }
      
    • 根据标记显示棋子,当玩家点击棋盘时调用mark函数更新棋盘状态
          void ShowBoard(){
              for (int i=0; i<3; ++i) {    
                  for (int j=0; j<3; ++j) {    
                      if (board [i, j] == 1)    
                          GUI.Button (new Rect (185 + i * 100, 120 + j * 100, 100, 100), "X");  
                      if (board [i, j] == 2)  
                          GUI.Button (new Rect (185 + i * 100, 120 + j * 100, 100, 100), "O");  
                      if (GUI.Button (new Rect (185 + i * 100, 120 + j * 100, 100, 100), "")) {    
                          mark(i,j);  
                      }    
                  }  
              }
          }
      
    • 显示游戏状态:0代表未完成,1代表Player 1 获胜,2代表Player 2 获胜,3代表平局
          void ShowResult(){
              GUI.Label (new Rect (265, 70, 100, 50), "Result:",fontStyle2);
              result = check ();    
              if (result == 1) {    
                  GUI.Label (new Rect (330, 70, 100, 50), "Player1 wins!", fontStyle2);    
              } else if (result == 2) {    
                  GUI.Label (new Rect (330, 70, 100, 50), "Player2 wins!", fontStyle2);    
              } else if (result == 3) {
                  GUI.Label (new Rect (330, 70, 100, 50), "No one wins", fontStyle2); 
              } else {
                  GUI.Label (new Rect (330, 70, 100, 50), "Playing...", fontStyle2);
              } 
          }
      

效果展示

启动游戏,初始界面如下图,轮次显示玩家1,每次游戏重新开始,棋盘重置后效果也如下:

请添加图片描述

玩家1下了一步棋后,棋盘上显示棋子,轮次转为玩家二

请添加图片描述

玩家2下了一步棋后,棋盘上显示棋子,轮次转为玩家一

请添加图片描述

玩家1获得胜利效果如下:

请添加图片描述

玩家2获得胜利效果如下:

请添加图片描述

平局效果如下:

请添加图片描述

心得体会

  • 了解了ECS的架构模式
  • 实践了使用Unity开发小游戏的基本操作
  • 学习了使用 IMGUI 构建 UI
  • 了解 OnGUI() 事件,提升 debug 能力
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值