Unity3D游戏编程——离散仿真作业
1.简答题asd
1.1解释游戏对象(GameObjects)和资源(Assets)的区别与联系
答:游戏对象(GameObjects)是一系列资源的组成序列。资源(Assets)是一系列可以被Unity采用的数据文件(如纹理,材质,音频等),只有有了相应的资源,在Unity项目中才能呈现出相应的效果(比如需要木质表面,项目文件中就必须有木质材质的资源文件),有了资源之后,我们便可以在Unity中利用已有的资源来构建我们各种各样的游戏对象了,游戏对象是游戏过程中的基本操作单位。
1.2下载几个游戏案例,分别总结资源,对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)
答:我在Github选取了游戏《天天萌泡泡》作为游戏案例来进行分析。它的游戏对象树和资源目录结构如下:
从游戏对象树我们可以看到,该游戏的游戏对象树的底层先分了几个大的GameObject来承担不同方面的工作,然后各个大GameObject又包含小GameObject来分管某方面的细节性工作。
从资源目录结构我们可以看出,游戏将资源分为动画,音频,字体,图片,插件等方面来分别存储。
1.3编写一个代码,使用debug语句来验证MonoBehaviour基本行为或事件触发的条件,基本行为包括Awake,Start,Update,FixedUpadate,LateUpdate,常用事件包括OnGUI,OnDisable,OnEnable。
答:
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstBeh : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log("This Start!");
}
void Awake()
{
Debug.Log("This Awake!");
}
void FixedUpdate()
{
Debug.Log("This FixedUpdate!");
}
void LateUpdate()
{
Debug.Log("This LateUpdate!");
}
void OnGUI()
{
Debug.Log("This OnGUI!");
}
void OnDisable()
{
Debug.Log("This OnDisable!");
}
// Update is called once per frame
void Update()
{
Debug.Log("This Update!");
}
void OnEnable()
{
Debug.Log("This Enabled!");
}
}
代码运行效果如下:
开始:
运行中:
结束
1.4查找脚本手册,了解GameObject,Transform,Component对象。
翻译官方对三个对象的描述:1.GmaeObject是所有Unity场景中的对象的基类。 2.Transform:是一个对象的位置,大小,角度等属性。每个对象都有Transform属性,Transform也有层次组织,以达到相对位置的效果。 3.Component:每个对象都有的基本类,它包括脚本等组件。
描述Table对象(实体)的属性,Table的Transform的属性,Table的组件:
我们可以看到Inspector中第一栏就是activeSelf属性,它的Transform属性在第二栏,包括了位置(0,0,0)
,旋转(0,0,0),大小(1,1,1),除了activeSelf都是Component,包括transform,mesh render等,还包括我自己加入的脚本。
UML图如下:
1.5整理相关学习资料,编写简单代码验证以下技术
查找对象:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SecnodBeh : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var ob = GameObject.Find("table");
if (ob != null)
{
Debug.Log("Success");
}
}
// Update is called once per frame
void Update()
{
}
}
添加子对象:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SecnodBeh : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject ob = GameObject.CreatePrimitive(PrimitiveType.Cube);
ob.name = "chair5";
ob.transform.position = new Vector3(0, 5, 5);
ob.transform.parent = this.transform;
}
// Update is called once per frame
void Update()
{
}
}
遍历对象树:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SecnodBeh : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log("begin");
foreach (Transform child in transform)
{
Debug.Log(child.name);
}
}
// Update is called once per frame
void Update()
{
}
}
清除所有子对象:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SecnodBeh : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
for(int i = 0; i < transform.childCount; i++)
{
Destroy(transform.GetChild(i).gameObject);
}
}
// Update is called once per frame
void Update()
{
}
}
1.6资源预设与对象克隆:
预设的好处:预设是指对GameObject的一种描述,它可以很方便的生成多个相同的对象,但他们实质上是同一份,有点类似于指针的感觉,预设能够方便我们重复性的创建某些功能形状都一致的对象。
预设与对象克隆的关系:他们的区别类似于C/C++中的深拷贝和浅拷贝,预设的修改会改变所有相关的对象,但是对象克隆产生的各个对象是独立的。
写一段代码将table预设资源实例化为游戏对象:
(GameObject)Instantiate(Resources.Load("table"));
使用这段代码的前提是要先将table预设进资源库中,此处参考了其它博客。
2.编程实践,小游戏
游戏内容:
井字棋
技术限制:
仅允许使用 IMGUI 构建 UI
作业目的:
了解 OnGUI() 事件,提升 debug 能力
提升阅读 API 文档能力
源代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoreCode : MonoBehaviour
{
private bool flag = false;
private int[, ] state = new int[3, 3];
private int turn = 1;
private int AI = 0;
GUIStyle white = new GUIStyle();
GUIStyle black = new GUIStyle();
// Start is called before the first frame update
void Start()
{
for(int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
state[i, j] = 0;
}
}
white.normal.background = null;
white.normal.textColor = new Color(1, 1, 1);
white.fontSize = 100;
black.normal.background = null;
black.normal.textColor = new Color(0, 0, 0);
black.fontSize = 100;
}
void Restart()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
state[i, j] = 0;
}
}
turn = 1;
}
int check()
{
for (int i = 0; i < 3; i++)
{
if (state[i, 0] == state[i, 1] && state[i, 1] == state[i, 2] && state[i, 2] != 0)
{
return state[i, 0];
}
}
for (int i = 0; i < 3; i++)
{
if (state[0, i] == state[1, i] && state[1, i] == state[2, i] && state[2, i] != 0)
{
return state[0, i];
}
}
if (state[0, 0] == state[1, 1] && state[1, 1] == state[2, 2] && state[1, 1] != 0)
return state[1, 1];
if (state[2, 0] == state[1, 1] && state[1, 1] == state[0, 2] && state[1, 1] != 0)
return state[1, 1];
return 0;
}
bool kill(int i,int j)
{
state[i, j] = turn;
if (check() != turn)
{
state[i, j] = 0;
return false;
}
else
{
state[i, j] = 0;
return true;
}
}
bool defend(int i,int j)
{
state[i, j] = 3 - turn;
if (check() == 3-turn)
{
state[i, j] = 0;
return true;
}
else
{
state[i, j] = 0;
return false;
}
}
bool full()
{
bool res = true;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
if (state[i, j] == 0)
res = false;
}
}
return res;
}
void OnGUI()
{
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
if (state[i, j] == 0)
{
if (GUI.Button(new Rect(i * 100, j * 100, 100, 100), ""))
{
if (check() != 0)
break;
state[i, j] = turn;
turn = 3 - turn;
if (AI == 1)
{
int done = 0;
for(int k = 0; k < 3; k++)
{
for(int p = 0; p < 3; p++)
{
if (state[k, p] == 0)
{
if (kill(k, p))
{
state[k, p] = turn;
done = 1;
}
else if (defend(k, p))
{
state[k, p] = turn;
done = 1;
}
}
if (done == 1)
break;
}
if (done == 1)
break;
}
if (done == 0)
{
for(int k = 0; k < 3; k++)
{
for(int p = 0; p < 3; p++)
{
if (state[k, p] == 0)
{
state[k, p] = turn;
done = 1;
break;
}
}
if (done == 1)
break;
}
}
turn = 3 - turn;
}
}
}
else if (state[i, j] == 1)
GUI.Button(new Rect(i * 100, j * 100, 100, 100), "O", white);
else if (state[i, j] == 2)
GUI.Button(new Rect(i * 100, j * 100, 100, 100), "X", black);
}
}
if(check()==1)
GUI.Label(new Rect(150, 300, 200, 200), "Player1 Win");
else if(check()==2)
GUI.Label(new Rect(150, 300, 200, 200), "Player2 Win");
if (GUI.Button(new Rect(350, 200, 100, 100), "Restart"))
Restart();
if (AI == 0)
{
if (GUI.Button(new Rect(450, 200, 100, 100), "AI ON"))
{
AI = 1;
}
}
else
{
if (GUI.Button(new Rect(450, 200, 100, 100), "AI OFF"))
{
AI = 0;
}
}
if (full())
{
GUI.Label(new Rect(150, 300, 200, 200), "Tied");
}
}
// Update is called once per frame
void Update()
{
}
}
实现效果:
思路解析:
利用IMGUI来搭建简单的界面,参考了官方文档和相关博客。游戏思路是设置一个turn变量来记录当前是谁的回合(白圈是玩家一,黑叉是玩家二),如果点击在空格上则产生相应的相应图案,每帧调用检测函数,暴力检测三纵行,三横行和两对角线是否存在三色相同图案,若存在则产生游戏结果,并停止游戏,Restart用来重置游戏。
后面我又增加了AI功能,我的AI的运行逻辑是先攻击再防御,先判断是否能绝杀,若可以则直接下子,否则判断是否下一步会被对方绝杀,若会则下子防守,否则随机选取一块进行下子。井字棋的规则十分简单,我这种AI逻辑能做到把把平局,效果良好。
思考题
1.微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”。为什么是“模板方法”模式而不是“策略模式”呢?
首先模板方法是给定一个基类模板,然后由程序员去以该模板实现各个具体类,而策略模式则是给定一系列策略,类似于 if else分支,由用户决定运行过程中的处理方式。XNA屏蔽了游戏循环的细节,用一组虚方法去让继承者完成他们,这说明微软XNA是在一个基类模板上进行子类的开发的,而不是开发出各种各样的类然后由用户进行选择。
2.将游戏对象组成树型结构,每个节点都是游戏对象(或数)。尝试解释组合模式(Composite Pattern / 一种设计模式)。使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?
BroadcastMessage(k):
For any child i of k:
Inform(i);
BroadcastMessage(i);
3.一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)。这是什么设计模式?为什么不用继承设计特殊的游戏对象?
这是装饰器模式。继承会造成高耦合,导致代码的复杂度上升,而采用组件可以降低耦合,增强内聚。