StrangeIOC游戏编程框架

StrangeIOC游戏编程框架_第1张图片
160010517611623.png
这是一个MVCS的游戏编程框架,下面是这个编程框架的框架图,在做项目的时候可以多对照这张框架图去实现模块与模块之间的对接。
ROOT:会去 启动整个StringeIOC框架
MVCS ConText:进行框架的绑定[图片上传中...(7SF3LKON@%M8FOZ%{RO][email protected])]

Controller:控制逻辑层
View:视图层,通过Mediator去与Controller模块交互
Services:服务层,在这里取得数据
Models:模型层,在这里将数据存储起来

StrangeIOC游戏编程框架_第2张图片
2345_image_file_copy_1.jpg
下面我们做个小案例,通过这个案例更彻底了解这个框架的执行流程
StrangeIOC游戏编程框架_第3张图片
Paste_Image.png

这个小Demo是这样的:创建一个Cube,Cube上有一个text的UI显示一个随机的分数,运行的时候CUbe会不停的在频幕上随机移动,Text上的值也是随机的,当我们鼠标点击中一次Cube分数就会增加一。

运行逻辑是:开始的时候View通过Mediator向Controller请求数据,然后Controller向服务端请求数据,服务端Service返回一个随机值给Controller里面创建的请求数据的Command,Controller逻辑层再将数据传递给Mediator,Mediator再负责显示到View上,然后为了将数据保存下来Controller再将数据传给Models模型层。然后我们再创建一个点击更新数据的Command,在这个Command里面我们将模型层的数据加一,然后再将数据返回给Services.
首先我们的代码有这些,我们分为Command层Model层,Service层,View层,就是MVCS,Command负责传递命名,Model负责保存数据,Service负责与服务器交互,View负责视图的显示,

StrangeIOC游戏编程框架_第4张图片
7SF3LKOS.png

首先创建ContextView负责开启整个框架,

using strange.extensions.context.impl;
//启动整个StringeIOC
public class Demo1ContextView : ContextView {
    private void Awake()
    {
        this.context = new Demo1Cntext(this);//启动StrangeIOC框架
    }
}

然后创建MVCSContext负责绑定各个事件

using strange.extensions.context.api;
using strange.extensions.context.impl;
using UnityEngine;

public class Demo1Cntext : MVCSContext {

    public Demo1Cntext(MonoBehaviour View):base(View) { }

    protected override void mapBindings()//进行绑定映射
    {
        //model    M
        injectionBinder.Bind().To().ToSingleton();
       
        //service   S
        injectionBinder.Bind().To().ToSingleton();//ToSingleton表示这个对象只会在整个工程中生成一个


        //command    C
        commandBinder.Bind(Demo1CommandEvent.RequeestScore).To();
        commandBinder.Bind(Demo1CommandEvent.UpdataScore).To();

        //mediator   V
        mediationBinder.Bind().To();//完成View和mediator的绑定


        //绑定开始事件  一个StartCommand  这个StartCommand会立即调用
        commandBinder.Bind(ContextEvent.START).To().Once();//把哪个事件与自己的StartCommand绑定上
    }
}

首先是开始的命令

using strange.extensions.command.impl;

//开始命令
public class StartCommand : Command {


    /// 
    ///重写  当这个命令被执行的时候默认会调用Execute方法
    /// 
    public override void Execute()
    {
   
    }
}

接下来是视图层的脚本显示UI部分

using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using UnityEngine;
using UnityEngine.UI;

public class CubeView : View {

    [Inject]
    public IEventDispatcher dispacher { get; set; }

    private Text scoreText;

    /// 
    /// 做初始化
    /// 
    public void Init() {
        scoreText =transform.Find("Canvas/ScoreText").GetComponent();
    }
    public void Update()
    {
        transform.Translate(new Vector3(Random.Range(-1,2), Random.Range(-1, 2), Random.Range(-1, 2))*.2f);
    }
    private void OnMouseDown()
    {
        //加分
        Debug.Log("OnMouDown");
        dispacher.Dispatch(Demo1MediatorEvent.ClickDown);
    }
    public void Updatescore(int score) {
        scoreText.text = score.ToString();
    }
}

