3D 游戏编程与设计 - 离散仿真引擎基础作业

简答题

1.

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

GameObject 是运行时在场景中被渲染的实体对象。而 Asset 是游戏的资源,包括预设、图片、脚本、场景等静态文件。
游戏的 GameObject 是游戏资源的载体,GameObject 由预设产生,可以算是预设的实例;GameObject 还可以添加组件,挂载脚本,此时挂载的脚本还可以绑定现有的材质。也就是说,其实 GameObject 就是游戏资源的组装成品以及设置。
特别的,GameObject 也可以产生 Prefab,Prefab 可以产生 GameObject。

2.

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

3.

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

  • 基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()
  • 常用事件包括 OnGUI() OnDisable() OnEnable()
    添加脚本 TestScript 并挂载到 Directional Light 上:
using UnityEngine;
public class TestScript : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake");
    }

    void Start()
    {
        Debug.Log("Start");
    }

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

    void Update()
    {
        Debug.Log("Update");
    }

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

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

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

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

    void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}

运行后可以看到如下输出:发现调用顺序为 AwakeOnEnableStartUpdateLateUpdateOnGUIOnDisableOnDestroy
在这里插入图片描述
点击停止按钮后的日志如下:
在这里插入图片描述
可以看到和官方网站提供的 MonoBehaviour 的生命周期一致:
在这里插入图片描述

4.

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

  • 分别翻译官方对三个对象的描述(Description)
  • 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件
    • 本题目要求是把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。
    • 例如:table 的对象是 GameObject,第一个选择框是 activeSelf 属性。
  • 用 UML 图描述 三者的关系(请使用 UMLet 14.1.1 stand-alone版本出图)

GameObject

GameObjects are the fundamental objects in Unity that represent characters, props and scenery. They do not accomplish much in themselves but they act as containers for Components, which implement the real functionality.
Class GameObject is the base class for all entities in Unity Scenes.

也就是说 GameObject 是 Unity 中表示游戏玩家、场景、物品的基础对象。GameObject 是 Prefab 的实例,GameObject 还是 Component 的容器,一个 GameObject 上可以挂载多个 Component 定制 GameObject 的行为。

Transform

The Transform component determines the Position, Rotation, and Scale of each object in the scene. Every GameObject has a Transform.

表示 GameObject 的位置、方向、缩放。

Component

Components are the nuts & bolts of objects and behaviors in a game. They are the functional pieces of every GameObject. If you don’t yet understand the relationship between Components and GameObjects, read the GameObjects page before going any further.

Component 是描述 GameObject 行为的对象,负责控制 GameObject 的生命周期以及行为,一个 GameObject 上可以有多个 Component。

5.

整理相关学习资料,编写简单代码验证以下技术的实现:

  • 查找对象
  • 添加子对象
  • 遍历对象树
  • 清除所有子对象

查找对象

// Finds a GameObject by name and returns it.
public static GameObject Find(string name);
// Returns a list of active GameObjects tagged tag. Returns empty array if no GameObject was found.
// 参数:
//   tag:
//     The name of the tag to search GameObjects for.
public static GameObject[] FindGameObjectsWithTag(string tag);
public static GameObject FindGameObjectWithTag(string tag);
// Returns one active GameObject tagged tag. Returns null if no GameObject was found.
// 参数:
//   tag:
//     The tag to search for.
public static GameObject FindWithTag(string tag);

验证:

// SceneBehaviour,这个脚本绑定在了场景中的某一个对象上
void Start()
{
	GameObject table = GameObject.Find("Table");
	Debug.Log(table.GetHashCode());
}

// TableBehaviour,这个脚本绑定在了 Table 对象上
void Start()
{
	Debug.Log(gameObject.GetHashCode());
}

可以看到两次的输出是一致的,说明找到了同一个对象。
注:C# 默认的 GetHashCode() 函数返回的是对象的地址,可以反映是否是同一个对象。
在这里插入图片描述

添加子对象

void Start()
{
    GameObjecttable = GameObject.Find("Table");
    GameObject chair5 = GameObject.CreatePrimitive(PrimitiveType.Cube);
    chair5.name = "Chair5";
    chair5.transform.position = new Vector3(0, 2, 0);
    chair5.transform.localScale = new Vector3(1, 0.2f, 1);
    chair5.transform.parent = table.transform;
}

可以看到新创建了一个方块:
在这里插入图片描述

遍历对象树

void Traverse(Transform transform)
{
	foreach (Transform child in transform)
	{
		Debug.Log(child.name);
		Traverse(child);
	}
}

清除所有子对象

foreach (Transform child in parent.transform)
{
    Destroy(child.gameObject);
}

