一、简答题
1.游戏对象运动的本质是什么?
游戏对象运动的本质就是使用矩阵变换(平移、旋转、缩放)改变游戏对象的空间属性。
2.请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)
方法一:修改Transform属性
由平抛运动,水平方向速度不变,竖直速度v=gt,根据这一公式,我们可以得到每一个时刻的位置。
```
public float Power = 10;//发射速度
public float Gravity = -10;//重力加速度
private float Time;//时间
private Vector3 angle;//角度
private Vector3 v1;//水平速度向量
private Vector3 v2 = Vector3.zero;//竖直速度向量
void Start()
{
//计算水平速度
v1 = Quaternion.Euler(new Vector3(0, 0, 45)) * Vector3.right * Power;
angle = Vector3.zero;
}
void FixedUpdate()
{
//计算竖直速度
v2.y = Gravity * (Time += Time.fixedDeltaTime);
transform.position += (v1 + v2) * Time.fixedDeltaTime;
//显示轨迹
angle.z = Mathf.Atan((v1.y + v2.y) / v1.x) * Mathf.Rad2Deg;
transform.eulerAngles = angle;
}
方法二:使用Vector3中的Vector3.Lerp
创建一个Vector3向量,每次把postion对该向量叠加,改变其y值
public float Power = 10;
public float Gravity = -10;
private float v1;
private float v2;
private float Angle = 45;
void Start () {
v1 = Power * Mathf.Cos(Angle);
v2 = Power * Mathf.Sin(Angle);
}
void Update () {
Vector3 vec = new Vector3(Time.deltaTime * v1, Time.deltaTime * v2, 0);
transform.position = Vector3.Lerp (transform.position, transform.position + vec, 1);
v2 += Gravity * Time.deltaTime;
}
}
方法三:使用transform中的transform.Translate
类比方法二,对竖直速度进行叠加。
public float Power = 10;
public float Gravity = -10;
private float v1;
private float v2;
private float Angle = 45;
void Start () {
v1 = Power * Mathf.Cos(Angle);
v2 = Power * Mathf.Sin(Angle);
}
void Update () {
Vector3 vec = new Vector3(Time.deltaTime * v1, Time.deltaTime * v2, 0);
this.transform.Translate(vec);
v2 += Gravity * Time.deltaTime;
}
}
3.写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。
首先,创建九个Sphere对象,将其中一个命名为Sun,并将另外八个作为Sun的子对象,分别命名为八大行星。
然后,编写代码,用RotationAround实现公转,用Rotation实现自转,并根据solar system中各行星的真实位置设置这两个函数的参数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SolarSystem : MonoBehaviour
{
public Transform Sun;
public Transform Earth;
public Transform Mercury;
public Transform Venus;
public Transform Mars;
public Transform Jupiter;
public Transform Saturn;
public Transform Uranus;
public Transform Neptune;
void Start()
{
Sun.position = Vector3.zero;
Mercury.position = new Vector3 (3, 0, 0);
Venus.position = new Vector3 (-3, 0, 0);
Earth.position = new Vector3 (6, 0, 0);
Uranus.position = new Vector3 (13, 0, 0);
Neptune.position = new Vector3 (-15, 0, 0);
Mars.position = new Vector3 (-8, 0, 0);
Jupiter.position = new Vector3 (-10, 0, 0);
Saturn.position = new Vector3 (8, 0, 0);
}
void Update()
{
Earth.RotateAround (sun.position, Vector3.up, 10 * Time.deltaTime);
Earth.Rotate (Vector3.up * 30 * Time.deltaTime);
Venus.RotateAround (sun.position, new Vector3(0, 2, 1), 15 * Time.deltaTime);
Venus.Rotate (new Vector3(0, 2, 1) * Time.deltaTime);
Mars.RotateAround (sun.position, new Vector3(0, 13, 5), 9 * Time.deltaTime);
Mars.Rotate (new Vector3(0, 12, 5) * 40 * Time.deltaTime);
Jupiter.RotateAround (sun.position, new Vector3(0, 8, 3), 8 * Time.deltaTime);
Jupiter.Rotate (new Vector3(0, 10, 3) * 30 * Time.deltaTime);
Mercury.RotateAround (sun.position, new Vector3(0, 3, 1), 20 * Time.deltaTime);
Mercury.Rotate ( new Vector3(0, 5, 1) * 5 * Time.deltaTime);
Saturn.RotateAround (sun.position, new Vector3(0, 2, 1), 7 * Time.deltaTime);
Saturn.Rotate (new Vector3(0, 3, 1) * 20 * Time.deltaTime);
Uranus.RotateAround (sun.position, new Vector3(0, 9, 1), 6 * Time.deltaTime);
uranus.Rotate (new Vector3(0, 10, 1) * 20 * Time.deltaTime);
Ueptune.RotateAround (sun.position, new Vector3(0, 7, 1), 5 * Time.deltaTime);
neptune.Rotate (new Vector3(0, 8, 1) * 30 * Time.deltaTime);
}
}
最后,我们将脚本直接拖到到main camera上。
二、编程实践
1.列出游戏中提及的事物(Objects)
牧师、恶魔、船、水、左河岸、右河岸
2.用表格列出玩家动作表(规则表),注意,动作越少越好
当前状态 | 玩家操作 | 结果 |
牧师或恶魔在岸边,船上有空位 | 玩家点击靠近船一侧岸上的恶魔或牧师 | 恶魔或牧师上船 |
牧师或恶魔在船上 | 玩家点击船上的恶魔或牧师 | 恶魔或牧师上靠近船的岸 |
其中一侧的恶魔的数量大于牧师的数量 | 显示玩家输 | |
恶魔和牧师全部到河边另一侧 | 显示玩家赢 |
然后,我们将游戏中的对象做成预制如下:
根据题意,我们需要使用MVC架构。MVC架构即就是Model,View和Controller。在牧师与魔鬼游戏中,我们的所有GameObject可以说就是Model,包括 牧师、魔鬼、船等。Model由Controller控制。例如船就受到控制船的Controller的控制。而View就是展示给用户的游戏界面,以及可供用户操作的各种方式。比如船点击就能动,牧师和魔鬼一点击就可以上船或下船等。之前说过,Controller可以控制Model。然而,Controller不仅可以控制实际的model,其可以控制游戏场景中的所有对象,包括用户输入、对象加载等。
接下来看具体实现,首先,我们先定义一个导演类Director,导演类是游戏的最高级别的Controller。可以说导演类控制着整个游戏。场景切换,游戏运行等都又导演类控制,且导演类自始至终只实例化一次。
public class Director : System.Object {
private static Director _instance;
public SceneController currentSceneController { get; set; }
public static Director getInstance() {
if (_instance == null) {
_instance = new Director ();
}
return _instance;
}
}
然后,我们再来看游戏角色的生成类,ManController类。
这个类就是用来创建游戏中的人物,即牧师与魔鬼,以将预制的对象通过代码生成到游戏中。并且,通过将clickGUI挂载到角色上,那么角色就可以实现鼠标点击事件,从而可以拖动人物上下船。
public class ManController {
readonly ClickGUI clickGUI;
readonly GameObject character;
readonly Moveable moveableScript;
readonly int characterType;
bool _isOnBoat;
CoastController coastController;
public ManController(string which_character) {
if (which_character == "priest") {
character = Object.Instantiate (Resources.Load ("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 0;
} else {
character = Object.Instantiate (Resources.Load ("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 1;
}
moveableScript = character.AddComponent (typeof(Moveable)) as Moveable;
clickGUI = character.AddComponent (typeof(ClickGUI)) as ClickGUI;
clickGUI.setController (this);
}
public void setName(string name) {
character.name = name;
}
public void setPosition(Vector3 pos) {
character.transform.position = pos;
}
public void moveToPosition(Vector3 destination) {
moveableScript.setDestination(destination);
}
public int getType() { // 0->priest, 1->devil
return characterType;
}
public string getName() {
return character.name;
}
public void getOnBoat(BoatController boatCtrl) {
coastController = null;
character.transform.parent = boatCtrl.getGameobj().transform;
_isOnBoat = true;
}
public void getOnCoast(CoastController coastCtrl) {
coastController = coastCtrl;
character.transform.parent = null;
_isOnBoat = false;
}
public bool isOnBoat() {
return _isOnBoat;
}
public CoastController getCoastController() {
return coastController;
}
public void reset() {
moveableScript.reset ();
coastController = (Director.getInstance ().currentSceneController as FirstController).fromCoast;
getOnCoast (coastController);
setPosition (coastController.getEmptyPosition ());
coastController.getOnCoast (this);
然后是BoatController和CoastController,这两个类就与ManController是类似的。但是,由于我们不能使用Find或SendMessage这类通讯耦合语句,所以我们就需要对每个对象都挂载一个单独的脚本。而对于这两个类,由于只是大致修改了ManController的少部分内容,而实现思想与ManController类似,这里就不赘述了。唯一需要说明的是这两个类的特殊之处就是要实现相关方法来实现可以容纳角色的功能——船可以坐人,岸上可以站人。当船上小于两人时,就可以再选择一个角色到船上。
接下来是MoveController,这个类是挂载到GameObject上的,从而可以实现船或人物的移动。
public class MoveController: MonoBehaviour {
int _toward;
Vector3 _dest1;
Vector3 _dest2;
public void setDestination(Vector3 _dest) {
if (_dest.y == transform.position.y) {
toward = 2;
}
else if (_dest.y < transform.position.y) {
middle.y = transform.position.y;
} else {
middle.x = transform.position.x;
}
toward = 1;
}
void Update() {
if (toward == 1) {
transform.position = Vector3.MoveTowards (transform.position, _dest1, move_speed * Time.deltaTime);
if (transform.position == _dest1) {
toward = 2;
}
} else if (moving_status == 2) {
transform.position = Vector3.MoveTowards (transform.position, _dest2, move_speed * Time.deltaTime);
if (transform.position == _dest2) {
toward = 0;
}
}
public void reset() {
toward = 0;
这里我们创建了一个_toward变量,用以表示船此时是要从左向右走还是从右向左走,从而可以触发不同的事件。
最后,比较主要的就是用户交互界面,创建如下:
public class GUI1 : MonoBehaviour {
void Start() {
action = Director.getInstance ().currentSceneController as UserAction;
style = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 20;
}
void OnGUI() {
if (status == 1) {
GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You lose!", style);
if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Play Again", buttonStyle)) {
status = 0;
action.restart ();
}
} else if(status == 2) {
GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You win!", style);
if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Play Again", buttonStyle)) {
status = 0;
action.restart ();
}
}
}
}
运行效果图如下:
github地址:
https://github.com/Jan-Jsr/3dhomework3/tree/master
三、思考题
使用向量与变换,实现并扩展 Tranform 提供的方法,如 Rotate、RotateAround 等。
实现RotateAround如下:
void RotateAround(Transform t, Vector3 center, Vector3 axis, float angle)
{
t.position = center + direction;
t.rotation *= rot;
direction = rot * direction;
var position = t.position;
var rot = Quaternion.AngleAxis(angle, axis);
var direction = position - center;
}