离散仿真引擎基础——井字棋设计

目录:

一、简答题

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

游戏对象(GameObjects)
是游戏中可进行实际操作的一个对象,是能够容纳组件的一个容器,可通过添加组件到游戏对象上实现对应的功能(如:一个长方体、一个空对象)
资源(Assets)
相当于模板,有些模板可以添加到游戏对象上,为游戏对象增添某些功能,有些模板可以直接实例化成为一个游戏对象(如:声音包,材质包,脚本,贴图,场景对象)
联系:
游戏对象可以打包生成资源,资源可以作为模板通过实例化成为游戏对象

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

首先我们从Unity 资源商店下载一个免费的实例,这里我们下载了一个树模型:

在这里插入图片描述
之后我们拖拽几个实例资源中的树模型进入项目,并为其加上素材包:

对象组织如下:

在这里插入图片描述

资源组织:
在这里插入图片描述
结果图:
在这里插入图片描述

资源组织结构
资源组织结构是一个Assets文件夹,包含了用户导入的资源素材包,和添加的脚本、图形、贴图、声音资源等,
如上图资源组织结构中Assets -> BrokenVector->LowPolyTreePack中Material中包含了材质包,Models中包含了树模型对象,通过将两者实例化结合,形成了结果图所示的带有材质颜色的树对象
对象组织结构
如上图目录组织结构所示,游戏对象树包含了场景中的所有游戏对象,对象可以为资源文件实例构成,可通过将游戏对象设置成另一个的子对象,从而在对象组织结构中形成树结构,同时子对象的坐标会以父对象为参考。
如上图中TreeType7 03被设置成为TreeType4 01的子对象,则其位于它的子目录中。

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

基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()
常用事件包括 OnGUI() OnDisable() OnEnable()

Test代码如图所示,通过在每个函数调用中:

public class TestBehavior : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("START");
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("UPDATE");
    }

    void Awake()
    {
        Debug.Log("AWAKE");
    }

    void FixedUpdate()
    {
        Debug.Log("FIXEDUPDATE");
    }

    void LateUpdate()
    {
        Debug.Log("LATEUPDATE");
    }

    void OnGUI()
    {
        Debug.Log("ONGUI");
    }

    void OnDisable()
    {
        Debug.Log("ONDISABLE");
    }

    void OnEnable()
    {
        Debug.Log("ONENABLE");
    }
}

之后运行查看调试结果:

在这里插入图片描述

分析
Awake:当一个脚本实例被载入时Awake被调用,用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次。
Start:Start只在脚本实例被启用时调用。
Update:当行为启用时,其update在每一帧被调用
FixedUpdate:当行为启用时,在每一时间片被调用
LateUpdate:当Behaviour启用时,其LateUpdate在每一帧被调用,LateUpdate是在所有Update函数调用后被调用。这可用于调整脚本执行顺序
OnGUI:渲染和处理GUI事件时调用
OnDisable:当对象变为不可用或非激活状态时此函数被调用,游戏结束时,物体会被禁用,该函数会被执行
OnEnable:当对象变为可用或激活状态时此函数被调用,在start前执行

在开始运行时,awake首先被调用去初始化变量和游戏状态,之后调用enable,标志物体变为可激活状态,之后调用start,标志物体启用,再调用FixedUpdate,启动行为。之后在每个时间片中,调用Update和LateUpdate更新行为,调用OnGUI渲染处理GUI时间,最后在结束时,调用OnDisable标志物体变为不可用状态。

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

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

NameDescription
GameObjectUnity场景中所有实体的基类
Transform物体的位置、旋转和比例
Component一切附加到游戏物体的基类

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

在这里插入图片描述
Table的组件

  1. 第一个选择框为activeSelf属性:设定对象的名称,动静态等属性
  2. 第二个选择框为Transform属性:设置对象的位置、旋转和比例
  3. 第三个选择框为Box Collider:可以调整坐标系的位置、大小
  4. 第四个选择框为Mesh Renderer属性:渲染由MeshFilter或TextMesh插入的网格,常用于渲染材质
Table对象的属性:
static,layer,Tag,Prefab;
Table的Transform属性:
position为(0,0,0),Rotation(0,0,0),Scale为(1,1,1)

1.4.3 用 UML 图描述 三者的关系

在这里插入图片描述

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

预设(Prefabs)有什么好处?

  • 通过使用预设的资源模板可以快速制作多个相同对象,并通过修改预设资源即可对所有调用该资源的对象进行修改,加快了制作时间。

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

  • 预设是使用资源模板创造对象,而克隆是对视图中已经存在的对象进行复制,预设产生的对象依旧会关联这他们的资源模板,通过修改资源模板可以实现对使用该模板的所有对象的修改。而克隆产生的对象可以看作一个新的个体,修改源对象对克隆对象不会产生影响。

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

GameObject NewTable = (GameObject)Instantiate(Resource.Load("table"));

二、编程实践、小游戏

2.1游戏内容: 井字棋

-仅允许使用IMGUI构建UI

