这次的作业是有关空间与运动的简答题和编程题

编程题:
github代码

  1. 简答题(1)游戏对象运动的本质是什么?
    (2)请用三种方法以上方法,实现物体的抛物线运动
    (3)太阳系
  2. 编程题(1)MVC
    (2)Model
    (3)Controller
    (4)View

一、简答题

(1)游戏对象运动的本质是什么?

游戏对象通过C#脚本改变Transform(position、rotation和scale)

(2)请用三种方法以上方法,实现物体的抛物线运动

a.第一种方法比较直观,就是直接改变游戏对象的position属性

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

public class test : MonoBehaviour
{
	public Vector3 v = new Vector3(3, 0, 0);//初速度
	public Vector3 g = new Vector3(0, -10, 0);//重力加速度

	void Update()
	{
		this.transform.position = (v * Time.time + 0.5f * g * Time.time * Time.time);
	}
}

b.第二种方法是使用translate

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

public class test : MonoBehaviour
{
	public Vector3 v = new Vector3(3, 0, 0);//初速度
	public Vector3 g = new Vector3(0, 0, 0);//重力方向速度

	private float tmpTime = 0;
	// Update is called once per frame
	void Update()
	{
		g.y -= 10 * Time.deltaTime;
		transform.Translate(v * Time.deltaTime);
		transform.Translate(g * Time.deltaTime);
	}
}

c、给物体直接增加重力

在右边的组件中添加刚体

unity 题库实现 unity3d答题系统_unity 题库实现

unity 题库实现 unity3d答题系统_Mercury_02

然后在刚体属性中默认勾选了重力选项(Use Gravity):

unity 题库实现 unity3d答题系统_加载_03


然后添加初速度,无论用前面三种方法中的哪一种,都可以形成抛物线

(3)太阳系

太阳系的构成有太阳、八大行星(含有月球)。于是我们不难得出游戏对象的树状结构:

unity 题库实现 unity3d答题系统_Time_04

这里我将地球和月球归入地月系统。很多人可能会将月球和地球并列,这在游戏对象比较少的时候当然是可以接受(或者说忍受)。但是当游戏对象很多的时候,这种随意安排对象间的“父子”关系将会带来许多缺点,如给读者带来极差的阅读体验和cs代码的混乱。所以这里特意将地月系统单独划出来。

相应的创建sphere后,加上贴图,调整位置,效果如下图所示:

unity 题库实现 unity3d答题系统_加载_05

现在我们给太阳系写一个旋转的脚本。注意满足以下要求:
星球围绕太阳的转速必须不一样,且不在一个法平面上
1、创建Transform对象,用于记录各个星体的状态

public Transform Sun;//太阳
public Transform Mercury;//水星
public Transform Venus;//金星
public Transform EarthMoon;//地月系统
……

Start的时候给星体一个初始位置(可以省略,因为已经摆放好了各个星体)

Sun.localPosition = Vector3.zero;
Mercury.localPosition = new Vector3(6, 0, 0);

最后在Update中考虑自转与公转。设立变量a,作为公转轴,以保证不同行星位于不同的法平面上

Vector3 a1 = new Vector3(0, -10, 5);
Vector3 a2 = new Vector3(0, -10, 1);
Vector3 a3 = Vector3.down;
//地球就用水平面作为参照
Vector3 a4 = new Vector3(0, -10, -5);
……

问题(待解决)
这里公转轴的选取很有讲究。我们想实现的效果,肯定是行星围绕太阳转。但是如果你将a1改为(5,10,0),你将会发现水星公转的圆心并不是(0,0,0),这可能和欧拉角和四元数的转换有关

然后设立公转和自转,注意保证公转速度不一样

Sun.Rotate(Vector3.down * 10 * Time.deltaTime);

Mercury.RotateAround(Sun.position, a1, 50 * Time.deltaTime);
Mercury.Rotate(Vector3.down * 10 * Time.deltaTime);

Venus.RotateAround(Sun.position, a2, 30 * Time.deltaTime);
Venus.Rotate(Vector3.up * 5 * Time.deltaTime);

EarthMoon.RotateAround(Sun.position, a3, 20 * Time.deltaTime);
……

注意
地月系统没有自转,就像太阳系不应该有自转一样。他们只是空对象,地球的自转应该交给地球去完成,而不是地月系统一起完成。如果在这里犯错,结果显而易见,月球被迫跟着地球以同样的速度“自转”。月球的这种“自转”更像是月球围着地球公转

注意
有几个有趣的天文现象。如金星的自转方向与其他行星相反、天王星的自转轴是“平躺着的”
Mercury.Rotate(Vector3.down * 10 * Time.deltaTime);//正常的水星↑Venus.Rotate(Vector3.up * 5 * Time.deltaTime);//金星↑Uranus.Rotate(Vector3.forward * 30 * Time.deltaTime);//天王星↑

把上述代码拖到太阳系上

unity 题库实现 unity3d答题系统_加载_06

再把各个星体对象拖到右侧

unity 题库实现 unity3d答题系统_Mercury_07

地月系统也是类似,但是我们还要注意一个问题

注意
RotateAround作用的是世界坐标。我们看看这一行代码
Moon.RotateAround(Earth.position, a, 10 * Time.deltaTime); 如果这里的position改为localPosition,月球将会将地球在地月系统中的坐标当成地球的世界坐标。因为地球位于地月系统的中央,地球在地月系统中的坐标是(0,0,0),所以月球就围绕太阳转了,变成了行星
进一步思考
此时月球围绕太阳公转的速度是,原本月球围绕地球公转的速度,加上地月系统公转的速度

放一个效果图吧

近距离看一看地月系统,可以明显看到月球围绕地球公转

