2019-09-08

离散仿真引擎基础

本文总阅读量

一.简答题

1. 解释对象与资源的区别与联系

  • 对象:对象直接出现在游戏场景中,例如游戏的角色,道具和环境,对象一般有玩家,场景等虚拟父类,这些父类没有实例化,而他们的子类实例化并包含了这些游戏对象,它们本身并没有完成多少工作,但是它们充当组件的容器,添加不同的组件组合到游戏对象中。组件实现真正的功能。我们可以对这些对象进行操作。
  • 资源:资源可以是我们自定义或下载下来的素材,作为一个模板被导入使用,可以被多个对象所使用或者被实例化为对象。如材质,模型,动画,音频等。还有一些资源类型可以在Unity中创建,比如动画控制器、音频混频器或渲染纹理。
  • 游戏对象可以理解为多个资源合并起来的表现,而资源被对象使用也可以实例化,可以理解为可扩展的模板包。

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

  • 官方案例中选择了两个:”Space Shooter tutorial LEGACY” 和 “Unity Playground”
  • 游戏对象树类类似于多个父子继承关系,一个游戏对象往往包括几个子对象
  • 资源的目录组织结构基本包括Audio,Prefabs,texture,Scripts,Sounds等

3. 编写一个代码,使用 debug 语句来验证 MonoBehaviour 基本行为或事件触发的条件

  • 基本行为包括 Awake() Start() Update() FixedUpdate() LateUpdate()

  • 常用事件包括OnGUI() OnDisable() OnEnable()

  • 可参见官方对于脚本生命周期的说明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class NewBehaviourScript : MonoBehaviour
    {
    // 当前控制脚本实例被装载的时候调用。一般用于初始化整个实例使用
    void Awake()
    {
    Debug.Log("onAwake");
    }
    // Start is called before the first frame update.当前脚本执行update之前执行
    void Start()
    {
    Debug.Log("Start");
    }

    // Update is called once per frame. 每一帧都执行
    void Update()
    {
    Debug.Log("Update");
    }
    ////每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率底下的时候FixedUpdate的调用次数就会下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关,而Update比较适合做控制。(放置游戏基本物理行为的代码,在Update之后执行)

    void FixedUpdate()
    {
    Debug.Log("FixedUpdate");
    }
    //每帧执行完毕调用,他在所有Update结束后才调用,比较适合于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有Update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现
    void LateUpdate()
    {
    Debug.Log("LateUpdate");
    }
    //类似OnUpdate 每一帧均执行,在绘制GUI是执行
    void OnGUI()
    {
    Debug.Log("OnGUI");
    }
    //当对象变为不可用或非激活状态时此函数被调用。当物体销毁时它被调用,并且可用于任意清理代码。当脚本编译完成之后被重新加载时,OnDisable将被调用,OnEnable在脚本被载入后调用。(物体被禁用时调用)
    void OnDisable()
    {
    Debug.Log("OnDisable");
    }
    // 对象变为可用或激活状态时此函数被调用,OnEnable不能用于协同程序
    void OnEnable()
    {
    Debug.Log("OnEnable");
    }
    }

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

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

    • GameObject are the fundamental objects in Unity that represent characters, props and scenery. They do not accomplish much in themselves but they act as containers for Components, which implement the real functionality.
      游戏对象是统一体中代表人物、道具和场景的基本对象。它们本身并没有完成多少工作,但是它们充当组件的容器,组件实现真正的功能。

    • The Transform component determines the Position, Rotation, and Scale of each object in the scene. Every GameObject has a Transform. Transform组件决定场景中每个对象的位置、旋转和比例。每个GameObject(游戏物体)都有一个变换。

    • Components are the nuts & bolts of objects and behaviors in a game. They are the functional pieces of every GameObject. 组件是游戏中对象和行为的螺母和螺栓。它们是每个游戏对象的功能部件。

  • 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件

    • 本题目要求是把可视化图形编程界面与 Unity API 对应起来,当你在 Inspector 面板上每一个内容,应该知道对应 API。
    • 对象属性:activeInHierarchy(表示GameObject是否在场景中处于active状态)、activeSelf(GameObject的本地活动状态)、isStatic(仅编辑器API,指定游戏对象是否为静态)、Tag、layer(游戏对象所在的图层。图层的范围为[0 … 31])、Prefab
    • Transform属性:position、rotation、scale
    • 部件:Mesh Filter、Box Collider、Mesh Renderer
  • 用 UML 图描述 三者的关系(请使用 UMLet 14.1.1 stand-alone版本出图)

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

    • 查找对象

      • 通过名字查找:public static GameObject Find(string name)
      • 通过标签名查找单个对象:public static GameObject FindWithTag(string tag)
      • 通过标签名查找多个对象:public static GameObject[] FindGameObjectsWithTag(string tag)
    • 添加子对象:public static GameObect CreatePrimitive(PrimitiveTypetype)

    • 遍历对象树

      1
      2
      3
      foreach (Transform child in transform) {
      Debug.Log(child.gameObject.name);
      }
    • 清除所有子对象

      1
      2
      3
      foreach (Transform child in transform) {
      Destroy(child.gameObject);
      }
  • 资源预设(Prefabs)与 对象克隆 (clone)

    • 预设(Prefabs)有什么好处?

      预设类似于一个模板,预设发生改变后,通过预设实例化的对象都会发生相应的改变,避免了重复改变,方便了对资源的重复使用

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

      • 克隆:克隆的对象是互相独立的,没有任何关联
      • 预设:预设出的实例化对象与预设对象是有关系的,修改预设对象会改变实例化对象,但修改实例化对象不会对原始的预设对象做出改变
    • 制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象

      1
      GameObject prefabTable = (GameObject)Instantiate(Resources.Load("table"));

