目录:
- 一、简答题
- 二、编程实践、小游戏
- 三、思考题
一、简答题
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)
Name | Description |
---|---|
GameObject | Unity场景中所有实体的基类 |
Transform | 物体的位置、旋转和比例 |
Component | 一切附加到游戏物体的基类 |
1.4.2 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件
Table的组件:
- 第一个选择框为activeSelf属性:设定对象的名称,动静态等属性
- 第二个选择框为Transform属性:设置对象的位置、旋转和比例
- 第三个选择框为Box Collider:可以调整坐标系的位置、大小
- 第四个选择框为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对象,则每次修改需要修改继承中的每个对象,导致修改调试难度高