接下来是视图层通过View层 Mediator向Command发起分数的请求

using strange.extensions.context.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;


public class CubeMediator : Mediator {

    [Inject]//可以访问到与自身绑定的 CubeView  完成注入的意思
    public CubeView cubeView { get; set; }

    [Inject(ContextKeys.CONTEXT_DISPATCHER)]//表示全局的派发器
    public IEventDispatcher dispatcher { get; set; }



    public override void OnRegister()//注册  当属性都调用完成后就会去调用这个方法  在OnRegister里面可以访问这些属性
    {
        cubeView.Init();

        dispatcher.AddListener(Demo1MediatorEvent.ScoreChange, OnScoreChange);//监听注册方法返回分数
        cubeView.dispacher.AddListener(Demo1MediatorEvent.ClickDown,OnClickDown);

        //通过dispatcher发起请求分数的命令
        dispatcher.Dispatch(Demo1CommandEvent.RequeestScore);

        base.OnRegister();
    }
    public override void OnRemove()//当取消运行的时候会调用这个   当Mediator对应的View的视图被销毁的时候会调用OnRemove
    {

        dispatcher.RemoveListener(Demo1MediatorEvent.ScoreChange, OnScoreChange);//移除监听
        cubeView.dispacher.RemoveListener(Demo1MediatorEvent.ClickDown, OnClickDown);
    }
    //将返回的分数传递给View层
    public void OnScoreChange(IEvent evt) {
        cubeView.Updatescore((int)evt.data);
    }

    //加分
    public void OnClickDown()
    {
        dispatcher.Dispatch(Demo1CommandEvent.UpdataScore);
    }
}

下面是几个发起请求需要的枚举值,有CommandEvent、ServiceEvent、MediatorEvent。

public enum Demo1CommandEvent  {
       RequeestScore,
       UpdataScore
}

public enum Demo1ServiceEvent  {

RequestScore
}

public enum Demo1MediatorEvent
{
    ScoreChange,
    ClickDown
}

接下来就是Command层了,有请求分数和更新分数的命令,所以两个Command

using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using UnityEngine;

public class RequestscoreCommand : EventCommand {

    [Inject]
    public IScoreService scoreService { get; set; }
    [Inject]
    public ScorgModel scoreModel { get; set; }

    //[Inject(ContextKeys.CONTEXT_DISPATCHER)]//全局的dispatcher
    //public IEventDispatcher dispacher { get; set; }

    public override void Execute()//表示命名执行的时候
    {
        Retain();//表示让这个请求先不销毁,等接收到数据后进行释放
        //添加监听器,监听OnComplete方法,第一个参数表示方法的事件枚举类型, 第二个参数表示一个方法
        scoreService.dispatcher.AddListener(Demo1ServiceEvent.RequestScore, OnComplete);

        scoreService.RequestScore("http://xx/xx/xxx");
    }

    //这个方法表示当scoreService请求分数完成后就会调用这个方法去取得数据
    private void OnComplete(IEvent evt) {//IEvent存储的就是参数

        Debug.Log("request score complete"+evt.data);
        scoreService.dispatcher.RemoveListener(Demo1ServiceEvent.RequestScore, OnComplete);//移除对OnComplete的监听
        scoreModel.score = (int)evt.data;
        dispatcher.Dispatch(Demo1MediatorEvent.ScoreChange, evt.data);

      

        Release();//释放请求,销毁当前对象
    }
}
using strange.extensions.command.impl;

public class UpdataScoreCommand : EventCommand
{
    [Inject]
    public ScorgModel ScoreModel { get; set; }
    [Inject]
    public IScoreService scoreServer { get; set; }