二.编程实践,小游戏

1. 游戏内容:井字棋

井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。

2. 技术限制: 仅允许使用 IMGUI 构建 UI

  • 创建空的游戏对象GameObject,然后把脚本挂在在空对象上就可以了

  • IMGUI官方教程说明

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class GUITest : MonoBehaviour
    {
    private int[,] chessBoard = new int[3, 3];
    private int turn = 1;
    int count = 0;
    private void Reset()
    {
    count = 0;
    for(int i = 0; i < 3; i++)
    {
    for(int j = 0; j < 3; j++)
    {
    chessBoard[i, j] = 0;
    }
    }
    }
    private void Start()
    {
    Reset();
    }
    private void OnGUI()
    {

    GUIStyle Style = new GUIStyle();
    Style.normal.background = null;
    Style.normal.textColor = new Color(1, 0, 0);
    Style.fontSize = 15;

    GUI.Label(new Rect(195, 10, 200, 100), "Welcome to Tic Tac Toe!", Style);
    GUI.Label(new Rect(20, 100, 200, 100), "Turn: ", Style);
    GUI.Label(new Rect(70, 120, 200, 100), "Player X", Style);
    GUI.Label(new Rect(70, 160, 200, 100), "Player O", Style);
    if (GUI.Button(new Rect(220, 300, 100, 50), "RESET"))
    Reset();
    int winner = check();
    if (winner == 1)
    {
    GUI.Label(new Rect(250, 260, 200, 100), "X wins!", Style);
    }
    if (winner == 2)
    {
    GUI.Label(new Rect(250, 260, 200, 100), "O wins!", Style);
    }
    else if(count==9) GUI.Label(new Rect(250, 260, 200, 100), "A draw!", Style);//不仅要没有人赢而且要下了九颗子才可以说平局
    if(turn ==0) GUI.Label(new Rect(160, 120, 200, 100), "*", Style);
    else GUI.Label(new Rect(160, 160, 200, 100), "*", Style);
    for (int i = 0; i < 3; i++)
    {
    for(int j = 0; j < 3; j++)
    {
    if (chessBoard[i, j] == 1)
    {

    GUI.Button(new Rect(195 + 50 * i, 100 + 50 * j, 50, 50), "X");
    }
    if (chessBoard[i, j] == 2)
    {

    GUI.Button(new Rect(195 + 50 * i, 100 + 50 * j, 50, 50), "O");
    }
    if (GUI.Button(new Rect(195 + 50 * i, 100 + 50 * j, 50, 50), ""))
    {
    count++;
    if (turn == 0) chessBoard[i, j] = 1;
    if (turn == 1) chessBoard[i, j] = 2;
    turn = 1 - turn;//是放在这里不是放在循环那里
    }

    }
    }
    }
    int check()
    {
    Debug.Log(count);
    for(int i = 0; i < 3; i++)
    {
    if (chessBoard[i, 0] != 0 && chessBoard[i, 1] == chessBoard[i, 0] && chessBoard[i, 2] == chessBoard[i, 0])
    {
    return chessBoard[i, 0];
    }
    }
    for (int i = 0; i < 3; i++)
    {
    if (chessBoard[0, i] != 0 && chessBoard[1,i] == chessBoard[0,i] && chessBoard[2,i] == chessBoard[0,i])
    {
    return chessBoard[0,i];
    }
    }
    if ((chessBoard[1, 1] != 0 && chessBoard[0, 0] == chessBoard[1, 1] && chessBoard[2, 2] == chessBoard[1, 1]) || (chessBoard[1, 1] != 0 && chessBoard[2, 0] == chessBoard[1, 1] && chessBoard[0, 2] == chessBoard[1, 1]))
    return chessBoard[1, 1];
    return 0;
    }
    private void Update()
    {

    }
    }
  • 完成效果
    动图太大了