1. 程式人生 > >Unity3d 實現簡單的劇情繫統

Unity3d 實現簡單的劇情繫統

劇情中需要做什麼?
1).建立物體
2).基礎位移,旋轉
3).UI控制
4).語音控制
等等…

命令類:


/// <summary>
/// 劇情命令基類
/// </summary>
public abstract class PlotCommand
{
    /// <summary>
    /// 劇情資料
    /// </summary>
    protected PlotInfo PlotInfo { get; set; }
    /// <summary>
    /// 上一條命令
    /// </summary>
    public
PlotCommand PreCommand { get; set; } /// <summary> /// 下一條命令 /// </summary> public PlotCommand NextCommand { get; set; } /// <summary> /// 解析 /// </summary> /// <param name="plotInfo"></param> /// <param name="command"></param> /// <returns>
</returns>
public bool OnParse(PlotInfo plotInfo, string command) { PlotInfo = plotInfo; return Parse(command); } /// <summary> /// 解析 /// </summary> /// <param name="command"></param> /// <returns></returns> public abstract bool
Parse(string command); /// <summary> /// 執行 /// </summary> /// <returns></returns> public abstract bool DoAction(); /// <summary> /// 命令結束 /// </summary> protected void OnComandFinsh() { if (NextCommand != null) NextCommand.DoAction(); else PlotInfo.Close(); } }

例如:

/// <summary>
/// 建立角色
/// </summary>
public class CreateActor : PlotCommand
{
    public string ActorName;
    public string ModelName;

    public override bool DoAction()
    {
        Debug.Log(string.Format("建立角色:{0}-{1}",ActorName,ModelName));
        PlotInfo.PlotManager.CreateActor(ActorName, ModelName);
        OnComandFinsh();
        return true;
    }

    public override bool Parse(string command)
    {
        string[] param = command.Split(',');
        ActorName = param[0];
        ModelName = param[1];
        return true;
    }
}

/// <summary>
/// 設定座標
/// </summary>
public class SetPosition : PlotCommand
{
    public string ActorName;
    public Vector3 ActorPos;
    public float Time;

    public int type;

    public override bool DoAction()
    {
        Debug.Log(string.Format("設定角色座標:{0}-{1}", ActorName, ActorPos));
        switch (type)
        {
            case 2:
                PlotInfo.PlotManager.SetActorPos(ActorName, ActorPos);
                break;
            case 3:
                PlotInfo.PlotManager.SetActorPos(ActorName, ActorPos,Time);
                break;
        }

        OnComandFinsh();
        return false;
    }

    public override bool Parse(string command)
    {
        string[] param = command.Split(',');
        ActorName = param[0];
        ActorPos = PlotTools.ParseVector3(param[1]);
        type = 2;
        if (param.Length > 2)
        {
            Time = float.Parse(param[2]);
            type = 3;
        }
        return true;
    }
}
/// <summary>
/// 設定旋轉
/// </summary>
public class SetRotation : PlotCommand
{
    public string ActorName;
    public Vector3 Angle;
    public float Speed;
    public override bool DoAction()
    {
        Debug.Log(string.Format("設定角色轉向:{0}-{1}", ActorName, Angle));
        PlotInfo.PlotManager.SetActorRot(ActorName,Angle, Speed);
        OnComandFinsh();
        return true;
    }

    public override bool Parse(string command)
    {
        string[] param = command.Split(',');
        ActorName = param[0];
        string[] rot = param[1].Split('|');
        Angle = PlotTools.ParseVector3(param[1]);
        Speed = float.Parse(param[2]);
        return true;
    }
}

等等,可以進行其他命令擴充套件,比如UI,音效,以及延時命令等

//延時
public class Delay : PlotCommand
{
    public float DelayTime;
    public override bool DoAction()
    {
        Debug.Log(string.Format("延時:{0}", DelayTime));
        PlotTools.Instance.Delay(DelayTime, delegate {
            OnComandFinsh();
        });
        return false;
    }

    public override bool Parse(string command)
    {
        DelayTime = float.Parse(command);
        return true;
    }
}

建立一個工程用來生成命令


public class PlotFactory {

    private static Hashtable lookUpType = new Hashtable();
    public static PlotCommand Create(string name)
    {
        PlotCommand c = null;
        try
        {
            var type = (Type)lookUpType[name];
            lock (lookUpType)
            {
                if (type == null)
                {
                    //Assembly curAssembly = Assembly.GetEntryAssembly();
                    type = Type.GetType(name);
                    //type = Type.GetType();
                    lookUpType[name] = type;
                }
            }
            if (type != null)
                c = Activator.CreateInstance(type) as PlotCommand;
        }
        catch (Exception ex)
        {
            Debug.Log( string.Format("建立劇情命令{0},失敗!!",ex.Message));
        }
        return c;
    }
}

劇情資料類:

using System.Collections.Generic;

public class PlotInfo
{
    private List<PlotCommand> PlotCommands = new List<PlotCommand>();