    public override void Execute()
    {
        ScoreModel.score++;
        scoreServer.UpdateScore("http://xx/xx", ScoreModel.score);

        dispatcher.Dispatch(Demo1MediatorEvent.ScoreChange, ScoreModel.score);
    }

}

然后就是Servicevice层了,一个是接口,一个是实现接口的类

using strange.extensions.dispatcher.eventdispatcher.api;

public interface IScoreService  {

    void RequestScore(string url);//请求分数

    void  OnReceiveScore();//收到服务器端发送的分数

    void UpdateScore(string url, int Score);//更新分数

    IEventDispatcher dispatcher { get; set; }
}

using strange.extensions.dispatcher.eventdispatcher.api;
using UnityEngine;

public class ScireService : IScoreService
{

    [Inject]
    public IEventDispatcher dispatcher { get; set; }


    public void RequestScore(string url)////请求分数
    {
        Debug.Log("Request Score from url:" + url);
        OnReceiveScore();
    }

    public void  OnReceiveScore()////收到服务器端发送的分数
    {
        int score = Random.Range(0, 100);
        dispatcher.Dispatch(Demo1ServiceEvent.RequestScore, score);//通过Demo1CommandEvent.RequeestScore这个事件将数据发送出去
    }
    public void UpdateScore(string url, int Score)//更新分数
    {
        Debug.Log("Update score to url:"+url+"new score:"+Score);
    }
}


最后就是模型层,保存分分数,数据。

public class ScorgModel  {
    public int score { get; set; }
}

这样就完成了,开始请求获取一个初始分数,往后每点击Cube一次数据就会更新一次数据+1。
StrangeIOC游戏编程框架_第5张图片
1510050484(1).jpg

创建编辑器扩展,用来添加需要的音效并用AudioManager管理起来

首先创建一个Editor文件夹,里面再创建一个编辑器扩展类AudioWindowEditor用来创建一个音效添加的面板,并将音效的路径内容保存到Text文本里面存储到Resources文件夹下,代码如下

using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;

public class AudioWindowEditor : EditorWindow
{

    //创建自定义窗口
    [MenuItem("Manager/AudioManager")]
    static void CreateWindow()
    {
        AudioWindowEditor audio = EditorWindow.GetWindow("音效管理");

        //Rect rect = new Rect(400, 400, 300, 300);
        //AudioWindowEditor audio = EditorWindow.GetWindowWithRect (typeof(AudioWindowEditor),rect)as AudioWindowEditor;
        audio.Show();
    }

    private string audioname;
    private string audiopath;
    private Dictionary audioDict = new Dictionary();//存储音效路径


    private void Awake()
    {
        LoadAudioList();
    }

    //OnGUI绘制
    private void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("音效名称");
        GUILayout.FlexibleSpace();
        GUILayout.Label("音效路径");
        GUILayout.FlexibleSpace();
        GUILayout.Label("操作");
        EditorGUILayout.EndHorizontal();
        foreach (string key in audioDict.Keys)
        {
            string value;
            audioDict.TryGetValue(key, out value);
            GUILayout.BeginHorizontal();
            GUILayout.Label(key);
            GUILayout.FlexibleSpace();
            GUILayout.Label(value);
            GUILayout.FlexibleSpace();
            if (GUILayout.Button("删除", GUILayout.Width(100), GUILayout.Height(20)))
            {
                audioDict.Remove(key);
                SaveAudioList();
                return;
            }
            GUILayout.EndHorizontal();
        }