6.

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

  • 预设(Prefabs)有什么好处?
  • 预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?
  • 制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象

预设有什么好处?

可以复用相似的游戏对象,减少建模的复杂度。而且复用可以使得批量更改所有的游戏对象。

预设与对象克隆关系?

预设的修改会影响相关的所有游戏对象。但对象克隆后两者其实没有关系,修改其中一个不会影响另一个。

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

编程实践,小游戏

游戏内容: 井字棋 或 贷款计算器 或 简单计算器 等等
技术限制: 仅允许使用 IMGUI 构建 UI
作业目的:提升 debug 能力提升阅读 API 文档能力

游戏截图

在这里插入图片描述

游戏实现

UML 结构图

在这里插入图片描述

GuiComponent

GuiComponent 为所有 2D 元素的基类,因此按钮、计时器、数字显示屏、棋盘都是 GuiComponent 的派生类。

游戏全部使用 IMGUI 构建,由于 IMGUI 过于原始,我们需要自己封装 Gui 类来帮助我们完成工作。

GuiComponent 本身使用了组合模式,每个 GuiComponent 有自己的孩子,每个孩子的定位相对于父组件定位,最后组件形成了组件树。

GuiTileBoard

GuiTile 为棋盘的每一个方块,并维护方块当前状态是未被揭开、已经揭开、插旗等。还负责自己的点击事件的处理。

GuiTileBoard 实现了游戏的逻辑,负责计算当前局面是胜利还是失败。GuiTileBoard 每次都会查询所有的 GuiTile 并维护当前局面形势。

思考题

1

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

  • 为什么是“模板方法”模式而不是“策略模式”呢?

模板方法模式:通过基类定义算法流程,并将算法的实现细节步骤通过抽象方法的方式要求子类实现补全算法。强调补全算法细节,以及强调原本算法本身的结构以及执行流程。
策略模式:允许运行时修改对象行为。强调修改的行为本身的独立性。
此处游戏循环本身是一个算法流程,强调了先初始化,再循环,最后退出的算法流程,我们的任务是补全该算法,实现特定的程序。而这些流程之间是有联系的,是整个算法不可分割的一部分。因此强调行为独立性的策略模式在此不适用。

2

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

  • 尝试解释组合模式(Composite Pattern / 一种设计模式)。
  • 使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?

组合模式

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

BroadcastMessage

我们为 Table 添加 TableScript 如下:

public class TableScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            BroadcastMessage("SayHello");
        }
    }
}

Chair 添加 ChairScript 如下:

public class ChairScript : MonoBehaviour
{
    void SayHello()
    {
        Debug.Log("Hello! " + transform.position.x + " " + transform.position.y + " " + transform.position.z);
    }
}

运行后点击鼠标可以发现 4 个 chair 对象的坐标都已经被打印出来了。
在这里插入图片描述

自己实现 BroadcastMessage

为了实现 BroadcastMessage,并支持调用相应的重载方法,我们需要遍历所有的函数,并且确保相应函数可以被调用:

public class TableScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            MyBroadcastMessage("SayHello", this);
            MyBroadcastMessage("SayHello", 123);
        }
    }

    public void MyBroadcastMessage(string methodName, params object[] parameters)
    {
        foreach (Transform child in this.transform)
        {
            foreach (MonoBehaviour behaviour in child.gameObject.GetComponents<MonoBehaviour>())
            {
                foreach (var method in behaviour.GetType().GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public))
                {
                    if (method.Name == methodName && method.GetParameters().Length == parameters.Length)
                    {
                        bool ok = true;
                        for (int i = 0; i < parameters.Length; ++i)
                        {
                            if (!method.GetParameters()[i].ParameterType.IsAssignableFrom(parameters[i].GetType()))
                            {
                                ok = false;
                                break;
                            }
                        }
                        if (ok)
                        {
                            method.Invoke(behaviour, parameters);
                        }
                    }
                }
            }
        }
    }
}

运行之后可以看到两个 SayHello 函数都能被正常调用
在这里插入图片描述

3

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

  • 这是什么设计模式?
  • 为什么不用继承设计特殊的游戏对象?

这是什么设计模式?

我认为这合用了模板模式策略模式
其中,Componet 本身使用了模板模式,是完成对象的游戏循环算法的类型,此外,游戏对象本身可以挂载多个 Component,这表明,游戏对象本身使用了策略模式,允许通过 Component 修改 GameObject 本身的行为。

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

  1. Delegation 允许动态修改游戏对象行为,而继承不允许,因此继承的灵活性差
  2. Delegation 性能比继承好
  3. Delegation 允许组合,而继承不能
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值