3D游戏编程与设计作业02

  • 简答题
  • 编程实践:井字棋小游戏
  • 思考题
  • 参考博客

简答题

  1. 解释 游戏对象(GameObjects) 和 资源(Assets)的区别与联系
  • 区别:
    游戏对象(GameObjects):相当于一个容器,可以容纳组件。它们本身不做任何事情,需要特殊属性(special properties)才能成为一个角色、一种环境或者一种特殊效果
    资源(Assets):在项目中可以导入使用的文件,包括图像、视频、脚本文件、预制文件等
  • 联系:
    游戏对象可以通过资源保存起来
    资源可以用来创建对象实例
    一个资源可以创建多个对象
  1. 下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)

以下下载了三个游戏案例:Fantasy Forest Environment Free Sample、race-trace-lake以及unity-chan!

总结——资源的目录组织结构:

  • Assets:主文件夹,包含所有工程需要用到的资源

  • Editor:所有在Editor和它的子文件夹的脚本,都不会作为运行期脚本被编译,而是作为动态添加Unity编译器功能的脚本来编译,在该文件夹和其子文件夹的脚本不能被添加到GameObject上

  • Materials:材料

  • Models:模型

  • Prefabs:预设

  • Scenes:场景

  • Textures:纹理

  • Scripts:C#脚本

    游戏对象树的层次结构的组织则主要是一个继承或是组合/聚合的关系

  1. 编写一个代码,使用 debug 语句来验证MonoBehaviour基本行为或事件触发的条件
  • 基本行为包括Awake()Start()Update()FixedUpdate()LateUpdate()
  • 常用事件包括OnGUI()OnDisable()OnEnable()

代码如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{private void Awake(){Debug.Log("awake\n");}// Start is called before the first frame updatevoid Start(){Debug.Log("start\n");}// Update is called once per framevoid Update(){Debug.Log("update\n");}private void FixedUpdate(){Debug.Log("fixed-update\n");}private void OnEnable(){Debug.Log("on-enbale\n");}private void OnDisable(){Debug.Log("on-disable\n");}private void OnGUI(){Debug.Log("on-GUI\n");}
}

运行结果如下:



事实上,查找脚本手册可知,MonoBehavior基本行为或事件触发的条件(按执行顺序):

  • MonoBehaviour.Awake:唤醒,当一个脚本实例被载入时Awake被调用,用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次。Awake不能用作协同程序

  • MonoBehaviour.Start:开始,仅在Update函数第一次被调用前调用,在behavior的生命周期里只被调用一次。它和Awake不同的是:Start指在脚本实例被启用时调用。在所有脚本实例中,Start函数总是在Awake函数之后调用

  • MonoBehaviour.Update:更新,当MonoBehaviour启用时,其Update在每一帧被调用。Update是实现各种游戏行为最常用的函数

  • MonoBehaviour.FixedUpdate:固定更新,当MonoBehaviour启用时,其FixedUpdate在每一帧被调用。处理Rigidbody时,需要用FixedUpdate代替Update

  • MonoBehaviour.LateUpdate:晚于更新,当Behaviour启用时,其LateUpdate在每一帧被调用。LateUpdate时在所有Update函数调用后被调用。这可用于调整脚本执行顺序

  • MonoBehaviour.OnEnable:当可用,当对象变为可用或激活状态时此函数被调用。OnEnable不能用于协同程序

  • MonoBehaviour.OnDisable:当不可用,当对象变为不可用或非激活状态时此函数被调用。当物体被销毁时它将被调用,并且可用于任意清理代码。OnDisable不能用于协同程序

  • MonoBehaviour.OnGUI:当界面,渲染和处理GUI事件时调用。如果MonoBehaviour的enabled属性设为falseOnGUI()将不会被调用

    P.S.
    协同程序:在主程序运行时同时开启另一段逻辑处理,来协同当前程序的执行(类似开启了一个线程,但两者不同)。在Unity3D中,使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序,也就是说该方法必须在MonoBehaviour或继承于MonoBehavior的类中调用。另外,可以使用StopCoroutine(string methodName)来终止一个线程和StopAllCoroutines()来终止所有可以终止的协同程序,不过这两个方法都只能终止该MonoBehaviour中的协同程序。更多详细内容可自行查询脚本手册。

  1. 查找脚本手册,了解GameObjectTransformComponent对象
  • 分别翻译官方对三个对象的描述(Description)
    GameObject:Unity场景中所有实体的基类。注意:GameObject类中的许多变量都被移除了。对于C#语言,要想访问这些变量,例如GameObject.renderer,需要使用GetComponent<Renderer>()
    Transform:一个对象的位置、旋转角度和大小。一个场景中的每一个大小都由一个Transform组件。它用来存储和控制物体的位置、旋转角度和大小。每一个Transform组件都有一个父Transform组件,这便允许我们分层地应用位置、旋转角度和大小。而这也正是层次结构面板中显示的层次结构。它们还支持枚举,例如:
    ```
    using UnityEngine;

      public class Example : MonoBehaviour {// Moves all tranform children 10 units upwardsvoid Start() {foreach (Transform child in transform) {child.positioin += Vector3.up * 10.0f;}}}````Component`:附加到GameObjects上的所有内容的基类。需要注意的是,你的代码不会直接创建一个Component,你需要将写好的脚本代码附到一个GameObject上
    
  • 描述下图中table对象(实体)的属性、tableTransform属性、table的部件

    - table对象(实体)的属性:
    - activeSelf = true(已勾选)
    - isStatic = false(未勾选)
    - layer = Default
    - scene = hello_start
    - tag = Untagged
    - Transform属性:Position = (0,0,0),Rotation = (0,0,0),Scale = (1,1,1)
    - table的部件有:TransformMeshFilterBoxColliderMeshRenderer

  • 用UML图描述三者的关系

  1. 整理相关学习资料,编写简单代码验证以下技术的实现
  • 查找对象:

    • 通过对象名称:public static GameObject Find(string name),通过名字寻找对象并返回它,只返回active GameObject,如果没有GameObject,则返回null。如果名称内包含“/”字符,会当做是hierarchy中的一个路径名
    • 通过标签获取单个游戏对象:public static GameObject FindWithTag(string tag)),返回一个用tag做标识的活动的对象,如果没有找到则为null
    • 通过标签获取多个游戏对象:public static GameObject[] FindGameObjectsWithTag(string tag),返回一个用对象标记的标签,如果没有找到对象则返回空数组
    • 通过类型获取单个游戏对象,返回类型为type的活动的第一个游戏对象
    • 通过类型获取多个游戏对象,返回类型为type的所有活动的游戏对象列表
  • 添加子对象:public static GameObject CreatePrimitive(PrimitiveType type),创建一个游戏对象与原始网格渲染器和适当的collider
  • 遍历对象树:
