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

为了演示,我将创建物体,剧情读取都直接进行了面板拖拽的方式,在正式的项目中需要调用项目的中创建接口和资源接口。

后期扩展可以写个时间轴工具,添加事件等等。
最终实现创建、位移,旋转等操作

这里写图片描述

  • 6
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Unity3D是一种跨平台的游戏引擎,我将分享关于我的Unity3D大作业的一些细节。 在这个大作业中,我选择了一个创意独特且具有挑战性的游戏项目。我决定开发一个冒险类游戏,玩家需要在一个神秘的迷宫中探索、解谜,并击败各种敌人来拯救失散的亲人。游戏采用第三人称视角,配有精美的3D图形和优秀的音效,以提供给玩家身临其境的游戏体验。 为了实现这个项目,我首先设计了迷宫的结构和随机生成系统,确保每次游戏都有不同的挑战和谜题。接着,我创建了角色,并实现了基本的移动功能和攻击动画。玩家需要通过控制角色在迷宫中寻找线索、解开谜题、战胜敌人以及推动剧情的发展。 在游戏的开发过程中,我还添加了一些额外的功能来增强游戏体验。例如,我设计了一个道具系统,玩家可以收集和使用不同的道具来帮助他们在游戏中取得优势。我还实现了一个升级系统,玩家可以通过击败敌人和完成任务来获得经验值和技能点,并用于提升角色的属性和技能。 为了增加游戏的可玩性和挑战性,我设计了多个关卡和BOSS战,每个关卡都有不同的环境、敌人和谜题。这样,玩家将面临各种不同的挑战和解谜,同时也可以感受到游戏的持续进展。 总之,这个Unity3D大作业是一个非常有趣且富有挑战性的项目。通过学习和实践,我不仅熟悉了Unity3D的开发流程和技术,并且加深了对游戏设计和开发的理解。我很自豪能够成功完成这个大作业,并且对未来在游戏开发领域的发展充满了信心。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘建宁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值