ch02离散仿真引擎基础——Unity3D学习

ch02离散仿真引擎基础——Unity3D学习


一、简答题


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

  • 游戏对象(GameObjects):一般为玩家,敌人,环境等
  • 资源(Assets):一般包括声音,脚本,材质等
  • 区别与联系:
    • 对象一般是一些资源的集合体
    • 资源可以被多个对象使用
    • 资源作为模版,可实例化游戏中具体的对象。


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

  • 资源目录下有艺术材料(动画、图像)、音频、预设、场景、脚本、地形等。
  • 游戏对象有摄像机,场景,光源,起始位置等。

资源目录
资源来自:Creator Kit: FPS 工作细胞游戏
https://assetstore.unity.com/packages/templates/tutorials/creator-kit-fps-149310



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

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

public class NewBehaviourScript : MonoBehaviour {

	void Awake () {
		Debug.Log ("onAwake");
	}
	// Use this for initialization
	void Start () {
		Debug.Log("onStart");
	}
	
	// Update is called once per frame
	void Update () {
		Debug.Log("onUpdate");
	}

	void FixedUpdate(){
		Debug.Log ("onFixedUpdate");
	}

	void LateUpdate(){
		Debug.Log ("onLateUpdate");
	}

	void OnGUI(){
		Debug.Log ("onGUI");
	}

	void OnDisable(){
		Debug.Log ("onDisable");
	}

	void OnEnable(){
		Debug.Log ("onEnable");
	}
}
MonoBehaviour
基本行为
【按执行顺序】
触发条件
Awake() 当前控制脚本实例被装载的时候调用。一般用于初始化整个事例使用。(此刻物体还未实例化出来,会影响程序的执行顺序)
Start() 当前控制脚本第一次执行Update之前调用。
Update() 每帧都执行一次,是最常用的时间函数。
FixedUpdate() 每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率底下的时候FixedUpdate的调用次数就会下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关,而Update比较适合做控制。(放置游戏基本物理行为的代码,在Update之后执行)
LateUpdate() 在每帧执行完毕调用,他在所有Update结束后才调用,比较适合于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有Update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现
OnEnable() 当对象变为可用或激活状态时此函数被调用,OnEnable不能用于协同程序。(物体启动时被调用)
OnDisable() 当对象变为不可用或非激活状态时此函数被调用。当物体销毁时它被调用,并且可用于任意清理代码。当脚本编译完成之后被重新加载时,OnDisable将被调用,OnEnable在脚本被载入后调用。(物体被禁用时调用)
OnGUI() 绘制GUI时候触发。一般在这个函数里绘制GUI菜单。(GUI显示函数只能在OnGUI中调用)


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

4.1 分别翻译官方对三个对象的描述(Description)
  • GameObject :游戏对象是Unity中表示游戏角色,游戏道具和游戏场景的基本对象。它们自身无法完成许多功能,但是它们充当了那些给予他们实体功能的组件的容器。
  • Transformer:转换组件决定了游戏场景中每个游戏对象的位置,旋转度和大小。每个游戏对象都有转换组件。
  • Component:组件是游戏中对象和行为的细节。它是每个游戏对象的功能部分。
4.2 描述下图中 table 对象(实体)的属性、table 的 Transform 的属性、 table 的部件

table截图

  • Table对象的属性
    • activeInHierarchy:表示GameObject是否在场景中处于active状态
    • activeSelf:GameObject的本地活动状态
    • isStatic:仅编辑器API,指定游戏对象是否为静态
    • layer:游戏对象所在的图层。图层的范围为[0 … 31]
    • scene:游戏对象所属的场景
    • tag:游戏对象的标签
    • transform:附加到这个GameObject的转换

  • Table的Transform属性:Position、Rotation、Scale
  • Table的部件:Mesh Filter、Box Collider、Mesh Renderer
4.3 用 UML 图描述 三者的关系(请使用 UMLet 14.1.1 stand-alone版本出图)

UML图



6.资源预设(Prefabs)与 对象克隆 (clone)

6.1.预设(Prefabs)有什么好处?

预设类似于一个模板,通过预设可以创建相同属性的对象,这些对象和预设关联。一旦预设发生改变,所有通过预设实例化的对象都会产生相应的变化(适合批量处理)。

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

预设的所有实例化对象跟随预设变化,而克隆对象和对象本体不相关

6.3.制作 table 预制,写一段代码将 table 预制资源实例化成游戏对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class shilihua : MonoBehaviour {
	public GameObject MyPrefabs; 
 
	// Use this for initialization
	void Start () {
		GameObject instance = (GameObject)Instantiate(MyPrefabs, transform.position, transform.rotation);
	}
 
	// Update is called once per frame
	void Update () {
 
	}
}



二、编程题


井字棋小游戏

主要是掌握GUI的一些API用法 (人话:掌握可视化图形用户界面的接口使用)

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

public class GUITest : MonoBehaviour
{
    //玩家
    private int player;
    private int winner;
    //棋盘格
    private int[,] chessBox = new int[3, 3];
    //局数
    private int count;

    private void Start()
    {
        StartGame();
    }

