3Dgame_homework2

3D游戏 作业2

  • 简答题
  • 编程实践 小游戏
  • 思考题(选做)

参考网站:
Manual
中文参考
UML绘图工具

简答题

1. 解释游戏对象(GameObjects) 和 资源(Assets)的区别与联系。

游戏引擎是为人们制作电子游戏而设计的软件开发环境。

其中,游戏对象(GameObjects)指游戏程序空间中的事物,可能是 Empty(空,最有用的事物)、2D、3D、光线、摄像机等,直接出现在场景中,在游戏中能实际被使用。GameObject为Unity 场景中所有实体的基类,因其复杂性而一般不被继承,而是用一组部件来表达不同的方面的要求,即软件设计原则“组合优于继承”。

而游戏资源(Assets)是游戏中需要的资源,构造游戏对象、装饰游戏对象、配置游戏的物体和数据,即序列化的或存储格式的游戏对象或数据,通常有对象、材质、场景、声音等,在这些文件夹下还可以继续划分。

对象是资源进行整合、实例化后的表现,是资源的衍变,本质上还是资源;而资源可以被一个或多个对象使用,可以作为模板,可以实例化成具体的对象。

2. 下载几个游戏案例,分别总结资源、对象组织的结构(指资源的目录组织结构与游戏对象树的层次结构)。

(以nvlmaker引擎及其的工程文件为例,参考网站TheNVLMaker-使用手册)
3Dgame_homework2_第1张图片3Dgame_homework2_第2张图片
3Dgame_homework2_第3张图片
根据图片,可以看出资源主要有背景图片,各种对象的图片,音乐,地图、面板配置文件,界面配置文件和自制宏文件,系统文件,插件,剧情脚本文件与视频文件等,且对象组织的结构如图所示。

3. 编写一个代码,使用 debug 语句来验证 MonoBehaviour基本行为或事件触发的条件。
基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate();
常用事件包括 OnGUI() 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");
    }
 }

以从UnityHub上下载的小游戏为例,

3Dgame_homework2_第4张图片3Dgame_homework2_第5张图片
运行结果为:

3Dgame_homework2_第6张图片
3Dgame_homework2_第7张图片

4. 查找脚本手册,了解GameObject,Transform,Component 对象。

(1)分别翻译官方对三个对象的描述(Description)。

GameObject:Unity 场景中所有实体的基类;
Transform:用于存储和操作对象场景中对象的变换,即位置、旋转和缩放比例。每个变换都可以有一个父级,层次结构窗格中显示的层次结构允许按层次应用位置、旋转和缩放,同时也支持枚举器;
Component: 能附加到游戏对象的部件的基类。不会直接创建组件,而是编写脚本代码,并将脚本附加到游戏对象。Component 的各种子类为包括空间与变换部件 Transform、各种 渲染部件Reander ,脚本部件 MonoBehaviour 的子类等等。

(2)描述table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件。要求把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。例如:table 的对象是 GameObject,第一个选择框是 activeSelf 属性。

3Dgame_homework2_第8张图片
table 的对象是 GameObject,第一个选择框是 activeSelf 属性,然后是名字属性static属性(对象是否为静态),tag属性(对象的标签,用于查找对象),layer属性(对象所在的层),prefab属性,Transform属性(对象的转换);
table的Transform属性包括position位置,Rotation旋转和Scale缩放比例;
table的部件有Mesh Filter(网格过滤器,作为物体与Mesh的接口接入实例化的Mesh),Box Collider(盒碰撞器),Mesh Renderer(网格渲染器)。

(3)用 UML 图描述三者的关系。

参考图片:

3Dgame_homework2_第9张图片
三者关系的UML图:

3Dgame_homework2_第10张图片

5. 资源预设(Prefabs)与 对象克隆 (clone)
(1)预设(Prefabs)有什么好处?

预设,即预制(Prefabs),是文件存储的游戏对象与属性的组合,可一次性方便地加载到内存,相当于“半成品”。预设是一种资源类型,一种模板,可创建属性相同的多个对象,又可在一个场景中被多次使用。
我们如果每次都从基础游戏对象构建游戏的话,就需要很多代码,且不易于修改。但如果可以把基本的游戏对象组合起来,制作成预制,相互关联,更改时也会作用于与之相连的所有实例,即把预制当作一个游戏对象使用,就会方便、合理很多。
游戏是预制好的对象的组合,代码是粘合它们的胶水,控制作这些事物的变化。从面向对象设计角度,游戏对象在内存中树形组织结构,称为“组合模式(Composite Pattern)”,而预制就是这种组合结构中的一部分。

(2)预设与对象克隆 (clone or copy or Instantiate of Unity Object) 关系?

二者的行为是相似的,但预设与被预设对象相互关联,但克隆与被克隆对象在克隆成功相互独立存在,因此克隆对象不会受被克隆对象的影响,不会随着其改变而改变。

(3)制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象。

public class Test : MonoBehaviour {
    private string prePath = "prefabs/table";
    private GameObject MyTable;
    void Start () {
        MyTable = (GameObject)Instantiate(Resource.Load(prePath), new Vector(4, 0, 0),Quaternion.identity) ;
    }
}

编程实践 小游戏