foreach (Transform child in transform) {Debug.Log(child.gameObject.name);
}
  • 清除所有子对象:
foreach (Transform child in transform) {Destroy(child.gameObject);
}
  1. 资源预设(Prefabs)与 对象克隆 (clone)
  • 预设(Prefabs)有什么好处?

预设是一个容易复用的类模板,可以迅速方便创建大量相同属性的对象、操作简单,代码量少,减少出错概率。修改的复杂度降低,一旦需要修改所有相同属性的对象,只需要修改预设即可,所有通过预设实例化的对象都会做出相应变化。

  • 预设与对象克隆(clone or copy or Instantiate of Unity Object)关系?
    两者都可用于批量产生对象,但是对象克隆不受克隆本体的影响,因此A对象克隆的对象B不会因为A的改变而相应改变。
  • 制作table预制,写一段代码将table预制资源实例化成游戏对象
    先按课堂上演示的方法制作一个table对象(其含有四个子对象:chair1、chair2、chair3、chair4),如下图所示:

    将table拖入Assets面板中,将自动生成table的预制

    将table游戏对象删除,接下来利用脚本实例化成游戏对象,方法是新建一个脚本例如命名为Instantiate,在Instantiate类中设置数据域public GameObject myObject,在可视化窗口,将table预制拖到该数据域上进行赋值,如图所示

编程实践:井字棋小游戏

技术限制:仅允许使用IMGUI构建UI
游戏设计元素:

  • 玩家:Player1(三笠 · 阿克曼)与Player2(艾伦 · 叶卡)
  • 游戏目标:在棋盘上成功地将自己的头像图片连续按行或按列或按对角线放置三个,则胜出
  • 规则:
    1. 每局Player1(三笠 · 阿克曼)先下(因为设计者比较喜欢三笠)
    2. 玩家轮流通过点击棋盘上未放置头像图片的空位放置自己的头像图片
    3. 每轮玩家只能点击一个空位,即只能放置一次头像图片
  • 挑战预想:玩家选择级别(棋盘大小和胜出所需的连续头像图片个数)

关键算法

  • 判断胜利:int check()函数
 int check(){//一行连续三个为同一种棋子,则该种棋子胜利for (int i = 0; i < dimension; i ++){for (int j = 0; j <= dimension-3; j ++){if (map[i, j] == map[i, j + 1] && map[i, j] == map[i, j + 2] && map[i,j] != 0){return map[i, j];}}}//一列连续三个为同一种棋子,则该种棋子胜利for (int i = 0; i < dimension; i ++){for (int j = 0; j <= dimension-3; j ++){if (map[j, i] == map[j+1, i] && map[j+2, i] == map[j, i] && map[j,i] != 0)return map[j, i];}}//对角线(left-to-right)连续三个为同一种棋子,则该种棋子胜利for (int i = 0; i <= dimension-3; i ++){for (int j = 0; j <= dimension-3; j ++){if (map[i, j] == map[i + 1, j + 1] && map[i, j] == map[i + 2, j + 2] && map[i,j] != 0) return map[i, j];}}//对角线(right-to-left)连续三个为同一种棋子,则该种棋子胜利for (int i = 2; i < dimension; i ++){for (int j = 0; j <= dimension-3; j ++){if (map[i, j] == map[i - 1, j + 1] && map[i, j] == map[i - 2, j + 2] && map[i, j] != 0) return map[i, j];}}return 0;}
  • 放置头像图片(棋子):在void onGUI()中实现
if (GUI.Button(new Rect((float)(Screen.width/2.0-restartWidth/2.0), (float)(originY+dimension*buttonHeight), restartWidth, restartHeight), "<color=#ADD8E6>Restart</color>"))reset();int result = check();if (result == 1)GUI.Label(new Rect((float)(Screen.width / 2.0 - winboxWidth / 2.0), (float)(originY -winboxHeight), winboxWidth, winboxHeight), "<color=#FFA500><size=30>Player 1 Wins!</size></color>");else if (result == 2)GUI.Label(new Rect((float)(Screen.width / 2.0 - winboxWidth / 2.0), (float)(originY -winboxHeight), winboxWidth, winboxHeight), "<color=#FFA500><size=30>Player 2 Wins!</size></color>");for (int i = 0; i < dimension; i ++){for (int j = 0; j < dimension; j ++){float positionX = originX + buttonWidth * i;float positionY = originY + buttonHeight * j;if (map[i, j] == 1)GUI.Button(new Rect(positionX, positionY, buttonWidth, buttonHeight), img1);else if (map[i, j] == 2)GUI.Button(new Rect(positionX, positionY, buttonWidth, buttonHeight), img2);else{if (GUI.Button(new Rect(positionX, positionY, buttonWidth, buttonHeight), "")) {if (result == 0){if (player == 1){map[i, j] = 1;player = 2;}else{map[i, j] = 2;player = 1;}}}}}}

示例图:

游戏效果演示视频
代码传送门

思考题

  1. 微软 XNA 引擎的 Game 对象屏蔽了游戏循环的细节;;,并使用一组虚方法让继承者完成它们,我们称这种设计为“模板方法模式”,为什么是“模板方法”模式而不是“策略模式”呢?

要弄清这个问题,首先我们先来看看两者的介绍:

模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

然后,值得一提的是微软 XNA 引擎所称的Game“对象”实际上是“类”。正如题干所描述的“Game 对象屏蔽了游戏循环的细节,并使用一组虚方法让继承者完成它们”,Game是一个抽象类,它声明了一些抽象方法(虚方法)来迫使子类实现剩余的逻辑,此外,Game中可能还有某些子类共有的属性。而策略模式用于封装不同算法的是“接口”,这个“接口”类中往往不含属性。

  1. 将游戏对象组成树型结构,每个节点都是游戏对象(或树)
  • 尝试解释组合模式(Composite Pattern / 一种设计模式)

组合模式允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。

组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
叶子(Leaf):在组合中表示子节点对象,叶子节点不能有子节点。
合成部件(Composite):定义有枝节点的行为,用来存储部件,实现在Component接口中的有关操作,如增加(Add)和删除(Remove)。
  • 使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?
foreach childObjectsendMessage();
  1. 一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)
  • 这是什么设计模式?
    策略(Strategy)模式
  • 为什么不用继承设计特殊的游戏对象?
    采用继承的方式进行设计,将使代码耦合性高而内聚性低。在游戏调试过程中,往往需要频繁变动组件(Component),如果使用继承设计,将使得调试变难(因为很可能会产生涟漪效应)。

参考博客

[1] https://www.cnblogs.com/java-my-life/archive/2012/05/14/2495235.html?tdsourcetag=s_pctim_aiomsg
[2] https://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html?tdsourcetag=s_pctim_aiomsg
[3] https://www.cnblogs.com/snaildev/p/7647190.html

