Unity3d 二、离散仿真引擎基础
简答题
解释游戏对象(GameObjects) 和 资源(Assets)的区别与联系。
游戏对象:出现在场景中,一般有人物、道具和风景等等,充当组件的容器,实现真正的功能。游戏对象自己不做任何事。他们需要专有属性,才可以成为一个角色,一个环境,或一个特殊效果。
资源:表示可以在项目中使用的任何素材。可能来自在Unity之外创建的文件,例如3D模型、音频文件,图像,脚本或Unity支持的任何其他类型的文件。还有一些可以在Unity中创建的资源类型,如动画控制器、音频混音器或渲染纹理。
下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)
在爱给网上下载游戏天天酷跑可以看到他的资源目录结构如下:
资源目录结构:
游戏对象树:
总结如下:
资源文件夹通常有对象、材质、场景、声音、预设、贴图、脚本、动作,在这些文件夹下可以继续划分。
游戏对象树中一般有玩家、敌人、环境、摄像机和音乐等虚拟父类,这些父类本身没有实体,但他们的子类包含了游戏中会出现的对象。
编写一个代码,使用 debug 语句来验证 MonoBehaviour基本行为或事件触发的条件
- 基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()
事件名称 | 执行条件或时机 |
---|---|
Awake | 当一个游戏对象实例被载入时Awake被调用,或者脚本构造时调用。 |
Start | 第一次进入游戏循环时调用 |
FixUpdate | 每个游戏循环,由物理引擎调用 |
Update | 所有 Start 调用完后,被游戏循环调用 |
LastUpdate | 所有 Update 调用完后,被游戏循环调用 |
OnGUI | 游戏循环在渲染过程中,场景渲染之后调用 |
- 常用事件包括 OnGUI() OnDisable() OnEnable()
代码:
// Start is called before the first frame update
void Start()
{
Debug.Log("Start");
}
// Update is called once per frame
void Update()
{
Debug.Log("Update");
}
private void Awake()
{
Debug.Log("Awake");
}
private void FixedUpdate()
{
Debug.Log("FixedUpdate");
}
private void LateUpdate()
{
Debug.Log("LateUpdate");
}
private void OnGUI()
{
Debug.Log("OnGUI");
}
private void OnDisable()
{
Debug.Log("OnDisable");
}
private void OnEnable()
{
Debug.Log("OnEnable");
}
查找脚本手册,了解 GameObject,Transform,Component 对象
- 分别翻译官方对三个对象的描述(Description)
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.
游戏对象是Unity中的基本对象,表示角色、道具和场景。它们本身不完成很多工作,但它们充当Component的容器,由Component来实现真正的功能。
The Transform component determines the Position, Rotation, and Scale of each object in the scene. Every GameObject has a Transform.
转换组件定义了组件的位置,旋转角度,各个方向的大小比例(缩放)。每一个游戏对象(GameObject)都有着一个Transform。
Components are the nuts & bolts of objects and behaviors in a game.They are the functional pieces of every GameObject.
组件是游戏中对象和行为之间的螺母和螺丝。它们是每个游戏对象的功能部件。一个游戏对象是许多不同的组件的容器。默认情况下,所有的游戏对象自动拥有Transform组件。这是因为Transform组件决定了游戏对象的位置,以及它如何旋转和缩放。没有Transform组件的游戏对象,不会在World中存在。
- 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件
本题目要求是把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。
例如:table 的对象是 GameObject,第一个选择框是 activeSelf 属性。
如上图,table的属性如下:
table对象的属性是GameObject(游戏对象),第一个选择框是activeSelf属性,第二个是对象名称,第三个是static属性,下一行是标签Tag和Layer,第三行是预设。
- Transform描述了table对象的位置,旋转角度,缩放, 图中的属性为,位置(0,0,0), 旋转(0,0,0),比例为(100%, 100%, 100%);
- Mesh Filter(网格过滤器)描述了几何体的形状,图中table为Cube(立方体)
- Box Colider(盒状碰撞器)则描述了物体的碰撞范围,图中的碰撞范围为以(0,0,0),(1,0,0),(0,1,0),(1,1,0),(0,0,1),(1,0,1),(0,1,1),(1,1,1)为顶点的一个正方体
- Mesh Renderer(网格渲染器)则从网格过滤器中获得几何体的形状然后根据要求进行渲染。图中的属性为 Cast Shadows(投射阴影方式)开启,接受阴影 Motion Vectors(运动向量生成方式)为PerObject Motion(会渲染出一个“每个对象”运动向量通道),材质为默认材质,Light Probes(光探头插值方式)以及Reflection Probes(反射探头都为混合),Anchor Override为当使用光探头或反射探头系统时,用来决定插值位置的Transform,图中未定义该Transform。
属于table的子部件有:
chair1 、chair2、chair3、chair4
- 用 UML 图描述 三者的关系(请使用 UMLet 14.1.1 stand-alone版本出图)
资源预设(Prefabs)与 对象克隆 (clone)
- 预设(Prefabs)有什么好处?
Unity通过预设(Prefabs)完整地储存了对象的组件、属性等内容,方便开发者创建具有相同属性的对象,方便了对象的复用。
- 预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?
对象克隆与预设不同的地方就在于,预设与实例化的对象是一致的,预设发生变化,所有通过其实例化的对象都会产生变化,而对象克隆本体和克隆出的对象不同,其不受本体改变影响。
- 制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Instantiate : MonoBehaviour
{
public GameObject obj; //传入的预设
void Start()
{
GameObject instance = (GameObject)Instantiate(obj, transform.position, transform.rotation);
}
}
或者
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoadBeh : MonoBehaviour {
public Transform res;
// Use this for initialization
void Start () {
// Load Resources
GameObject newobj = Instantiate<Transform> (res, this.transform).gameObject;
newobj.transform.position = new Vector3 (0, Random.Range (-5, 5), 0);
}
}
结果:
编程实践,小游戏
- 游戏内容: 井字棋 或 贷款计算器 或 简单计算器 等等
- 技术限制: 仅允许使用 IMGUI 构建 UI
- 作业目的:
- 了解 OnGUI() 事件,提升 debug 能力
- 提升阅读 API 文档能力
思考题【选做】
微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”。
为什么是“模板方法”模式而不是“策略模式”呢?
如果你还不了解模板方法模式和策略模式,请先阅读《 策略模式(strategy)》和《模板方法模式》
模板方法模式的主要思想:定义一个算法流程,将一些特定步骤的具体实现、延迟到子类。使得可以在不改变算法流程的情况下,通过不同的子类、来实现“定制”流程中的特定的步骤。
策略模式的主要思想:使不同的算法可以被相互替换,而不影响客户端的使用。
模板方法更接近于一项规则,为程序的算法规定一套流程也就是模板,代码需要遵循这个模板来编写,通过修改流程每个部分内部的实现来改变功能。而策略模式更强调的是内部变化与外部的分离,及内部的修改不会影响到外部其他部分的功能。
对于游戏来说,游戏对象更趋于组合形成,那么就不可避免地需要内部与外部的交互,即使内部实现逻辑可以不变,但是两个游戏部件组合在一起后的行为逻辑和其内部的实现有一定的关系,不能被完全分离,所以采用“模板方法”模式而不是“策略模式”。
将游戏对象组成树型结构,每个节点都是游戏对象(或数)。
尝试解释组合模式(Composite Pattern / 一种设计模式)。
组合模式允许用户将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?
//为父对象添加方法
void Start () {
this.BroadcastMessage("getMessage", "hello?");
}
//为子对象添加方法
void getMessage(string msg) {
Debug.Log("Get msg: " + msg + "!");
}
伪代码:
void BoradcastMessage(string message){
foreach (child of this){
if (child.hasFunction(message))
child.stringToFunction(message);
}
}
例子:如我们为父Cube添加含有以上Start方法的脚本LoadBeh.cs,为子Cube添加含有以上getMessage方法的Instantiate.cs(在层次视图中右键父Cube即可创建子Object),得到结果如下:
一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)。
- 这是什么设计模式?
装饰器模式。说明如:
Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
(装饰器)动态地将接口附加到对象上。若要扩展功能,decorator提供了比继承更有弹性的替代方案。
- 为什么不用继承设计特殊的游戏对象?
- 用继承机制去描述所有对象是几乎不可能的。现实世界的需求和构成过于复杂导致不能够仅仅使用继承机制来实现。
- 在用继承机制时,多重继承产生的类过于冗杂和难以使用。
- 继承中的多态特性使得游戏对象的行为变得难以预测和维护。
- 继承是静态的,在运行前就已经决定了继承的所有性质;而游戏往往需要实时的、多种的组合方式来实现。
- 软件设计原则“组合优于继承”。