        audioname = EditorGUILayout.TextField("音效名字", audioname);
        audiopath = EditorGUILayout.TextField("音效路径", audiopath);
        if (GUILayout.Button("添加音效"))
        {
            object o = Resources.Load(audiopath);//加载音效路径
            if (o == null)
            {
                Debug.LogWarning("输入的音效不存在于:" + audiopath + ",添加不成功");
                audiopath = "";
            }
            else
            {
                if (audioDict.ContainsKey(audioname))
                {

                    Debug.LogWarning("已添加成功,请勿重复添加");
                }
                else
                {
                    audioDict.Add(audioname, audiopath);
                    SaveAudioList();
                }
            }
        }
    }
    //窗口面板被更新时调用
    private void OnInspectorUpdate()
    {
        LoadAudioList();
    }

    //存储成文本
    private void SaveAudioList()
    {
        StringBuilder sb = new StringBuilder();

        foreach (string key in audioDict.Keys)
        {
            string value;
            audioDict.TryGetValue(key, out value);
            sb.Append(key + "," + value + "\n");//组拼字符串
        }
        //AudioManager.AudioTextPath里面是创建并保存文本的路径 第二个参数是保存内容的路径
        File.WriteAllText(AudioManager.AudioTextPath, sb.ToString());//可覆盖之前内容
        //File.AppendAllText(savePath,sb.ToString());
    }
    //读取音效文本里面的内容并加载到字典里面显示到面版上
    private void LoadAudioList()
    {
        audioDict = new Dictionary();
        if (File.Exists(AudioManager.AudioTextPath) == false) return;
        string[] lines = File.ReadAllLines(AudioManager.AudioTextPath);
        foreach (string line in lines)
        {
            if (string.IsNullOrEmpty(line)) continue;
            string[] keyvalue = line.Split(',');
            audioDict.Add(keyvalue[0], keyvalue[1]);
        }

    }
}

,这样音效添加面板创建出来了并将其内容保存到了Text文本里,然后再Scripts文件夹下创建音效管理脚本AudioManager,用来管理所有的音效,并写入播放音效的方法,代码下

using System.Collections.Generic;
using UnityEngine;

public class AudioManager
{

    private static string audioTextPathPrefix = Application.dataPath + "\\FrameworkAudio\\Resources\\";
    private const string audioTextPathMiddle = "audiolist";
    private const string audioTextPathPostfix = ".txt";


    public static string AudioTextPath
    {
        get
        {
            return audioTextPathPrefix + audioTextPathMiddle + audioTextPathPostfix;
        }
    }




    Dictionary AudioClipDict = new Dictionary();//存储音效

    private bool IsMult = false;//是否静音

    //public AudioManager()
    //{
    //    LoadAudioClip();
    //}

        //初始化
    public void Init()
    {

        LoadAudioClip();
    }
    //加载解析音效并保存到字典里
    private void LoadAudioClip()
    {
        AudioClipDict = new Dictionary();
        TextAsset ta = Resources.Load(audioTextPathMiddle);
        string[] lines = ta.text.Split('\n');

        foreach (string line in lines)
        {
            if (string.IsNullOrEmpty(line)) return;
            string[] keyvalue = line.Split(',');
            string key = keyvalue[0];
            AudioClip value = Resources.Load(keyvalue[1]);
            AudioClipDict.Add(key, value);
        }
    }
    //播放音效
    public void playAudio(string name)
    {
        if (IsMult) return;
        AudioClip ac;
        AudioClipDict.TryGetValue(name, out ac);
        if (ac != null)
        {
            AudioSource.PlayClipAtPoint(ac, Vector3.zero);
        }
    }
    //有位置的音效播放
    public void playAudio(string name, Vector3 position)
    {
        if (IsMult) return;
        AudioClip ac;
        AudioClipDict.TryGetValue(name, out ac);
        if (ac != null)
        {
            AudioSource.PlayClipAtPoint(ac, position);
        }
    }
}

这样就可以在面板里面创建音效并保存到文本里面,然后管理到了AudioManager里面,下次需要用的时候只需要添加路径然后调用AudioManager里面的playAudio方法就可以播放音效了,非常方便


