3d游戏设计读书笔记四

这篇博客介绍了在Unity3D中如何进行3D游戏设计,包括使用FantasySkyboxFREE构建场景,理解天空盒的概念。此外,重点讲解了游戏对象的分类和使用,如Empty、3DObject、2DObject、Light、Audio等。还探讨了动作分离的设计原则,通过ActionManager和Judger类实现了游戏对象的动作管理和结束条件判断,降低了代码复杂性并提高了复用性。
摘要由CSDN通过智能技术生成

3d游戏设计读书笔记四

一、基本操作演练【建议做】

  1. 下载 Fantasy Skybox FREE, 构建自己的游戏场景
    a. 在AssetStore中搜索Fantasy Skybox FREE并下载。
    在这里插入图片描述
    b. unity添加天空盒有两种方法,分别为向场景添加和向摄像机添加。首先我们在assert里面导入skybox素材。
    (1)向camera里导入:
    点击camera或者daomain camera,再点击component,选renderin,点skybox.再inspector里面会成这样,注意,第一个框框必须是skybox。然后在第二个框框里面导入素材。
    (2)向scene里导入:
    旧版:
    在edit里点render setting即可。再在红框框处选择。
    scene里面添加skybox什么时候都可以看到,camera只有在该摄像机里面可以看到。
    新版:
    在 window菜单下的lighting,找到Environment选项,天空盒和雾效等等都在里面
    在这里插入图片描述

c. 在场景中添加地形并画出路、草、树、房子。这些材料都可以在AssetStore中找到。

在这里插入图片描述
ps:理解天空盒子(Understanding skybox)

天空盒是一个全景视图,分为六个纹理,表示沿主轴(上,下,左,右,前,后)可见的六个方向的视图。如果天空盒被正确的生成,纹理图像会在边缘无缝地拼接在一起,可以在内部的任何方向看到周围连续的图像。全景图片会被渲染在场景中的所有其他物体后面,并旋转以匹配相机的当前方向(它不会随着相机的位置而变化,而照相机的位置总是位于全景图的中心)。因此,天空盒子是在图形硬件上以最小负载向场景添加现实性的简单方式。
在这里插入图片描述

  1. 写一个简单的总结,总结游戏对象的使用

Empty:空对象,常用作载体,可用来挂载脚本或作为其他游戏对象的父类
3D Object:三维实体对象,有几何属性。游戏可见的对象之一
2D Object:二维实体对象,有几何属性,游戏中可见的对象之一
Light:光源,用来营造光影效果
Audio:音频,用以创造游戏音效
Video:视频
Camera:摄像机,用以从某个视角观察游戏世界
游戏对象可通过事先创建,脚本创建,加载预设等方式进行创建,通过改变其属性实现游戏场景的切换。
游戏对象包括常规的3D和2D对象,还包括摄像机和光源等这种特殊对象。每个对象都有自己的相关属性(如材质,位置等等),还可以附加component(脚本等),对象之前也可以有附属关系,对象还可以通过脚本来创建。

二、编程实践(二选一)

牧师与魔鬼 动作分离版

【2019开始的新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束

动作分离的原因:场记(控制器)管的事太多,不仅要处理用户交互事件,还要游戏对象加载、游戏规则实现、运动实现等等,而显得非常臃肿。一个最直观的想法就是让更多的人(角色)来管理不同方面的工作。显然,这就是面向对象基于职责的思考,例如:用专用的对象来管理运动,专用的对象管理播放视频,专用的对象管理规则。就和踢足球一样,自己踢5人场,一个裁判就够了,如果是国际比赛,就需要主裁判、边裁判、电子裁判等角色通过消息协同来完成更为复杂的工作。

该版本改进的目的:
把每个需要移动的游戏对象的移动方法提取出来,建立一个动作管理器来管理不同的移动方法。
根据原先版本,每一个可移动的游戏对象的组件都有一个Move脚本,当游戏对象需要移动时候,游戏对象自己调用Move脚本中的方法让自己移动。
而动作分离版,则剥夺了游戏对象自己调用动作的能力,建立一个动作管理器,通过场景控制器(在我的游戏中是Controllor)把需要移动的游戏对象传递给动作管理器,让动作管理器去移动游戏对象。
当动作很多或是需要做同样动作的游戏对象很多的时候,使用动作管理器可以让动作很容易管理,也提高了代码复用性。