    public PlotInfo() { }

    public PlotManager PlotManager { get; private set; }

    public PlotInfo(PlotManager plotManager)
    {
        PlotManager = plotManager;
    }

    /// <summary>
    /// 開始劇情
    /// </summary>
    public void Play()
    {
        PlotManager.ClearPlotActor();
        if (Count > 0)
            PlotCommands[0].DoAction();
    }

    /// <summary>
    /// 停止劇情
    /// </summary>
    public void Stop()
    {

    }

    /// <summary>
    /// 關閉劇情
    /// </summary>
    public void Close()
    {

    }

    public void AddCommand(PlotCommand PlotCommand)
    {
        if (Count > 0)
        {
            PlotCommand.PreCommand = PlotCommands[Count - 1];
            PlotCommands[Count - 1].NextCommand = PlotCommand;
        }
        PlotCommands.Add(PlotCommand);
    }

    public int Count
    {
        get{ return PlotCommands.Count;}
    }

}

寫了一個實現介面

public interface IPlot {

    //建立角色
    void CreateActor(string name, string assetName);

    //設定座標
    void SetActorPos(string name, Vector3 pos);
    void SetActorPos(string name, Vector3 pos, float time);
    void SetActorPos(int index, Vector3 pos);
    void SetActorPos(int index, Vector3 pos, float time);


    //設定旋轉
    void SetActorRot(string name, Vector3 Angle, float time);
    void SetActorRot(int index, Vector3 Angle, float time);

    void Play(string plotName);

}

管理類:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(PlotTools))]
public class PlotManager : MonoBehaviour, IPlot
{
    private static PlotManager _instance;
    public static PlotManager Instance
    {
        get
        {
            return _instance;
        }
    }

    /// <summary>
    /// 解析劇情
    /// </summary>
    /// <param name="command"></param>
    /// <returns></returns>
    public static PlotInfo ParsePlot(string command)
    {
        command = command.Replace("\r", "");
        string[] commandAry = command.Split('\n');
        PlotInfo pi = new PlotInfo(PlotManager.Instance);
        for (int i = 0; i < commandAry.Length; i++)
        {
            if (commandAry[i].Equals("")) continue;
            string[] commandStruct = commandAry[i].Split(':');
            PlotCommand pc = PlotFactory.Create(commandStruct[0]);
            if (pc != null)
            {
                pc.OnParse(pi,commandStruct[1]);
                pi.AddCommand(pc);
            }
            else
                Debug.Log(string.Format("建立劇情命令{0},失敗!!", commandStruct[0]));
        }
        return pi;
    }

    /// <summary>
    /// 解析劇情
    /// </summary>
    /// <param name="PlotId"></param>
    /// <returns></returns>
    public static PlotInfo ParsePlot(int PlotId)
    {
        return null;
    }


    public GameObject obj;
    public TextAsset plot;

    private Dictionary<string, GameObject> PlotActors = new Dictionary<string, GameObject>();

    void Awake()
    {
        _instance = this;
    }

    void Start()
    {
        PlotInfo pi = ParsePlot(plot.text);
        pi.Play();
    }

    /// <summary>
    /// 建立角色
    /// </summary>
    /// <param name="name"></param>
    /// <param name="assetName"></param>
    public void CreateActor(string name, string assetName)
    {
        GameObject actor = GameObject.Instantiate(obj) as GameObject;
        actor.transform.parent = this.transform;
        actor.name = name;
        PlotActors.Add(actor.name,actor);
    }

    /// <summary>
    /// 設定角色位置
    /// </summary>
    /// <param name="name"></param>
    /// <param name="pos"></param>
    public void SetActorPos(string name, Vector3 pos)
    {
        GameObject actor = GetActor(name);
        if (actor != null)
            actor.transform.localPosition = pos;
    }

    /// <summary>
    /// 設定角色位置 時間
    /// </summary>
    /// <param name="name"></param>
    /// <param name="pos"></param>
    /// <param name="time"></param>
    public void SetActorPos(string name, Vector3 pos, float time)
    {
        MoveTick tick = new MoveTick();
        tick.moveActor = GetActor(name).transform;
        tick.moveEndPos = pos;
        float dis = Vector3.Distance(tick.moveActor.localPosition, pos);
        tick.moveSpeed = dis / time;
        PlotTools.Instance.AddPlotTick(tick);
    }

    /// <summary>
    /// 設定角色選擇 速度
    /// </summary>
    /// <param name="name"></param>
    /// <param name="Angle"></param>
    /// <param name="speed"></param>
    public void SetActorRot(string name, Vector3 Angle, float speed)
    {
        RotTick tick = new RotTick();
        tick.rotActor = GetActor(name).transform;
        tick.EndAngle = Angle;
        tick.rotSpeed = speed;
        PlotTools.Instance.AddPlotTick(tick);
    }