StrangeIOC游戏编程框架_第6张图片
D`0DQ2.png

下面去框架里面使用下音效试下效果,首先去Demo1Cntext里面绑定我们的音效的管理类。


StrangeIOC游戏编程框架_第7张图片
QERMSA.png
然后在调用开始命令StartCommand的时候去初始化就是读取解析文本里面的音效内容存储到字典里,所以在StartCommand里面写入
using strange.extensions.command.impl;

//开始命令
public class StartCommand : Command {

    [Inject]
    public AudioManager audioManager { get; set; }
    /// 
    ///重写  当这个命令被执行的时候默认会调用Execute方法
    /// 
    public override void Execute()
    {
        audioManager.Init();
    }
}

接下来我们就可以在每次点击的时候使用文本里面的一个音效了,在视图层CubeView的OnMouseDown方法里面,每次点击Cube的时候调用一下音效管理里面的播放方法,把需要播放的音效名字输入上去就OK了

    [Inject]
    public AudioManager audiomanger { get; set; }
  private void OnMouseDown()
    {
        //加分
        Debug.Log("OnMouDown");
        audiomanger.playAudio("Hit");
        dispacher.Dispatch(Demo1MediatorEvent.ClickDown);
    }

这样就完成了一个音效的播放

PoolManager资源池

创建资源池,就好比子弹需要不断的生成创建这样会很耗费性能,这时候我们就可以把子弹放到List集合里面,比如我们有十发子弹,在最开始的时候List会保存这十发子弹,后面这十发子弹是不会销毁的,只需要把其SetActive设置成False就行,下次再生成子弹的时候只需要去List集合里面去取得自动将其SetActive设置成True就行。这样这些子弹就会不断的循环利用。当然不止子弹,或者其他的物体需要不停的创建的就可以使用PoolManager去管理,例如一些特效、NPC等等。

下面我来做一个简单的池子,这里可以使用Unity里面的一个工具:使用定制资源配置文件
相关学习文档:http://www.360doc.com/content/14/0323/13/12282510_363016017.shtml
首先创建GameobjectPool一个资源池里面写入需要的属性,让其成为可序列化的

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

/// 
/// 资源池  用来单独编辑一个资源池
/// 
[Serializable]//序列化  可以把这个类保存到本地文件
public class GameobjectPool  {

    [SerializeField]
    private  string name;//表示这个池子的名字
    [SerializeField]
    private  GameObject prefab;
    [SerializeField]
    private  int maxAmount;//池子最大可容纳对象

    [NonSerialized]//不需要序列化
    private List goList = new List();

}

然后再创建一个资源管理器GameobjectPoolList

using System.Collections.Generic;
using UnityEngine;

/// 
/// 管理所有的资源池
/// 
public class GameobjectPoolList : ScriptableObject
{//表示把GameobjectPoolList变成可以自定义资源配置的文件

    public List poolList;
}

接着创建一个编辑器类PoolManagerEditor,让其可生成一个配置文件

using UnityEditor;
using UnityEngine;

public class PoolManagerEditor
{
    [MenuItem("Manager/Crate GameobjectPoolConfig")]
    static void CreateGameObjectPoolList()
    {
        GameobjectPoolList poolList =ScriptableObject.CreateInstance();
        //创建一个资源,c参数一创建什么类型的资源,参数二 资源的路径
        AssetDatabase.CreateAsset(poolList, "Assets/FrameworkAudio/Resources/gameobjectpool.asset");//gameobjectpool.property文件名+后缀
        AssetDatabase.SaveAssets();
    }
}

这样我们只要一点击打Manager下Crate GameobjectPoolConfig,就会在Resouces下生成一个资源配置文件,里面可以管理我们所有的资源池,这里我在里面创建了两个资源池,一个子弹Bullet,一个特效HitEff,并设置了其属性


StrangeIOC游戏编程框架_第8张图片
W`HBTHRD%8L.png

