文章目录
1、基本操作演练【建议做】
下载 Fantasy Skybox FREE, 构建自己的游戏场景
把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本身是地图,然后又附带了绘制地图的各项工具(造山、种树种草等)。
2、编程实践
牧师与魔鬼 动作分离版
设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束。
首先,要在上一次作业的基础上实现动作分离版的牧师与魔鬼,就需要在原来的基础上新增动作管理相关的类。
先前的作业就已经实现了一个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>();
。
本来想着是将前面自己构建的游戏场景用上去,但效果实在是。。。倒不如用原来的场景。
最后,附上完整代码
3、材料与渲染联系【可选】
Standard Shader自然场景渲染器。
- 阅读官方Standard Shader手册 。
- 选择合适内容,如Albedo Color and Transparency,寻找合适素材,用博客展示相关效果的呈现
在场景中新建一个”球“,再在Asset新建一个Material并拖到球体上。
首先可以在Albedo处改变颜色。
将Rendering Mode勾选为Transparent模式,再修改Albedo里面Color的A值,就能改变球体的透明度。
还可以改变Metallic的值,使其带上一种金属质感。
还可以改变Smoothness的值。
呃。。拉满之后简直一言难尽。。
声音
- 阅读官方Audio手册
- 用博客给出游戏中利用Reverb Zones呈现车辆穿过隧道的声效的案例
直接在前面的球体上(偷懒),AddComponent->Audio->Audio Source和Audio Reverb Zones,将下载到的音频拖放到音源处。
再将Reverb Zone的Reverb Preset设为Cave。
点击运行即可。