unity 题库实现 unity3d答题系统_Mercury_08

二、编程题

这次的编程题是牧师与魔鬼。游戏规则很简单,用一艘能承载两个单位的船,将三个牧师与三个魔鬼送到对岸。需要满足的条件是,在两岸和船上的牧师数量都分别不少于魔鬼(法师别送

注意
1、列出游戏中提及的事物(Objects)
2、用表格列出玩家动作表(规则表),注意,动作越少越好
3、请将游戏中对象做成预制
4、在 GenGameObjects 中创建长方形、正方形、球及其色彩代表游戏中的对象
5、使用 C# 集合类型有效组织对象
6、整个游戏仅主摄像机和一个 Empty 对象,其他对象必须代码动态生成。整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的通讯耦合语句
7、请使用课件架构图编程,不接受非 MVC 结构程序
8、注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件

1、游戏中提及的事物(Objects):Priest(牧师)、Devil(魔鬼)、船(boat)、岸(bank)
2、玩家动作表

动作

含义

点击牧师或魔鬼

如果牧师或魔鬼在岸上,且船在被点击的这一侧,且船上位置充足,牧师或魔鬼上船;如果牧师或魔鬼在船上,牧师或魔鬼下船

点击船

如果船上至少有一个牧师和魔鬼,船到另一侧

点击restart

重新开始游戏

3、游戏中的对象都是预制,比如生成water:

unity 题库实现 unity3d答题系统_加载_09

(1)MVC

MVC架构包括:

unity 题库实现 unity3d答题系统_Mercury_10

在这次作业中,应该这样理解,模型是预设的游戏对象,需要在代码中指定。控制器提供交互接口和场景控制器。具体到“牧师与魔鬼”游戏中,应该指玩家点击事件的处理,以及游戏对象的运动控制管理等。界面是GUI界面,负责处理input,也就是UserGUI

(2)Model

作业要求不能有预设的对象,所以我们应该在代码中生成游戏对象,我以下面这段代码为例:

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;
}

这是生成牧师或者魔鬼的函数。然后在FirstController中,就可以调用这些函数,动态生成对象。其他的对象也是类似的。不过牧师与魔鬼的数量不止一个,需要有一个编号的过程:

for (int i = 0; i < 3; i++)
{
	MyCharacterController priest = new MyCharacterController("priest");
	priest.setName("priest" + i);
	priest.setPosition(fromBank.getEmptyPosition());
	priest.getOnBank(fromBank);
	fromBank.getOnBank(priest);
	characters[i] = priest;
}
for (int i = 0; i < 3; i++)
{
	MyCharacterController devil = new MyCharacterController("devil");
	devil.setName("devil" + i);
	devil.setPosition(fromBank.getEmptyPosition());
	devil.getOnBank(fromBank);
	fromBank.getOnBank(devil);
	characters[i + 3] = devil;
}
(3)Controller

控制部分分为两个部分,一部分是FirstController,它负责调用具体的控制器,充当导演的角色。而具体的控制器在Controller中,充当场记的角色。我现在分开概述一下
首先是导演——FirstController。首先FirstController需要有几个场记——具体的Controller:

// 用户界面
UserGUI userGUI;
// 两个河岸的控制器
public BankController fromBank;
public BankController toBank;
// 船的控制器
public BoatController boat;
// 人物控制器
private MyCharacterController[] characters;

然后导演就可以调用场记的函数了,如之前提到的加载水、河岸、船只、人物等:

public void loadResources()
{
	// 加载水
	GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), new Vector3(0, 0.5F, 0), Quaternion.identity, null) as GameObject;
	water.name = "water";
	// 加载河岸
	fromBank = new BankController("from");
	toBank = new BankController("to");
	// 加载船只
	boat = new BoatController();

	// 加载人物
	for (int i = 0; i < 3; i++)
	{
		MyCharacterController priest = new MyCharacterController("priest");
		priest.setName("priest" + i);
		priest.setPosition(fromBank.getEmptyPosition());
		priest.getOnBank(fromBank);
		fromBank.getOnBank(priest);
		characters[i] = priest;
	}
	for (int i = 0; i < 3; i++)
	{
		MyCharacterController devil = new MyCharacterController("devil");
		devil.setName("devil" + i);
		devil.setPosition(fromBank.getEmptyPosition());
		devil.getOnBank(fromBank);
		fromBank.getOnBank(devil);
		characters[i + 3] = devil;
	}
}

还有重新开始的函数:

public void restart()
{
	boat.reset();
	fromBank.reset();
	toBank.reset();
	for (int i = 0; i < characters.Length; i++)
	{
		characters[i].reset();
	}
}

比如boat.reset();就是调用了boat控制器(场记)

不同场记对应不同的控制器,一共有:移动控制器、人物控制器、河岸控制器、船只控制器等。他们分别只负责相应对象的行为。代码较长,这里就不展示了

(4)View

用户交互界面:

void OnGUI()
{
	// 游戏进行中界面
	if(state==0)
	{
		GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 80, 100, 50), "绿色方块表示牧师,红色球表示魔鬼", style2);
	}
	// 游戏失败界面
	else if (state == 1)
	{
		GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 80, 100, 50), "Gameover", style1);
		if (GUI.Button(new Rect(Screen.width / 2 - 60, Screen.height / 2, 120, 50), "Restart", buttonStyle))
		{
			state = 0;
			action.restart();
		}
	}
	// 游戏成功界面
	else if (state == 2)
	{
		GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 80, 100, 50), "Win", style1);
		if (GUI.Button(new Rect(Screen.width / 2 - 60, Screen.height / 2, 120, 50), "Restart", buttonStyle))
		{
			state = 0;
			action.restart();
		}
	}
}

完整代码地址:[github]