3D Computer Game Programming-Note 3

1、简答并用程序验证

  • 游戏对象运动的本质是什么?

  游戏对象运动的本质是游戏对象的空间属性的变化,通过矩阵变换(平移、旋转、缩放)实现

  • 请用三种方法以上方法,实现物体的抛物线运动。

  方法一:直接修改物体的 Transform 的属性 position

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Sports1 : MonoBehaviour {
  5. private float Xspeed = 2.0f;
  6. private float Yspeed = 0;
  7. private float gravity = 9.8f;
  8. // Start is called before the first frame update
  9. void Start() {
  10. }
  11. // Update is called once per frame
  12. void Update() {
  13. this.transform.position += Xspeed * Vector3.left * Time.deltaTime;
  14. this.transform.position += Yspeed * Vector3.down * Time.deltaTime;
  15. Yspeed += gravity * Time.deltaTime;
  16. }
  17. }

  方法二:使用 Transform 的方法 Translate

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Sports2 : MonoBehaviour {
  5. private float Xspeed = 2.0f;
  6. private float Yspeed = 0;
  7. private float gravity = 9.8f;
  8. // Start is called before the first frame update
  9. void Start() {
  10. }
  11. // Update is called once per frame
  12. void Update() {
  13. this.transform.Translate(Xspeed * Time.deltaTime, 0, 0);
  14. this.transform.Translate(0, -Yspeed * Time.deltaTime, 0, Space.World);
  15. Yspeed += gravity * Time.deltaTime;
  16. }
  17. }

  方法三:使用 Vector3 的方法构造向量。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Sports3 : MonoBehaviour {
  5. private float Xspeed = 2.0f;
  6. private float Yspeed = 0;
  7. private float gravity = 9.8f;
  8. // Start is called before the first frame update
  9. void Start() {
  10. }
  11. // Update is called once per frame
  12. void Update() {
  13. Vector3 myVector = new Vector3(Xspeed * Time.deltaTime, -Yspeed * Time.deltaTime, 0);
  14. this.transform.position += myVector;
  15. Yspeed += gravity * Time.deltaTime;
  16. }
  17. }
  • 写一个程序,实现一个完整的太阳系,其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

    • 在编写脚本前先创建所有天体对应的游戏对象,使八大行星成为太阳的子对象:
      Unity-空间与运动 - 图1

    • 编写脚本 SolarSystem.cs,使用 RotateAround;使用Rotate实现天体自转;参数参考真实宇宙大致给出:
      ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine;

public class SolarSystem : MonoBehaviour { public Transform Sun; public Transform Mercury; public Transform Venus; public Transform Earth; public Transform Mars; public Transform Jupiter; public Transform Saturn; public Transform Uranus; public Transform Neptune;

