1、简答题
·解释游戏对象(GameObjects)和资源(Assets)的区别与联系。
区别:游戏对象是代表角色、道具和场景的基本对象。它们本身不能完成很多工作,但它们容纳实现实际功能的组件。
资源是游戏或项目中可以使用的对象,可能来自Unity外部创建的文件,也可能是在Unity中创建的。
相对而言,游戏对象更抽象,而资源更具体。
联系:资源可以被游戏对象使用。
·下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)
下载的游戏文件后缀为unitypackage,导入方法是新建一个project,进入project,点击Assets->Import Package->Custom Package…,选择游戏文件,然后选择需要导入的部分进行导入。
资源的目录组织结构如下。可以从文件夹名称看出,有Editor、Prefabs、Resources、Scenes、Scripts、Sprites、Textures。
游戏对象树的层次结构如下。有Canvas、EventSystem、MainCamera
·编写一个代码,使用 debug 语句来验证MonoBehaviour基本行为或事件触发的条件
基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()
常用事件包括 OnGUI() OnDisable() OnEnable()
基本行为:
Awake:始终在任何 Start 函数之前并在实例化预制件之后调用此函数。
Start:仅当启用脚本实例后,才会在第一次帧更新之前调用。
Update:每帧调用一次 Update。这是用于帧更新的主要函数。
FixedUpdate:调用 FixedUpdate 的频度常常超过 Update。如果帧率很低,可以每帧调用该函数多次;如果帧率很高,可能在帧之间完全不调用该函数。在 FixedUpdate 之后将立即进行所有物理计算和更新。在 FixedUpdate 内应用运动计算时,无需将值乘以 Time.deltaTime。这是因为 FixedUpdate 的调用基于可靠的计时器(独立于帧率)。
LateUpdate:每帧调用一次 LateUpdate__(在 Update__ 完成后)。LateUpdate 开始时,在 Update 中执行的所有计算便已完成。
常用事件:
OnGUI:每帧调用多次以响应 GUI 事件。首先处理布局和重新绘制事件,然后为每个输入事件处理布局和键盘/鼠标事件。
OnDisable:行为被禁用或处于非活动状态时,调用此函数。
OnEnable:在启用对象后立即调用此函数。
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour
{
void Awake () {
Debug.Log("Awake");
}
void Start () {
Debug.Log("Start");
}
void Update () {
Debug.Log("Update");
}
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");
}
}
运行后结果如下图:
·查找脚本手册,了解GameObject,Transform,Component 对象
分别翻译官方对三个对象的描述(Description)
描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件
本题目要求是把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。
例如:table 的对象是 GameObject,第一个选择框是 activeSelf 属性。
用 UML 图描述 三者的关系(请使用 UMLet 14.1.1 stand-alone版本出图)
GameObject对象描述:Unity 场景中所有实体的基类。
Transform对象描述:对象的位置、旋转和缩放。场景中的每个对象都有一个变换。 它用于存储和操作对象的位置、旋转和缩放。 每个变换都可以有一个父级,让你能够分层应用位置、旋转和缩放。这是“Hierarchy”面板中显示的层级视图。 它们还支持枚举器。
Component 对象描述:附加到 GameObject 的所有内容的基本类。注意,代码不会直接创建 Component,而是编写脚本代码,然后将该脚本附加到 GameObject。
table 对象(实体)的属性:activeSelf,isStatic,Layer,Tag,Transform
table 的 Transform 的属性:Position(0,0,0),Rotation(0,0,0),Scale(1,1,1)
table 的部件:Mesh Filter,Box Collider,Mesh Renderer
UML图描述三者关系:
·资源预设(Prefabs)与 对象克隆 (clone)
预设(Prefabs)有什么好处?
预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?
制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象
预设的好处:可以容易地复用实体集,再次使用实体集时不需要再创建拼接对象。
预设与对象克隆的关系:预设的实例之间是相关的,如果预设改变,实例也会相应改变。对象克隆的实例之间是彼此独立的,更改一个实例,其他实例不会变化。
制作table预制的方法在课程网页有详细描述,将table预制资源实例化成游戏对象的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class instantiate: MonoBehaviour
{
public GameObject table;
void Start () {
GameObject newinstance = (GameObject)Instantiate(table, transform.position, transform.rotation);
}
}
2、编程实践,小游戏
·游戏内容:井字棋或贷款计算器或简单计算器等等
·技术限制:仅允许使用IMGUI构建UI
·作业目的:
了解 OnGUI() 事件,提升 debug 能力
提升阅读 API 文档能力
将chess.cs的文件拖进层次结构中的MainCamera,chess.cs的代码如下。点击棋盘可以下棋,结果会显示在右侧,点击reset可以清空棋盘,重新开始游戏。
项目保存在 https://github.com/loudax/3d-game/tree/master/tictactoe
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class chess : MonoBehaviour
{
int turn, counter;
int[,] board = new int[3, 3];
// Start is called before the first frame update
void Start()
{
init();
}
void init(){
turn = 1;
counter = 0;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
board[i, j] = 0;
}
}
}
int check(){
for(int i = 0; i < 3; i++){
if(board[0, i] == board[1, i] && board[1, i] == board[2, i]){
return board[0, i];
}
if(board[i, 0] == board[i, 1] && board[i, 1] == board[i, 2]){
return board[i, 0];
}
}
if((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];
}
if(counter == 9){
return 3;
}
return 0;
}
void OnGUI(){
if(GUI.Button(new Rect(220, 100, 100, 50),"Restart")){
init();
}
int result = check();
if(result == 1){
GUI.Label(new Rect(220, 50, 100, 50), "Player1 wins!");
}
else if(result == 2){
GUI.Label(new Rect(220, 50, 100, 50), "Player2 wins!");
}
else if(result == 3){
GUI.Label(new Rect(220, 50, 200, 50), "No winner, let's play again.");
}
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(board[i, j] == 1){
GUI.Button(new Rect(50+i*50, 20+j*50, 50, 50), "O");
}
if(board[i, j] == 2){
GUI.Button(new Rect(50+i*50, 20+j*50, 50, 50), "X");
}
if(GUI.Button(new Rect(50+i*50, 20+j*50, 50, 50), "")){
if(result == 0){
board[i, j] = turn;
counter++;
if(turn == 1){
turn = 2;
}
else{
turn = 1;
}
}
}
}
}
}
// Update is called once per frame
void Update()
{
}
}
3、思考题【选做】
·微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”。
为什么是“模板方法”模式而不是“策略模式”呢?
模板方法指的是模板决定算法的步骤,具体的实现是使用接口在子类实现,这种方法可以实现逻辑分离,可以在不改变整体的条件下,对子步骤进行修改。XNA引擎的方法更符合模板方法的描述。
·将游戏对象组成树型结构,每个节点都是游戏对象(或数)。
尝试解释组合模式(Composite Pattern / 一种设计模式)。
使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?
将相似的对象组合起来成为树型结构,使得可以对这一组对象统一处理。
void BoradcastMessage(string msg){
foreach child
child.boradcast(msg);
}
·一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)。
这是什么设计模式?
为什么不用继承设计特殊的游戏对象?
Decorator模式。使用继承设计特殊的游戏对象会导致数量过多管理困难,并且降低了其灵活性。使用Decorator模式可以灵活地向对象加入部件。