一、下载Fantasy Skybox FREE,构建自己的游戏场景
- 在unity资源商店搜索Fantasy Skybox FREE,找到相应的资源并打开页面,点击页面右侧的添加至我的资源
2. 在我的资源中点击在unity中打开,系统将自动启动unity的Package Manager,找到Fantasy Skybox FREE,点击download下载
3. 下载好以后点击import,导入材质包
4. 在菜单栏中选择Window->Rendering->Lighting,打开后再选择Environment,在Skybox Material字段选择喜欢的天空盒材质
5. 选好以后关闭选项卡,即可看到效果如下图所示:
二、牧师与魔鬼 - 动作分离版
- 改进思路
在上一个版本的牧师与魔鬼实现中,我们的控制器要管理的事情太多,不仅要处理用户交互事件,还要游戏加载、游戏规则实现、运动实现等等,显得非常臃肿。
为了改进代码结构,在本次实现中,我们采用 cocos2d 的方案,用一组简单的动作组合成复杂的动作,建立与 CCAtion 类似的类。设计思路如下:
1.设计一个动作管理类来输出组合好的几个简单动作,使得动作的组成成为动作模块内部的事务
2.设计一个抽象类来代表基本动作和组合动作,然后按照组合模式的设计方法实现动作组合
3.通过回调,实现动作完成时的通知。
对比上一个版本的游戏实现,本次设计可以方便地定义动作并实现动作的自由组合,使得:
1.程序更能适应需求变化;
2.对象更容易被复用;
3.程序更易于维护。
改进后动作管理部分类图如下:
2. 代码结构优化
本次我们要设计牧师与魔鬼的动作分离版,以下是具体优化及实现。
- 场景控制与模型控制之中的修改
由于采用动作分离,所以BoatMove函数不再需要直接在模型中对船的移动进行控制,反之,我们现在只需要让BoatMove函数返回需要移动的位置,然后交给动作控制器进行处理即可。
//改变船的位置
public Vector3 BoatMove()
{
if (boat_sign == -1)
{
boat_sign = 1;
return new Vector3(-0.2F, 0.05F, -0.35F);
}
else
{
boat_sign = -1;
return new Vector3(-0.2F, 0.05F, -2.2F);
}
}
由于BoatMove函数的修改,现在我们在场景控制中只需要调用上述函数得知物体移动的位置,再通过动作管理即可实现对应位置的移动动作。
public void MoveBoat()
{
if (boat.IsEmpty() || user_gui.sign != 0) return;
actionManager.moveBoat(boat.getGameObject(), boat.BoatMove(), boat.move_speed);
user_gui.sign = judge.Check();
}
与船的动作管理相同,调用动作管理中的moveRole函数就可以执行一系列的人物移动动作。
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());
actionManager.moveRole(role.getGameObject(), new Vector3(role.getGameObject().transform.position.x, land.GetEmptyPosition().y, land.GetEmptyPosition().z), land.GetEmptyPosition(), 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());
actionManager.moveRole(role.getGameObject(), new Vector3(boat.GetEmptyPosition().x, role.getGameObject().transform.position.y, boat.GetEmptyPosition().z), boat.GetEmptyPosition(), role.move_speed);
role.GoBoat(boat);
boat.AddRole(role);
}
user_gui.sign = judge.Check();
}
需要注意的是,由于我们有了动作管理器,这本质上替代了之前move类的工作,因此我们就可以将之前的move类删去。
- 动作基类SSAction类
由上面的UML图我们可以知道,SSAction类是所有动作的基类,SSAction继承了ScriptableObject,这里的ScriptableObject 是不需要绑定 GameObject 对象的可编程基类。这些对象受 Unity 引擎场景管理。除此之外,通过protected 可以防止用户自己 new 对象;使用 virtual 声明虚方法,通过重写实现多态。这样继承者就明确使用 Start 和 Update 编程游戏对象行为;利用接口(ISSACtionCallback)实现消息通知,避免与动作管理者直接依赖。
动作基类SSAction的子类有Sequence Action和MoveToAction,根据门面模式将其功能抽象为SSActionManager。
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
- 简单动作实现MoveToAction
MoveToAction类负责实现某一具体的动作,将一个物体移动到目标位置,并通知任务完成。这里的设计要点是让 Unity 创建动作类,确保内存正确回收。另外就是当动作完成时,需要发出事件通知管理者,并期望管理程序自动回收运行对象。
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>();
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()
{
}
}
- 顺序动作组合类实现SequenceAction
SequenceAction类负责实现一个动作组合序列,顺序播放动作,设计要点有:
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1;
public int start = 0;
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
{
SequenceAction action = ScriptableObject.CreateInstance<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();
}
}
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()
{
}
}
- 动作事件接口定义
接口作为接收通知对象的抽象类型,设计要点有:事件类型定义,使用了枚举变量;定义了事件处理接口,所有事件管理者都必须实现这个接口,来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
- 动作管理基类 SSActionManager
这是动作对象管理器的基类,实现了所有动作的基本管理。它通过创建MonoBehaviour来管理一个动作集合,动作做完自动回收动作。该类拥有一个核心方法RunAction,通过该方法来添加新动作,使得游戏对象与动作之间得到绑定,并且绑定该动作事件的消息接收者。
public class SSActionManager : MonoBehaviour, ISSActionCallback
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction>();
private List<int> waitingDelete = new List<int>();
protected void Update()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
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);
DestroyObject(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)
{
}
}
- 场景下动作管理MySceneActionManager
MySceneActionManager类负责当前场景下的动作管理的具体实现,在场景控制类中调用它的方法,实现对当前场景的动作管理,详情见下面的代码注释:
//需要管理两个动作,一个是场景中船的移动,还有一个动作是人物的移动
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);
}
}
最后是设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束,具体功能见下面的代码注释:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using modelcon;
public class Judge
{
LandModel start_land;
LandModel end_land;
BoatModel boat;
//构造函数初始化
public Judge(LandModel start_, LandModel end_, BoatModel boat_)
{
start_land = start_;
end_land = end_;
boat = boat_;
}
//检查游戏是否满足结束条件
public int Check()
{
int start_priest = (start_land.GetRoleNum())[0];
int start_devil = (start_land.GetRoleNum())[1];
int end_priest = (end_land.GetRoleNum())[0];
int end_devil = (end_land.GetRoleNum())[1];
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;
}
}
修改完毕后运行游戏,效果图如下所示:
三、参考资料
[1]. unity官方文档 [2]. 师兄的博客(牧师与魔鬼)