一般来说Bundle之间共同使用的资源会提取出来为一个公用Bundle,但是可能不注意的情况下会导致Bundle下的Prefab引用到了其他Bundle的资源,若这种情况一直积累,就有可能发生启动加载Bundle的时候连带引用加载了一堆Bundle,启动卡顿及占用内存。所以需要根据个项目不同的Bundle打包策略定制一个检测Bundle的错误引用关系,及时纠正。
一般来说都是按目录就区分Bundle的,大概的思路就是这样:
public void Start()
{
//获取所有的Prefab和材质
string[] listString = AssetDatabase.FindAssets("t:Prefab t:Material", new string[] { "Assets/AssetsPackage" });
for (int i = 0; i < listString.Length; i++)
{
string cur = listString[i];
List<string> refs = new List<string>();
string assetPath = AssetDatabase.GUIDToAssetPath(cur);
string headPath = GetHeadPath(assetPath);//获取当前资源应该在的Bundle
string[] names = AssetDatabase.GetDependencies(new string[] { assetPath }); //依赖的东东
foreach (var name in names)
{
bool is_common = false;
//排除引用的是公用资源 commonRef需要定制(值为目录数组),有的第三方库,有的公用资源Bundle等,项目不同这个也不同
foreach (var cc in commonRef)
{
if (name.Contains(cc))
{
is_common = true;
break;
}
}
if (!is_common)
{
if (name.Contains(headPath)) //不是用公用,看看引用的是不是自己所属的bundle
{
is_common = true;
}
}
//上面情况都不是的话应该是有问题的
if (!is_common)
{
if (name == assetPath)
{
is_common = true;
}
}
if (!is_common)
AddProblem(assetPath, name);
}
EditorUtility.DisplayProgressBar("Progress", (i + 1) + "/" + listString.Length, (i + 1) / listString.Length);
}
EditorUtility.ClearProgressBar();
isDone = true;
this.ShowNotification(new GUIContent("Done!"));
}客户端的事件id可以用一个单例递增来,GetEventID每次都返回一个+1的id,区别对待就好了,这样可以有限防止内部通讯的id重复或者定义麻烦
Event.ON_WINDOW_CREATE = Messenger:GetEventId() Event.ON_WINDOW_OPEN = Messenger:GetEventId() Event.ON_WINDOW_CLOSE = Messenger:GetEventId()
思想:场景由一些渲染设置(雾、环境光、光晕等)、天空盒子材质球、地形、场景中的静态模型、烘焙的灯光贴图等构成的,在Uinty使用编辑器将这些内容设置好,排列好,渲染好,随后编写一个方法将这些设置或者是物体序列化成一个json文件,资源细化成一个一个prefab并以材质为分类命名好。当加载场景的时候,就加在那么一个json文件,设置场景中的渲染设置,还有将那些静态的模型物件加载出来,同一个材质的放置到同一的父物体下(用于动态的静态批处理),设置好物体的位置、旋转和缩放等。加载lightmap,渲染灯光,从而完成一个场景的加载。使用数据驱动的形式,当需要改变场景的某一个物体时,就可以直接修改配置文件和资源就行了,可以达到场景的热更新和一些渲染氛围的改变,满足节日性的更新。
一、序列化编辑好的场景为json数据
[MenuItem ("Custom/Scene/Selected GameObjects And To Json")]
static void SaveAllGameObjectToJson()
{
if (m_GameObjects == null)
{
m_GameObjects = new Dictionary<string, GameObjectResInfo>();
}
string path_out = "Assets/Resources/Terrains/";
Directory.CreateDirectory( path_out );
char[] delimiter = {'/', '.'};
string[] words = EditorApplication.currentScene.Split(delimiter);
string sceneName = words[words.Length-2];
string Json_out = path_out + FormatString(sceneName) + "/";
if ( !Directory.Exists(Json_out) )
Directory.CreateDirectory(Json_out);
string[] files = Directory.GetFiles(Json_out, "*.*");
foreach (string fileName in files)
{
if ( !fileName.Contains("obstruct") )
File.Delete( fileName );
}
string strJsonFileName = Json_out + FormatString(sceneName) + "_json.txt";
FileStream fs = new FileStream (strJsonFileName,FileMode.Create);
StreamWriter out_stream = new StreamWriter (fs);
//序列化的数据有
/**
* RenderSettings.fog 是否开启雾效
* RenderSettings.fogMode.ToString() 雾的模式(线性、指数、指数的平方)
* RenderSettings.fogColor.rgba 雾的颜色
* RenderSettings.fogStartDistance 线性模式雾的起始距离
* RenderSettings.fogEndDistance 线性模式雾的结束距离
* RenderSettings.fogDensity 雾的强度
*
* RenderSettings.ambientLight.rgba 环境光的颜色
* RenderSettings.haloStrength 光晕强度
* RenderSettings.flareStrength 耀光的强度
* RenderSettings.skybox 字符串保存 天空盒子材质
*/
//天空盒子材质的序列化
UnityEngine.Material skyMat = RenderSettings.skybox;
if (skyMat != null)
{
string skyPath = AssetDatabase.GetAssetPath(skyMat);
string skyMatPath = Json_out + FormatString(skyMat.name) + ".mat";
AssetDatabase.CopyAsset( skyPath, skyMatPath );
//skyMat.name 保存材质的名字 对应的去复制到的资源目录中查找
}
//序列化烘焙好的光照贴图
foreach (LightmapData data in LightmapSettings.lightmaps)
{
if (data.lightmapFar != null)
{
string nameFar = FormatString(sceneName + "_" + data.lightmapFar.name) + "_tex";
string lightBinPath = Json_out + nameFar;
string lightResPath = AssetDatabase.GetAssetPath( data.lightmapFar );
int idx = lightResPath.LastIndexOf( '.' );
lightBinPath = lightBinPath + lightResPath.Substring( idx, lightResPath.Length-idx );
AssetDatabase.CopyAsset( lightResPath, lightBinPath );
//data.lightmapFar.name 名字
//nameFar 光照贴图资源名字
}
if (data.lightmapNear != null)
{
string nameNear = FormatString(sceneName + "_" + data.lightmapNear.name) + "_tex";
string lightBinPath = Json_out + nameNear;
string lightResPath = AssetDatabase.GetAssetPath( data.lightmapNear );
int idx = lightResPath.LastIndexOf( '.' );
lightBinPath = lightBinPath + lightResPath.Substring( idx, lightResPath.Length-idx );
AssetDatabase.CopyAsset( lightResPath, lightBinPath );
//data.lightmapNear.name 名字
//nameNear 光照贴图资源名字
}
}
//序列化场景中被选中的物体
UnityEngine.Object[] objects = Selection.GetFiltered(typeof(GameObject), SelectionMode.DeepAssets);//获取场景中选中的物体们
int wuJianIndex = 0;
foreach (GameObject obj in objects)
{
//如果物体有材质,会以材质的名字为前缀命名(combineDC),后面加载时分组,方便静态绑定
string strName = combineDC + obj.name + ResIndex + ".prefab";
string prefabFile = prefabsPath + strName;
bool isExistPrefab = false;
if (!isExistPrefab)
{
//创建prefab
UnityEngine.Object objprefab = PrefabUtility.CreateEmptyPrefab(prefabFile);
PrefabUtility.ReplacePrefab(obj, objprefab);
}
//根据obj.name的命名 确定它是什么类型的物体
//序列化的信息有
/**
* 物体的类型
* 物体的名字 材质为组的命名
* 物体预设名字
* 物体的位置
* 物体的旋转
* 物体的缩放
* 如果物体是特殊类型,保存一些组件信息,例如灯光或则相机
* */
}
out_stream.Flush();
fs.Close();
}二、解析和加载场景
virtual public void Initial_local()
{
ParseJson_local(); //解析json数据保存到对象中
_LoadAssets_local(); //以材质为组加载物体 设置渲染设置
SetSceneLightMap(m_mapTable.extralightmap);//设置光照贴图
CorResMgr.Get().UnloadUnusedAssets();
}
protected virtual void _LoadAssets_local()
{
Camera mainCam = CameraMagr.Get().GetMainCamera().GetCamera();
SceneAsset asset;
for(int i=0; i<m_AssetList.Count; i++)
{
asset = m_AssetList[i];
if(asset == null)
continue;
switch(asset.Type)
{
case GameObjectType.Camera_Type:
{
if(asset.isLoadFinished)
break;
mainCam.transform.position = asset.Position;
mainCam.transform.rotation = asset.Rotation;
CameraMagr.Get().GetMainCamera().static_forward = mainCam.transform.forward;
asset.isLoadFinished = true;
break;
}
case GameObjectType.Light_Type:
case GameObjectType.Water_Type:
default:
if(asset.isLoadFinished)
break;
_LoadAsset_local(asset);
break;
}
}
foreach(GameObject g in parentDic.Values)
{
if(g !=null && !g.name.Contains("eft") )
{
g.AddComponent<StaticBatch>();//静态批处理
}
}
}
protected void _LoadAsset_local(SceneAsset asset)
{
try
{
UnityEngine.Object obj = CorResMgr.Get().LoadResource( asset.SourcePath );
if(obj == null)
return;
//实例化 指定位置和旋转
asset.instObject = (GameObject)UnityEngine.Object.Instantiate( obj, asset.Position, asset.Rotation );
if(asset.instObject == null)
{
Debug.Log("Init map asset is error! path="+asset.SourcePath);
return;
}
if(mapRoot == null)
{
mapRoot = new GameObject(Prefab);
mapRoot.transform.localPosition = Vector3.zero;
mapRoot.transform.localScale = Vector3.one;
}
//根据不同的类型做不同的处理
switch(asset.Type)
{
case GameObjectType.Actor_Type:
case GameObjectType.Monster_Type:
case GameObjectType.Generic_Type:
{
asset.instObject.transform.parent = mapRoot.transform;
asset.instObject.transform.localScale = asset.Scale;
break;
}
case GameObjectType.Building_Type:
{
string[] middleStr = asset.Name.Split('-');
asset.instObject.transform.localScale = asset.Scale;
if(parentDic.ContainsKey(middleStr[0]))
{
if(parentDic[middleStr[0]] ==null)
{
GameObject go =new GameObject();
parentDic[middleStr[0]] = go;
parentDic[middleStr[0]].transform.localPosition = Vector3.zero;
parentDic[middleStr[0]].transform.localScale = Vector3.one;
parentDic[middleStr[0]].transform.parent = mapRoot.transform;
parentDic[middleStr[0]].name =middleStr[0]+"Parent";
asset.instObject.transform.parent = parentDic[middleStr[0]].transform;
}
else
{
asset.instObject.transform.parent = parentDic[middleStr[0]].transform;
}
}
else
{
asset.instObject.transform.parent = mapRoot.transform;
}
break;
}
case GameObjectType.Light_Type:
{
asset.instObject.transform.parent = mapRoot.transform;
Light light = (Light)asset.instObject.GetComponent("Light");
light.color = asset.LightParam.Color;
light.intensity = asset.LightParam.Intensity;
light.shadows = (LightShadows)asset.LightParam.Shadow;
light.shadowStrength= asset.LightParam.Strength;
light.cullingMask = CONST_VALUE.SHADOWLAYER;
light.tag = "dirLight";
dirLightParam.intensity = asset.LightParam.Intensity;
dirLightParam.lightColor = asset.LightParam.Color;
dirLightParam.rotation = asset.Rotation;
WeatherSystem.Get().SetLightDir(light.transform.forward);
break;
}
default:
{
asset.instObject.transform.parent = mapRoot.transform;
break;
}
}
asset.isLoadFinished = true;
_InitAsset_Shader(asset);
}
catch(System.Exception exp)
{
Debug.LogWarning("_LoadAsset_local Error! path="+asset.SourcePath+" exp="+exp.Message);
asset.isLoadFinished = false;
}
}
public void SetSceneLightMap (string name = null)
{
//场景的光照贴图
if(m_LightmapList == null)
return;
List<LightmapData> listLight = new List<LightmapData>();
for(int i=0; i<m_LightmapList.Count; i++)
{
LightmapSet lightMap = m_LightmapList[i];
LightmapData lightData = null;
//far
if(!lightMap.m_bFarLoaded)
{
if(lightMap.m_NameFar == null || lightMap.m_NameFar == string.Empty)
{
lightMap.m_bFarLoaded = true;
}
else
{
if (!string.IsNullOrEmpty(name))
{
lightMap.m_SourcePathFar = "Terrains/ExtraLightmap/" + name.Split('|')[i];
}
Texture2D texAsset = (Texture2D) CorResMgr.Get().LoadResource(lightMap.m_SourcePathFar);
if(texAsset != null)
{
if(lightData == null)
lightData = new LightmapData();
lightData.lightmapFar = texAsset;
lightMap.m_bFarLoaded = true;
}
}
}
//near
if(!lightMap.m_bNearLoaded)
{
if(lightMap.m_NameNear == null || lightMap.m_NameNear == string.Empty)
{
lightMap.m_bNearLoaded = true;
}
else
{
Texture2D texAsset = (Texture2D) CorResMgr.Get().LoadResource(lightMap.m_SourcePathNear);
if(texAsset != null)
{
if(lightData == null)
lightData = new LightmapData();
lightData.lightmapNear = texAsset;
lightMap.m_bNearLoaded = true;
}
}
}
//fill
if(lightData == null)
return;
listLight.Add(lightData);
}
//fill light map
LightmapSettings.lightmaps = listLight.ToArray();
#if UNITY_5
LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
#endif
}
在说UI系统的时候,先说一下有点关联的配置管理器,因为有着那么一个配置是ui配置,它记录了ui面板的名称对应的prefab和使用的lua文件,还有一些与其他面板的互斥信息,还有其他情况该不该显示的信息等。
public class GameTableMagr
: Singleton<GameTableMagr>
{
// 加载表格
public void LoadTable()
{
LoadTable("ui", typeof(TableUI));//TableUI
}
}在GameTableMagr加载了ui的配置文件后,在GameUIManager单例管理器中去把配置的ui解析为一个一个的UIAsset信息,它保存了配置信息和面板的GameObject和面板的Behaviour等。
public class UIAsset
{
public TableUI.UI ui = null;
public GameObject oUIObject = null;
public PanelBase panel = null;
public float timer = 0;
public AssetBundle bundle = null;
public UILuaBehaviour luaBehaviour = null;
}
public class GameUIManager
: Singleton<GameUIManager>
{
Dictionary<string, UIAsset> uiMap = new Dictionary<string, UIAsset>();
List<UIAsset> uiAllList = new List<UIAsset>();
public void InitAfterLoadData()
{
TableUI table = GameTableMagr.Get().GetTable("ui") as TableUI;
for(int i=0; i<table.uiList.Count; i++)
{
if ( i == 1 )
continue;
UIAsset asset = new UIAsset();
asset.ui = table.uiList[i];
uiMap.Add(asset.ui.name, asset);
uiAllList.Add( asset );
}
Resources.UnloadUnusedAssets();
GC.Collect();
}
}UI系统围绕这一个叫做UICmdSystem的单例管理器进行,首先是很多的命令数组,每个命令都是一个字符串,代表的是一个事件。而每一个命令都对应一个UI面板的名称,在所有资源都加载完成和配置加载完成的时候就会初始化命令信息,通过命令的事件为key,对应的面板名字对应的UIAsset为值,放置到一个列表中,多对多的关系。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class UICmd
{
public string key = string.Empty;
public string values = string.Empty;
}
public class UICmdSystem
: Singleton<UICmdSystem>
{
Hashtable cmdList = null;
Queue<UICmd> cmdQueue = null;
string [] regCmd =
{
"update_gold",
};
string[,] regUI =
{
{"update_gold", "Main"},
};
public void Initial()
{
cmdList = new Hashtable();
cmdQueue= new Queue<UICmd>();
for(int i=0; i<regCmd.Length; i++)
RegCmd(regCmd[i]);
}
public void InitAfterLoadData()
{
for(int i=0; i<regUI.GetLength(0); i++)
RegUICmd(regUI[i,0], regUI[i,1]);
}
void RegCmd(string key)
{
if(cmdList.ContainsKey(key))
return;
cmdList.Add(key, new List<UIAsset>());
}
void RegUICmd(string key, string panel)
{
if(cmdList.ContainsKey(key) == false)
{
Debug.LogError("[UICmdSystem] UICmd:" + key + ",not register!" );
return;
}
UIAsset asset = GameUIManager.Get().GetUIAsset(panel);
if(asset == null)
{
Debug.LogError("[UICmdSystem] UICmd:" + key + ",Panel:" + panel + ",not ui" );
return;
}
List<UIAsset> list = cmdList[key] as List<UIAsset>;
if(list.Contains(asset))
{
Debug.LogError("[UICmdSystem] UICmd:" + key + ",Panel:" + panel + ",has register" );
return;
}
list.Add(asset);
}
public void AddCommand(UICmd cmd)
{
if(cmd == null)
return;
if(cmdList.ContainsKey(cmd.key) == false)
return;
cmdQueue.Enqueue(cmd);
}
public void Tick()//Tick方法会在Main里面的Update方法调用
{
while(cmdQueue.Count != 0)
{
UICmd cmd = cmdQueue.Dequeue();
//Debug.LogError(cmd.key + " | " + cmd.values);
if(cmd == null)
continue;
List<UIAsset> uiAsset = cmdList[cmd.key] as List<UIAsset>;
for(int i=0; i<uiAsset.Count; i++)
{
UIAsset asset = uiAsset[i];
if(asset == null)
continue;
if ((asset.panel == null && asset.luaBehaviour == null) || asset.oUIObject == null)
continue;
if (asset.luaBehaviour != null) {
asset.luaBehaviour.HandleCommand(cmd);
}
if (asset.panel != null) {
asset.panel.HandleCommand(cmd);
}
}
}
cmdQueue.Clear();
}
public bool HaveCommand(UICmd cmd)
{
if(cmd == null)
return false;
if(cmdList.ContainsKey(cmd.key) == false)
return false;
UICmd[] cmds = cmdQueue.ToArray();
for(int i = 0; i < cmds.Length; i ++)
{
if(cmd.key == cmds[i].key)
return true;
}
return false;
}
}看上面的UICmdSystem就知道了,它当前已经知道哪些命令归哪些UI去处理了,当我们需要通知一个事件的时候,通过单例的UICmdSystem的AddCommand方法添加到列表里面,在Update里面就会取出来,找到命令key对应的UIAsset,再在里面找到脚本(UI面板),调用其的HandleCommand方法处理事件,并将UICmd传递进去,通过UICmd.values区分命令处理。
public override void ValueChange(UICmd cmd)//HandleCommand方法调用了ValueChange虚方法,用来给具体的Panel重写的
{
if (!m_isInit)
initUI();
if (cmd.key.Equals("loginselect_ver"))
{
if (cmd.values.Equals("loadLocVer"))
{
curVersion.gameObject.SetActive(true);
curVersion.text = CorResMgr.Get().locVer;
}
else if (cmd.values.Equals("update"))
{
curProgTxt.gameObject.SetActive(true);
curProgTxt.text = CorResMgr.Get().infoTxt;
progBar.gameObject.SetActive(true);
progBar.value = 0.0f;
updateProgCheck = StartCoroutine(updateProg());
}
}
}
首先是资源(我这里是UI,一个Panel为一个资源),对应着一个MonoBehaviour的脚本和一个功能一致的Lua脚本,将资源打包成AssetBundle,lua脚本不需要打包(加密应该是必须的),都放到远程服务器上。通过配置表Table(也是远程加载的)决定是使用MonoBehavoiur还是Lua脚本,使用一个UILuaBehaviour来承载保存lua的模块名,UILuaBehaviour是一些UI常用到的方法,从而在Lua脚本中可以获取这个脚本调用这些功能。
启动的时候,在GameLuaManager中初始化时实例化LuaState或LuaScriptMgr,调用Start方法开始,调用一个Lua脚本的管理器加载一些公用的依赖,以便后面lua脚本使用这些公用方法。
using UnityEngine;
using System.Collections;
using System.IO;
using System;
public class GameLuaManager : Singleton<GameLuaManager> {
LuaScriptMgr luaScript = null;
public void Initialize()
{
if (luaScript == null)
{
luaScript = new LuaScriptMgr();
luaScript.Start();
luaScript.DoFile("Logic/GameManager");
Util.CallMethod("GameManager", "Main");
}
}
public LuaScriptMgr LuaManager
{
get{return luaScript;}
}
}--Lua GameManager.lua
require "Common/define" --在这里就引入全局了,一些全局定义
require "Net/Network" --网络通信
require "Common/functions" --一些工具的方法
--管理器--
GameManager = {};
local this = GameManager;
function GameManager.Main()
require("Net/Network").new()
end
--例如functions定义了一些打印日志的方法,Util是c#脚本warp映射到lua中的
--输出日志--
function log(str)
Util.Log(str);
end
--错误日志--
function error(str)
Util.LogError(str);
end
--警告日志--
function warn(str)
Util.LogWarning(str);
end然后各个管理器初始化完成,检查资源更新,会把需要的UI面板和对应的Lua脚本下载到本地上,GameUIManager会根据需求加载UI面板并通过配置查看是否使用lua从而添加UILuaBehaviour,UILuaBehaviour会根据面板的名字找到Lua模块,然后Awake,Start等方法触发是直接Call的Lua中的方法。
//GameUIManager
public PanelBase Show(string strName, bool useCoroutine = true, bool fromPakLoad = false, bool bNeedDarkFollow = false)
{
UIAsset asset = GetUIAsset(strName);
if (asset == null)
return null;
//控制主界面隐藏
if (asset.ui.IsCloseMain)
{
GetUIAsset("Main").panel.gameObject.SetActive(false);
}
if (asset.ui.IsHideChar)
{
CharMagr.Get().GetLoader().IsVisible = false;
}
if (Main.gameState == Game_State_Type.GST_Fight)
{
if (asset.ui.fightShow == 0)
return null;
}
HideAll(asset);
if (asset.ui.enableDark)
ShowDark(true , false ,asset);
uiTimeList.Remove(asset);
Main.Game.IsHoverUI = false;
if (asset.oUIObject != null)
{
asset.oUIObject.SetActive(true);
if (asset.panel != null)
{
asset.panel.OnShow();
}
else if (asset.luaBehaviour != null)
{
asset.luaBehaviour.OnShow();
}
uiActive.Add(asset);
return asset.panel;
}
//主要在这里,首次加载的
UnityEngine.Object obj = null;
if (asset.ui.lua == "")
{ // cs的界面
obj = CorResMgr.Get().LoadResource(CONST_VALUE.UI_ROOT_PATH + asset.ui.prefab);
if (obj == null)
return null;
asset.oUIObject = (GameObject)GameObject.Instantiate(obj);
asset.oUIObject.transform.parent = root.transform;
asset.oUIObject.transform.localScale = Vector3.one * CalcScale(asset);
asset.oUIObject.transform.localPosition = Vector3.zero;
asset.panel = asset.oUIObject.GetComponent<PanelBase>();
}
else
{ // lua的界面
obj = CorResMgr.Get().LoadResource(CONST_VALUE.UI_ROOT_PATH + asset.ui.prefab);
if (obj == null)
return null;
asset.oUIObject = (GameObject)GameObject.Instantiate(obj);
asset.oUIObject.transform.parent = root.transform;
asset.oUIObject.transform.localScale = Vector3.one * CalcScale(asset);
asset.oUIObject.transform.localPosition = Vector3.zero;
GameLuaManager.Get().LuaManager.DoFile("UI/" + asset.ui.lua);//编译配置表指定Lua脚本
//添加UILuaBehaviour脚本来做中间人去驱动调用lua方法
asset.luaBehaviour = asset.oUIObject.AddComponent<UILuaBehaviour>();
}
UIPanel n_ObjPanel = asset.oUIObject.GetComponent<UIPanel>();
if (n_ObjPanel)
n_ObjPanel.depth = asset.ui.Depth;
UIAnchor[] anchors = asset.oUIObject.GetComponentsInChildren<UIAnchor>();
for (int i = 0; i < anchors.Length; i++)
{
anchors[i].uiCamera = uiCamera;
}
if (asset.panel != null)
{
asset.panel.OnShow();
}
else if (asset.luaBehaviour != null)
{
asset.luaBehaviour.OnShow();
}
uiActive.Add(asset);
if (asset.panel != null)
{
asset.panel.m_UIName = strName;
}
obj = null;
return asset.panel;
}下面看一下UILuaBehaviour
public class UILuaBehaviour : PanelBase
{
protected static bool initialize = false;
private string data = null;
private List<LuaFunction> buttons = new List<LuaFunction>();
private string luaFileName = "";
protected void Awake()
{
luaFileName = name.Split('_')[1]; //这里自动就知道模块lua的文件名了
CallMethod("Awake", gameObject);//把gameobject传递给lua知道,然它知道控制的是谁
}
void Start()
{
if (initialize)
return;
CallMethod("Start");
initialize = true;
}
public override void OnShow()
{
CallMethod("OnShow");
}
public virtual void OnHide()
{
CallMethod("OnHide");
}
public override void ValueChange(UICmd cmd)
{
CallMethod("ValueChange", cmd.key, cmd.values);
}
/// <summary>
/// 添加单击事件
/// </summary>
public void AddClick(string ctrName, LuaFunction luafunc)
{
if (string.IsNullOrEmpty(ctrName))
return;
GameObject go = transform.FindChild(ctrName).gameObject;
if (go == null)
return;
UIEventListener.Get(go).onClick = delegate (GameObject o)
{
luafunc.Call(go);
buttons.Add(luafunc);
};
}
/// <summary>
/// 根据gameobject绑定按钮事件
/// </summary>
/// <param name="btn"></param>
/// <param name="luafunc"></param>
public void AddClickForObj(GameObject btn, LuaFunction luafunc)
{
if (btn == null)
return;
UIEventListener.Get(btn).onClick = delegate (GameObject o)
{
luafunc.Call(btn);
buttons.Add(luafunc);
};
}
/// <summary>
/// 带一个整形参数的回调
/// </summary>
public void AddClickForObjWithIntParam(GameObject btn, LuaFunction luafunc, int iParam)
{
if (btn == null)
return;
UIEventListener.Get(btn).onClick = delegate (GameObject o)
{
luafunc.Call(iParam);
buttons.Add(luafunc);
};
}
/// <summary>
/// 清除单击事件
/// </summary>
public void ClearClick()
{
for (int i = 0; i < buttons.Count; i++)
{
if (buttons[i] != null)
{
buttons[i].Dispose();
buttons[i] = null;
}
}
}
/// <summary>
/// 执行Lua方法
/// </summary>
protected object[] CallMethod(string func, params object[] args)
{
return Util.CallMethod(luaFileName, func, args);
}
public override void OnDestroy()
{
ClearClick();
initialize = false;
CallMethod("OnDestroy");
}
}最后看一下上面最常用到的Util.CallMethod方法,工具方法调用lua的方法
/// <summary>
/// 执行Lua方法
/// </summary>
public static object[] CallMethod(string module, string func, params object[] args) {
LuaScriptMgr luaMgr = GameLuaManager.Get().LuaManager;
if (luaMgr == null)
return null;
string funcName = module + "." + func;
funcName = funcName.Replace("(Clone)", "");
return luaMgr.CallLuaFunction(funcName, args);
}最后看一个ui的lua脚本例子
--LoginZiJia.lua
LoginZiJia = {} --这个模块,对应LoginZiJia这个UIPrefab
local this = LoginZiJia
local gameObject = nil
local playerName = ""
local playerSex = 0
local playerProfession = 0
local iP = ""
local port = 0
function LoginZiJia.Awake(obj)
gameObject = obj --把实例化的GameObject传递过来了
end
function LoginZiJia.Start()
playerName = GameObject.Find('Scale/Input_Name'):GetComponent('UIInput')
playerName.text = "test2018"
playerSex = GameObject.Find('Scale/Input_Sex'):GetComponent('UIInput')
playerSex.text ='0'
playerProfession = GameObject.Find('Scale/Input_Profession'):GetComponent('UIInput')
playerProfession.text = '0'
iP = GameObject.Find('Scale/Input_IP'):GetComponent('UIInput')
iP.text = GameAPI.getZiJiaIP()
port = GameObject.Find('Scale/Input_Port'):GetComponent('UIInput')
port.text = tostring(GameAPI.getZiJiaPort())
--获取UILuaBehaviour可以使用一些方便的方法
local uilua = gameObject:GetComponent('UILuaBehaviour')
uilua:AddClick('Scale/LoginBtn',this.onLogin)
uilua:AddClick('Scale/LoginReturn',this.onBack)
end
在很多类型的游戏中,麻将等的桌子型游戏,都是把自己的相关内容放在自己屏幕下方的。而每个客户端都是一个个体,他们都是需要放置在下方的。
解决办法就是:
前端定义好方位的索引,比如说下方是0,下一个是右边1......等,简称为显示ID。还有一个就是真实的桌子ID了,有几位位置就有几个id,0,1,2,3等,简称椅子ID。自己分配到的ID是不确定的,可能是3也可能是4,所以需要做一个映射对应到显示ID。
那么这个映射就很简单了:(椅子ID - 我的椅子ID + 总的容量) % 总的容量
同理反映射就是:(显示ID + 我的椅子ID) % 总的容量
抽象场景中活动的物体,添加上一个叫做Entity的脚本的,Entity是一个继承自MonoBehaviour的脚本组件,用来控制播放动画和检测进入泉水,Entity拥有一个ientity或则是继承自ientity的iplayer和iselfplayer,ientity用来保存同步信息(Transform组件,血值,攻击,模型id等)和提取了一些公共的方法(进入站立,进入移动,移动中,释放技能(进入,前摇,释放),击飞,下落,更新数据,打开商店,创建和显示隐藏血条等),如果是特殊的会声明为virtual在player那里重写并可以扩展新的方法。ientity已经有了很多的行为了,现在就需要在某种情况调用某种行为了,所以ientity会拥有一个EntityFSM状态机,这些状态机状态其实是一个工具类,他们直接调用的还是具体某个ientity的某个方法,只要将ientity的状态设置为具体的某个EntityFSM
public interface EntityFSM
{
bool CanNotStateChange{set;get;}
FsmState State { get; }
void Enter(Ientity entity , float stateLast);
bool StateChange(Ientity entity , EntityFSM state);
void Execute(Ientity entity);
void Exit(Ientity Ientity);
}
public class EntityIdleFSM : EntityFSM
{
public static readonly EntityFSM Instance = new EntityIdleFSM();
public FsmState State
{
get
{
return FsmState.FSM_STATE_IDLE;
}
}
public bool CanNotStateChange{
set;get;
}
public bool StateChange(Ientity entity , EntityFSM fsm)
{
return CanNotStateChange;
}
public void Enter(Ientity entity , float last)
{
entity.OnEnterIdle();
}
public void Execute(Ientity entity)
{
if (EntityStrategyHelper.IsTick(entity, 3.0f))
{
entity.OnFSMStateChange(EntityFreeFSM.Instance);
}
}
public void Exit(Ientity entity){
}
}在ientity中维护这一个EntityFSM,提供了转换的接口,在转换时修改这个EntityFSM的引用实例并调用这个EntityFSM的Enter方法,随后在Entity组件的Update方法中调用的ientity的Update方法再调用的EntityFSM的Update方法,而实际Update和Enter方法都会传入ientity,然后调用的实际还是ientity里的具体方法,只是EntityFSM的不同,调用的不同!
EntityFSM的状态有:EntityFSM、EntityDeadFSM、EntityForceMoveFSM、EntityFreeFSM、EntityIdleFSM、EntityLastingFSM、EntityLeadingFSM、EntityReleaseSkillFSM、EntityReliveFSM、EntityRunFSM、EntitySingFSM。
架构:Many Manager的方式,管理器全局唯一单例。EventCenter中心事件机制(中介者),负责NetMsgHandler、State、Controller、View(UI,Model)之间的解耦。
首先是ResourceUpdateManager进行资源的更新,随后GameStateManager(事先new了所有的状态)进入默认的LoginState(监听状态改变的事件,调用WindowManager的转换State方法(事先准备Windows和Windows的注册监听事件),调用LoginCtrl的进入方法),LoginCtrl注册监听事件后广播显示UI的消息,Window接受到后显示出来,然后UI交互,接受输入事件后广播传递给LoginCtrl,处理调用NetWorkManager发送消息,NetWorkManager收到回复后交给具体的NetMsgHandler处理,广播具体事件(LoginState或LoginCtrl处理),LoginCtrl处理就是进入循环了,如果是LoginState处理说明要转换状态了,设置GameStateManager要进入的状态,然后就回到开头了,只是状态不同了,控制器不同了,监听也不同了,Window不同了等。
主要的状态有:LoginState、UserState、LobbyState、RoomState、HeroState、LodingState、PlayState、OverState。除了PlayState状态,其实其他的都是UI和网络的事件、资源加载的处理。核心部分都在PlayState,而PlayState最重要的应该就是状态机。
LoginState为登陆状态,UserState为获取玩家信息状态,LobbyState为大厅状态,RoomState为房间状态,HeroState为选英雄状态,LodingState为加载状态,PlayState为游戏中状态,OverState为清算状态。归结为就是:打开游戏,登陆游戏,得到你的信息,进入大厅,匹配或自己开房间进入房间,开始后选英雄,选完英雄加载游戏场景,加载完了就可以玩了,玩完了就清算显示你的战绩,然后回到房间或大厅。