一 简答并用程序验证
(1)游戏对象运动的本质
使用矩阵变换(平移,旋转,缩放)改变游戏对象的空间属性,
(2)三种方法实现物体的抛物线运动
Method1:直接修改transform中的position
public class Motion1 : MonoBehaviour {
// Use this for initialization
void Start () {
this.transform.position = new Vector3(0, -5, 0);
}
// Update is called once per frame
void Update () {
this.transform.position += Vector3.right * Time.deltaTime;
this.transform.position += Vector3.up * (5 - this.transform.position.x * this.transform.position.x)*Time.deltaTime;
}
}
Method2:使用Vector3.MoveTowards
void Update () {
Debug.Log (nowVy + Vy);
if (nowVy+Vy > 0.00001) {
Vector3 target = this.transform.position + Vector3.up * Time.deltaTime * nowVy + Vector3.left * Time.deltaTime * Vx;
this.transform.position = Vector3.MoveTowards (this.transform.position, target, Time.deltaTime);
nowVy -= 10 * Time.deltaTime;
} else {
}
}
Method3:使用transform.Translate
public float Vx;
public float Vy;
private float nowVy;
private Vector3 speed;
private Vector3 Gravity;
// Use this for initialization
void Start () {
Gravity = Vector3.zero;
speed = new Vector3 (Vx, Vy, 0);
}
// Update is called once per frame
void Update () {
if (2*Vy+Gravity.y > 0.00001) {
Gravity.y -= 10 * Time.fixedDeltaTime;
this.transform.Translate (speed*Time.fixedDeltaTime);
this.transform.Translate (Gravity*Time.fixedDeltaTime);
} else {
}
}
(3)写一个程序,实现一个完整的太阳系,其他星球围绕太阳的转速必须不一样,且不在一个法平面上。
直接上代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class plantMove : MonoBehaviour
{
public Transform Sun;
public Transform Mercury;
public Transform Venus;
public Transform Earth;
public Transform Moon;
public Transform Mars;
public Transform Jupiter;
public Transform Saturn;
public Transform Uranus;
public Transform Neptune;
// Start is called before the first frame update
void Start () {
Sun.position = Vector3.zero;
Mercury.position = new Vector3 (4, 0, 0);
Venus.position = new Vector3 (6, 0, 0);
Earth.position = new Vector3 (8, 0, 0);
Moon.position = new Vector3 (10, 0, 0);
Mars.position = new Vector3 (12, 0, 0);
Jupiter.position = new Vector3 (16, 0, 0);
Saturn.position = new Vector3 (20, 0, 0);
Uranus.position = new Vector3 (24, 0, 0);
Neptune.position = new Vector3 (28, 0, 0);
}
// Update is called once per frame
void Update () {
Vector3 a1 = new Vector3 (0, 9, 2);
Vector3 a2 = new Vector3 (0, 257, 135);
Vector3 a3 = new Vector3 (0, 45, 339);
Vector3 a4 = new Vector3 (0, 4, 9);
Vector3 a5 = new Vector3 (0, 8, 19);
Vector3 a6 = new Vector3 (0, 11, 9);
Vector3 a7 = new Vector3 (0, 6, 137);
Vector3 a8 = new Vector3 (0, 3, 13);
Vector3 a9 = new Vector3 (0, 13, 122);
Mercury.RotateAround (Sun.position, a1, 20*Time.deltaTime);
Mercury.Rotate (Vector3.up*50*Time.deltaTime);
Venus.RotateAround (Sun.position, a2, 10*Time.deltaTime);
Venus.Rotate (Vector3.up*30*Time.deltaTime);
Earth.RotateAround (Sun.position, a3, 10*Time.deltaTime);
Earth.Rotate (Vector3.up*30*Time.deltaTime);
Moon.transform.RotateAround (Earth.position, Vector3.up, 359 * Time.deltaTime);
Mars.RotateAround (Sun.position, a4, 8*Time.deltaTime);
Mars.Rotate (Vector3.up*30*Time.deltaTime);
Jupiter.RotateAround (Sun.position, a5, 7*Time.deltaTime);
Jupiter.Rotate (Vector3.up*30*Time.deltaTime);
Saturn.RotateAround (Sun.position, a6, 6*Time.deltaTime);
Saturn.Rotate (Vector3.up*30*Time.deltaTime);
Uranus.RotateAround (Sun.position, a7, 5*Time.deltaTime);
Uranus.Rotate (Vector3.up*30*Time.deltaTime);
Neptune.RotateAround (Sun.position, a8, 4*Time.deltaTime);
Neptune.Rotate (Vector3.up*30*Time.deltaTime);
}
}
具体项目跟牧师与魔鬼项目放在一起,下面是太阳系项目的MVC框架:
二 编程实践—牧师与魔鬼
游戏简介:牧师和魔鬼是一款益智游戏,你将帮助牧师和魔鬼在限定时间内过河。河的一边有三个牧师和三个魔鬼。他们都想去这条河的对岸,但是只有一艘船,这艘船每次只能载两个人。必须有一个人把船从一边开到另一边。在flash游戏中,你可以点击它们移动它们,然后点击go按钮将船移动到另一个方向。如果在河的两边,牧师的人数少于魔鬼,他们就会被杀,游戏就结束了。你可以用许多的方法来尝试。让所有牧师活着!
- 游戏中提及的事物(objects):
- 牧师
- 魔鬼
- 河
- 船
- 岸
-
用表格列出玩家动作表(规则表),注意,动作越少越好
|玩家动作 | 执行条件 |
|–|--|
| 牧师/魔鬼上船 | 船上有空位且船在岸边 |
开船 | 船上有人
牧师/魔鬼下船 | 船到达岸边 -
浅谈MVC架构
MVC架构(Model View Controller),用一种逻辑,数据,界面显示分离的方法组织代码,将业务聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。它把软件系统分为三个基本部分:
- M(Model): Unity中,Model就是游戏场景中GameObject、和它们的关系以及后台的数据模型(比如用户信息等),它们都受到相应的Controller的控制
- V(View): View这部分在游戏中起到的是与用户进行交互的功能,它们负责向用户战术游戏结果,处理GUI事件并通过IUserAction接口来与Controller进行交互。
- C(Controller): Controller控制着游戏场景中所有的对象,其负责接收来自View的用户的指令并根据用户输入的指令,更新Models以及更新View的状态,其可以被认为是连接Model与View的桥梁。
视图和控制器共同构成了用户接口。且每个视图都有一个相关的控制器组件。控制器接受输入,通常作为将鼠标移动、鼠标按钮的活动或键盘输入编码的时间。时间被翻译成模型或试图的服务器请求。用户仅仅通过控制器与系统交互。
实现一种动态的程序设计,是后序对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。
-
游戏UML框架如下:
-
接下来基于游戏框架开始编程
Director
导演的职责包括:
- 获取当前游戏的场景
- 控制场景运行、切换、入栈与出栈,暂停、恢复、退出
- 管理游戏全局状态
- 设定游戏的配置
- 设定游戏全局视图
在这个游戏中导演主要负责的是获取当前游戏场景,具体代码如下:
public class GameDirector : System.Object {
private static GameDirector _instance;
public ISceneController currentSceneController { get; set; }
public bool running { set; get; }
public static GameDirector getInstance()
{
if (_instance == null)
{
_instance = new GameDirector();
return _instance;
}
return _instance;
}
public int getFPS()
{
return Application.targetFrameRate;
}
public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}
}
Director在运行的时候只有一个实例在运行,在上面的代码中,我们可以看到其应用了单例模式保证只有一个实例生成,方便了类之间的事件通信,方便了MVC的实现。
Director作为游戏最为高层的Controller,其不具体的控制游戏中的任一对象,其通过对currentSceneController的维护,把控制游戏对象的任务交给不同的SceneController。
ISceneController
ISCeneController是游戏中的场景管理器,俗称场记,其主要职责如下:
- 管理游戏在该游戏场景中的所有的游戏对象
- 协调游戏对象、游戏对象管理器(GameObjectControllers)之间的通信响应外部输入事件
- 管理该游戏场景的所有的规则
- 管理一些其他的东西
该统一的接口的实现正是GameDirector对各个游戏场景管理的关键,他是导演控制场景的渠道,GameDirector通过对于currentSceneController的维护,可以简单实现游戏场景的各种管理
//interface of Scene
public interface ISceneController
{
void genGameObjects();
}
IUserAction
IUserAction中体现了游戏编程的门面模式,游戏外部与一个子系统的通信必须通过一个统一的门面对象进行,而我们现在实现的IUserAction就是这样的一个对象,GUI类通过IUserAction来与Controller进行通信,我们通过实现IUserAction接口来定义Controller与GUI的交互,这样我们在实现Controller类时只需要实现IUserAction对应的接口,他就可以与任意使用IUserAction接口的View即GUI类调用,GUI类也是只需调用接口中的方法即可实现与Controller的通信, 方便了我们MVC架构的实现。
public interface IUserAction {
void restart();
void ToggleBoat();
void ClickCharacter(ICharacterController chracter);
}
UserGUI
public class UserGUI : MonoBehaviour {
public int status = 0;
private IUserAction action;
GUIStyle headerStyle;
GUIStyle buttonStyle;
// Use this for initialization
void Start () {
action = GameDirector.getInstance().currentSceneController as IUserAction;
headerStyle = new GUIStyle();
headerStyle.fontSize = 40;
headerStyle.alignment = TextAnchor.MiddleCenter;
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 30;
}
// Update is called once per frame
void OnGUI () {
GUI.Label(new Rect(Screen.width / 2 - 100, 10, 200, 50), "Priests & Demons", headerStyle);
if (status == 1)
{
GUI.Label(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 90, 100, 50), "Gameover!", headerStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 65, Screen.height / 2, 140, 70), "Restart", buttonStyle))
{
status = 0;
action.restart();
}
}
else if (status == 2)
{
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 90, 100, 50), "Win!", headerStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle))
{
status = 0;
action.restart();
}
}
}
}
由上我们可以看到,UserGUI只是简单地根据游戏状态,对游戏信息等进行展示,然后通过IUserAction与Controller进行交互,这也是我们在上面所提到的,我们只需要在GUI中调用IUserAction中的方法就可以迅速实现对游戏场景的控制,下面的ClickGUI也是一样
ClickGUI
public class ClickGUI : MonoBehaviour {
IUserAction action;
ICharacterController character;
public void setController(ICharacterController characterController)
{
character = characterController;
}
void Start()
{
action = GameDirector.getInstance().currentSceneController as IUserAction;
}
void OnMouseDown()
{
if (gameObject.name == "boat")
{
action.ToggleBoat();
}
else
{
action.ClickCharacter(character);
}
}
}
在ClickGUI中,我们同样用到了IUserAction, ClickGUI监听事件的发生,然后通过IUserAction提供的类似移动船,游戏角色上下船的函数,把用户指令传给Controller进行执行。从而实现View和Controller之间的交互。
FirstController
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
readonly Vector3 riverPosition = new Vector3(0, -0.25f, 0);
UserGUI userGUI;
public LandController rightLand;
public LandController leftLand;
public BoatController boat;
public ICharacterController[] characters;
void Awake()
{
GameDirector director = GameDirector.getInstance();
director.currentSceneController = this;
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
genGameObjects();
}
public void genGameObjects()
{
characters = new ICharacterController[6];
GameObject river = Instantiate(Resources.Load("Prefabs/River", typeof(GameObject)), riverPosition, Quaternion.identity, null) as GameObject;
river.name = "river";
boat = new BoatController();
leftLand = new LandController(-1);
rightLand = new LandController(1);
for (int i = 0; i < 3; i++)
{
ICharacterController priest = new ICharacterController(0, "priest" + i);
priest.setPosition(rightLand.getEmptyPosition());
priest.getOnLand(rightLand);
rightLand.getOnLand(priest);
characters[i] = priest;
}
for (int i = 0; i < 3; i++)
{
ICharacterController demon = new ICharacterController(1, "demon" + i);
demon.setPosition(rightLand.getEmptyPosition());
demon.getOnLand(rightLand);
rightLand.getOnLand(demon);
characters[i+3] = demon;
}
}
public void ClickCharacter(ICharacterController character)
{
if (userGUI.status != 0 || !boat.available())
{
return;
}
if (character.isOnBoat()) {
LandController land;
if (boat.getBoatPos() == 0)
{
land = leftLand;
}
else
{
land = rightLand;
}
boat.getOffBoat(character.getName());
character.MoveTo(land.getEmptyPosition());
character.getOnLand(land);
land.getOnLand(character);
}
else
{
LandController land = character.getLandController();
if (boat.getEmptyIndex() == -1)
return;
int landPos = land.getType(), boatPos = (boat.getBoatPos() == 0) ? -1 : 1;
if (landPos != boatPos)
return;
land.getOffLand(character.getName());
character.MoveTo(boat.getEmptyPosition());
character.getOnBoat(boat, boat.getEmptyIndex());
boat.getOnBoat(character);
}
userGUI.status = checkResult();
}
public void ToggleBoat()
{
if (userGUI.status != 0 || boat.isEmptty())
return;
boat.Move();
userGUI.status = checkResult();
}
int checkResult()
{
int leftPriests = 0;
int rightPriests = 0;
int leftDemons = 0;
int rightDemons = 0;
int[] leftStatus = leftLand.getStatus();
leftPriests += leftStatus[0];
leftDemons += leftStatus[1];
if (leftPriests + leftDemons == 6)
return 2;
int[] rightStatus = rightLand.getStatus();
rightPriests += rightStatus[0];
rightDemons += rightStatus[1];
int[] boatStatus = boat.getBoatStatus();
if (boat.getBoatPos() == 0)
{
leftPriests += boatStatus[0];
leftDemons += boatStatus[1];
}
else
{
rightPriests += boatStatus[0];
rightDemons += boatStatus[1];
}
if (leftPriests > 0 && leftPriests < leftDemons)
return 1;
if (rightPriests > 0 && rightPriests < rightDemons)
return 1;
return 0;
}
public void restart()
{
boat.reset();
leftLand.reset();
rightLand.reset();
for (int i = 0; i < characters.Length; i++)
characters[i].reset();
}
}
Gameobject的move
public class Move : MonoBehaviour {
readonly float speed = 20;
int status;//0: 静止, 1: 处于前段移动, 2: 处于后段移动
Vector3 middle;
Vector3 destination;
void Update()
{
if (status == 1)
{
this.transform.position = Vector3.MoveTowards(this.transform.position, middle, Time.deltaTime * speed);
if (transform.position == middle)
{
status = 2;
}
}
else if (status == 2)
{
this.transform.position = Vector3.MoveTowards(this.transform.position, destination, Time.deltaTime * speed);
if (this.transform.position == destination)
{
status = 0;
}
}
}
public int getStatus()
{
return status;
}
public void moveTo(Vector3 _destination)
{
destination = _destination;
middle = _destination;
if (_destination.y == this.transform.position.y)
{
status = 2;
return;
}
else if (_destination.y < this.transform.position.y)
{
middle.y = transform.position.y;
}
else
{
middle.x = transform.position.x;
}
status = 1;
}
public void reset()
{
status = 0;
}
}
为了让物体不要直接穿过land的墙体,当character上船时,先把物体移动到destination的对应的x坐标,然后再把物体移动到对应的位置,当charcter下船时,先把物体移动到destination的对应的y坐标,然后再把物体移动到对应的位置,这样保证了物体不会穿越墙体进行移动,且实现方式较为简单。
ICharacterController
因为牧师与恶魔的动作等都是一样的,就只是外形、类型不一样,所以我把其抽象为一个Controller,通过type区分它们
public class ICharacterController
{
readonly GameObject character;
readonly Move moveAction;
readonly int type;//0: Priest, 1: Demon
readonly ClickGUI clickGUI;
bool onBoat;
LandController landController;
public ICharacterController(int chracterType, string name)
{
if (chracterType == 0)
{
this.character = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
this.type = 0;
}
else
{
this.character = Object.Instantiate(Resources.Load("Prefabs/Demon", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
this.type = 1;
}
character.name = name;
moveAction = character.AddComponent(typeof(Move)) as Move;
clickGUI = character.AddComponent(typeof(ClickGUI)) as ClickGUI;
clickGUI.setController(this);
}
public string getName()
{
return character.name;
}
public void setPosition(Vector3 pos)
{
character.transform.position = pos;
}
public void MoveTo(Vector3 destination)
{
moveAction.moveTo(destination);
}
public int getType()
{
return type;
}
public void getOnBoat(BoatController boatController, int pos)
{
landController = null;
if (pos == 0)
{
character.transform.rotation = Quaternion.AngleAxis(90, Vector3.up);
}
else
{
character.transform.rotation = Quaternion.AngleAxis(270, Vector3.up);
}
character.transform.parent = boatController.getBoat().transform;
onBoat = true;
}
public void getOnLand(LandController land)
{
landController = land;
if (land.getType() == -1)
{
character.transform.rotation = Quaternion.AngleAxis(90, Vector3.up);
}
else
{
character.transform.rotation = Quaternion.AngleAxis(270, Vector3.up);
}
character.transform.parent = null;
onBoat = false;
}
public bool isOnBoat()
{
return onBoat;
}
public LandController getLandController()
{
return landController;
}
public void reset()
{
moveAction.reset();
landController = (GameDirector.getInstance().currentSceneController as FirstController).rightLand;
getOnLand(landController);
setPosition(landController.getEmptyPosition());
landController.getOnLand(this);
}
}
CharcterController类的实现十分简单,就是根据type导入预制,然后再实现上船,上岸,重置以及一些其他的get, set函数就完成了,具体可以看以上代码的实现。
BoatController
public class BoatController {
readonly GameObject boat;
readonly Move moveAction;
readonly Vector3 right = new Vector3(3.5f, 0, 0);
readonly Vector3 left = new Vector3(-3.5f, 0, 0);
readonly Vector3[] right_positions;
readonly Vector3[] left_positions;
int status;// 0: left, 1: right
ICharacterController[] characterOnBoat = new ICharacterController[2];
public BoatController()
{
status = 1;
right_positions = new Vector3[] { new Vector3(2.5F, 0.2F, 0), new Vector3(4.5F, 0.2F, 0) };
left_positions = new Vector3[] { new Vector3(-4.5F, 0.2F, 0), new Vector3(-2.5F, 0.2F, 0) };
boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), right, Quaternion.identity, null) as GameObject;
boat.name = "boat";
moveAction = boat.AddComponent(typeof(Move)) as Move;
boat.AddComponent(typeof(ClickGUI));
}
public void Move()
{
if (status == 1)
{
moveAction.moveTo(left);
}
else
{
moveAction.moveTo(right);
}
status = 1 - status;
}
public int getEmptyIndex()
{
for (int i = 0; i < characterOnBoat.Length; i++)
{
if (characterOnBoat[i] == null)
{
return i;
}
}
return -1;
}
public Vector3 getEmptyPosition()
{
int index = getEmptyIndex();
if (status == 0)
{
return left_positions[index];
}
else
{
return right_positions[index];
}
}
public bool isEmptty()
{
for (int i = 0; i < characterOnBoat.Length; i++)
{
if (characterOnBoat[i] != null)
{
return false;
}
}
return true;
}
public void getOnBoat(ICharacterController character)
{
characterOnBoat[getEmptyIndex()] = character;
}
public ICharacterController getOffBoat(string name)
{
for (int i = 0; i < characterOnBoat.Length; i++)
{
if (characterOnBoat[i] != null && characterOnBoat[i].getName() == name)
{
ICharacterController character = characterOnBoat[i];
characterOnBoat[i] = null;
return character;
}
}
return null;
}
public GameObject getBoat()
{
return boat;
}
public int getBoatPos()
{
return status;
}
public int[] getBoatStatus()
{
int[] boatStatus = { 0, 0 };
for (int i = 0; i < characterOnBoat.Length; i++)
{
if (characterOnBoat[i] == null)
continue;
boatStatus[characterOnBoat[i].getType()]++;
}
return boatStatus;
}// 0: Priests, 1: Demon
public bool available()
{
return (moveAction.getStatus() == 0);
}
public void reset()
{
moveAction.reset();
if (status == 0)
{
Move();
}
characterOnBoat = new ICharacterController[2];
}
}
Boat的实现也差不多,只需要实现初始化、移动(ToggleBoat),character上船、下船相关的函数就可以实现了。
LandController
public class LandController {
readonly GameObject land;
readonly Vector3 leftPos = new Vector3(-8.5f, 0f, 0f);
readonly Vector3 rightPos = new Vector3(8.5f, 0f, 0f);
readonly Vector3[] landPositions;
readonly int type;// 1:right, -1: left
ICharacterController[] characterOnLand;
public LandController(int _type)
{
landPositions = new Vector3[] { new Vector3(6F,0.5F,0), new Vector3(7F,0.5F,0), new Vector3(8F,0.5F,0),
new Vector3(9F,0.5F,0), new Vector3(10F,0.5F,0), new Vector3(11F,0.5F,0) };
characterOnLand = new ICharacterController[6];
type = _type;
if (type == 1)
{
land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), rightPos, Quaternion.identity, null) as GameObject;
land.name = "rightLand";
}
else
{
land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), leftPos, Quaternion.identity, null) as GameObject;
land.name = "leftLand";
}
}
public Vector3 getEmptyPosition()
{
Vector3 pos = landPositions[getEmptyIndex()];
pos.x *= type;
return pos;
}
public int getEmptyIndex()
{
for (int i = 0; i < characterOnLand.Length; i++)
{
if (characterOnLand[i] == null)
return i;
}
return -1;
}
public void getOnLand(ICharacterController chracter)
{
characterOnLand[getEmptyIndex()] = chracter;
}
public ICharacterController getOffLand(string name)
{
for (int i = 0; i < characterOnLand.Length; i++)
{
if (characterOnLand[i] != null && characterOnLand[i].getName() == name)
{
ICharacterController tmp = characterOnLand[i];
characterOnLand[i] = null;
return tmp;
}
}
return null;
}
public int getType()
{
return type;
}
public int[] getStatus()
{
int[] status = { 0, 0 };
for (int i = 0; i < characterOnLand.Length; i++)
{
if (characterOnLand[i] == null)
continue;
status[characterOnLand[i].getType()]++;
}
return status;
}// 0: priests, 1: Demon
public void reset()
{
characterOnLand = new ICharacterController[6];
}
}
landController的实现和上面的Controller也类似,也是实现初始化,上岸,下岸的相关函数就完成了
这次游戏的实现将接口和Director放在同一个脚本的同一命名空间下,所以总共只有三个脚本,等之后对项目进行扩展时再进行扩充