    /// <summary>
    /// 獲取角色
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public GameObject GetActor(string name)
    {
        if (PlotActors.ContainsKey(name))
            return PlotActors[name];
        return null;
    }

    /// <summary>
    /// 獲取角色
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public GameObject GetActor(int index)
    {
        //-1 表示為主角
        if (index == -1)
        {
            //獲取主角資料
            return null;
        }
        if (PlotActors.Count > index)
        {
            List<GameObject> actor = new List<GameObject>(PlotActors.Values);
            return actor[index];
        }
        return null;
    }

    /// <summary>
    /// 清楚角色
    /// </summary>
    public void ClearPlotActor()
    {
        List<GameObject> obj = new List<GameObject>(PlotActors.Values);
        for (int i = 0; i < obj.Count; i++)
        {
            Destroy(obj[i]);
        }
        PlotActors.Clear();
    }

    public void SetActorPos(int index, Vector3 pos)
    {
        throw new NotImplementedException();
    }

    public void SetActorPos(int index, Vector3 pos, float time)
    {
        throw new NotImplementedException();
    }

    public void SetActorRot(int index, Vector3 Angle, float time)
    {
        throw new NotImplementedException();
    }

    public void Play(string plotName)
    {
        throw new NotImplementedException();
    }
}


迴圈方法,例如位移,旋轉等需要沒幀執行的

public abstract class PlotTick
{
    public System.Action OnFinsh;
    public bool IsFinsh { get; protected set; }
    public abstract void OnUpdate();
}

public class MoveTick : PlotTick
{
    public float moveSpeed;
    public Vector3 moveEndPos;
    public Transform moveActor;

    public override void OnUpdate()
    {
        moveActor.localPosition = Vector3.MoveTowards(moveActor.localPosition, moveEndPos, moveSpeed * Time.deltaTime);
        if (moveActor.localPosition == moveEndPos)
        {
            IsFinsh = true;
            if (OnFinsh != null)
                OnFinsh();
        }
    }
}

public class RotTick : PlotTick
{
    public float rotSpeed;
    public Vector3 EndAngle;
    public Transform rotActor;

    public override void OnUpdate()
    {
        rotActor.localEulerAngles = Vector3.Lerp(rotActor.localEulerAngles, EndAngle, rotSpeed * Time.deltaTime);
        if (Vector3.Distance(rotActor.localEulerAngles,EndAngle)<0.02f)
        {
            IsFinsh = true;
            if (OnFinsh != null)
                OnFinsh();
        }
    }
}

工具類:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlotTools : MonoBehaviour {

    private static PlotTools _instance;
    public static PlotTools Instance
    {
        get {
            return _instance;
        }
    }

    void Awake()
    {
        _instance = this;
    }

    void Start()
    {

    }

    private List<PlotTick> plotTick = new List<PlotTick>();
    private List<PlotTick> removePlotTick = new List<PlotTick>();

    void Update()
    {
        removePlotTick.Clear();
        for (int i = 0; i < plotTick.Count; i++)
        {
            if (!plotTick[i].IsFinsh)
            {
                plotTick[i].OnUpdate();
                continue;
            }
            removePlotTick.Add(plotTick[i]);
        }
        for (int i = 0; i < removePlotTick.Count; i++)
        {
            RemovePlotTick(removePlotTick[i]);
        }
    }

    public void AddPlotTick(PlotTick tick)
    {
        plotTick.Add(tick);
    }

    public void RemovePlotTick(PlotTick tick)
    {
        if(plotTick.Contains(tick))
            plotTick.Remove(tick);
    }

    /// <summary>
    /// 延時執行某個方法
    /// </summary>
    /// <param name="time"></param>
    /// <param name="OnFinsh"></param>
    public void Delay(float time, System.Action OnFinsh)
    {
        StartCoroutine(IeDelay(time, OnFinsh));
    }

    IEnumerator IeDelay(float time, System.Action OnFinsh)
    {
        yield return new WaitForSeconds(time);
        if (OnFinsh != null)
            OnFinsh();
    }

    public static Vector3 ParseVector3(string content)
    {
        string[] data = content.Split('|');
        return new Vector3(float.Parse(data[0]), float.Parse(data[1]), float.Parse(data[2]));
    }

    public static Vector2 ParseVector2(string content)
    {
        string[] data = content.Split('|');
        return new Vector2(float.Parse(data[0]), float.Parse(data[1]));
    }

}

配置劇情:

這裡寫圖片描述

最後需要將PlotManager和PlotTools指令碼掛載到物體上
這裡寫圖片描述

播放劇情,就是PlotManager中的Start方法

   void Start()
    {
        PlotInfo pi = ParsePlot(plot.text);
        pi.Play();
    }

為了演示,我將建立物體,劇情讀取都直接進行了面板拖拽的方式,在正式的專案中需要呼叫專案的中建立介面和資源介面。

後期擴充套件可以寫個時間軸工具,新增事件等等。
最終實現建立、位移,旋轉等操作

這裡寫圖片描述