游戏内容: 井字棋;
技术限制: 仅允许使用IMGUI构建 UI;
作业目的: 了解 OnGUI() 事件,提升 debug 能力;提升阅读 API 文档能力。

步骤:

  1. 创建一个unity2D项目,在其中创建一个对象:

3Dgame_homework2_第11张图片
3. 编写代码,并将代码加到对象上:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TicTacToe : MonoBehaviour{
    private int turn = 1;
    private int[,] grid = new int[3, 3];

    // 开始
    void Start(){
        Restart();
    }
    void Restart(){
        turn = 1;
        for (int i = 0; i < 3; ++i){
            for (int j = 0; j < 3; ++j){
                grid[i, j] = 0;
            }
        }
    }

    //胜利或平局判断
    int judge(){
        // 一列三个
        for (int i = 0; i < 3; ++i){
            if (grid[i, 0] != 0 && grid[i, 0] == grid[i, 1] && grid[i, 1] == grid[i, 2]){
                return grid[i, 0];
            }
        }
        // 一行三个 
        for (int j = 0; j < 3; ++j){
            if (grid[0, j] != 0 && grid[0, j] == grid[1, j] && grid[1, j] == grid[2, j]){
                return grid[0, j];
            }
        }
        // 斜着三个   
        if (grid[1, 1] != 0 && grid[0, 0] == grid[1, 1] && grid[1, 1] == grid[2, 2] || grid[0, 2] == grid[1, 1] && grid[1, 1] == grid[2, 0]){
            return grid[1, 1];
        }
        // 所有格都被点过了
        bool allClick = true;
        for (int i = 0; i < 3; i++){
            for (int j = 0; j < 3; j++){
                if (grid[i, j] == 0){
                    allClick = false;
                }
            }
        }
        if (allClick)
            return 3;
        return 0;
    }

    //设置UI外观
    void OnGUI(){
        //按钮
        GUI.skin.button.fontSize = 20;

        //GUIStyle
        GUIStyle style = new GUIStyle();
        style.fontSize = 40;
        style.normal.textColor = new Color(255, 255, 255);

        //重新开始游戏
        if (GUI.Button(new Rect(360, 330, 80, 40), "Restart")){
            Restart();
        }

        //游戏结果表现
        int result = judge();
        if (result == 1){
            GUI.Label(new Rect(340, 30, 100, 50), "O wins!", style);
        }
        else if (result == 2){
            GUI.Label(new Rect(340, 30, 100, 50), "X wins!", style);
        }
        else if (result == 3){
            GUI.Label(new Rect(340, 30, 100, 50), "DRAW!", style);
        }

        //棋盘
        for (int i = 0; i < 3; i++){
            for (int j = 0; j < 3; j++){
                if (grid[i, j] == 1){
                    GUI.Button(new Rect(280 + i * 80, 80 + j * 80, 80, 80), "O");
                }
                else if (grid[i, j] == 2){
                    GUI.Button(new Rect(280 + i * 80, 80 + j * 80, 80, 80), "X");
                }
                else if (GUI.Button(new Rect(280 + i * 80, 80 + j * 80, 80, 80), "") && result == 0){
                    if (turn == 1){
                        grid[i, j] = 1;
                    }
                    else{
                        grid[i, j] = 2;
                    }
                    turn = -turn;
                }
            }
        }
    }
}

3Dgame_homework2_第12张图片

  1. 运行成功:

3Dgame_homework2_第13张图片

  1. 游戏试玩:

3Dgame_homework2_第14张图片
3Dgame_homework2_第15张图片

思考题(选做)

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

模板方法模式定义了一个算法的骨架,将一些特定步骤的具体实现延迟到子类之中,使得子类可以不改变一个算法的流程结构而重定义该算法的某些特定步骤,是一种行为模式。
策略模式是定义了一系列算法,将每个不同的算法封装起来并可以被相互替换,而不影响客户端的使用,让算法独立于使用它的客户而变化,是一种对象行为模式。
微软的XNA引擎只是给出了虚方法,等待子类实现,而不是给出现成的算法,屏蔽了游戏循环的细节,并使用一组虚方法由继承者完成,目的就是使得可以在不改变代码基本结构的情况下将一些具体模块交由继承者具体实现,所以是模板方法模式而不是策略模式。

  1. 将游戏对象组成树型结构,每个节点都是游戏对象(或数)。尝试解释组合模式(Composite Pattern / 一种设计模式)。使用 BroadcastMessage() 方法,向子对象发送消息。你能写出 BroadcastMessage() 的伪代码吗?

组合模式(Composite Pattern),又称部分整体模式,是用于把一组相似的对象当作一个单一的对象,依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构,创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

  1. 一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)。这是什么设计模式?为什么不用继承设计特殊的游戏对象?

装饰(Decorator)模式。

装饰模式给一个对象增加一些额外的职责,是一种对象结构型模式,优点有提供了比继承更多的灵活性,可以动态地扩展一个对象的功能,且具体构件类和具体装饰类可以独立地变化。
而继承相比于装饰模式,限制了系统的灵活性,增强了耦合,因为继承是侵入式的,子类必须拥有父类的所有方法和属性。 如果使用继承的话,在编译的时候就得分配职责,缺乏灵活性,而且会产生子类,导致混杂。

你可能感兴趣的:(3Dgame_homework2)