简答
- 游戏对象运动的本质是什么?
游戏对象运动的本质是随着每一帧刷新,对象的positoin、rotation离散地进行变化。
- 请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)
无论用什么样的方法,我们都要让运动满足抛物线的定义。即x轴上做匀速直线运动,y轴上匀变速直线运动。所以首先我们都要定义好的就是x轴上的速度,y轴上的加速度和初始速度。
首先是最简单的通过vector3向量来改变position。
public class test : MonoBehaviour {
public float vY = 3.0f;
public float aY = 1.0f;
public float vX = 1.0f;
void Start(){
Debug.Log("Start!");
}
void Update(){
Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
this.transform.position += change;
vY -= aY * Time.deltaTime;
}
}
然后是使用Vector3.Lerp()函数。
public class test : MonoBehaviour {
public float vY = 3.0f;
public float aY = 1.0f;
public float vX = 1.0f;
void Start(){
Debug.Log("Start!");
}
void Update(){
Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
this.transform.postion = Vector3.Lerp(this.transform.position, this.transform.position + move, 1);
vY -= aY * Time.deltaTime;
}
}
最后一种方法是使用transform.translate()函数
public class test : MonoBehaviour {
public float vY = 3.0f;
public float aY = 1.0f;
public float vX = 1.0f;
void Start(){
Debug.Log("Start!");
}
void Update(){
Vector3 change = new Vector3(Time.deltaTime * vX, Time.deltaTime * vY, 0);
this.transform.Translate(change);
vY -= aY * Time.deltaTime;
}
}
- 写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。
首先需要分别创建太阳系中所有的恒星和行星,在Start中导入并确认Sun的位置。然后在Update中用RotateAround实现公转,Rotate实现自传。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript1 : MonoBehaviour
{
GameObject Sun, Moon, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune;
void Start(){
Sun = GameObject.Find("Sun");
Moon = GameObject.Find("Moon");
Mercury = GameObject.Find("Mercury");
Venus = GameObject.Find("Venus");
Earth = GameObject.Find("Earth");
Mars = GameObject.Find("Mars");
Jupiter = GameObject.Find("Jupiter");
Saturn = GameObject.Find("Saturn");
Uranus = GameObject.Find("Uranus");
Neptune = GameObject.Find("Neptune");
}
void Update(){
Sun.transform.Rotate(Vector3.up * Time.deltaTime * 5 );
Mercury.transform.RotateAround(Sun.transform.position, new Vector3(0.1f, 1, 0), 60 * Time.deltaTime);
Mercury.transform.Rotate(Vector3.up * 10000/58 * Time.deltaTime);
Venus.transform.RotateAround(Sun.transform.position, new Vector3(0, 1, -0.1f), 55 * Time.deltaTime);
Venus.transform.Rotate(Vector3.up * 10000/243 * Time.deltaTime);
Earth.transform.RotateAround(Sun.transform.position, Vector3.up, 50 * Time.deltaTime);
Earth.transform.Rotate(Vector3.up * 10000 * Time.deltaTime);
Moon.transform.RotateAround(Earth.transform.position, Vector3.up, 5 * Time.deltaTime);
Moon.transform.Rotate(Vector3.up * 10000/27 * Time.deltaTime);
Mars.transform.RotateAround(Sun.transform.position, new Vector3(0.2f, 1, 0), 45 * Time.deltaTime);
Mars.transform.Rotate(Vector3.up * 10000 * Time.deltaTime);
Jupiter.transform.RotateAround(Sun.transform.position, new Vector3(-0.1f, 2, 0), 38 * Time.deltaTime);
Jupiter.transform.Rotate(Vector3.up * 10000/0.3f * Time.deltaTime);
Saturn.transform.RotateAround(Sun.transform.position, new Vector3(0, 1, 0.2f), 36 * Time.deltaTime);
Saturn.transform.Rotate(Vector3.up * 10000/0.4f * Time.deltaTime);
Uranus.transform.RotateAround(Sun.transform.position, new Vector3(0, 2, 0.1f), 35 * Time.deltaTime);
Uranus.transform.Rotate(Vector3.up * 10000/0.6f * Time.deltaTime);
Neptune.transform.RotateAround(Sun.transform.position, new Vector3(-0.1f, 1, -0.1f), 33 * Time.deltaTime);
Neptune.transform.Rotate(Vector3.up * 10000/0.7f * Time.deltaTime);
}
}
牧师与魔鬼
所谓MVC架构,是指将程序分为三个部分:
- 模型(Model):数据对象及关系
- 游戏对象、空间关系
- 控制器(Controller):接受用户事件,控制模型的变化
- 一个场景一个主控制器
- 至少实现与玩家交互的接口(IPlayerAction)
- 实现或管理运动
- 界面(View):显示模型,将人机交互事件交给控制器处理
- 处收 Input 事件
- 渲染 GUI ,接收事件
简单来说,MVC架构很好地降低了系统的耦合性,是一种好的设计。在接下来这个小游戏的设计中,我们也采用这种架构,具体的UML图大概如下图:
其中场景中的所有GameObject就是MVC中的Model,也即上图中被FirstController直接控制的GameModels,包括boat、coast以及character;同时为了降低耦合性,每种Object都有对应的controller,最终整合在FirstController中,也即MVC中的C。而MVC中的V是指View,就是GUI界面,具体在这次的程序中就是UserGUI和ClickGUI,用于与用户进行交互。
在具体的代码中,director就直接使用单例模式。
public class SSDirector : System.Object {
// singlton instance
private static SSDirector _instance;
public SceneController currentSceneController { get; set; }
public bool running { get; set; }
// get instance anytime anywhere!
public static SSDirector getInstance() {
if (_instance == null) {
_instance = new SSDirector ();
}
return _instance;
}
}
然后是ISceneControl和IUserAction两个接口。这里结合应用场景,用户的行为只有点击按钮移动船,点击character移动上船下船,以及点击按钮重置;而SceneControl要做的也就只有加载资源。
public interface IUserAction {
void moveBoat();
void characterIsClicked(MyCharacterController characterCtrl);
void restart();
}
public interface ISceneController {
void loadResources ();
}
接下来就是针对三种对象分别写三个controller,分别写上需要的操作函数,然后整合在firstController中。这里就不把代码全部贴上来了,我们直接看firstController。
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
readonly Vector3 water_pos = new Vector3(0,0.5F,0);
UserGUI userGUI;
public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
private MyCharacterController[] characters;
void Awake() {
SSDirector director = SSDirector.getInstance ();
director.currentSceneController = this;
userGUI = gameObject.AddComponent <UserGUI>() as UserGUI;
characters = new MyCharacterController[6];
loadResources ();
}
public void loadResources() {
GameObject water = Instantiate (Resources.Load ("Perfabs/Water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
water.name = "water";
fromCoast = new CoastController ("from");
toCoast = new CoastController ("to");
boat = new BoatController ();
// load characters
for (int i = 0; i < 3; i++) {
MyCharacterController newCharacter = new MyCharacterController ("priest");
newCharacter.setName("priest" + i);
newCharacter.setPosition (fromCoast.getEmptyPosition ());
newCharacter.getOnCoast (fromCoast);
fromCoast.getOnCoast (newCharacter);
characters [i] = newCharacter;
}
for (int i = 0; i < 3; i++) {
MyCharacterController newCharacter = new MyCharacterController ("devil");
newCharacter.setName("devil" + i);
newCharacter.setPosition (fromCoast.getEmptyPosition ());
newCharacter.getOnCoast (fromCoast);
fromCoast.getOnCoast (newCharacter);
characters [i+3] = newCharacter;
}
}
public void moveBoat() {
if (boat.isEmpty ())
return;
boat.Move ();
userGUI.status = check_game_over ();
}
public void characterIsClicked(MyCharacterController characterCtrl) {
if (characterCtrl.isOnBoat ()) {
CoastController whichCoast;
if (boat.get_to_or_from () == -1) { // to->-1; from->1
whichCoast = toCoast;
} else {
whichCoast = fromCoast;
}
boat.GetOffBoat (characterCtrl.getName());
characterCtrl.moveToPosition (whichCoast.getEmptyPosition ());
characterCtrl.getOnCoast (whichCoast);
whichCoast.getOnCoast (characterCtrl);
} else { // character on coast
CoastController whichCoast = characterCtrl.getCoastController ();
if (boat.getEmptyIndex () == -1) { // boat is full
return;
}
if (whichCoast.get_to_or_from () != boat.get_to_or_from ()) // boat is not on the side of character
return;
whichCoast.getOffCoast(characterCtrl.getName());
characterCtrl.moveToPosition (boat.getEmptyPosition());
characterCtrl.getOnBoat (boat);
boat.getOnBoat (characterCtrl);
}
userGUI.status = check_game_over ();
}
int check_game_over() { // 0->not finish, 1->lose, 2->win
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
int[] fromCount = fromCoast.getCharacterNum ();
from_priest += fromCount[0];
from_devil += fromCount[1];
int[] toCount = toCoast.getCharacterNum ();
to_priest += toCount[0];
to_devil += toCount[1];
if (to_priest + to_devil == 6) // win
return 2;
int[] boatCount = boat.getCharacterNum ();
if (boat.get_to_or_from () == -1) { // boat at toCoast
to_priest += boatCount[0];
to_devil += boatCount[1];
} else { // boat at fromCoast
from_priest += boatCount[0];
from_devil += boatCount[1];
}
if (from_priest < from_devil && from_priest > 0) { // lose
return 1;
}
if (to_priest < to_devil && to_priest > 0) {
return 1;
}
return 0; // not finish
}
public void restart() {
boat.reset ();
fromCoast.reset ();
toCoast.reset ();
for (int i = 0; i < characters.Length; i++) {
characters [i].reset ();
}
}
}
每次启动首先调用Awake函数,创建导演等实例,然后加载资源。其他的就是一些完成程序运行的功能函数如chek_game_over()来检查游戏是否结束以及最终的游戏结果。另外值得一提的是移动character的部分,为了降低耦合性,要分别调用三个对象对应的函数,包括载coast中移除或加入character、移动character本身,以及在船上移除或加入character。然后在每一次移动了character或者boat之后,都要做一次check_game_over的判定。
还有值得一提的是,为了降低代码的重复性,这里将船和人与移动有关的代码封装到一个movable类中,然后在两个controller中分别调用。
/*-----------------------------------Moveable------------------------------------------*/
public class Moveable: MonoBehaviour {
readonly float move_speed = 20;
// change frequently
int moving_status; // 0->not moving, 1->moving to middle, 2->moving to dest
Vector3 dest;
Vector3 middle;
void Update() {
if (moving_status == 1) {
transform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);
if (transform.position == middle) {
moving_status = 2;
}
} else if (moving_status == 2) {
transform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);
if (transform.position == dest) {
moving_status = 0;
}
}
}
public void setDestination(Vector3 _dest) {
dest = _dest;
middle = _dest;
if (_dest.y == transform.position.y) { // boat moving
moving_status = 2;
}
else if (_dest.y < transform.position.y) { // character from coast to boat
middle.y = transform.position.y;
} else { // character from boat to coast
middle.x = transform.position.x;
}
moving_status = 1;
}
public void reset() {
moving_status = 0;
}
}
最终游戏的效果如下图所示: