【3D游戏编程与设计】二 离散引擎基础 & 3D井字棋

简答题

解释 游戏对象(GameObjects) 和 资源(Assets)的区别与联系。

  • 游戏对象(GameObjects):GameObject是Unity中的基类对象,其是所有其他游戏对象的基类,其包括很多抽象方法。包括玩家,敌人,游戏场景,摄像机等类,可以实现这些抽象方法,成为可以被实例化的对象。我们可以在游戏中使用这些可以被实例化的对象。这些对象由资源组成,是游戏运行过程中动态的,可以交互的实体。
  • 资源(Assets):是游戏包括的素材资源, 他们可以被多个对象使用,有些资源可以做为预设并实例化为多个对象。资源通常包括预设,脚本,材料,场景,声音等等。

下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)

  • 资源的目录组织结构:主要包括动画,文本,场景,素材,材料,模型,预设以及说明文档等。
  • 游戏对象树的层次结构: 主要包括摄像机,光源,游戏开始位置,场景布局以及文本内容等。

编写一个代码,使用 debug 语句来验证 MonoBehaviour 基本行为或事件触发的条件

public class TryDebug : MonoBehaviour {
    void Start (){
        Debug.Log ("onStart");
    }
    // Update is called once per frame
    void Update (){
        Debug.Log ("onUpdate");
    }
    void Awake(){
        Debug.Log ("onAwake");
    }
    void FixedUpdate(){
        Debug.Log ("onFixedUpdate");
    }
    void LateUpdate(){
        Debug.Log ("onLateUpdate");
    }
    void OnGUI(){
        Debug.Log ("onGUI");
    }
    void OnDisable(){
        Debug.Log ("onDisable");
    }
    void OnEnable(){
        Debug.Log ("onEnable");
    }
}

查找脚本手册,了解 GameObject,Transform,Component 对象

分别翻译官方对三个对象的描述(Description)

GameObjects是Unity中代表角色,道具和风场景的基本对象。它们本身并不是很完整,但它们充当组件的容器实现了具体的功能。

Transform变换组件决定了每个对象在场景中的位置,比例和旋转,每个对象都有一个变换组件。

Component是用来绑定到游戏对象(Game Object)上的一组相关属性。本质上每个组件是一个类的实例。

描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件

table的对象是GameObject,第一个复选框是activeSelf 属性,第二个文本框对应对象的名称,第三个复选框为static属性。第二行有Tag属性和Layer属性,第三行为prefabs属性,也就是预设属性。Transform属性包括position(位置),Rotation(旋转角度),Scale(大小比例)等。

其包括的Component部件有Transform,Mesh Filter,Box Collider,Mesh Renderer。

用 UML 图描述 三者的关系

UML
三者的关系如上图。

资源预设(Prefabs)与 对象克隆 (clone)

预设(Prefabs)有什么好处?

1.使对象能够很方便地重复利用,不需要多次手动创建。
2. 相同的游戏对象可以用同一个预设建模并创建。
3. 对预设进行更新后,由该预设创建的游戏对象都可以很方便地更新。
4. 通过代码直接加载预设也非常方便。
5. 预设的使用使得整个游戏项目更有维护性和可扩展性。
6. 预设资源完整储存了对象的组件和属性。可以使用预设作为一个模板,方便我们创建相同的游戏对象并赋予他们相同的行为,比如用棋盘格子创建一个很大的棋盘。

预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?

克隆游戏对象需要场景中有被克隆对象,而创建预制只需assets中有预设即可,不需要场景中存在对应的游戏对象。
对象克隆出来的对象并不会随着母体的变化而变化,但预设创建出来的对象会随着预设的更新而发生改变。

制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象

可以按照文档的指引创建名为table的桌子预设。

GameObject mytable = Instantiate(Resources.Load("table"), new Vector3(0, 0, 0), Quaternion.identity) as GameObject;

编程实践,小游戏

编程实践了3D井字棋小游戏。并且完善其,使得其可以多次运行,并且可以有效避免多种用户非法操作的情况,增加了项目的健壮性。
github上项目网址https://github.com/alphabstc/TicTacToe
下载上面的项目可以直接运行。
(gitee上传不了这么大的项目)

创建材料和预设

创建材料和预设

首先,在Assets文件夹下创建Resources,Scenes和Scripts三个文件夹。

之后在Resources文件夹中创建上图所示的4种颜色的材料(名称为black,blue,red,white)。

之后,创建两个3D立方体,将black和white材料分别拖上去,得到黑色和白色的立方体。分别命名为Black Cube和White Cube。之后将他们从对象层次视图中拖到Resources文件夹中,成为预设。他们将作为棋盘格子的预设。

同样,创建两个3D球体,将red和blue材料分别拖上去,得到红色和蓝色的球体。分别命名为Red Chess和Blue Chess。之后将他们从对象层次视图中拖到Resources文件夹中,成为预设。他们将作为红蓝两方棋子的预设。

创建脚本

