操作与总结 参考 Fantasy Skybox FREE 构建自己的游戏场景 利用 Skybox 和 Terrain 构建游戏场景。Unity 的 Terrain 有十分方便的地形设计工具,利用它可以轻松绘制自己的地形及素材。地形工具和场景效果如下:
总结游戏对象的使用 常用游戏对象
空对象 (Empty):不显示的游戏对象,一般用于挂载管理器等脚本。
摄像机 (Camera):游戏显示画面的来源。创建多个摄像机可以展现多个游戏视角。
3D 物体 (3D Object):游戏中的三维物体,由三角面拼接而成。
2D 物体 (2D Object):游戏中的二维物体。
光线 (Light):游戏画面表现的灵魂所在,用于营造游戏场景的光线效果。
声音 (Audio):游戏中的声音素材,例如背景音乐。
视频 (Video):游戏中的视频素材。
UI:游戏中用户的交互接口,如按钮。
游戏对象的使用方法 游戏对象有不同的创建方法,可以在游戏运行前创建,可以在游戏运行过程中动态创建,还可以使用预设创建对象。创建位置变化不大、数量较少的对象可以采用前两种方法,而创建大量重复的、类似的对象则采用预设方法较好。
游戏对象有各种各样的组件 (Component),组件决定了游戏对象在游戏中的表现形式,包括外观和动作。使用组件时,应该仔细查阅文档,了解组件各个属性的功能和用法,再根据游戏的设计,实现游戏对象的各个需求。
编程实践:牧师与魔鬼 动作分离版 基础版本:牧师与魔鬼基础 MVC 版
在上一个版本中,我实现了该游戏的基本 MVC 架构,但是,场记和控制器负责的功能太多,导致代码耦合性太强。这一版本将游戏对象的基本动作抽离出来,并引入动作管理器来管理动作,更好地实现了模块间的松耦合,提高了代码的复用性。
实现思路 实现动作基类 在动作基类里面,要声明游戏对象的动作的基本属性和方法。在这个游戏中,游戏对象只有直线运动,因此动作基类只需包含一个用于指定对象的 GameObject 属性和一个用于指定运动路径的 Transform 属性。虚函数 Start 和 Update 是实现动作的基本方法,需要由子类重载。动作基类的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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(); } }
实现直线运动 定义 SSMoveToAction 类,并继承动作基类,来实现对象的直线运动。具体实现如下:
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 public class SSMoveToAction : SSAction { public Vector3 target; public float speed; public static SSMoveToAction GetSSMoveToAction (Vector3 target, float speed ) { SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>(); action.target = target; action.speed = speed; 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 .destroy = true ; this .Callback.SSActionEvent(this ); } } }
实现动作序列 在游戏中,船的动作是简单的直线运动,而游戏角色的运动是由两段直线运动组成的折线运动。通过定义 SSSequenceAction 类,将基本动作(直线运动)组合在一起,可以实现折现运动。在这个类中,用 List 列表记录待执行的动作序列,并依次执行,执行完毕后清空列表,从而完成动作的组合(其中可以自定义循环的次数)。具体实现如下:
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 public class SSSequenceAction : SSAction, ISSActionCallback { public List<SSAction> sequence; public int repeat = -1 ; public int start = 0 ; public static SSSequenceAction GetSSSequenceAction (int repeat, int start, List<SSAction> sequence ) { SSSequenceAction action = ScriptableObject.CreateInstance<SSSequenceAction>(); 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(); } } void OnDestroy ( ) { foreach (SSAction action in sequence) { DestroyObject(action); } } 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 ); } } } }
实现动作管理 在定义基本动作和动作序列之后,需要有一个基础动作管理器来管理要执行的动作,它继承自 MonoBehaviour,以便实现对游戏对象动作的控制。以下是 SSActionManager 类的定义:
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 public class SSActionManager : MonoBehaviour { 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 action in waitingAdd) { actions[action.GetInstanceID()] = action; } waitingAdd.Clear(); foreach (KeyValuePair<int , SSAction> KeyValue in actions) { SSAction action = KeyValue.Value; if (action.destroy) { waitingDelete.Add(action.GetInstanceID()); } else if (action.enable) { action.Update(); } } foreach (int key in waitingDelete) { SSAction action = actions[key]; actions.Remove(key); DestroyObject(action); } waitingDelete.Clear(); } public void RunAction (GameObject gameObject, SSAction action, ISSActionCallback callback ) { action.GameObject = gameObject; action.Transform = gameObject.transform; action.Callback = callback; waitingAdd.Add(action); action.Start(); } }
修改原有的游戏动作 分离好动作后,要修改原来的动作执行方式。首先定义一个具体的动作管理者,继承自上述的基础动作管理者,来管理第一个游戏场景的动作(本游戏仅有一个场景)。
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 public class FirstActionManager : SSActionManager, ISSActionCallback { public void MoveBoat (BoatController boatController ) { SSMoveToAction action = SSMoveToAction.GetSSMoveToAction(boatController.getDestination(), 20 ); RunAction(boatController.boat, action, this ); } public void MoveCharacter (MyCharacterController myCharacterController, Vector3 destination ) { Vector3 current = myCharacterController.character.transform.position; Vector3 middle = destination; if (destination.y < current.y) { middle.y = current.y; } else { middle.x = current.x; } SSAction firstMove = SSMoveToAction.GetSSMoveToAction(middle, 20 ); SSAction secondMove = SSMoveToAction.GetSSMoveToAction(destination, 20 ); SSAction sequenceAction = SSSequenceAction.GetSSSequenceAction(1 , 0 , new List<SSAction> { firstMove, secondMove }); RunAction(myCharacterController.character, sequenceAction, this ); } public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Completed, int intParam = 0 , string strParam = null , object objectParam = null ) {}}
接着修改 FirstController 中的动作内容,利用 FirstActionManager 来调用动作的执行。
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 public class FirstController : MonoBehaviour, SceneController, IUserAction { private FirstActionManager actionManager; void Start ( ) { actionManager = GetComponent<FirstActionManager>(); } public void moveBoat ( ) { if (boat.isEmpty ()) return ; actionManager.MoveBoat(boat); boat.ChangePosition(); userGUI.status = check_game_over (); } public void characterIsClicked (MyCharacterController characterCtrl ) { if (characterCtrl.isOnBoat ()) { ShoreController whichShore; if (boat.get_to_or_from () == -1 ) { whichShore = toShore; } else { whichShore = fromShore; } boat.GetOffBoat (characterCtrl.getName()); actionManager.MoveCharacter(characterCtrl, whichShore.getEmptyPosition()); characterCtrl.getOnShore (whichShore); whichShore.getOnShore (characterCtrl); } else { ShoreController whichShore = characterCtrl.getShoreController (); if (boat.getEmptyIndex () == -1 ) { return ; } if (whichShore.get_to_or_from () != boat.get_to_or_from ()) return ; whichShore.getOffShore(characterCtrl.getName()); actionManager.MoveCharacter(characterCtrl, boat.getEmptyPosition()); characterCtrl.getOnBoat (boat); boat.GetOnBoat (characterCtrl); } userGUI.status = check_game_over (); } }
至此,动作分离全部完成。
第二版游戏美化 在游戏中,加入了天空盒和水环境,使得整体界面比上一版更加美观,立体感更强。
游戏预览视频及完整项目见 GitHub .
返回 Unity 3D Learning