3D游戏编程与设计作业02相关推荐

  1. 3D游戏编程与设计作业10

    3D游戏编程与设计作业10 环境说明 Unity3D 导航与寻路 Agent 和 Navmesh 练习 Obstacle和Off-Mesh-Link练习 P&D 过河游戏智能帮助实现 状态图 ...

  2. 3D游戏编程与设计作业09

    3D游戏编程与设计作业09 UGUI基础 画布 基础概念 测试渲染模式 UI布局基础 基本概念 锚点练习 UI组件与元素 基本概念 Mask练习 动画练习 富文本测试 简单血条 血条(Health B ...

  3. 3D游戏编程与设计作业六

    改进飞碟(Hit UFO)游戏 要求 按 adapter模式 设计图修改飞碟游戏 使它同时支持物理运动与运动学(变换)运动 实现 原项目:3D编程与游戏设计作业五 仅仅对其中的一些类进行改动就能实现. ...

  4. 3D游戏编程与设计作业6-Unity实现打飞碟游戏改进版(Hit UFO)

    改进飞碟(Hit UFO)游戏 游戏内容要求 按adapter模式设计图修改飞碟游戏 使它同时支持物理运动与运动学(交换)运动 编程实践 本次作业直接在上一次打飞碟游戏的基础上增加adapter设计模 ...

  5. 3D游戏编程与设计作业4-Skybox_牧师与魔鬼进阶版

    基本操作演练 下载Fantasy Skybbox FREE,构建自己的游戏场景 直接在上一次作业的Priests and Devils游戏场景中添加天空盒和地形构建场景.首先在Unity Assets ...

  6. 3D游戏编程与设计作业四

    第四次3D编程作业 本次作业源代码链接:点击此处进行跳转 一. 基本操作演练 下载Fantasy Skybox Free,构建自己的游戏场景 场景总览图: 游玩截图: 写一个简单的总结,总结游戏对象的 ...

  7. 3D游戏编程与设计作业三

    第三次3D编程作业 本次作业源代码链接:点击此处进行跳转 1. 简答并用程序验证 游戏对象运动的本质是什么 游戏对象空间属性(坐标.旋转度.大小)的变化 请用三种以上方法,实现物体的抛物线运动 此处我 ...

  8. 3D游戏编程与设计作业2-太阳系-Priests and Devils

    简答题 一.游戏对象运动的本质是什么?   游戏对象的本质是游戏对象每一帧的由空间属性决定的坐标随时间发生改变,其中空间属性包括对象transform属性中的空间位置Position属性.旋转角度Ro ...

  9. 3D游戏编程与设计作业一

    游戏分类与热点探索 使用思维导图描述游戏的分类.(游戏分类方法特别多) 所使用的思维导图绘图工具为mindmaster. 结合手机游戏市场的下载量与排名等数据,结合游戏分类图,描述游戏市场的热点. 华 ...

  10. 3D游戏编程与设计作业4——使用skybox构建游戏场景

    步骤1: 首先下载支持使用Fantacy Skybox FREE 的Unity版本(2021.3) 步骤2:打开unity store, 搜索Fantacy Skybox FREE 并进行下载 步骤3 ...

最新文章

  1. Nodejs+Express学习二(Mongoose基础了解)
  2. linux c数字转字符串函数,Linux常用C函数—字符串转换篇
  3. MySQL数据库的常用操作
  4. Acronis True Image无法卸载或者卸载导致无法开机解决办法
  5. 【iHMI43 应用演示】之 modbus 协议(从机)通信演示
  6. python编程中的运算_Python编程中的四大运算法则
  7. 详解数字电视机顶盒的功能技术与应用3
  8. UvaLive7362 Fare(欧拉函数)
  9. 董事、执行董事、总裁、总经理
  10. JS面向对象之封装自定义构造函数
  11. img src请求后台值值能判断_图片src拼接后台返回ID
  12. mcem r语言代码_生态学数据处理常用R语言代码
  13. 机器学习笔记(二十三):算法精准率、召回率
  14. Cesium 鼠标单击和双击事件
  15. 西部数据硬盘不同色彩的含义
  16. 应用网易轻舟,德邦快递核心系统入选云原生应用十大优秀案例
  17. 【记录一次服务器被攻击】-[附带解决方案]
  18. twitter开源_30位开源社区经理将在Twitter上关注
  19. SpringBoot整合Apollo配置中心快速使用
  20. Windows日志查看工具分享

热门文章

  1. 顶部标题栏的布局设计
  2. 从程序员到项目经理(8):程序员加油站 -- 再牛也要合群
  3. python 运行报错 Process finished with exit code -1073740791 (0xC0000409)
  4. ARM实现LED灯亮灭
  5. springboot 集成 fastdfs
  6. 为出海掘金创造更多可能 助力开发者触达全球用户
  7. conan-transit服上的库列表
  8. VINS-MONO边缘化策略
  9. Django实现websocket聊天室
  10. linux安装360wifi 3驱动,CentOS-6.4使用360wifi无线上网配置方法