  1. // Start is called before the first frame update
  2. void Start() {
  3. //初始化天体的位置
  4. Sun.position = Vector3.zero;
  5. Mercury.position = new Vector3(0.9f, 0, 0);
  6. Venus.position = new Vector3(1.5f, 0, 0);
  7. Earth.position = new Vector3(2.2f, 0, 0);
  8. Mars.position = new Vector3(3.0f, 0, 0);
  9. Jupiter.position = new Vector3(4.0f, 0, 0);
  10. Saturn.position = new Vector3(5.2f, 0, 0);
  11. Uranus.position = new Vector3(6.5f, 0, 0);
  12. Neptune.position = new Vector3(7.8f, 0, 0);
  13. //调整天体的大小
  14. Sun.localScale += new Vector3(0.5f, 0.5f, 0.5f);
  15. Mercury.localScale -= new Vector3(0.5f, 0.5f, 0.5f);
  16. Venus.localScale -= new Vector3(0.4f, 0.4f, 0.4f);
  17. Earth.localScale -= new Vector3(0.4f, 0.4f, 0.4f);
  18. Mars.localScale -= new Vector3(0.5f, 0.5f, 0.5f);
  19. Saturn.localScale -= new Vector3(0.05f, 0.05f, 0.05f);
  20. Uranus.localScale -= new Vector3(0.2f, 0.2f, 0.2f);
  21. Neptune.localScale -= new Vector3(0.2f, 0.2f, 0.2f);
  22. }
  23. // Update is called once per frame
  24. void Update() {
  25. //RotateAround 方法模拟天体绕太阳公转,Rotate 方法模拟天体自转
  26. Mercury.RotateAround(Sun.position, new Vector3(0, 1, 2), 48 * Time.deltaTime);
  27. Mercury.Rotate(Vector3.up * 30 * Time.deltaTime);
  28. Venus.RotateAround(Sun.position, new Vector3(0, 1, -1), 35 * Time.deltaTime);
  29. Venus.Rotate(Vector3.up * 30 * Time.deltaTime);
  30. Earth.RotateAround(Sun.position, Vector3.up, 30 * Time.deltaTime);
  31. Earth.Rotate(Vector3.up * 30 * Time.deltaTime);
  32. Mars.RotateAround(Sun.position, new Vector3(0, 1, 1), 24 * Time.deltaTime);
  33. Mars.Rotate(Vector3.up * 30 * Time.deltaTime);
  34. Jupiter.RotateAround(Sun.position, new Vector3(0, 5, 1), 13 * Time.deltaTime);
  35. Jupiter.Rotate(Vector3.up * 30 * Time.deltaTime);
  36. Saturn.RotateAround(Sun.position, new Vector3(0, 4, 1), 10 * Time.deltaTime);
  37. Saturn.Rotate(Vector3.up * 30 * Time.deltaTime);
  38. Uranus.RotateAround(Sun.position, new Vector3(0, 6, 1), 7 * Time.deltaTime);
  39. Uranus.Rotate(Vector3.up * 30 * Time.deltaTime);
  40. Neptune.RotateAround(Sun.position, new Vector3(0, 2, 1), 5 * Time.deltaTime);
  41. Neptune.Rotate(Vector3.up * 30 * Time.deltaTime);
  42. }

}

  1. - 将脚本拖放到 Main Camera,并设置相应的公有变量,运行:<br />![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509379-97145e71-44ab-4473-8e8d-7458b7ed25b3.png#align=left&display=inline&height=564&margin=%5Bobject%20Object%5D&originHeight=564&originWidth=1106&size=0&status=done&style=none&width=1106)
  2. - 为所有天体添加部件 Trail Renderer,可以在运行时显示天体运动轨迹:<br />![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509415-6fa49b86-2fae-4c1c-9945-17749243c0bd.png#align=left&display=inline&height=297&margin=%5Bobject%20Object%5D&originHeight=297&originWidth=441&size=0&status=done&style=none&width=441)
  3. ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509390-0a35c530-a6ea-4950-a330-1a3ceea0000a.png#align=left&display=inline&height=569&margin=%5Bobject%20Object%5D&originHeight=569&originWidth=1104&size=0&status=done&style=none&width=1104)
  4. ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509440-38a82b49-49d4-4ae4-ba0d-fcef1ec5c27a.png#align=left&display=inline&height=570&margin=%5Bobject%20Object%5D&originHeight=570&originWidth=1110&size=0&status=done&style=none&width=1110)
  5. 游戏成果动态展示[🔗视频链接](https://www.bilibili.com/video/BV1R54y117nB/)
  6. 2、编程实践
  7. - 阅读以下游戏脚本<br />

Priests and Devils

Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many ways. Keep all priests alive! Good luck!

  1. - 列出游戏中提及的事物(Objects)<br />牧师(Priest)、恶魔(Devil)、船(boat)、河(river)、河岸(sides of river)。
  2. - 用表格列出玩家动作表(规则表)
  3. | 玩家动作(事件) | 条件 | 结果 |
  4. | --- | --- | --- |
  5. | 点击牧师/恶魔 | 牧师/恶魔在岸上且船未满员 | 牧师/恶魔上船 |
  6. | 点击牧师/恶魔 | 牧师/恶魔在船上且船靠岸 | 牧师/恶魔上岸 |
  7. | 点击船 | 船上至少有一个人物 | 船驶向对岸 |
  8. | / | 三个牧师均已过河 | 游戏胜利 |
  9. | / | 有一侧河岸魔鬼数量多于牧师数量 | 游戏失败 |
  10. - 编程要求<br />

请将游戏中对象做成预制 在场景控制器 LoadResources 方法中加载并初始化长方形、正方形、球及其色彩代表游戏中的对象 使用 C# 集合类型有效组织对象 整个游戏仅主摄像机和一个 Empty 对象,其他对象必须代码动态生成整个游戏不许出现 Find 游戏对象,SendMessage 这类突破程序结构的通讯耦合语句 请使用课件架构图编程,不接受非 MVC 结构程序 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件

  1. - **编程实现**
  2.   【游戏效果图】<br />  ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907510233-9c793114-f291-48da-8c6e-927e61f01773.png#align=left&display=inline&height=417&margin=%5Bobject%20Object%5D&originHeight=417&originWidth=785&size=0&status=done&style=none&width=785)
  3.   ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509935-f3fce7d9-aa69-4d1b-98d1-99dff623c792.png#align=left&display=inline&height=396&margin=%5Bobject%20Object%5D&originHeight=396&originWidth=772&size=0&status=done&style=none&width=772)
  4.   MVC架构设计】
  5.   ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907510166-7f34f772-04d0-48b4-98d0-f5f1a7ec7831.png#align=left&display=inline&height=660&margin=%5Bobject%20Object%5D&originHeight=660&originWidth=1190&size=0&status=done&style=none&width=1190)
  6.   【游戏对象预制】
  7.   ![](https://cdn.nlark.com/yuque/0/2020/png/2589459/1601907509921-207b9f31-5b68-47a9-a64b-427f9ab056f6.png#align=left&display=inline&height=167&margin=%5Bobject%20Object%5D&originHeight=167&originWidth=493&size=0&status=done&style=none&width=493)
  8.   【脚本实现】
  9.   以下按照 MVC 架构三个部分介绍,受篇幅影响此处省去代码细节,完整代码参见个人 [🔗github](https://github.com/sherryjw/3D-Computer-Game-Programming/tree/master/Homework3)。
  10.   ①.模型(Model):主要处理数据对象及关系(包括游戏对象、空间关系等)
  11.   **CoastModel.cs**:处理游戏对象——河岸<br />
  12. ```csharp
  13. public class CoastModel {
  14. public GameObject obj;
  15. public int priestNum, devilNum;
  16. public CoastModel(string name, Vector3 position) {
  17. }
  18. }

  BoatModel.cs:处理游戏对象——船

  1. public class BoatModel {
  2. public GameObject boat;
  3. public RoleModel[] roles;
  4. public int priestNum;
  5. public int devilNum;
  6. public bool OnRight;
  7. public BoatModel(Vector3 position) {
  8. }
  9. }

  RiverModel.cs:处理游戏对象——河

  1. public class RiverModel {
  2. private GameObject river;
  3. public RiverModel(Vector3 position) {
  4. }
  5. }

  RoleModel.cs:处理游戏对象——角色:牧师、魔鬼

  1. public class RoleModel {
  2. public GameObject role;
  3. public int tag;
  4. public int flag;
  5. public bool OnBoat;
  6. public bool OnRight;
  7. public RoleModel(Vector3 position, int flag, int tag) { // 0 represents Priest, 1 represents Devil
  8. }
  9. }

  PositionModel.cs:处理游戏对象的空间位置

  1. public class PositionModel
  2. {
  3. public static Vector3 src_coast = new Vector3(-11, -2, 6);
  4. public static Vector3 des_coast = new Vector3(11, -2, 6);
  5. public static Vector3 river = new Vector3(1, -2.4f, 6);
  6. public static Vector3 boat_on_left = new Vector3(-4, -2, 6);
  7. public static Vector3 boat_on_right = new Vector3(4, -2, 6);
  8. public static Vector3[] roles = new Vector3[]{new Vector3(-0.2f, 0.8f, 0), new Vector3(-0.1f, 0.8f, 0), new Vector3(0, 0.8f, 0),
  9. new Vector3(0.1f, 0.8f,0), new Vector3(0.2f, 0.8f, 0), new Vector3(0.3f, 0.8f, 0)};
  10. public static Vector3[] roles_on_boat = new Vector3[] { new Vector3(-0.1f, 1.2f, 0), new Vector3(0.2f, 1.2f, 0) };
  11. }

  ②.控制器(Controller):接受用户事件,控制模型的变化

  • 一个场景一个主控制器
  • 至少实现与玩家交互的接口
  • 实现或管理运动

  Director.cs:获取当前游戏的场景,控制场景运行、切换、入栈与出栈

  1. public class Director : System.Object {
  2. private static Director _instance;
  3. public ISceneController CurrentSenceController { get; set; }
  4. public static Director GetInstance() {
  5. }
  6. }

  CoastController.cs:处理与河岸相关的事件,包括牧师/魔鬼上岸、离岸等

  1. public class CoastController {
  2. private CoastModel coast;
  3. public void CreateCoast(string name, Vector3 position) {
  4. }
  5. public Vector3 AddRole(RoleModel roleModel) {
  6. }
  7. public CoastModel GetCoastModel() {
  8. }
  9. public void RemoveRole(RoleModel roleModel) {
  10. }
  11. }

  BoatController.cs:处理与船相关的事件,包括牧师/恶魔上船、下船等

  1. public class BoatController : ClickAction {
  2. BoatModel boatModel;
  3. IUserAction userAction;
  4. public BoatController() {
  5. }
  6. public void CreateBoat(Vector3 position) {
  7. }
  8. public Vector3 AddRole(RoleModel roleModel) {
  9. }
  10. public BoatModel GetBoatModel() {
  11. }
  12. public void RemoveRole(RoleModel roleModel) {
  13. }
  14. public void OnClick() {
  15. }
  16. }

  RoleController.cs:处理与游戏角色——牧师和魔鬼相关的事件,包括点击角色等

  1. public class RoleController : ClickAction {
  2. RoleModel roleModel;
  3. IUserAction userAction;
  4. public RoleController() {
  5. }
  6. public void CreateRole(Vector3 position, int flag, int tag) {
  7. }
  8. public RoleModel GetRoleModel() {
  9. }
  10. public void OnClick() {
  11. }
  12. }

  MoveController.cs:处理与移动相关的事件,包括船离岸、靠岸等

  1. public class MoveController {
  2. private GameObject obj;
  3. public void SetMove(Vector3 destination, GameObject obj) {
  4. }
  5. public bool GetIsMoving() {
  6. }
  7. }

  FirstController.cs:管理本次场景所有的游戏对象,协调游戏对象之间的通讯等

  1. public class FirstController : MonoBehaviour, ISceneController, IUserAction {
  2. private CoastController DesCoastController;
  3. private CoastController SrcCoastController;
  4. private BoatController boatController;
  5. private RoleModelController[] roleModelControllers;
  6. private MoveController moveController;
  7. private RiverModel river;
  8. private bool isRuning;
  9. private float time;
  10. void Awake() {
  11. }
  12. public void LoadResources() {
  13. }
  14. public void MoveBoat() {
  15. }
  16. public void MoveRole(RoleModel roleModel) {
  17. }
  18. public void Restart() {
  19. }
  20. public void Check() {
  21. }
  22. void Update() {
  23. }
  24. }

  ISceneController.cs:提供实现资源加载的接口

  1. public interface ISceneController {
  2. void LoadResources();
  3. }

  IUserAction.cs:提供用户交互事件的接口

  1. public interface IUserAction {
  2. void MoveBoat();
  3. void MoveRole(RoleModel roleModel);
  4. void Check();
  5. void Restart();
  6. }

  ClickAction.cs:提供点击事件的接口

  1. public interface ClickAction {
  2. void OnClick();
  3. }

  Move.cs:处理每一帧的场景变化

  1. public class Move : MonoBehaviour {
  2. public bool isMoving = false;
  3. public float speed = 8;
  4. public Vector3 destination;
  5. void Update() {
  6. }
  7. }

  ③.界面(View):显示模型,将人机交互事件交给控制器处理

  • 处理接收 Input 事件
  • 渲染 GUI ,接收事件

  Click.cs:接收点击事件并交给控制器处理

  1. public class Click : MonoBehaviour {
  2. ClickAction clickAction;
  3. void OnMouseDown() {
  4. }
  5. public void setClickAction(ClickAction clickAction) {
  6. }
  7. }

  UserGUI.cs:渲染 GUI,并接收事件

  1. public class UserGUI : MonoBehaviour {
  2. private IUserAction userAction;
  3. public int time;
  4. public string result;
  5. void Start() {
  6. }
  7. void OnGUI() {
  8. }
  9. }
  1. 【游戏成果动态展示】

  🔗视频链接

3、思考题

  • 使用向量与变换,实现并扩展 Tranform 提供的方法,如 Rotate、RotateAround 等。

  实现如下形式的 Rotate:

  1. public void Rotate(Vector3 axis, float angle, Transform t);

  可以使用函数public static Quaternion AngleAxis(float angle, Vector3 axis);来实现,该函数创建一个绕轴 axis 成 角度 angle 的旋转。为了实现 Rotate 效果的呈现,还需要与 t 点乘:

  1. public void Rotate(float angle, Vector3 axis, Transform t) {
  2. var a = Quaternion.AngleAxis(angle, axis);
  3. t.rotation *= a;
  4. }

  实现如下形式的 RotateAround:

  1. public void RotateAround(Vector3 point, Vector3 axis, float angle, Transform t);

  同样使用函数public static Quaternion AngleAxis(float angle, Vector3 axis);来实现,由于是围绕一个中心点旋转,需要计算物体的位移矢量,对其进行旋转后再计算得到新的位置:

  1. public void RotateAround(Vector3 point, Vector3 axis, float angle, Transform t) {
  2. var a = Quaternion.AngleAxis(angle, axis);
  3. var distance = t.position - point;
  4. distance *= a;
  5. t.position = distance + point;
  6. t.rotation *= a;
  7. }