一、导入StrangeIoc框架
Asset Store 搜 StrangeIoc 下载 导入
二、StrangeIoC的流程图
三、实战
新建一个Unity3D工程,3D的,用StrangeIoc框架去实现我们最原始的打砖块!
Project文件:
Scripts-> Framework存的全是我们框架相关的C#脚本文件
01-StrangeIoc是StrangeIoc场景下的普通C#脚本即与框架无关,或关系不大
1.ROOT文件下的框架脚本
StrangeIocContextView对应着
StrangeIocMVCSContextView对应着
代码如下:
StrangeIocContextView
using strange.extensions.context.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StrangeIocContextView : ContextView {
/// <summary>
/// ContextView绑定MVCSContext
/// </summary>
private void Awake()
{
this.context = new StrangeIocMVCSContextView(this);
}
}
StrangeIocMVCSContextView
using strange.extensions.context.api;
using strange.extensions.context.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StrangeIocMVCSContextView : MVCSContext {
/// <summary>
/// 构造MVCSContext需要ContextView
/// </summary>
/// <param name="view"></param>
public StrangeIocMVCSContextView(MonoBehaviour view):base(view)
{
}
/// <summary>
/// 初始化捆绑关系(类与类,接口与类,消息(enum)与类之间)
/// </summary>
protected override void mapBindings()
{
//inject
injectionBinder.Bind<ILocalDataService>().To<LocalDataService>().ToSingleton();
injectionBinder.Bind<GameModel>().To<GameModel>().ToSingleton();
//command
commandBinder.Bind(CommandEvent.RequestScore).To<RequestScoreCommand>();//这样就可以用全局发送器的Dispatch方法发送RequestScore消息,去到RequestScoreCommand处理了
commandBinder.Bind(CommandEvent.HitCube).To<HitCubeCommand>();
//START消息是启动StrangeIoc框架时发送的,这样会触发StartCommand中的Execute方法
commandBinder.Bind(ContextEvent.START).To<StartCommand>().Once();
//mediator
mediationBinder.Bind<PlayerView>().To<PlayerMediator>();//这样Mediator即可直接使用Inject注入方式获取到View
}
}
先不用管方法中的具体内容到底是什么意思,看方法前面的注释去好好地理解一下,我们这样就实现了
将StrangeIocContextView挂载到一个空物体身上,空物体随你命名。(一定要做这一步!)
这样,我们每次启动游戏的时候就由StrangeIocContextView启动了StrangeIoc框架,可以发现我们就只是在Awake写了一条代码而已,即这条代码
this.context = new StrangeIocMVCSContextView(this);
意思是将ContextView与MVCSContextView捆绑,同时也实例化MVCSContextView.整个框架主要还是靠MVCSContextView来管理的。ContextView只是启动框架的。
2.Model文件夹的框架脚本:
存游戏数据的普通脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameModel {
public int Score { get; set; }//我们需要一个分数来显示集中球体后的得分,假设击中就有一分
}
3.View文件夹的框架脚本:
PlayerView是一个视图类,继承于框架提供的View,挂载于主摄像机Main Camera身上,作用是射球,与PlayerMediator交互更新分数。(View继承于Monobehaviour,所以它可以挂载到游戏物体身上)
PlayerMediator是一个Mediator类,继承于框架提供的Mediator,它不能挂载到游戏物体上,它充当PlayerView的“传话人”
MediatorMessage是一个枚举类,保存了Mediator消息类型。(其实这个我不发代码,你们也能懂,就是个枚举类嘛)
下面的很难一口气讲解完,需要配合其他的框架脚本解释,不过,只要你跟着思路走就没什么难度。
代码如下:
PlayerView
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerView : View {
//局部发送器:PlayerView利用它与PlayerMediator交互,在下面的HitCube方法中使用到,传话给Mediator,执行Mediator的HitCub方法 具体流程下面说
[Inject] //Inject注入,框架特性之一,使用了该特性的属性,都是框架自动赋值的,目前,我所知的只有发送器是不需要捆绑关系的,也就是内置捆绑好了。
public IEventDispatcher dispatcher { get; set; }//注意一定要是属性,才能用[Inject]注入的方法给它赋值
public GameObject bullet;
public float speed;
public Text scoreText;//直接外部拖拽赋值
protected override void Start()
{
base.Start();
}
//功能:点击鼠标左击时,射线检测到Cube方块,实例化子弹,给子弹一个冲击力Impulse,方向是dir(这个大家都懂吧,A-B 就是B指向A的向量)
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray;
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray,out hit))
{
if(hit.collider.tag=="Cube")
{
GameObject bulletGo = Instantiate(bullet, transform.position, Quaternion.identity);
bulletGo.GetComponent<Bullet>().cameraGo = this.gameObject;
Vector3 dir = hit.transform.position - transform.position;//获取从摄像机指向射线撞击点的方向,下面还要对dir做一步标准化normalized
bulletGo.GetComponent<Rigidbody>().AddForce(dir.normalized * speed, ForceMode.Impulse);//Impulse是冲击力
}
}
}
}
public void UpdateScoreText(int score)//更新分数
{
scoreText.text = score.ToString();
}
public void HitCube()//子弹撞到Cube后 子弹会调用该方法
{
//发送 撞击到方块 事件
dispatcher.Dispatch(MediatorMessage.CallPlayerMediator_HitCube);
//Debug.Log("PlayerViewHitCube");
}
}
PlayerMediator
using strange.extensions.context.api;
using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMediator : Mediator {
//创建一个全局Dispatcher发送器
[Inject(ContextKeys.CONTEXT_DISPATCHER)]
public IEventDispatcher Dispatcher { get; set; }
[Inject]
public PlayerView PlayerView { get; set; } //我们是用注入的方法得到PlayerView的,注意我们在StrangeIocMVCSContextView对PlayerView与PlayerMediator做了捆绑才有这个效果
public override void PreRegister()
{
//Debug.Log("PlayerMediator捆绑之前");
}
public override void OnRegister()
{
//Debug.Log("PlayerMediator捆绑之后");//这个方法是StrangeIocMVCSContextView里的mapBindings的mediationBinder.Bind<PlayerView>().To<PlayerMediator>();执行后会立刻执行
Dispatcher.AddListener(MediatorMessage.CallPlayerMediator_UpdateScore, UpdateScore);//全局发送器注册一个消息事件,消息是第一个参数,事件是第二个参数(局部发送器一样道理),这里就是让其他类能够直接调用UpdateScore方法。
PlayerView.dispatcher.AddListener(MediatorMessage.CallPlayerMediator_HitCube,HitCube);//给PlayerView的局部发送器添加一个监听,消息是第一个参数,事件是第二个参数,这样PlayerView就能够发送这个消息,去调用PlayerMediator的HitCube方法了,是不是很有意思!完全隔离全靠消息。
//发送 AddScore事件
Dispatcher.Dispatch(CommandEvent.RequestScore);//用全局发送器的Dispatch方法发送一个消息,事件是什么呢?好!让我们回到StrangeIocContextView类的mapBindings方法是不是有个捆绑是关于RequestScore消息捆绑的,它对应事件是RequestScoreCommand
}
public override void OnRemove()
{
//Debug.Log("PlayerMediator移除之前");
Dispatcher.RemoveListener(MediatorMessage.CallPlayerMediator_UpdateScore, UpdateScore); //销毁了当前Mediator,我们也应该把全局发送器的相关监听给销毁。
}
private void UpdateScore(IEvent ie)
{
PlayerView.UpdateScoreText((int)ie.data);
}
public void HitCube()
{
//发送 碰撞方块事件
Dispatcher.Dispatch(CommandEvent.HitCube);
//Debug.Log("PlayerMediatorHitCube");
}
}
4.Command文件夹的框架脚本:
RequestScoreCommand
using strange.extensions.command.impl;
using strange.extensions.dispatcher.eventdispatcher.api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//继承于EventCommand 是能够用它里面定义的一个全局发送器,全局发送器是全局唯一的。
public class RequestScoreCommand : EventCommand {
[Inject]
public GameModel gameModel { get; set; }//注入方式,我们捆绑了这个GameModel,所以我们会自动地用这个GameModel来注入给这个gameModel对象,而且是全局唯一的。
[Inject]
public ILocalDataService localDataService { get; set; }//注入,同上,在捆绑处的To<LocalDataService>() 会注入Bind<ILocalDataService>()里面。好好地理解我这一句话,To注入到Bind
//用全局发送器发送它捆绑的消息后会调用该方法
public override void Execute()
{
Retain();//保持该Command的存在,只要没有Release(),那么就不会销毁该Command
localDataService.dispatcher.AddListener(CommandMessage.CallRequestScoreCommand, OnCompleteRequestScore);//localDataService的局部发送器注册监听
//从本地服务器获取分数请求
localDataService.RequestScore();
}
//从本地服务器获取到了分数之后的操作
private void OnCompleteRequestScore(IEvent ie)
{
localDataService.dispatcher.RemoveListener(CommandMessage.CallRequestScoreCommand, OnCompleteRequestScore);
int score = (int)ie.data;
gameModel.Score = score;
dispatcher.Dispatch(MediatorMessage.CallPlayerMediator_UpdateScore, score);
Release();
}
}
5.Service文件夹的框架脚本:
ILocalDataService:
using strange.extensions.dispatcher.eventdispatcher.api;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ILocalDataService {
void RequestScore();
void OnRequestScore(int score);
void UpdateScore(int score);
IEventDispatcher dispatcher { get; set; }
}
LocalDataService:
using System.Collections;
using System.Collections.Generic;
using strange.extensions.dispatcher.eventdispatcher.api;
using UnityEngine;
using System.IO;
public class LocalDataService : ILocalDataService {
[Inject]
public GameModel gameModel { get; set; }
//局部发送器,用于返回Command
[Inject]
public IEventDispatcher dispatcher { get; set; }
//请求本地文本分数
public void RequestScore()
{
int score = 0;
string path = Application.dataPath + "/Resources/" + GameStatic.PlayerData + ".txt";
//从本地加载分数
if (File.Exists(path))
{
string str = Resources.Load<TextAsset>(GameStatic.PlayerData).text;
if (string.IsNullOrEmpty(str)==false)
{
score = int.Parse(str);
}
}
else
{
Debug.LogWarning("本地保存文件不存在,正在帮您生成中");
File.WriteAllText(path, "0");
score = 0;
}
//拿到分数之后要做的事情
OnRequestScore(score);
}
public void OnRequestScore(int score)
{
//发送回去RequestScoreCommand,把分数作为参数发送回去
dispatcher.Dispatch(CommandMessage.CallRequestScoreCommand, score);
}
/// <summary>
/// 更新本地文本分数
/// </summary>
public void UpdateScore(int score)
{
string path = Application.dataPath + "/Resources/" + GameStatic.PlayerData + ".txt";
File.WriteAllText(path, score.ToString());
}
}
上面的一大坨代码,用框架流程图来解释一下,我们到底干了什么事情!
还有一个没画出来,更新GameModel的Score,你能理解上面的,基本上,就OK了。服务器我随便弄了个脚本来做的,终结!