其实这个操作很简单,只要在Asset Store中搜索Fantasy Skybox FREE,然后在Camera中添加Component,然后添加Skybox,再将相应的Skybox图案添加上去,就能够完成了。
添加天空盒具体的操作可以参照下面左图的图示,在主摄像机Main Camara中添加组件Component->Rendering->Skybox.
将这个组件添加以后,能够看到如上边右图所示的属性栏,其中有一个Custom Skybox的属性,将自己喜欢的天空盒模型拖拽到里面,然后按运行,就能够得到自己喜欢的场景了,看到一幅比较逼真的画面,比上次制作太阳系制作的盒子创造的背景好看多了,同时也省去了下面的牧师与魔鬼的背景载入器。
效果图如下所视:
这是在添加了Terrain后的场景示意图,可以在Terrain中创造一些自己的场景,具体的一些方法可以在下面的图示中看到,在这些设置中,添加了自己的assets以后,就能够创造出自己喜欢的树,草,地板等样式,这样一个游戏环境就能够大概的创造出来,而这个过程中并没有自己去写代码。
游戏对象主要包括:空对象,摄像机,光线,3D物体,声音,UI基于事件的new UI系统和粒子系统与特效
- 空对象(Empty):不显示却是最常用的对象之一
- 摄像机(Camara):观察游戏世界的窗口
- 光线(Light):游戏世界的光源,让游戏世界富有魅力
- 3D物体 :3D游戏中的重要组成部分,可以改变其网格和材质,三角网格是游戏物体表面的唯一形式
- 声音(Audio):3D游戏中的声音来源
本次作业需要在上次作业的基础上,将场记中的动作分离出来,首先要创建动作管理器
动作管理是游戏内容的重要内容,这次作业要搞清楚简单动作的组合问题,实现动作管理器,实现基础动作类,回调实现动作完成通知(经典方法),其规划与设计的UML图如下所示:
动作基类(SSAction)的设计,在下面代码中
ScriptableObject是不需要绑定GameObject对象的可编程基类。这些对象受Unity引擎场景管理
protected SSAction()是防止用户自己new对象
使用virtual申明虚方法,通过重写实现多态。这样继承者就能明确使用Start和Update编程游戏对象行为
利用接口实现消息通知,避免与动作管理者直接依赖
SSAction的相关代码如下所示:
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }
protected SSAction() {}
public virtual void Start() {
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
在动作分离版本的牧师与恶魔中,通过写具体的动作类来实现动作的分离,这些动作类都会通过动作管理器来管理,然后对传入其中的对象进行动作分配。
在下面代码中,通过重写Update和Start来实现多态,在动作完成后,将这个SSAction的destroy设置为true,并且将消息传递通知管理者,就能够将动作销毁回收。
CCMoveToAction的作用主要是游戏中移动的动作,通过传入游戏对象的位置和设置好的动作,就能够使游戏对象移动起来。
CCMoveToAction的相关代码如下所示:
public class CCMoveToAction : SSAction {
public Vector3 target;
public float speed;
public static CCMoveToAction GetSSAction(Vector3 target, float speed) {
CCMoveToAction action = ScriptableObject.CreateInstance();
action.target = target;
action.speed = speed;
return action;
}
public override void Update () {
this.transform.position = Vector3.MoveTowards(this.transform.position,target,speed);
if(this.transform.position == target) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
public override void Start() {}
}
接下来是组合动作实现的类CCSequenceAction,这个类创建一个动作顺序执行序列,在下面的代码中,repeat的值为-1表示动作无限循环,而start则表示动作开始
Update的重写则是表示执行当前的动作
而SSActionEvent则是一个回调通知的动作,当收到当前动作执行完成后,则推下一个动作,如果完成一次循环,则减少它的次数。如果当所有动作完成,就通知动作的管理者,将其销毁。
而Start的重写则是表示,在执行动作前,为每个动作注入当前动作的游戏对象,并将自己作为动作事件的接收者。
SSActionEvent的相关代码如下所示:
public class CCSequenceAction : SSAction, ISSActionCallback {
public List sequence;
public int repeat = -1; //repeat forever
public int start = 0;
public static CCSequenceAction GetSSAction(int repeat, int start, List sequence) {
CCSequenceAction action = ScriptableObject.CreateInstance();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
public override void Start() {
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}
public override void Update() {
if (sequence.Count == 0) return;
if (start < sequence.Count)
sequence[start].Update();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
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);
}
else {
sequence[start].Start();
}
}
}
private void OnDestroy() {
//destory
}
}
接下来是动作事件接口的代码分析,在定义了时间处理接口以后,所有的事件管理者都必须实现这个接口来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。
SSActionEventType的相关代码如下所示:
public enum SSActionEventType : int { Started, Completed }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null);
}
然后就到动作管理器的设计了,在动作管理器中RunAction的作用是提供了一个新动作的方法,并且该方法把游戏对象与动作绑定,并绑定该动作事件的消息接收者。相关代码如下所示:
public class SSActionManager : MonoBehaviour {
private Dictionary actions = new Dictionary();
private List waitingAdd = new List();
private List waitingDelete = new List();
void Start() {}
protected void Update() {
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair 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();
}
}
最后就是关键的事例,个人理解,上面的内容都相当于是模版一样,真正要做到实现应用的还是下面这个CCActionManager,这个能够具体将相关的对象运行起来
例如moveBoat动作,原来是在FirstController里面的通过调用Move()方法来实现的,但是在动作分离后,动作管理者管理着Move这个动作,在CCActionManager中通过返回一个SSAction的实例,将相应运动的参数传送到这个动作实例里面,然后通过RunAction把游戏对象和动作联系起来,从而达到游戏对象运动的效果。
而牧师或者魔鬼的移动则是分开为两部分执行的,其中一部分,例如上传动作,则是先水平移动到船的上方,然后再垂直移动,因此有两个动作action1,action2,将其加入到动作列表中,1表示只执行一次,0则表示开始。其他部分与单个动作的执行过程相同。
public class CCActionManager : SSActionManager, ISSActionCallback
{
public FirstController sceneController;
public CCMoveToAction MoveBoatAction;
public CCSequenceAction MoveCharacterAction;
// Use this for initialization
protected void Start () {
sceneController = (FirstController)Director.getInstance().currentScenceController;
sceneController.actionManager = this;
}
public void moveBoat(GameObject boat, Vector3 target, float speed) {
MoveBoatAction = CCMoveToAction.GetSSAction(target,speed); //将相应的参数传递给SSAction实例
this.RunAction(boat, MoveBoatAction, this);//通过调用RunAction将游戏对象和动作联系起来,实现动作
}
public void moveCharacter(GameObject character, Vector3 middle, Vector3 end, float speed) {
SSAction MoveToMiddle = CCMoveToAction.GetSSAction(middle,speed);
SSAction MoveToEnd = CCMoveToAction.GetSSAction(end,speed);
MoveCharacterAction = CCSequenceAction.GetSSAction(1, 0, new List{ MoveToMiddle, MoveToEnd });
this.RunAction(character, MoveCharacterAction, this);
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null) {}
}
其它部分的代码,其实跟上一次作业的代码差不多,只不过是把moveablescript中相关的代码去掉,交给动作管理器来管理,然后我把上次的代码的类做了一些合并操作。具体代码可以看我的Github。
最后上传一张游戏的效果图,其实和上次的作业差不多,但是多了几个bug,其实这次作业是真的难,很多代码都要借鉴别人的,要是自己一个人写还真写不出来,很多地方还是不太理解。