3D游戏(4)——游戏对象与图形基础

    科技2026-01-18  11

    文章目录

    1、基本操作演练【建议做】下载 Fantasy Skybox FREE, 构建自己的游戏场景写一个简单的总结,总结游戏对象的使用 2、编程实践牧师与魔鬼 动作分离版 3、材料与渲染联系【可选】Standard Shader自然场景渲染器。声音

    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。

    点击运行即可。

    Processed: 0.018, SQL: 9