操作与总结
参考 Fantasy Skybox FREE 构建自己的游戏场景
在Unity3D中,Skybox可以方便我们通过贴图快速创建游戏场景,而对于小白用户来说,Assets Store中的Skybox资源已然足够丰富,随意找到一个免费资源,已然足够精美。在本项目中,为了实现更好的效果,我们通过球形组件来模拟Skybox,并结合Standard Assets中的Water预设建立场景。
写一个简单的总结,总结游戏对象的使用
作为游戏中的实体,游戏对象在游戏中的地位重要性不言而喻。因而,游戏对象的组织也是相当重要的课题。通过分离功能、分层管理,我们就能更精准、更有条理的对对象们进行操作。譬如在这两次作业中,MVC和预制的结合很好地帮助我们对对象进行管理,给予了我们项目更好的可维护性和可拓展性。如此产出的代码质量和第一次作业时的产物是不可同日而语的。
编程实践 - 动作分离版牧师与魔鬼
为什么要动作分离?
在我们原本的架构中,对象的动作由其对应的Controller负责,这样虽然通过对象类型的不同区分了各个动作,然而却忽略了一个关键事实:在游戏中,对象的运动其实都是相似的,这种相似使得其可以单独作为一类存在。再者,我们也需要一个单独的Manager来对Action进行统一的管理,以此进一步降低维护成本。
如何实现?
动作基类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destoried = 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 ();
}
}SSAction为所有动作的基类,包含对对象进行操作的必须属性。同时,enable和destoried两个bool值分别决定其是否在Manager的Update中被更新,以及是否被Manager清除。callback为动作完成时的回调,在实现组合动作时通知上个action的完成,并让manager调用下一个action。
两种动作
在本项目中,我们有两种动作亟待实现:船的移动动作和人物的上/下船动作。也就是实际上是一个直线平移,一个是跳跃动作(在本项目中,我们使用抛物线而不是折线实现)。那么首先是直线移动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class CCMoveToAction : SSAction {
public Vector3 target;
public float speed;
public static CCMoveToAction GetSSAction (Vector3 target, float speed, GameObject gameobject) {
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction> ();
action.target = target;
action.speed = speed;
action.gameobject = gameobject;
action.transform = gameobject.transform;
return action;
}
public override void Start() {
}
public override void Update() {
this.transform.position = Vector3.MoveTowards (this.transform.position, target, speed * Time.deltaTime);
if (this.transform.position == target) {
this.destoried = true;
}
}
}需要特别注意的是Update在判断到达指定位置后需要将destoried设为true,通知manager释放掉这个action。然后我们看看抛物线运动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class CCJumpToAction : SSAction {
public Vector3 target;
public float speedx;
public float speedy;
public static readonly float gravity = 100;
public static CCJumpToAction GetSSAction (Vector3 target, float speed, GameObject gameobject) {
CCJumpToAction action = ScriptableObject.CreateInstance<CCJumpToAction> ();
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.target = target;
action.speedx = speed * ((target.x < action.transform.position.x) ? -1 : 1);
float deltaTime = (target.x - action.transform.position.x) / action.speedx;
action.speedx *= deltaTime * 2;
deltaTime = 0.5F;
action.speedy = Mathf.Abs((target.y - action.transform.position.y) / deltaTime + 0.5F * gravity * deltaTime);
return action;
}
public override void Start () { }
public override void Update ()
{
var newSpeedy = speedy - gravity * Time.deltaTime;
this.transform.position += (Vector3.right * speedx * Time.deltaTime + Vector3.up * 0.5F * (newSpeedy + speedy) * Time.deltaTime);
speedy = newSpeedy;
if (this.transform.position.x == target.x || this.transform.position.y <= target.y) {
this.transform.position = target;
this.destoried = true;
}
}
}和直线运动类似,不过我们需要在创建的时候计算初始速度。并且由于我们使用近似的轨迹算法,所以在最后判断到达的时候需要用不等号连接,确保不会越界。
动作管理
顾名思义,动作管理类是为了管理SSAction派生出来的众多动作而设置的,它通过维护三个列表来实现对全体动作的管理,我们另外提供了一个子类,来对动作进行进一步的分类,使之和具体对象产生联系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59public class SSActionManager : MonoBehaviour, ISSActionCallback {
private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction>();
private List <SSAction> waitngAdd = new List<SSAction> ();
private List <int> waitingDelete = new List<int> ();
protected void Update() {
foreach (SSAction ac in waitngAdd)
actions [ac.GetInstanceID ()] = ac;
waitngAdd.Clear ();
foreach (KeyValuePair <int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destoried)
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 (SSAction action, ISSActionCallback manager) {
action.callback = manager;
waitngAdd.Add (action);
action.Start ();
}
public void SSActionEvent (SSAction source,
SSActionEventType type,
int intPar,
string strPar,
Object objPar) {}
protected void Start() { }
}
public class EmiliaScenceActionManager : SSActionManager {
private readonly static float defaultSpeed = 10;
public void MoveBoat (BoatController boat) {
CCMoveToAction action = CCMoveToAction.GetSSAction (boat.getDestination(), defaultSpeed, boat.boat);
RunAction (action, this);
}
public void MoveCharacter (ICharacterController character, Vector3 destination) {
CCJumpToAction action = CCJumpToAction.GetSSAction (destination, defaultSpeed, character.character);
RunAction (action, this);
}
public void Update() {
base.Update ();
}
}如此之后,我们在FirstScenceController中实例化一个EmiliaScenceActionManager即可实现对全体对象的动作管理。