脚本1
创建名为Chessboard的空对象,并且创建名为Chessboard的脚本,将Chessboard脚本拖到Chessboard空对象上。Chessboard脚本的内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Chessboard : MonoBehaviour{//棋盘类
    private GameObject[,] Chesses = new GameObject[3, 3];//存放棋盘格子对象的数组
    private int[,] value = new int[3,3];//存放棋盘每个格子状态的数组
    private int counter = 0;//计数器 当前棋盘上有多少个棋子
    private GameObject[] chess = new GameObject[9];//存放棋子对象的数组
    private bool gameover = true;//是否游戏结束
    // Start is called before the first frame update
    void Start(){
        Debug.Log("Start");
        //创建所有棋盘格子对象
        for(int i = 0;i < 3;i++){
            for(int j = 0;j <3;j++){
                if (i%2 != j%2){//根据坐标创造黑白相间的棋盘格子
                    Chesses[i, j] = Instantiate(Resources.Load("White Cube"), new Vector3(i, 0, j), Quaternion.identity) as GameObject;
                }
                else
                {
                    Chesses[i, j] = Instantiate(Resources.Load("Black Cube"), new Vector3(i, 0, j), Quaternion.identity) as GameObject;
                }
                Chesses[i, j].transform.parent = this.transform;
            }
        }
        Restart();
    }

    void Restart(){//重新开始游戏
        for(int i = 0; i < counter; i++)
            Destroy(chess[i]);//销毁棋子
        counter = 0;//计数器清零
        for(int i = 0; i < 3;i++){
            for(int j = 0; j <3;j++){
                //初始化棋盘状态
                value[i, j] = 100 + i * 10 + j;//为超过100且互不相等的数 则都不会相等 不会触发胜利条件
            }
        }    
        gameover = false;
    }

    // Update is called once per frame
    void Update(){
        
    }

    bool judgeWin(){
        //同一行 同一列 或者对角线上的状态相等 说明有玩家胜利
        for(int i = 0; i < 3; ++i)
            if (value[i, 1] == value[i, 0] && value[i, 2] == value[i, 0])
                return true;
        for(int i = 0; i < 3; ++i)
            if (value[1, i] == value[0, i] && value[2, i] == value[0, i])
                return true;
        if (value[1, 1] == value[0, 0] && value[2, 2] == value[0, 0])
            return true;
        if (value[1, 1] == value[0, 2] && value[2, 0] == value[0, 2])
            return true;                
        return false;
    }

    void OnGUI(){
        //生成一个菜单栏
        GUI.Box (new Rect (10, 10, 100, 50), "菜单");
        if (GUI.Button (new Rect (20, 35, 80, 20), "新游戏")) {
            Restart();//按下新游戏键则重新开始游戏
        }
        //判断是否有玩家胜利
        bool result = judgeWin();
        if (result){//有玩家胜利
            //胜利的是最后一个落子的玩家
            GUI.Box(new Rect (Screen.width / 2 - 100, Screen.height / 2 + 50, 200, 50), counter % 2 == 0 ? "蓝方胜" : "红方胜");
            gameover = true;
        }
        else{
            if (counter == 9){//没有胜利 但已经下完了9个棋子 则平局
                GUI.Box (new Rect (Screen.width / 2 - 100, Screen.height / 2 + 50, 200, 50), "平 局");
                gameover = true;
            }
        }
    }
    public void NewChess(GameObject grid){
        if (gameover)//游戏已经结束
            return;
        //已经放过了棋子
        if (value[(int)(grid.transform.position.x + 1e-5), (int)(grid.transform.position.z + 1e-5)] < 2)
            return;
        if (counter % 2 == 0){
            //是红方落子
            Debug.Log("Click Red");
            chess[counter++] = Instantiate(Resources.Load("Red Chess"), grid.transform.position + new Vector3(0, 1, 0), Quaternion.identity) as GameObject;
            value[(int)(grid.transform.position.x + 1e-5), (int)(grid.transform.position.z + 1e-5)] = 0;
        }
        else{
            //是蓝方落子
            Debug.Log("Click Blue");
            chess[counter++] = Instantiate(Resources.Load("Blue Chess"), grid.transform.position + new Vector3(0, 1, 0), Quaternion.identity) as GameObject;
            value[(int)(grid.transform.position.x + 1e-5), (int)(grid.transform.position.z + 1e-5)] = 1;
        }
    }
}

之后,创建名为ClickGrid的脚本,将其拖到Black Cube和White Cube两种预设上。编辑ClickGrid脚本的内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ClickGrid : MonoBehaviour{
    // Start is called before the first frame update
    void Start(){
        
    }

    // Update is called once per frame
    void Update(){
        
    }
    void OnMouseUpAsButton(){
        Debug.Log("Click");
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//通过射线来检测是否碰撞
        RaycastHit hit;
        if(Physics.Raycast(ray, out hit)){//和棋盘格子碰撞
            Debug.Log("Click Success");
            GameObject grid = hit.collider.gameObject;//获得碰撞的对象
            GameObject.Find("Chessboard").SendMessage("NewChess", grid);//调用棋盘对象的NewChess函数
        }
    }
}

调整摄像头

调整摄像头的位置和姿态,使得其可以以比较好的视角看见创建后的棋盘。

这样就完成了项目的配置。

效果结果

截图1

截图2
截图3

AI思路

考虑到可以使用动态规划实现AI井字棋。f[mask]表示在当前棋盘二进制为mask的状态下,是先手必胜还是后手必胜还是平局。可以使用动态规划求出结果。

由于前几天考试周,没有太多时间实现,留坑以后再实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值