以下版本与上一篇版本有一些不同(原先版本我只参考了一位师兄的博客,后来再次复习老师网页上的内容时,发现在类等设置上有一些出入,所以重做了一版,同时知道了如何从商店下载模型,所以模型也有改动,同时省去了一些功能),以下只讲动作分离和裁判类,其余请参考代码。

以下是老师的UML图:
在这里插入图片描述
新增一个ActionManager脚本和一个Judger裁判类,将UserGUI中判断游戏是否结束以及结束时的操作全部转移给Judger,将游戏中控制小船和角色移动的动作全部改为actionManager的操作(对BaseCode.cs,Control.cs,Firstcontrol.cs需要进行一点修改)。
在这里插入图片描述

ActionManage

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() { }

    // Use this for initialization
    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    // Update is called once per frame
    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

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(){

    }

    // 在 Update 函数中用 Vector3.MoveTowards 实现直线运动
    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);
        }
    }
}

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;
    }
	
	// 在 Update 中执行当前动作
    public override void Update(){
        if (sequence.Count == 0) return;
        if (start < sequence.Count)
        {
            sequence[start].Update();
        }
    }

    // 更新当前执行的动作
    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Complete, 
    							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);
            }
        }
    }

    // Use this for initialization
    public override void Start(){
        foreach (SSAction action in sequence)
        {
            action.GameObject = this.GameObject;
            action.Transform = this.Transform;
            action.Callback = this;
            action.Start();
        }
    }

  

    // 执行完毕后销毁动作
    void OnDestroy()
    {
        foreach (SSAction action in sequence)
        {
            DestroyObject(action);
        }
    }

    
}

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()); // release action
            }
            else if (action.enable){
                action.Update(); // update action
            }
        }

        foreach (int key in waitingDelete){
            SSAction action = actions[key];
            actions.Remove(key);
            DestroyObject(action);
        }
        waitingDelete.Clear();
    }
    protected void Start(){ 

    }
    // 执行动作
    public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback callback){
        action.GameObject = gameObject;
        action.Transform = gameObject.transform;
        action.Callback = callback;
        waitingAdd.Add(action);
        action.Start();
    }
    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Complete,
        int intParam = 0, string strParam = null, Object objectParam = null){
        
    }
}
public class ActionManager : SSActionManager, ISSActionCallback  
{

    private SSMoveToAction boat_move;     
    private SSSequenceAction char_move;     

    public FirstController sceneController;

    protected new void Start()
    {
        sceneController = (FirstController)Director.getInstance().currentSceneController;
        sceneController.actionManager = this;
    }
    public void moveBoat(BoatController boatCtrl, Vector3 dest, float speed)
    {
        boat_move = SSMoveToAction.GetSSMoveToAction(dest, speed);
        this.RunAction(boatCtrl.getGameobj(), boat_move, this);
    }

    public void moveChar(MyCharacterController charCtrl,Vector3 dest, float speed)
    {
        //moveable.setDestion()的功能
        Vector3 middle = dest;
        Vector3 chara = charCtrl.getGameobj().transform.position;
        if (dest.y < chara.y) {	// character from bank to boat,说明角色在岸上,可以上船
			middle.y = chara.y;
		}else {								// character from boat to bank   说明角色在船上,可以上岸
			middle.x = chara.x;
		}

        SSAction action1 = SSMoveToAction.GetSSMoveToAction(middle, speed);
        SSAction action2 = SSMoveToAction.GetSSMoveToAction(dest, speed);
        char_move = SSSequenceAction.GetSSSequenceAction (1, 0, new List<SSAction> { action1, action2 });
        this.RunAction(charCtrl.getGameobj(), char_move, this);
    }
}

Judger

public class Judger : MonoBehaviour{
	

    void Start(){
        
    }

    public void judge(UserGUI userGUI,GUIStyle style,GUIStyle buttonStyle){
        if (userGUI.status == 1) {
            GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You Lose!", style);
            if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", buttonStyle)) {
                userGUI.status = 0;
                userGUI.action.restart ();
            }
        } else if(userGUI.status == 2) {
            GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You win!", style);
            if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", buttonStyle)) {
                userGUI.status = 0;
                userGUI.action.restart ();
            }
        }

    }
}

结果图:
在这里插入图片描述
我的github
运行视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值