    private void StartGame()
    {
        //初始化先手玩家
        player = 1;
        //赢家初始化为0
        winner = 0;
        //初始化局数
        count = 0;
        //初始化棋盘格
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                chessBox[i, j] = 0;
            }
        }
    }

    void OnGUI()
    {
        //初始化背景棋格,必须要有后面的“”
        GUI.Box(new Rect(250, 50, 300, 300),"");
        //初始化九个格子
        if (!EndGame())
        {
            for(int i = 0; i < 3; i++)
            {
                for(int j = 0; j < 3; j++)
                {
                    //button放在里面反应响应事件
                    if (chessBox[i, j] == 0 && GUI.Button(new Rect(295 + 70 * i, 95 + 70 * j, 70, 70), ""))
                    {
                        PutChess(i,j);
                    }
                    else if(chessBox[i,j]==1) GUI.Button(new Rect(295 + 70 * i, 95 + 70 * j, 70, 70), "X");
                    else if(chessBox[i,j]==2) GUI.Button(new Rect(295 + 70 * i, 95 + 70 * j, 70, 70), "O");
                }
            }
        }
        else
        {
            if (winner!=0)
            {
                GUI.Label(new Rect(320, 150, 160, 100), "            玩家 " + winner + " 获胜!"); 
            }
            else
            {
                GUI.Label(new Rect(320, 150, 160, 100), "                平局!");
            }
            //重新开始
            if (GUI.Button(new Rect(350, 200, 100, 50), "重新开始"))
            {
                StartGame();
            }
            ///退出游戏
            if (GUI.Button(new Rect(350, 250, 100, 50), "退出游戏"))
            {
                ExitGame();
            }
        }
    }
    private void PutChess(int i,int j)
    {
        chessBox[i, j] = player;
        player = 3 - player;
        count++;
    }

    private bool EndGame()
    {
 
        //判断横行
        for(int i = 0; i < 3; i++)
        {
            if(chessBox[i,0] == chessBox[i,1] && chessBox[i, 1] == chessBox[i, 2])winner = chessBox[i, 1];
        }
        //判断竖行
        for (int i = 0; i < 3; i++)
        {
            if (chessBox[1, i] == chessBox[2, i] && chessBox[2, i] == chessBox[0, i])winner = chessBox[1, i];
        }
        //判断斜方向
        if (chessBox[1, 1] == chessBox[2, 2] && chessBox[0, 0] == chessBox[2, 2]) winner = chessBox[1, 1];
        if (chessBox[0, 2] == chessBox[1, 1] && chessBox[2, 0] == chessBox[1, 1]) winner = chessBox[1, 1];

        if (count < 9 && winner == 0) return false;
        return true;
    }

    public void ExitGame()
    {
        //退出游戏
        #if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
        #else
        Application.Quit();
        #endif
    }
}

实现效果图如下:

解释 图片
局中 在这里插入图片描述
玩家获胜 在这里插入图片描述
平局 在这里插入图片描述

题外话:笔者尝试为这个简单的井字棋加入动图,查阅资料后发现:
在这里插入图片描述
MovieTexture已经不支持,需要用VideoPlayer
考虑到小游戏的核心并不在此,没必要把一个简单的小游戏做得更加复杂,不过以后有需要的话还是会去查一下是否可行
一下是手动制作的动画

手写X特效

手写O特效



三、思考题

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

  • 模板方法定义了一个过程或算法中的核心步骤,而对于子步骤或是子方法则是使用继承或接口在子类中实现,从而使得可以通过不改变整体而对一些子步骤进行重构;
  • 策略模式则是指对象具备某个行为,但是在不同的场景中,该行为有不同的实现算法。
  • 对于微软的XNA引擎,其屏蔽了游戏循环的细节,并使用一组虚方法由继承者完成,目的就是使得可以在不改变代码基本结构的情况下将一些具体模块交由继承者具体实现,这明显更符合“模板方法”,而非“策略模式”。

2、将游戏对象组成树型结构,每个节点都是游戏对象(或数)。

2.1尝试解释组合模式(Composite Pattern / 一种设计模式)。
  • 将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
  • 组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口,这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
2.2使用 BroadcastMessage() 方法向子对象发送消息

父对象方法:

public class NewBehaviourScript : MonoBehaviour {
    void message() {
        Debug.Log("HelloWorld!");
    }
    void Start () {
        this.BroadcastMessage("message");
    }
}

子对象方法

public class NewBehaviourScript1 : MonoBehaviour {
    void message() {
        Debug.Log("HelloWorld!");
    }
}


3、一个游戏对象用许多部件描述不同方面的特征。我们设计坦克(Tank)游戏对象不是继承于GameObject对象,而是 GameObject 添加一组行为部件(Component)。

3.1这是什么设计模式?

Decorator模式。

3.2为什么不用继承设计特殊的游戏对象?

Decorator模式允许向一个现有的对象添加新的功能,同时又不改变其结构。与Decorator模式相比,使用多重继承容易使代码变得脆弱且不易理解,并且使得耦合变得很高。因此,我们应该优先使用模式,而非继承,从而能够更加灵活地给对象添加额外的职责,并更加清晰的划分各个代码模块。



参考:

【1】https://blog.csdn.net/Alva112358/article/details/79675793
【2】https://cloud.tencent.com/developer/article/2028350

猜你喜欢

转载自blog.csdn.net/yesor_not/article/details/126982036
今日推荐