目录
1. 基本操作演练
- 下载 Fantasy Skybox FREE, 构建自己的游戏场景
- 写一个简单的总结,总结游戏对象的使用
__游戏对象__是 Unity 中的基础对象,表示角色、道具和景物。它们本身并没有取得多大作为,但它们充当__组件__的容器,而组件可实现真正的功能。(例如,通过将光源组件附加到游戏对象来创建光源对象。)
游戏对象是许多不同组件的容器。默认情况下,所有游戏对象都自动拥有__变换__(Transform)组件。这是因为变换组件可指示游戏对象的位置以及是如何旋转和缩放的。如果没有变换组件,游戏对象将在世界中没有位置。
__组件__是游戏中对象和行为的基本要素。它们是每个__游戏对象__的功能单元。可以将任意数量的组件或组件的组合附加到单个游戏对象。某些组件与其他组件结合使用效果最佳。例如,刚体可与任何碰撞体 (Collider) 配合使用。刚体通过 NVIDIA PhysX 物理引擎来控制变换组件,而碰撞体允许刚体与其他碰撞体碰撞和交互。
创建脚本并将脚本附加到__游戏对象__时,脚本将显示在__游戏对象__的 Inspector 中,就像内置__组件__一样。这是因为,将脚本保存到项目后,脚本也变成了__组件__。
2. 编程实践——牧师与魔鬼 动作分离版
2.1 设计说明
相较于上一版本,该版本主要做了如下改动:
- 把每个需要移动的游戏对象的移动方法提取出来,建立一个动作管理器来管理不同的移动方法。
- 在上一个版本中,每一个可移动的游戏对象都有相应的移动脚本,当游戏对象需要移动时候,游戏对象自己调用相应脚本中的方法让自己移动。而该版本则剥夺了游戏对象自己调用动作的能力,建立一个动作管理器,通过场景控制器把需要移动的游戏对象传递给动作管理器,让动作管理器去移动游戏对象。
- 增加裁判类,使用裁判类中的方法判断游戏是否结束,当游戏结束时通知场景控制器。
改进后的动作管理部分UML类图如下:
2.2 动作管理类实现
2.2.1 动作事件接口 ISSActionCallback
动作、__动作管理器__的接口,当动作完成时,动作会调用这个接口的方法,通知其管理者该动作已完成(便于动作管理器处理下一动作)。
public interface ISSActionCallback {
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
2.2.2 动作管理器基类 SSActionManager
SSActionManager 对场景中动作的执行进行管理,可以通过给 SSActionManager 传递游戏对象,使对象执行一定的动作或组合动作,并且控制着动作的切换。SSActionManager 继承了 ISSActionCallback 接口,通过这个接口,当动作或组合动作完成时,动作类会稿纸通知 SSActionManager,然后 SSActionManager 去决定如何执行下一个动作。
public class SSActionManager : MonoBehaviour, ISSActionCallback //action管理器
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //将执行的动作的字典集合,int为key,SSAction为value
private List<SSAction> waitingAdd = new List<SSAction>(); //等待去执行的动作列表
private List<int> waitingDelete = new List<int>(); //等待删除的动作的key
protected void Update() {
foreach(SSAction ac in waitingAdd) {
actions[ac.GetInstanceID()] = ac; //获取动作实例的ID作为key
}
waitingAdd.Clear();
foreach(KeyValuePair<int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if(ac.destroy) {
waitingDelete.Add(ac.GetInstanceID());
}
else if(ac.enable) {
ac.Update();
}
}
foreach(int key in waitingDelete) {
SSAction ac = actions[key];
actions.Remove(key);
Object.Destroy(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
//牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空
}
}
2.2.3 场景动作管理类 MySceneActionManager
该类继承动作管理器基类 SSActionManager,定义且实现了牧师与魔鬼游戏场景中角色上下船、移动船两个动作的实现,其中角色上下船为组合动作,而船的移动为普通动作。对不同的游戏对象可以调用不同的方法,使游戏对象完成其相应的移动。
public class MySceneActionManager : SSActionManager //本游戏管理器
{
private SSMoveToAction moveBoatToEndOrStart; //移动船到结束岸,移动船到开始岸
private SequenceAction moveRoleToLandorBoat; //移动角色到陆地,移动角色到船上
public Controller sceneController;
protected void Start() {
sceneController = (Controller)SSDirector.GetInstance().CurrentScenceController;
sceneController.actionManager = this;
}
public void moveBoat(GameObject boat, Vector3 target, float speed) {
moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);
this.RunAction(boat, moveBoatToEndOrStart, this);
}
public void moveRole(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed) {
SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);
SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);
moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
this.RunAction(role, moveRoleToLandorBoat, this);
}
}
2.2.3 动作基类 SSAction
SSAction 是所有动作的基类。SSAction 继承了 ScriptableObject,代表 SSAction 不需要绑定 GameObject 对象,且受Unity引擎场景管理。在该类中定义了动作的基本数据成员和虚方法。
public class SSAction : ScriptableObject //动作
{
public bool enable = true; //是否正在进行此动作
public bool destroy = false; //是否需要被销毁
public GameObject gameobject; //动作对象
public Transform transform; //动作对象的transform
public ISSActionCallback callback; //回调函数
protected SSAction() { } //保证SSAction不会被new
public virtual void Start() //子类可以使用这两个函数
{
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
2.2.4 移动动作类 SSMoveToAction
该类是动作基类 SSAction 的子类,定义了移动动作的实现,能够使游戏对象从当前位置移动到所设置的目的地处,无论是船的移动,还是牧师、魔鬼上下船的动作,都是基于该类的方法完成的。
public class SSMoveToAction : SSAction //移动
{
public Vector3 target; //移动到的目的地
public float speed; //移动的速度
private SSMoveToAction() { }
public static SSMoveToAction GetSSAction(Vector3 target, float speed) {
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();//让unity自己创建一个MoveToAction实例,并自己回收
action.target = target;
action.speed = speed;
return action;
}
public override void Update() {
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
if(this.transform.position == target) {
this.destroy = true;
this.callback.SSActionEvent(this); //告诉动作管理或动作组合这个动作已完成
}
}
public override void Start() {
//移动动作建立时候不做任何事情
}
}
2.2.5 组合动作类 SequenceAction
组合动作类 SequenceAction 是动作基类 SSAction 的子类,并且实现了 ISSActionCallback 接口。该类既是一个动作,也是一个动作管理器,因为它既可以表示组合动作这一整体,也管理着组成这一组动作的诸多小动作的执行与切换。
public class SequenceAction : SSAction, ISSActionCallback {
public List<SSAction> sequence; //动作的列表
public int repeat = -1; //-1就是无限循环做组合中的动作
public int start = 0; //当前做的动作的索引
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence) {
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
public override void Update() {
if(sequence.Count == 0)
return;
if(start < sequence.Count) {
sequence[start].Update(); //一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现
}
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
source.destroy = false; //先保留这个动作,如果是无限循环动作组合之后还需要使用
this.start++;
if(this.start >= sequence.Count) {
this.start = 0;
if(repeat > 0)
repeat--;
if(repeat == 0) {
this.destroy = true; //整个组合动作删除
this.callback.SSActionEvent(this); //告诉组合动作的管理对象组合做完了
}
}
}
public override void Start() {
foreach(SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this; //组合动作的每个小的动作的回调是这个组合动作
action.Start();
}
}
void OnDestroy() {
//如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
}
}
2.2.6 场景控制器 Controllor
当 Controllor 接收到移动游戏对象的信息的时候,只要将相应的参数和游戏对象传给 MySceneActionManager 就可以进行令游戏对象进行移动。
public class Controller : MonoBehaviour, ISceneController, IUserAction {
public LandModel start_land; //开始陆地
public LandModel end_land; //结束陆地
public BoatModel boat; //船
private RoleModel[] roles; //角色
UserGUI user_gui;
public MySceneActionManager actionManager; //动作管理
private Referee referee; //裁判
void Start() {
...
}
public void LoadResources() //创建水,陆地,角色,船
{
...
}
public void MoveBoat() //移动船
{
if(boat.IsEmpty() || user_gui.sign != 0)
return;
actionManager.moveBoat(boat.getGameObject(), boat.BoatMoveToPosition(), boat.move_speed);
user_gui.sign = referee.Check(boat, (start_land.GetRoleNum())[0], (start_land.GetRoleNum())[1],
(end_land.GetRoleNum())[0], (end_land.GetRoleNum())[1]);
}
public void MoveRole(RoleModel role) //移动角色
{
if(user_gui.sign != 0)
return;
if(role.IsOnBoat()) {
LandModel land;
if(boat.GetBoatSign() == -1)
land = end_land;
else
land = start_land;
boat.DeleteRoleByName(role.GetName());
Vector3 end_pos = land.GetEmptyPosition();
Vector3 middle_pos = new Vector3(role.getGameObject().transform.position.x, end_pos.y, end_pos.z);
actionManager.moveRole(role.getGameObject(), middle_pos, end_pos, role.move_speed);
role.GoLand(land);
land.AddRole(role);
}
else {
LandModel land = role.GetLandModel();
if(boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign())
return; //船没有空位,也不是船停靠的陆地,就不上船
land.DeleteRoleByName(role.GetName());
Vector3 end_pos = boat.GetEmptyPosition();
Vector3 middle_pos = new Vector3(end_pos.x, role.getGameObject().transform.position.y, end_pos.z);
actionManager.moveRole(role.getGameObject(), middle_pos, end_pos, role.move_speed);
role.GoBoat(boat);
boat.AddRole(role);
}
user_gui.sign = referee.Check(boat, (start_land.GetRoleNum())[0], (start_land.GetRoleNum())[1],
(end_land.GetRoleNum())[0], (end_land.GetRoleNum())[1]);
}
public void Restart() {
...
}
}
2.2.7 裁判类 Referee
裁判类用于判断游戏是否达到结束条件,当游戏结束时,将结束信息通知场景控制器,场景控制器将结束游戏。
public class Referee {
public int Check(BoatModel boat, int start_priest, int start_devil, int end_priest, int end_devil) {
if(end_priest + end_devil == 6) //获胜
return 2;
int[] boat_role_num = boat.GetRoleNumber();
if(boat.GetBoatSign() == 1) //在开始岸和船上的角色
{
start_priest += boat_role_num[0];
start_devil += boat_role_num[1];
}
else //在结束岸和船上的角色
{
end_priest += boat_role_num[0];
end_devil += boat_role_num[1];
}
if(start_priest > 0 && start_priest < start_devil) //失败
{
return 1;
}
if(end_priest > 0 && end_priest < end_devil) //失败
{
return 1;
}
return 0; //未完成
}
}