文章目录
1.简答并用程序验证
游戏对象运动的本质是什么
游戏对象的空间属性在同一个坐标系下随每一帧的变化。这里的空间属性是指transform中的position和rotation属性,即位置参数和旋转参数,运动可以看成是这些数值的改变。
请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)
- 分别在两个方向上改变transform中的position属性
计算出x方向和y方向的位移改变量,然后position在两个方向上分别加上改变量即可
public float v_x0 = 1.0f;
public float v_y0 = 0.5f;
public float t = 0f;
void Update () {
Debug.Log("Update");
t += (float)Time.deltaTime;
float delta_x = (float)(v_x0 * t);
float delta_y = (float)(v_y0 * t - 0.5 * 2 * t * t);
this.transform.position += Vector3.left * delta_x;
this.transform.position += Vector3.up * delta_y;
}
- 直接使用一个Vector3改变position
public float v_x0 = 1.0f;
public float v_y0 = 0.5f;
public float t = 0f;
// Update is called once per frame
void Update () {
Debug.Log("Update");
t += (float)Time.deltaTime;
float delta_x = (float)(v_x0 * t);
float delta_y = (float)(v_y0 * t - 0.5 * 2 * t * t);
//上面部分相同
this.transform.position += new Vector3(delta_x, delta_y, 0);
}
- 使用translate函数
1、2中的方法是直接让position加上改变量的Vector3;这里的方法是将这个改变量的Vector3作为参数传入Translate函数中
public float v_x0 = 1.0f;
public float v_y0 = 0.5f;
public float t = 0f;
// Update is called once per frame
void Update () {
Debug.Log("Update");
t += (float)Time.deltaTime;
float delta_x = (float)(v_x0 * t);
float delta_y = (float)(v_y0 * t - 0.5 * 2 * t * t);
//上面部分相同
Vector3 change = new Vector3(delta_x, delta_y, 0);
this.transform.Translate(change);
}
- 设置游戏对象的重力和初速度
void Start () {
Debug.Log("Start");
this.GetComponent<Rigidbody>().velocity = new Vector3(5, 10, 0);
}
写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。
主要用到两个函数:
1.RotateAround 函数用于实现星球的公转
function RotateAround (point : Vector3, axis : Vector3, angle : float) : void
围绕穿过point点的axis轴旋转,速度为angle度每秒。
在本程序中的用法,表示星球围绕经过太阳的坐标的y轴进行旋转。
axisEarth = new Vector3(0, 1, 0);
Earth.RotateAround(Sun.position, axisEarth, 20 * Time.deltaTime);
通过改变axis,可以让星球在不同的法平面上运动
axisMars = new Vector3(1, 5, 0);
Mars.RotateAround(Sun.position, axisMars, 60 * Time.deltaTime);
此时围绕的轴与y轴的夹角是arctan(1/5),因此Mars运动的平面与Earth运动的平面不同
2.Rotate函数用于实现星球的自转
function Rotate (eulerAngles : Vector3, relativeTo : Space = Space.Self) : void
应用一个欧拉角的旋转角度,eulerAngles.z度围绕z轴,eulerAngles.x度围绕x轴,eulerAngles.y度围绕y轴。如果第二个参数为空或者设置为Space.Self 旋转角度被应用围绕变换的自身轴。
在本程序中的用法:
Earth.Rotate(Vector3.up * 360 * Time.deltaTime);
//表示星球沿着自己的y轴以360度每秒的速度旋转
具体做法
1 设定各个星球的transform变量,以及它们公转围绕的轴
public Transform Sun, Mercury, Venus, Earth, Moon, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto;
Vector3 axisMercury, axisVenus, axisEarth, axisMoon, axisMars, axisJupiter, axisSaturn, axisUranus, axisNeptune, axisPluto;
2 start函数
在start函数中设定好各个星球的位置,以及它们公转所围绕的轴的角度
void Start () {
Debug.Log("Start");
Sun.position = Vector3.zero;
Mercury.position = new Vector3(16, 0, 0);
Venus.position = new Vector3(20, 0, 0);
Earth.position = new Vector3(30, 0, 0);
Moon.position = new Vector3((float)(34.1), 0, 0);
Mars.position = new Vector3(47, 0, 0);
Jupiter.position = new Vector3(68, 0, 0);
Saturn.position = new Vector3(88, 0, 0);
Uranus.position = new Vector3(102, 0, 0);
Neptune.position = new Vector3(110, 0, 0);
Pluto.position = new Vector3(117, 0, 0);
axisMercury = new Vector3(3, 11, 0);
axisVenus = new Vector3(2, 3, 0);
axisEarth = new Vector3(0, 1, 0);
axisMoon = Vector3.up;
axisMars = new Vector3(1, 5, 0);
axisJupiter = new Vector3(1, 5, 0);
axisSaturn = new Vector3(1, 9, 0);
axisUranus = new Vector3(2, 7, 0);
axisNeptune = new Vector3(1, 5, 0);
axisPluto = new Vector3(1, 3, 0);
}
3 update函数
就上面提及的用法,设定公转和自转,它们具有不同的转速和法平面。其中月球的旋转中心在地球。
void Update () {
Mercury.RotateAround(Sun.position, axisMercury, 10 * Time.deltaTime);
Venus.RotateAround(Sun.position, axisVenus, 30 * Time.deltaTime);
Earth.RotateAround(Sun.position, axisEarth, 20 * Time.deltaTime);
Moon.RotateAround(Earth.position, Vector3.up, 359 * Time.deltaTime);
Mars.RotateAround(Sun.position, axisMars, 60 * Time.deltaTime);
Jupiter.RotateAround(Sun.position, axisJupiter, 5 * Time.deltaTime);
Saturn.RotateAround(Sun.position, axisSaturn, 6 * Time.deltaTime);
Uranus.RotateAround(Sun.position, axisUranus, 35 * Time.deltaTime);
Neptune.RotateAround(Sun.position, axisNeptune, 10 * Time.deltaTime);
Pluto.RotateAround(Sun.position, axisPluto, 20 * Time.deltaTime);
Sun.Rotate(Vector3.up * 100 * Time.deltaTime);
Mercury.Rotate(Vector3.up * 600 * Time.deltaTime);
Venus.Rotate(Vector3.up * 400 * Time.deltaTime);
Earth.Rotate(Vector3.up * 360 * Time.deltaTime); //自转
Moon.Rotate(Vector3.up * 1000 * Time.deltaTime);
Mars.Rotate(Vector3.up * 300 * Time.deltaTime);
Jupiter.Rotate(Vector3.up * 300 * Time.deltaTime);
Saturn.Rotate(Vector3.up * 200 * Time.deltaTime);
Uranus.Rotate(Vector3.up * 400 * Time.deltaTime);
Neptune.Rotate(Vector3.up * 500 * Time.deltaTime);
Pluto.Rotate(Vector3.up * 400 * Time.deltaTime);
}
4 最后在Unity中建立大小适合的球体,并将这些游戏对象赋给脚本中的变量。
为了美观,还需要为每个球体拖上对应图片,并将宇宙背景图安在主摄像机的skybox上
效果图:
可见星球不在一个法平面上,并以不同的转速进行公转和自转
2、编程实践
阅读以下游戏脚本
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!
程序需要满足的要求:
- play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )列出游戏中提及的事物(Objects)
- 用表格列出玩家动作表(规则表),注意,动作越少越好
- 请将游戏中对象做成预制
- 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。
- 使用 C# 集合类型 有效组织对象
- 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 - 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分
- 请使用课件架构图编程,不接受非 MVC 结构程序
- 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!
1. 列出所有游戏对象:
左岸、右岸、河、船、牧师、恶魔
2. 动作表:
动作 | 条件 | 效果 |
---|---|---|
点击牧师/恶魔 | 牧师/恶魔与船在同一边 ,船有空位 | 牧师/恶魔上船 |
点击牧师/恶魔 | 牧师/恶魔在船上,船靠岸 | 牧师/恶魔上与船同一侧的岸 |
点击船 | 船上有牧师或者恶魔 | 船移动到另一岸 |
3. 将游戏中的对象做成预制
由于所有的游戏对象都必须动态生成,所以要先将所有的上述游戏对象做好,包括游戏对象的形状、大小、材料、背景,然后生成预制。其中游戏对象的位置不用提前设置,在脚本中直接更改会比较灵活。
在脚本中使用预制,对游戏对象进行实例化
GameObject river = Instantiate(Resources.Load("Prefabs/river"), new Vector3(0, -12, 0), Quaternion.identity) as GameObject;
4. MVC架构图
- 场景中的所有GameObject就是Model,它们受到Controller的控制,比如说牧师和魔鬼受到MyCharacterController类的控制,船受到BoatController类的控制,河岸受到CoastController类的控制。
- View就是UserGUI和ClickGUI,它们展示游戏结果,并提供用户交互的渠道(点击物体和按钮)。
- Controller:除了刚才说的MyCharacterController、BoatController、CoastController以外,还有更高一层的Controller:FirstController(场景控制器)
- FirstController控制着这个场景中的所有对象,包括其加载、通信、用户输入,它统筹各个controller。
- 最高层的Controller是Director类,一个游戏中只能有一个实例,它控制着场景的创建、切换、销毁、游戏暂停、游戏退出等等最高层次的功能。
5. 程序
5.1 Director
- Director是单实例的,拥有一些全局属性,这样可以让任何地方的代码访问它
- 继承至 C# 根对象,所以不会受 Unity 引擎管理,也不要加载
- 通过一个抽象的场景接口访问不同场的场记(控制器)
- 例如:它不知道每个场景需要加载哪些资源
- 它可以与实现 ISceneController 的不同场记对话
public class Director : System.Object
{
private static Director _instance;
public ISceneController currentSceneController { get; set; }
public bool running { get; set; }
public static Director GetInstance()
{
if(_instance == null)
{
_instance = new Director();
}
return _instance;
}
}
5.2 接口
每个场景都有自己的场记,导演需要与不同场景打交道,导演只知道场记有加载资源、暂停、恢复等行为,但并不知道实现细节,如:暂停前要保存哪些状态等
所以使用接口规定有哪些动作,隐藏实现细节。
通过接口的多态实现,场记和导演之间关系变松弛了。导演不需要知道场记是张三、李四,仅需要知道这个人穿了件带场记标记(接口)的衣服,就可执行导演的指挥任务。
根据MVC架构,设计接口:
public interface ISceneController
定义场景加载资源的操作
public interface IUserAction
为玩家与游戏交互的接口,定义玩家的所有操作
5.3 FirstController
FirstController的工作:
- 继承自
ISceneController, IUserAction
,需要实现接口中的所有定义函数,即加载资源(与游戏模型相关)和响应用户点击事件 - 同时FirstController需要控制游戏流程,统筹所有的controllers,包括控制牧师/恶魔的
charactersController
,控制船的BoatController
,控制岸的BankController
,控制移动的Move
- FirstController还需要代理
userGUI
,将游戏中的一些信息(比如游戏的输赢情况)传给userGUI,让userGUI呈现出来
5.4 charactersController,BoatController,BankController,Move
- 在这些类中具有
GameObject
变量,即与模型相关。 - 这些类需要处理各自控制对象的逻辑管理,注意它们只能处理自己的控制对象,而不能参与到其他类中(比如BoatController类中含有charactersController对象),否则会增加耦合性。如果需要借助其他类的信息进行动作,应该由
FirstController
进行消息传递 - 移动动作是一个比较复杂的过程,因为需要在每次
update
函数中不断更改状态信息,由于boat和character都要用到,所以适合将move分离出一个类来
5.5 ClickGUI
需要监控点击信息,通过代理IUserAction调用FirstController的处理函数
5.6 UserGUI
简单地显示界面信息