下面再两个Prefaba两个添加禁用的脚本DeactiveForTime,让其在三秒的时候禁用.

using UnityEngine;

public class DeactiveForTime : MonoBehaviour {

    private void OnEnable()
    {
        Invoke("Deactive", 3);//5秒后禁用 
    }
    void Deactive()
    {
        this.gameObject.SetActive(false);
    }
}

这样在生成后的三秒就会禁用。
接着创建一个PoolManager脚本去解析gameobjectpool配置文件里面属性,将里面所有的资源池放到Dictionary里面管理起来,需要用的时候只有根据PoolName就可以获取到相应的资源池了,所以在PoolManager脚本里面的代码如下

using System.Collections.Generic;
using UnityEngine;

public class PoolManager : MonoBehaviour {

    private static PoolManager _instance;
    public static PoolManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new PoolManager();
            }
            return _instance;      
        }
    }

    private static string poolConfigPathPrefix = "Assets/FrameworkAudio/Resources/";
    private const string pooConfigPathMiddle = "gameobjectpool";
    private const string pooConfigPathPostfix = ".asset";


    public static string pooConfigPath
    {
        get
        {
            return poolConfigPathPrefix + pooConfigPathMiddle + pooConfigPathPostfix;
        }
    }
    private Dictionary poolDict;
    private PoolManager()
    {
        GameobjectPoolList pooList = Resources.Load(pooConfigPathMiddle);//加载资源池
        poolDict = new Dictionary();
        foreach (GameobjectPool pool in pooList.poolList)
        {
            poolDict.Add(pool.name,pool);//将所有资源池里面的属性存储进poolDict里面
        }
    }
    public void Init()
    {
        //Do nothing

    }
    public GameObject GetInstBullet(string poolName)
    {
        GameobjectPool pool;
        //判断有没有这个资源池
        if (poolDict.TryGetValue(poolName, out pool))
        {
            return pool.GetInstance();
        }
        Debug.LogWarning("Pool:"+poolName+"is not exits!!");
        return null;
    }

}

接下来去GameobjectPool里面写入实例化的代码,在GameobjectPool里面添加代码

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

/// 
/// 资源池  用来单独编辑一个资源池
/// 
[Serializable]//序列化  可以把这个类保存到本地文件
public class GameobjectPool  {


    public   string name;//表示这个池子的名字
    [SerializeField]
    private  GameObject prefab;
    [SerializeField]
    private  int maxAmount;//池子最大可容纳对象


    [NonSerialized]//不需要序列化
    private List goList = new List();

    /// 
    /// 表示从资源池中获取一个实例
    /// 
    public GameObject GetInstance()
    {
        foreach (GameObject go in goList)
        {
            //判断里面的每个游戏对象是否启用,如果里面有没有启用的表示这个游戏对象可用,返回当前游戏对象
            if (go.activeInHierarchy == false)
            {
                go.SetActive(true);
                return go;
            }

        }
        //如果池子里的游戏对象大于或等于我们需要的最大的容量
        if (goList.Count>=maxAmount )
        {
            //从集合里面销毁一个
            GameObject.Destroy(goList[0]);
            goList.RemoveAt(0);
        }

        //如果池子里面既没有可用的并且池子里还有容量那么就需要创建新的游戏对象了
        GameObject temp=GameObject.Instantiate(prefab);
 
        goList.Add(temp);

        return temp;
    }
}

接下来我们就需要去框架里面应用一下了,只需要在游戏的视图层里面调用PoolManager里面的 GetInstBullet()方法,将需要的资源池PoolName传递上去就OK了。我这里在CubeView里面的Updata里面写入以下代码,让每点击一次就生产一个Bullet。

 if (Input.GetMouseButtonDown(0))
        {
            PoolManager.Instance.GetInstBullet("Bullet");
        }

这样就完成了所有资源池的管理。

你可能感兴趣的:(StrangeIOC游戏编程框架)