把Fantasy Skybox FREE添加至我的资源,从Unity的Window选项里进入Package Manager下载Fantasy Skybox FREE并导入到Asset中。
首先创建天空盒,在Assert中右键Create一个Material,命名为Sky。
然后设置这个Material的Shader为Skybox/Panoramic,再从下载到的素材里选一张合适的图片贴入Spherical中,最终将该天空盒拖入场景中即可。
然后创建一个地形,GameObject->3D Object->Terrain。先创建一个光地板,再慢慢通过Terrain的各项工具绘制山、草、树等等。
成果:
好像还可以(×),真的一言难尽(√)
游戏对象本身作为组件的容器使用,可以通过向其中添加不同的组件来调用不同的功能。一个对象挂载了组件,便拥有了组件的相关属性。
同时,不同的游戏对象还有着它独特的功能。比如:
Camera:作为游戏的眼睛,是玩家观察游戏世界的媒介;Light:光源,既可以用来照明也可用于添加阴影;Empty空对象:多被用于当做载体,例如挂载游戏脚本、成为其他对象的父对象等。;Cube等3D Object:搭建游戏世界的组成元素,通过设置其Transform等属性来变换它们的Position、Rotation、Scale;Terrain等:既是组成元素,又是编辑工具,Terrain本身是地图,然后又附带了绘制地图的各项工具(造山、种树种草等)。设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束。
首先,要在上一次作业的基础上实现动作分离版的牧师与魔鬼,就需要在原来的基础上新增动作管理相关的类。
先前的作业就已经实现了一个Move和一个MoveController来对动作进行管理了。但这只是一个简单的动作管理类,这一次需要实现的是更细分的动作管理。
因而,最基本要新增SSAction、CCMoveToAction、CCSequenceAction、ISSActionCallback、SSActionManager、CCActionManager这几个类。
在这里,因为我的人物动作几乎都是直线移动即可,CCSequenceAction就没实现了,因为懒。
1.首先,实现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(){} // Start is called before the first frame update public virtual void Start(){ throw new System.NotImplementedException(); } // Update is called once per frame public virtual void Update(){ throw new System.NotImplementedException(); } }这里利用ISSACtionCallback来实现消息的通知,同时使用virtual申明虚方法,通过重写实现多态。
2.CCMoveToAction。
public class CCMoveToAction : SSAction { public Vector3 target; public float speed; public static CCMoveToAction GetSSAction(Vector3 target, float speed){ CCMoveToAction action=ScriptableObject.CreateInstance<CCMoveToAction>(); action.target=target; action.speed=speed; return action; } // Start is called before the first frame update public override void Start() { } // Update is called once per frame public override void Update() { this.transform.localPosition=Vector3.MoveTowards(this.transform.localPosition,target,speed*Time.deltaTime); if(this.gameObject==null||this.transform.localPosition==target){ this.destroy=true; this.callback.SSActionEvent(this); } } }此类继承的是SSAction,实现较为简单的移动。
3.ISSActionCallback接口。
public enum SSActionEventType : int{Started,Competed} public interface ISSActionCallback{ void SSActionEvent(SSAction source, SSActionEventType events=SSActionEventType.Competed, int intParam=0, string strParam=null, Object objectParam=null); }由始至终都是在copy老师课件上的代码。
4.SSActionManager动作管理基类。
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 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); 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(); } // Start is called before the first frame update protected void Start() { } }依旧在copy老师的代码。
SSActionManager是作为动作生成、运行与销毁的管理者。actions以字典的形式将正在运行中的动作存储起来。waitingAdd保存的是即将被运行的动作。waitingDelete保存的是即将被删除的动作。Update()每次都会将waitingAdd中的动作加入到actions当中,然后遍历actions中的动作,运行每一个动作。如果动作已经结束,则加入到waitingDelete中,最后将waitingDelete中的动作删除并销毁。5.CCActionManager动作管理者。
public class CCActionManager : SSActionManager, ISSActionCallback { private bool isMoving=false; public CCMoveToAction moveObjAction; public FirstController controller; protected new void Start() { controller=SSDirector.getInstance().currentSceneController as FirstController; } public bool IsMoving(){ return isMoving; } public void MoveObj(GameObject obj,Vector3 target,float speed){ if(isMoving) return; isMoving=true; moveObjAction=CCMoveToAction.GetSSAction(target,speed); this.RunAction(obj,moveObjAction,this); } public void SSActionEvent(SSAction source, SSActionEventType events=SSActionEventType.Competed, int intParam=0, string strParam=null, Object objectParam=null){ isMoving=false; } }到这里,老师课件上的代码就不太适用了,最好是根据自己的实际情况稍作修改。没得直接照抄了
CCActionManager跟上一次的Move和MoveController都有着较大的交集,最主要的功能还是给FirstController提供一个移动游戏对象的函数接口。
将一系列的动作管理类实现好了之后,接下来就需要修改原来的代码了。
若目标只是能运行的话,只需要将FirstController原来对MoveController相关函数的调用改为CCActionManager里面的函数即可。将原来的MoveController注释掉,并在Awake()函数里将CCActionManager脚本挂载上this.gameObject.AddComponent<CCActionManager>();,这样直接就能够正常运行了。
然而,题目还有一个要求是要实现一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束。也就是说将原先FirstController中判断游戏结束的功能分离出来,单独实现一个Judgment类来完成。
其实,原来为了方便(懒),在FirstController中存储了leftPriestNum、leftDevilNum、rightPriestNum、rightDevilNum四个变量来对游戏状态进行判断。但是呢,在把Check的功能分离出去之后,在FirstController中存储这四个变量显然是不合适的,说到底还是应该放去LandModel中去。同时,对这四个变量的初始化也就放去LandModelController里的CreateLand函数里面。
那么,在Judgement里就是通过LandModelController来获取河两岸牧师魔鬼的个数信息了。
public class Judgement : MonoBehaviour { private FirstController controller; private LandModelController landRoleController; // Start is called before the first frame update void Start() { controller=SSDirector.getInstance().currentSceneController as FirstController; landRoleController=controller.GetLandModelController(); } // Update is called once per frame void Update() { if(!controller.GetIsRuning()) return; this.gameObject.GetComponent<UserGUI>().gameMessage=""; if(landRoleController.GetLandModel().rightPriestNum==3&&landRoleController.GetLandModel().rightDevilNum==3){ controller.JudgeCallback(false,"You Win!!"); } else if((landRoleController.GetLandModel().leftPriestNum!=0 &&landRoleController.GetLandModel().leftPriestNum<landRoleController.GetLandModel().leftDevilNum) ||(landRoleController.GetLandModel().rightPriestNum!=0 &&landRoleController.GetLandModel().rightPriestNum<landRoleController.GetLandModel().rightDevilNum)){ controller.JudgeCallback(false,"Game Over!!"); } } }实质上就是将原来的Check函数放去update()函数里面,改改变量名,最后再调用相关的回调函数JudgeCallback就行了。(FirstController里面还需要添加GetIsRuning()和GetLandModelController()两个函数)
最后,将原来跟Check()函数相关的语句注释掉,再在FirstController的Awake()函数里将Judgement脚本挂载上就可以了this.gameObject.AddComponent<Judgement>();。
本来想着是将前面自己构建的游戏场景用上去,但效果实在是。。。倒不如用原来的场景。
最后,附上完整代码
在场景中新建一个”球“,再在Asset新建一个Material并拖到球体上。
首先可以在Albedo处改变颜色。
将Rendering Mode勾选为Transparent模式,再修改Albedo里面Color的A值,就能改变球体的透明度。
还可以改变Metallic的值,使其带上一种金属质感。
还可以改变Smoothness的值。
呃。。拉满之后简直一言难尽。。
下载汽车音频
直接在前面的球体上(偷懒),AddComponent->Audio->Audio Source和Audio Reverb Zones,将下载到的音频拖放到音源处。
再将Reverb Zone的Reverb Preset设为Cave。
点击运行即可。
