Unity3d–离散仿真基础
1.简答题
- 解释 游戏对象(GameObjects)和 资源(Assets)的区别与联系。
区别:游戏对象(GameObjects)是Unity中的基本对象,代表角色,道具和风景等等。它本身并不完成实际的功能,而是各种组件的容器,根据要创建的对象类型,可以添加不同的组件组合到游戏对象中。资源(Assets)包含在游戏文件中,可以是我们自定义或下载下来的素材。资源可能来自Unity外部创建的文件,例如3D模型,音频文件,图像或Unity支持的其他文件。还有一些是可以在Unity中创建的资源。
联系:资源可以被多个游戏对象使用,有些资源可以做为模板并实例化为对象。 - 下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)
我在Asset Store中下载了DreamForestTree项目,通过这个案例总结资源和对象组织的结构。
资源的目录组织结构:资源结构主要包括动画,文本,场景,素材,模型,预设以及使用说明等。
对象树的层次结构:对象组织的结构主要包括摄像机包,游戏开始位置,场景布局以及文本管理等。 - 编写一个代码,使用 debug 语句来验证 MonoBehaviour 基本行为或事件触发的条件
- 基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()
- 常用事件包括 OnGUI() OnDisable() OnEnable()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
//程序一开始调用
void Awake(){
Debug.Log ("Awake");
}
// Use this for initialization
//在Awake之后调用
void Start () {
Debug.Log ("Start");
}
// Update is called once per frame
//每帧调用一次
void Update () {
Debug.Log ("Update");
}
//每隔特定的时间更新
void FixedUpdate(){
Debug.Log ("FixedUpdate");
}
//所有对象渲染完毕调用
void LateUpdate(){
Debug.Log ("LateUpdate");
}
//对GUI的对象产生交互时调用
void OnGUI(){
Debug.Log ("OnGUI");
}
//对象不可用时调用
void OnDisable(){
Debug.Log ("OnDisable");
}
//对象可用时调用
void OnEnable(){
Debug.Log ("OnEnable");
}
}
-
查找脚本手册,了解 GameObject,Transform,Component 对象
- 分别翻译官方对三个对象的描述(Description)
GameObject:The fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it.
游戏对象(GameObject):Unity场景中的基本对象,可以表示角色,道具,风景,相机,航点等。 GameObject的功能由附加到其上的组件定义。
Transform:The Transform component determines the Position, Rotation, and Scale of each object in the scene. Every GameObject has a Transform.
变换组件(Transform):变换组件确定场景中每个对象的位置,旋转和缩放。 每个游戏对象都有一个变换组件。
Component:A functional part of a GameObject. A GameObject can contain any number of components. Unity has many built-in components, and you can create your own by writing scripts that inherit from MonoBehaviour.
组件(Component):游戏对象的功能部分。游戏对象可以包含任意数量的组件。Unity有许多内置组件,您可以通过编写从MonoBehaviour继承的脚本来创建自己的组件。 - 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件
- 本题目要求是把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。
- 例如:table 的对象是 GameObject,第一个选择框是 activeSelf 属性。
table 的对象是 GameObject,第一个选择框是 activeSelf 属性。第二个文本框是对象名称,第三个选择框为static属性。第二行有Tag属性和Layer属性,第三行为prefabs(预设)属性。
Transform属性包括position(位置),Rotation(旋转),Scale(比例)。
Component对象有Transform,Mesh Filter,Box Collider,Mesh Renderer。
- 用 UML 图描述 三者的关系(请使用UMLet 14.1.1 stand-alone版本出图)
- 分别翻译官方对三个对象的描述(Description)
-
整理相关学习资料,编写简单代码验证以下技术的实现:
- 查找对象
通过名字查找:
public static GameObject Find(string name)
通过标签查找单个对象:
public static GameObject FindWithTag(string tag)
通过标签查找多个对象:
public static GameObject[] FindGameObjectsWithTag(string tag)
- 添加子对象
- 遍历对象树
- 清除所有子对象
- 查找对象
-
资源预设(Prefabs)与 对象克隆 (clone)
- 预设(Prefabs)有什么好处?
1.使对象和资源能够重复利用。
2.相同的游戏对象可以用同一个预制来创建。
3.对预设进行修改后,所有的游戏对象都会发生改变。
总体而言,预设资源储存了完整储存了对象的组件和属性,相当于模板,使用预制能够方便我们创建相同的游戏对象并赋予他们相同的行为。 - 预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?
1.克隆游戏对象需要场景中有被克隆对象,而创建预制只需事先创建预制即可,允许场景中一开始并不存在该游戏对象。
2.克隆出来的游戏对象并不会随着被克隆体的变化而发生变化,但是使用预制创建出来的对象会随着预制的改变而发生改变。
- 预设(Prefabs)有什么好处?
-
制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象
制作table预制:
代码实例化:
table = Resources.Load (“table”);
GameObject instance = Instantiate(table);
2、 编程实践,小游戏
- 游戏内容: 井字棋 或 贷款计算器 或 简单计算器 等等
- 技术限制: 仅允许使用 IMGUI 构建 UI
- 作业目的:
- 了解 OnGUI() 事件,提升 debug 能力
- 提升阅读 API 文档能力
首先是游戏效果展示
共三种情况,玩家1胜,玩家2胜和平局。
下面是代码逻辑:
通过一个二维数组board来表示棋盘上每个点的状态信息,0为未点中,1为玩家1点中,2为玩家2点中。
turn表示当前到哪个玩家进行选择,1为玩家1选择,2为玩家2选择,初始为玩家1进行选择。
res表示结果,0为正在游戏中,1为玩家1获胜,2为玩家2获胜,3为平局。
游戏结束后需要重新开始,其逻辑是将board上每个位置的状态都重新初始化为0,代表每个位置都还未被点中。
在游戏中需要检查游戏是否结束,并判断其结果,井字棋获胜要求是行或列或对角线连成三个相同的棋子即获胜,所以需要从每行,每列,每条对角线来判断三个棋子是否相同,若都不相同且所有格子未被填满,则说明游戏还在进行中,若9个位置都有棋子且没有连城3个的棋子,则说明游戏是平局。最终通过board中连成三个的是1还是2来判断玩家1或玩家2获胜。
初始的Start函数逻辑与reset函数逻辑相同,直接调用reset函数即可。
接下来进入GUI函数,首先搭建游戏菜单界面以及字体格式。
玩家1为绿色圆圈,玩家2为红色叉。
接着是构建棋盘,需要用到GUI.Button函数,它会一直监听整个事件,一旦被点击则返回true执行下面的逻辑。首先创建3*3的空棋盘,若其中某个按钮被点击,且此时游戏未结束(防止游戏结束后仍可以点击棋盘),那么就将这个棋盘位置的状态从0变为此时turn的值,也就是哪个玩家点下的这个位置,接下来若此处为1,则将此处按钮换为绿色圆圈,若为2,则换为红色叉,这样就完成了游戏期盼的构建。
在玩家交换点击的过程中需要知道此时应该哪个玩家点击,所以设计了提示,在轮到改为玩家时,玩家右侧会出现小点。
接下来需要创建reset按钮,按下代表游戏重新开始。
最后需要实时判断游戏结果,根据check函数返回不同的值来判断游戏是否结束以及结束状态。
完整代码如下所示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 井字棋 : MonoBehaviour {
private int turn = 1;
private int[,] board = new int [3, 3];
private int res = 0;
// Use this for initialization
void reset(){
turn = 1;
res = 0;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j) board [i,j] = 0;
}
int check(){
int count = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) if (board [i, j] != 0) count++;
for (int i=0; i<3; ++i) if (board[i,0]!=0 && board[i,0]==board[i,1] && board[i,1]==board[i,2]) return board[i,0];
for (int j=0; j<3; ++j) if (board[0,j]!=0 && board[0,j]==board[1,j] && board[1,j]==board[2,j]) return board[0,j];
if (board[1,1]!=0 && (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 (count == 9) return 3;
return 0;
}
void Start () {
reset ();
}
// Update is called once per frame
void OnGUI(){
GUIStyle Style = new GUIStyle();
Style.normal.background = null;
Style.normal.textColor= new Color(1, 0, 0);
Style.fontSize = 20;
GUIStyle Style2 = new GUIStyle ();
Style2.normal.background = null;
Style2.normal.textColor= new Color(1, 0, 0);
Style2.fontSize = 50;
GUIStyle Style3 = new GUIStyle();
Style3.normal.textColor= new Color(0, 1, 0);
Style3.alignment = TextAnchor.MiddleCenter;
Style3.fontSize = 40;
GUIStyle Style4 = new GUIStyle();
Style4.normal.textColor= new Color(1, 0, 0);
Style4.alignment = TextAnchor.MiddleCenter;
Style4.fontSize = 40;
GUIStyle Style5 = new GUIStyle();
Style5.normal.background = null;
Style5.normal.textColor= new Color(0, 1, 0);
Style5.fontSize = 20;
GUIStyle Style6 = new GUIStyle ();
Style6.normal.background = null;
Style6.normal.textColor= new Color(0, 1, 0);
Style6.fontSize = 50;
GUIStyle Style7 = new GUIStyle();
Style7.normal.background = null;
Style7.normal.textColor= new Color(0, 0, 0);
Style7.fontSize = 20;
GUI.Label (new Rect (400, 50, 200, 50), "Welcome To TicTacToe",Style7);
GUI.Label (new Rect (280, 25, 100, 50), "Turns:", Style7);
GUI.Label (new Rect (280, 55, 100, 50), "Player1", Style5);
GUI.Label (new Rect (280, 85, 100, 50), "Player2", Style);
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j) {
if (board [i,j] == 0)
if (GUI.Button (new Rect (430+50*i, 100+50*j, 50, 50), "")&&check()==0) {
board [i, j] = turn;
turn = 3 - turn;
}
if (board [i, j] == 1) GUI.Button (new Rect (430+50*i, 100+50*j, 50, 50), "o",Style3);
if (board [i, j] == 2) GUI.Button (new Rect (430+50*i, 100+50*j, 50, 50), "x",Style4);
}
if (turn == 1) GUI.Label (new Rect (350, 40, 100, 50), "·", Style6);
else GUI.Label (new Rect (350, 70, 100, 50), "·", Style2);
if (GUI.Button (new Rect (280, 200, 100, 50), "Reset")) reset ();
res = check ();
if(res==1) GUI.Label (new Rect (280, 150, 100, 50), "Player 1 Win",Style7);
else if(res==2) GUI.Label (new Rect (280, 150, 100, 50), "Player 2 Win",Style7);
else if(res==3) GUI.Label (new Rect (280, 150, 100, 50), "Tie",Style7);
else GUI.Label (new Rect (280, 150, 100, 50), "Playing...",Style7);
}
}
在界面中我创建了一个平面,并调整摄像头角度使平面铺满整个摄像头,然后将写好的C#文件挂载在平面上,就完成了整个游戏的设计。
完整工程见github地址:井字棋
3.思考题
- 微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”。
- 为什么是“模板方法”模式而不是“策略模式”呢?
模板方法模式:定义一个算法流程,将一些特定步骤的具体实现、延迟到子类。使得可以在不改变 算法流程的情况下,通过不同的子类、来实现“定制”流程中的特定的步骤。
策略模式:策略模式是使用委托改变整个算法,使不同的算法可以被相互替换,而不影响客户端的使用。
XNA的game基类,屏蔽了游戏循环的细节,给了用户一个编程模板,等待用户实现,而不是给出现成的算法,所以是模板方法模式而不是策略模式。
- 为什么是“模板方法”模式而不是“策略模式”呢?
- 将游戏对象组成树型结构,每个节点都是游戏对象(或数)。
- 尝试解释组合模式(Composite Pattern / 一种设计模式)。
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。 - 使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?
- 尝试解释组合模式(Composite Pattern / 一种设计模式)。
BroadcastMessage(child,message){
Send message to child;
BroadcastMessage(child->left,message);
BroadcastMessage(child->right,messge);
}
- 一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject添加一组行为部件(Component)。
- 这是什么设计模式?
装饰器模式。 - 为什么不用继承设计特殊的游戏对象?
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。而继承则限制了系统的灵活性,增强了耦合,因为继承的子类必须拥有父类的所有方法和属性。
- 这是什么设计模式?