WillKen's Blog.

3D游戏编程与设计-空间与运动

Word count: 2.3kReading time: 11 min
2019/09/21 Share

3D游戏编程与设计-空间与运动

简答并用程序验证

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

    游戏对象运动的本质是通过矩阵变换在每一帧改变游戏对象的position、rotation、scale等属性的参数。

  • 请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)

    • 修改Transform属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class NewBehaviourScript : MonoBehaviour
      {
      private float x_v;
      private float y_v;
      private float g;
      // Start is called before the first frame update
      void Start()
      {
      x_v = 10f;
      y_v = 10f;
      g = 10f;
      }

      // Update is called once per frame
      void Update()
      {
      y_v -= g * Time.deltaTime;
      this.transform.position += new Vector3(Time.deltaTime * x_v, Time.deltaTime * y_v, 0);
      }
      }
    • 使用向量Vector3.Lerp方法

      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
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class NewBehaviourScript : MonoBehaviour
      {

      private float x_v;
      private float y_v;
      private float g;
      // Start is called before the first frame update
      void Start()
      {
      x_v = 10f;
      y_v = 10f;
      g = 10f;
      }

      // Update is called once per frame
      void Update()
      {
      y_v -= g * Time.deltaTime;
      transform.position = Vector3.Lerp(
      transform.position, transform.position + new Vector3(Time.deltaTime * x_v, Time.deltaTime * y_v, 0), 1);
      }
      }
    • 直接使用Vector3方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;

      public class NewBehaviourScript : MonoBehaviour
      {

      public float vY = 3.0f;
      public float aY = 1.0f;
      public float vX = 1.0f;

      void Start()
      {

      }

      void Update()
      {
      Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
      this.transform.position += change;
      vY -= aY * Time.deltaTime;
      }
      }
  • 写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

  1. 首先制作许多3D球体并进行贴图

  2. 新建脚本并写代码

    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
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class NewBehaviourScript : MonoBehaviour
    {

    public Transform Sun, Moon, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune;

    void Start()
    {

    }

    void Update()
    {
    Sun.Rotate(Vector3.up * Time.deltaTime * 5);

    Mercury.RotateAround(Sun.position, new Vector3(0.1f, 1, 0), 60 * Time.deltaTime);
    Mercury.Rotate(Vector3.up * 10000 / 58 * Time.deltaTime);

    Venus.RotateAround(Sun.position, new Vector3(0, 1, -0.1f), 55 * Time.deltaTime);
    Venus.Rotate(Vector3.up * 10000 / 243 * Time.deltaTime);

    Earth.RotateAround(Sun.position, Vector3.up, 50 * Time.deltaTime);
    Earth.Rotate(Vector3.up * 10000 * Time.deltaTime);
    Moon.RotateAround(Earth.position, Vector3.up, 5 * Time.deltaTime);
    Moon.Rotate(Vector3.up * 10000 / 27 * Time.deltaTime);

    Mars.RotateAround(Sun.position, new Vector3(0.2f, 1, 0), 45 * Time.deltaTime);
    Mars.Rotate(Vector3.up * 10000 * Time.deltaTime);

    Jupiter.RotateAround(Sun.position, new Vector3(-0.1f, 2, 0), 38 * Time.deltaTime);
    Jupiter.Rotate(Vector3.up * 10000 / 0.3f * Time.deltaTime);

    Saturn.RotateAround(Sun.position, new Vector3(0, 1, 0.2f), 36 * Time.deltaTime);
    Saturn.Rotate(Vector3.up * 10000 / 0.4f * Time.deltaTime);

    Uranus.RotateAround(Sun.position, new Vector3(0, 2, 0.1f), 35 * Time.deltaTime);
    Uranus.Rotate(Vector3.up * 10000 / 0.6f * Time.deltaTime);

    Neptune.RotateAround(Sun.position, new Vector3(-0.1f, 1, -0.1f), 33 * Time.deltaTime);
    Neptune.Rotate(Vector3.up * 10000 / 0.7f * Time.deltaTime);
    }
    }
  1. 将脚本拖至摄像机上,并将transform进行绑定。

Solar-System 项目地址

Solar-System 视频演示

编程实践

  • 阅读以下游戏脚本

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!

程序需要满足的要求:

条件 动作 结果
右边河岸有牧师/魔鬼且魔鬼数<=牧师数且船上<=2人 右岸的牧师或魔鬼上船 船上人数+1,右岸人数-1
船上有人且船在右岸 船上的人上到右岸 右岸人数+1,船上人数-1
船上有人,船在右/左岸 开船 如果某个岸上的魔鬼数>牧师数,游戏结束,否则船和船上的人向对岸运动
船上有人且船在左岸 船上的人上到左岸 左岸人数+1,船上人数-1
左边河岸有牧师/魔鬼且魔鬼数<=牧师数且船上<=2人 左岸的牧师或魔鬼上船 船上人数+1,左岸人数-1
  • 请将游戏中对象做成预制

  • 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。

  • 使用 C# 集合类型 有效组织对象

  • 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分

  • 请使用课件架构图编程,不接受非 MVC 结构程序

  • 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

    主要代码片段:

    1. 初始化:通过FirstController进行资源加载(预制)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public void LoadResources()
      {
      GameObject water = Instantiate(Resources.Load("Prefabs/water", typeof(GameObject))) as GameObject;
      fromCoast = new CoastController(0, new Vector3(0, 0, 0));
      toCoast = new CoastController(1, new Vector3(12, 0, 0));
      boat = new BoatController();

      characters = new ICharacterController[6];
      for (int i = 0; i < 3; i++)
      {
      characters[i] = new ICharacterController(i, "priest", new Vector3((float)2.5 - i, (float)1.25, 0));
      }
      for (int i = 3; i < 6; i++)
      {
      characters[i] = new ICharacterController(i, "devil", new Vector3((float)2.5 - i, (float)1.25, 0));
      }
      fromCoast.initStorage(characters);
      }
  1. 移动控制器

    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
    public class MoveController : MonoBehaviour
    {
    float speed = 20f;
    Vector3 destination;
    int status = 0;//0为结束,1为开始

    void Update()
    {
    if (status == 1)
    {
    transform.position = Vector3.MoveTowards(transform.position, destination, speed * Time.deltaTime);

    if (Vector3.Distance(transform.position, destination) < 0.0001)
    {
    status = 0;
    }
    }
    }

    public void Move(Vector3 dest)
    {
    destination = dest;
    status = 1;
    }
    }
  1. 河岸控制器

    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
    public class CoastController
    {
    readonly public GameObject coast;
    public CoastStorage storage;

    public CoastController(int status, Vector3 pos)
    {
    coast = Object.Instantiate(Resources.Load("Prefabs/stone", typeof(GameObject))) as GameObject;
    coast.name = "from_coast";
    storage = new CoastStorage();
    if (status == 1)
    {
    coast.transform.position = pos;
    coast.name = "to_coast";
    }
    }

    public void initStorage(ICharacterController[] characters)
    {
    storage.clear();
    for (int i = 0; i < 6; i++)
    {
    storage.insert(characters[i]);
    }
    }

    public int OnCoast(ICharacterController character, int boatStatus)
    {
    if (storage.isFull())
    return -1;
    else
    {
    int pos = storage.insert(character);
    Vector3 relativeVec;
    if (coast.name == "from_coast")
    {
    relativeVec = new Vector3(2.5f - pos, 1.25f, 0);
    }
    else
    {
    relativeVec = new Vector3(-2.5f + pos, 1.25f, 0);
    }
    character.moveOffBoat(coast.transform.position, boatStatus, relativeVec);
    return pos;
    }
    }
    public bool OffCoast(int pos)
    {
    bool flag = storage.remove(pos);
    return flag;
    }
    public void OffCoast(ICharacterController Mycharacter)
    {
    storage.delete(Mycharacter);
    }
    public void reset()
    {
    storage.clear();
    }

    public bool check_over(BoatController boat)
    {
    return storage.check_over(boat);
    }
    public bool check_over()
    {
    return storage.check_over();
    }

    public bool check_win()
    {
    return storage.isFull();
    }
    }
  1. 船控制器

    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
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    public class BoatController
    {
    readonly public GameObject boat;
    readonly MoveController movescript;
    public int boatStatus;//0为从fromCoast开到toCoast,1为回来

    //两个ICharacter对象
    public ICharacterController frontCharacter;
    public ICharacterController backCharacter;

    //两个相对向量,表示相对于船的两个乘员的位置
    readonly Vector3 front = new Vector3(0.5f, 0.5f, 0);
    readonly Vector3 back = new Vector3(-0.5f, 0.5f, 0);
    public BoatController()
    {
    boat = Object.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject;
    boat.name = "boat";
    movescript = boat.AddComponent(typeof(MoveController)) as MoveController;
    boat.AddComponent(typeof(UserClick));
    boatStatus = 0;
    frontCharacter = null;
    backCharacter = null;
    }
    public void move()
    {
    if (frontCharacter == null && backCharacter == null)
    return;

    if (boatStatus == 0)
    {
    boatStatus = 1;
    movescript.Move(new Vector3(8, 0, 0));
    }
    else
    {
    boatStatus = 0;
    movescript.Move(new Vector3(4, 0, 0));
    }
    }
    public bool boatFull()
    {
    if (frontCharacter != null && backCharacter != null)
    {
    return true;
    }
    else
    return false;
    }

    //OnBoat和OffBoat,负责操控船的数据结构,同时负责管理移动
    public void OnBoat(ICharacterController element)
    {
    if (this.boatFull())
    {
    return;
    }

    if (boatStatus == 0)//从from向to,front为前
    {
    if (frontCharacter == null)
    {
    frontCharacter = element;
    element.character.transform.parent = boat.transform;
    element.moveOnBoat(boat.transform.position, boatStatus, front);
    }
    else
    {
    backCharacter = element;
    element.character.transform.parent = boat.transform;
    element.moveOnBoat(boat.transform.position, boatStatus, back);
    }
    }
    else // 从to开向from,back为前
    {
    if (backCharacter == null)
    {
    backCharacter = element;
    element.character.transform.parent = boat.transform;
    element.moveOnBoat(boat.transform.position, boatStatus, back);
    }
    else
    {
    frontCharacter = element;
    element.character.transform.parent = boat.transform;
    element.moveOnBoat(boat.transform.position, boatStatus, front);
    }
    }
    }

    //下船需要的支持
    public void OffBoat(ICharacterController element)
    {
    element.character.transform.parent = null;
    if (frontCharacter == element)
    {
    frontCharacter = null;
    }
    else
    {
    backCharacter = null;
    }
    }

    public void reset()
    {
    boatStatus = 0;
    movescript.Move(new Vector3(4, 0, 0));
    frontCharacter = null;
    backCharacter = null;
    }

    }
  1. 人物控制器

    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
    public class ICharacterController
    {
    readonly public GameObject character;
    readonly public string race;
    readonly UserClick userclick;
    readonly MoveController movescript;
    public bool onBoat;
    public string place;

    readonly Vector3 frontmiddle1 = new Vector3(3.5f, 1.25f, 0);
    readonly Vector3 frontmiddle2 = new Vector3(3.5f, 0.5f, 0);
    readonly Vector3 backmiddle1 = new Vector3(8.5f, 1.25f, 0);
    readonly Vector3 backmiddle2 = new Vector3(8.5f, 0.5f, 0);
    public ICharacterController(int index, string racing, Vector3 pos)
    {
    string path = "Prefabs/" + racing;
    character = Object.Instantiate(Resources.Load(path, typeof(GameObject))) as GameObject;
    character.name = racing + index.ToString();
    character.transform.position = pos;
    race = racing;
    onBoat = false;
    place = "from";

    movescript = character.AddComponent(typeof(MoveController)) as MoveController;

    userclick = character.AddComponent(typeof(UserClick)) as UserClick;
    userclick.setController(this);
    }
    //上船的动作
    public void moveOnBoat(Vector3 pos, int boatStatus, Vector3 relativeMove)
    {
    place = "boat";
    onBoat = true;
    if (boatStatus == 0)
    {
    movescript.Move(frontmiddle1);
    movescript.Move(frontmiddle2);
    }
    else
    {
    movescript.Move(backmiddle1);
    movescript.Move(backmiddle2);
    }
    movescript.Move(pos + relativeMove);


    }

    public void moveOffBoat(Vector3 pos, int boatStatus, Vector3 relativeMove)
    {
    onBoat = false;
    if (boatStatus == 0)
    {
    movescript.Move(frontmiddle2);
    movescript.Move(frontmiddle1);
    place = "from";
    }
    else
    {
    movescript.Move(backmiddle2);
    movescript.Move(backmiddle1);
    place = "to";
    }
    movescript.Move(pos + relativeMove);
    }
    public void reset()
    {
    onBoat = false;
    character.transform.parent = null;
    place = "from";
    }
    }

思考题

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

    • Rotate

      1
      2
      3
      4
      5
      void myRotate(Vector3 up, float degree)
      {
      Quaternion q = Quaternion.AngleAxis(degree, up);
      this.transform.rotation *= q;
      }
    • RotateAround

      1
      2
      3
      4
      5
      6
      void myRotateAround(Vector3 pos, Vector3 up, float degree)
      {
      Quaternion q = Quaternion.AngleAxis(degree, up);
      this.transform.position = q * (this.transform.position-pos) + pos;
      this.transform.rotation *= q;
      }
CATALOG
  1. 1. 3D游戏编程与设计-空间与运动
    1. 1.1. 简答并用程序验证
    2. 1.2. 编程实践
    3. 1.3. 思考题