Unity3D课程学习笔记(三)

1.Fantasy Skybox FREE 构建自己的游戏场景

其实这个操作很简单,只要在Asset Store中搜索Fantasy Skybox FREE,然后在Camera中添加Component,然后添加Skybox,再将相应的Skybox图案添加上去,就能够完成了。


添加天空盒具体的操作可以参照下面左图的图示,在主摄像机Main Camara中添加组件Component->Rendering->Skybox.

    

将这个组件添加以后,能够看到如上边右图所示的属性栏,其中有一个Custom Skybox的属性,将自己喜欢的天空盒模型拖拽到里面,然后按运行,就能够得到自己喜欢的场景了,看到一幅比较逼真的画面,比上次制作太阳系制作的盒子创造的背景好看多了,同时也省去了下面的牧师与魔鬼的背景载入器。

效果图如下所视:


这是在添加了Terrain后的场景示意图,可以在Terrain中创造一些自己的场景,具体的一些方法可以在下面的图示中看到,在这些设置中,添加了自己的assets以后,就能够创造出自己喜欢的树,草,地板等样式,这样一个游戏环境就能够大概的创造出来,而这个过程中并没有自己去写代码。

2.游戏对象总结

游戏对象主要包括:空对象,摄像机,光线,3D物体,声音,UI基于事件的new UI系统和粒子系统与特效
- 空对象(Empty):不显示却是最常用的对象之一
- 摄像机(Camara):观察游戏世界的窗口
- 光线(Light):游戏世界的光源,让游戏世界富有魅力
- 3D物体 :3D游戏中的重要组成部分,可以改变其网格和材质,三角网格是游戏物体表面的唯一形式
- 声音(Audio):3D游戏中的声音来源

3.编程实践,牧师与魔鬼动作分离版

本次作业需要在上次作业的基础上,将场记中的动作分离出来,首先要创建动作管理器

动作管理是游戏内容的重要内容,这次作业要搞清楚简单动作的组合问题,实现动作管理器,实现基础动作类,回调实现动作完成通知(经典方法),其规划与设计的UML图如下所示:


动作基类(SSAction)的设计,在下面代码中

ScriptableObject是不需要绑定GameObject对象的可编程基类。这些对象受Unity引擎场景管理

protected SSAction()是防止用户自己new对象

使用virtual申明虚方法,通过重写实现多态。这样继承者就能明确使用Start和Update编程游戏对象行为

利用接口实现消息通知,避免与动作管理者直接依赖

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

    public virtual void Start() {
        throw new System.NotImplementedException();
    }

    public virtual void Update() {
        throw new System.NotImplementedException();
    }
}

在动作分离版本的牧师与恶魔中,通过写具体的动作类来实现动作的分离,这些动作类都会通过动作管理器来管理,然后对传入其中的对象进行动作分配。

在下面代码中,通过重写Update和Start来实现多态,在动作完成后,将这个SSAction的destroy设置为true,并且将消息传递通知管理者,就能够将动作销毁回收。

CCMoveToAction的作用主要是游戏中移动的动作,通过传入游戏对象的位置和设置好的动作,就能够使游戏对象移动起来。

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;
	}

	public override void Update () {
		this.transform.position = Vector3.MoveTowards(this.transform.position,target,speed);
		if(this.transform.position == target) {
			this.destroy = true;
			this.callback.SSActionEvent(this);
		}
	}

	public override void Start() {}
}

接下来是组合动作实现的类CCSequenceAction,这个类创建一个动作顺序执行序列,在下面的代码中,repeat的值为-1表示动作无限循环,而start则表示动作开始

Update的重写则是表示执行当前的动作

而SSActionEvent则是一个回调通知的动作,当收到当前动作执行完成后,则推下一个动作,如果完成一次循环,则减少它的次数。如果当所有动作完成,就通知动作的管理者,将其销毁。

而Start的重写则是表示,在执行动作前,为每个动作注入当前动作的游戏对象,并将自己作为动作事件的接收者。

SSActionEvent的相关代码如下所示:

public class CCSequenceAction : SSAction, ISSActionCallback {
    public List<SSAction> sequence;
    public int repeat = -1; //repeat forever
    public int start = 0;

    public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence) {
        CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
        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();
    }

    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);
            }
            else {
                sequence[start].Start();
            }
        }
    }

    private void OnDestroy() {
        //destory
    }
}

接下来是动作事件接口的代码分析,在定义了时间处理接口以后,所有的事件管理者都必须实现这个接口来实现事件调度。所以,组合事件需要实现它,事件管理器也必须实现它。

SSActionEventType的相关代码如下所示:

public enum SSActionEventType : int { Started, Completed }

public interface ISSActionCallback
{
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0, string strParam = null, Object objectParam = null);
}

然后就到动作管理器的设计了,在动作管理器中RunAction的作用是提供了一个新动作的方法,并且该方法把游戏对象与动作绑定,并绑定该动作事件的消息接收者。相关代码如下所示:

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

    void Start() {}

    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); DestroyObject(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();
    }
}

最后就是关键的事例,个人理解,上面的内容都相当于是模版一样,真正要做到实现应用的还是下面这个CCActionManager,这个能够具体将相关的对象运行起来

例如moveBoat动作,原来是在FirstController里面的通过调用Move()方法来实现的,但是在动作分离后,动作管理者管理着Move这个动作,在CCActionManager中通过返回一个SSAction的实例,将相应运动的参数传送到这个动作实例里面,然后通过RunAction把游戏对象和动作联系起来,从而达到游戏对象运动的效果。

而牧师或者魔鬼的移动则是分开为两部分执行的,其中一部分,例如上传动作,则是先水平移动到船的上方,然后再垂直移动,因此有两个动作action1,action2,将其加入到动作列表中,1表示只执行一次,0则表示开始。其他部分与单个动作的执行过程相同。

public class CCActionManager : SSActionManager, ISSActionCallback
{
    public FirstController sceneController;
    public CCMoveToAction MoveBoatAction;
    public CCSequenceAction MoveCharacterAction;

    // Use this for initialization
    protected void Start () {
        sceneController = (FirstController)Director.getInstance().currentScenceController;
        sceneController.actionManager = this;
    }

    public void moveBoat(GameObject boat, Vector3 target, float speed) {
        MoveBoatAction = CCMoveToAction.GetSSAction(target,speed); //将相应的参数传递给SSAction实例
        this.RunAction(boat, MoveBoatAction, this);//通过调用RunAction将游戏对象和动作联系起来,实现动作
    }

    public void moveCharacter(GameObject character, Vector3 middle, Vector3 end, float speed) {
        SSAction MoveToMiddle = CCMoveToAction.GetSSAction(middle,speed);
        SSAction MoveToEnd = CCMoveToAction.GetSSAction(end,speed);
        MoveCharacterAction = CCSequenceAction.GetSSAction(1, 0, new List<SSAction>{ MoveToMiddle, MoveToEnd });
        this.RunAction(character, MoveCharacterAction, this);
    } 

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0, string strParam = null, Object objectParam = null) {}
}

其它部分的代码,其实跟上一次作业的代码差不多,只不过是把moveablescript中相关的代码去掉,交给动作管理器来管理,然后我把上次的代码的类做了一些合并操作。具体代码可以看我的Github

最后上传一张游戏的效果图,其实和上次的作业差不多,但是多了几个bug,其实这次作业是真的难,很多代码都要借鉴别人的,要是自己一个人写还真写不出来,很多地方还是不太理解。


猜你喜欢

转载自blog.csdn.net/Alva112358/article/details/79845024