2.2具体实现

  • 通过init初始化和reset棋盘状态
  • 在OnGUI中更新期盼状态,通过turn和chessboard[i , j]来决定棋盘状态
  • check_win计算是否获胜
  • rank1和rank2维护计分板,re用来防止重复计数
  • replay和clear用来重置游戏和计分板
public class NewBehavior : MonoBehaviour
{
    private int situation = 9;  //mark empty position
    private int turn = 1;   //mark whose turn
    private int rank1 = 0;  //record player 1 rank
    private int rank2 = 0;  //record player 2 rank
    int re = 1;     //to avoid re record
    private int[,] chessboard = new int[3, 3];  //chessboard status
    // Start is called before the first frame update
    void Start()
    {
        init();
    }
    //reset 
    void init()
    {
        situation = 9;
        turn = 1;
        re = 1;
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                chessboard[i, j] = 0;
            }
        }
    }

    private void OnGUI()
    {
        GUI.skin.button.fontSize = 20;
        GUI.skin.label.fontSize = 30;
        //title style
        GUIStyle style = new GUIStyle
        {
            fontSize = 50,
            fontStyle = FontStyle.Bold
        };
        //title label
        GUI.Label(new Rect(320, 100, 200, 80), "Tic Tac Toe",style);
        //replay button
        if (GUI.Button(new Rect(350, 550, 200, 80), "RePlay"))
        {
            init();
        }
        //clear rank table and replay
        if(GUI.Button(new Rect(100, 250, 80, 80), "clear"))
        {
            rank1 = 0;
            rank2 = 0;
            init();
        }
        //mark table
        GUI.Label(new Rect(100, 150, 200, 80), rank1+ " : " + rank2);

        //check_win
        int win = check_win();
        if (win == 1 && re ==1)
        {
            rank1++;
            re = 0;
        }
        if (win == 2 && re == 1)
        {
            rank2++;
            re = 0;
        }

        //draw the keyboard
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                if(GUI.Button(new Rect(i * 100 + 300, j * 100 + 200, 100, 100), ""))
                {
                    if(win == 0 && chessboard[i,j]==0)
                    {
                        chessboard[i, j] = turn;
                        turn= turn * (-1);
                        situation--;
                    }
                }
                // use X and O to mark chess
                if (chessboard[i,j] == 1)
                {
                    GUI.Button(new Rect(i * 100+300, j * 100 + 200, 100, 100), "X");
                }
                if (chessboard[i, j] == -1)
                {
                    GUI.Button(new Rect(i * 100+ 300, j * 100 + 200, 100, 100), "O");
                }
                
            }
        }

        
        //Game result
        if (win == 1)
        {
            GUI.Label(new Rect(350, 150, 200, 80), "Player 1 Win!");
        }else if(win == 2){
            GUI.Label(new Rect(350, 150, 200, 80), "Player 2 Win!");
        }else if(win == 3)
        {
            GUI.Label(new Rect(350, 150, 200, 80), "No One Win~");
        }



    }
    
    int check_win()
    {
        //check every row and col == 3 or -3
        for(int i =0; i < 3; i++)
        {
            int row_num = 0;
            int col_num = 0;
            for(int j = 0; j < 3; j++)
            {
                row_num += chessboard[i, j];
                col_num += chessboard[j, i];
                if(row_num == 3 || col_num == 3)
                {
                    return 1;
                }
                if (row_num == -3 || col_num == -3)
                {
                    return 2;
                }
            }
        }
        // check diagonal
        int fir = 0;
        int sec = 0;
        for(int i = 0; i < 3; i++)
        {
            fir += chessboard[i, i];
            sec += chessboard[i, 3 - i - 1];
        }
        if (fir == 3 || sec == 3)
        {
            return 1;
        }
        if (fir == -3 || sec == -3)
        {
            return 2;
        }
        if (situation == 0) return 3;
        return 0;
    }
}

在这里插入图片描述

三、思考题

3.1 微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”。

为什么是“模板方法”模式而不是“策略模式”呢?
  • 模板方法是定义一个算法流程,并通过一定的特定步骤实现到子类。使得不改变算法流程的情况下,通过不同的子类来实现流程中的特定步骤;
  • 策略模式是使不同的算法可以相互替换,不影响客户端使用。
  • 这里是使用让继承者们继承Game的虚方法,更注重的是通过让继承者继承父类的方法,所以是模板方法

3.2 将游戏对象组成树型结构,每个节点都是游戏对象(或数)

尝试解释组合模式(Composite Pattern / 一种设计模式)。

  • 组合模式也叫部分整体模式,用于把一组相似的对象当作一个单一的对象。根据树形结构来组合对象,用来表示部分以及整体层次。

使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?

BroadcastMessage()
{
	for each childObject
		sendMessage();
}

3.3 一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)

这是什么设计模式?

  • 策略模式

为什么不用继承设计特殊的游戏对象

  • 因为在游戏的设计中,游戏对象需要多次的修改,如果继承于GameObject对象,则每次修改需要修改继承中的每个对象,导致修改调试难度高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值