整了个小Demo仿照《塞尔达传说:旷野之息》,实现 鼠标悬停在Button上时,能够改变Button-Text颜色,并且在Button前显示一个小箭头
标题鼠标指针悬停和移走,改变标题颜色和箭头显示
Unity框架性内容使用C#编写,这些部分均是未来不考虑热更新的配置型的程序,例如如何将UI的Object连接到Lua成为Lua可以调用的变量。Button文字的变色,箭头的显示,这些业务逻辑(考虑到未来可能需要热更新)全部由Lua实现,以后可以在不更新整包软件的情况下,通过热更新的方式只更新Lua脚本来重新编写UI界面的业务逻辑,例如Button直接隐藏,位置修改,scale放大,文字颜色修改rgba等。
附C#代码和Lua代码,,,详细的如何配置Lua解释器运行lua脚本和unity的热更新实现,会在下一篇笔记中全部展示。代码中的UILogic脚本以及Manager.Resource.LoadUI等函数三个脚本属于之后会详细展示的笔记。
//挂到需要检测鼠标悬停的UI物体上
public class UIMenuPointerHover : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
UISelect uiSelect;
void Awake()
{
uiSelect = this.GetComponentInParent<UISelect>();
}
public void OnPointerEnter(PointerEventData eventData)
{
(uiSelect.m_OnPointerClickOrHover)?.Invoke(this.gameObject.name.Replace(" ", ""));
}
public void OnPointerExit(PointerEventData eventData)
{
(uiSelect.m_OnPointerExit)?.Invoke(this.gameObject.name.Replace(" ", ""));
}
}
//通过UIManager挂到ButtonGroup上
public class UISelect : LuaBehaviour
{
//自定义类,连接C# 与 Lua
[System.Serializable]
public class Injection
{
public string name;
public GameObject button;
public GameObject arrow;
}
[SerializeField] private List<Injection> buttons;
[SerializeField] private Image ornament;
Action m_OnMenuUIUpdate;
Action m_OnMenuUIOpen;
public UnityEngine.Events.UnityAction<string> m_OnPointerClickOrHover;
public UnityEngine.Events.UnityAction<string> m_OnPointerExit;
//初始化配置,创建C#与Lua的变量关系
public override void Init(string luaName)
{
base.Init(luaName);
m_ScriptEnv.Get("OnMenuUIUpdate", out m_OnMenuUIUpdate);
m_ScriptEnv.Get("OnMenuUIOpen", out m_OnMenuUIOpen);
m_ScriptEnv.Get("OnPointerClickOrHover", out m_OnPointerClickOrHover);
m_ScriptEnv.Get("OnPointerExit", out m_OnPointerExit);
buttons = new List<Injection>();
for (int i = 0; i < 6; i++)
{
Injection temp = new Injection();
temp.name = this.gameObject.transform.GetChild(i).name.Replace(" ","");
temp.button = this.gameObject.transform.GetChild(i).gameObject;
temp.button.gameObject.AddComponent<UIMenuPointerHover>();
temp.arrow = this.gameObject.transform.GetChild(i).gameObject.transform.GetChild(1).gameObject;
buttons.Add(temp);
m_ScriptEnv.Set(buttons[i].name + "_button", buttons[i].button);
m_ScriptEnv.Set(buttons[i].name + "_arrow", buttons[i].arrow);
}
}
//调用Lua的自定义的Open函数,类似于Awake
public void OnOpen()
{
m_OnMenuUIOpen?.Invoke();
}
//调用Lua的自定义的Update
public override void Update()
{
m_OnMenuUIUpdate?.Invoke();
}
//删除回调
protected override void Clear()
{
base.Clear();
m_OnMenuUIUpdate = null;
m_OnMenuUIOpen = null;
m_OnPointerClickOrHover = null;
m_OnPointerExit = null;
}
}
//Lua的脚本类
public class LuaBehaviour : MonoBehaviour
{
//全局只能有一个LuaEnv
private LuaEnv m_LuaEnv = Manager.Lua.LuaEnv;
protected LuaTable m_ScriptEnv;
//private Action m_LuaAwake;
//private Action m_LuaStart;
private Action m_LuaInit;
private Action m_LuaUpdate;
private Action m_LuaOnDestroy;
public string luaName;
void Awake()
{
m_ScriptEnv = m_LuaEnv.NewTable();
// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
LuaTable meta = m_LuaEnv.NewTable();
meta.Set("__index", m_LuaEnv.Global);
m_ScriptEnv.SetMetaTable(meta);
meta.Dispose();
m_ScriptEnv.Set("self", this);
}
//用Init来代替unity的awake
public virtual void Init(string luaName)
{
m_LuaEnv.DoString(Manager.Lua.GetLuaScript(luaName), luaName, m_ScriptEnv);
m_ScriptEnv.Get("Update", out m_LuaUpdate);
m_ScriptEnv.Get("OnInit", out m_LuaInit);
m_LuaInit?.Invoke();
}
// Start直接删掉
// Update is called once per frame
public virtual void Update()
{
m_LuaUpdate?.Invoke();
}
//父类的需要是保护级,因为子类更特殊有其他需要进行的操作
protected virtual void Clear()
{
m_LuaOnDestroy = null;
//运行环境释放掉
m_ScriptEnv?.Dispose();
m_ScriptEnv = null;
m_LuaInit = null;
m_LuaUpdate = null;
}
//两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写Clear
private void OnDestroy()
{
m_LuaOnDestroy?.Invoke();
Clear();
}
private void OnApplicationQuit()
{
Clear();
}
}
UI = CS.UnityEngine.UI
local uiButtonName =
{
Continue = nil,
NewGame = nil,
PlayinMasterMode = nil,
Options = nil,
amiibo = nil,
DLCPurchased = nil
}
local uiArrowName =
{
Continue = nil,
NewGame = nil,
PlayinMasterMode = nil,
Options = nil,
amiibo = nil,
DLCPurchased = nil
}
function OnMenuUIUpdate()
--print("OnMenuUIUpdate")
end
--第一次打开UI进行配置,等价于Awake
function OnMenuUIOpen()
uiButtonName.Continue = Continue_button;
uiArrowName.Continue = Continue_arrow;
uiButtonName.NewGame = NewGame_button;
uiArrowName.NewGame = NewGame_arrow;
uiButtonName.PlayinMasterMode = PlayinMasterMode_button;
uiArrowName.PlayinMasterMode = PlayinMasterMode_arrow;
uiButtonName.Options = Options_button;
uiArrowName.Options = Options_arrow;
uiButtonName.amiibo = amiibo_button;
uiArrowName.amiibo = amiibo_arrow;
uiButtonName.DLCPurchased = DLCPurchased_button;
uiArrowName.DLCPurchased = DLCPurchased_arrow;
end
--鼠标悬停执行的函数
function OnPointerClickOrHover(buttonName)
GetButtonInTable(buttonName).transform:GetChild(0).gameObject:GetComponent(typeof(UI.Text)).color = CS.UnityEngine.Color(238/255,237/255,228/255,255/255)
GetArrowInTable(buttonName):SetActive(true)
end
--鼠标移开执行的函数
function OnPointerExit(buttonName)
GetButtonInTable(buttonName).transform:GetChild(0).gameObject:GetComponent(typeof(UI.Text)).color = CS.UnityEngine.Color(188/255,185/255,169/255,255/255)
GetArrowInTable(buttonName):SetActive(false)
end
function GetButtonInTable(name)
for k,v in pairs(uiButtonName) do
if k == name then
return v
end
end
end
function GetArrowInTable(name)
for k,v in pairs(uiArrowName) do
if k == name then
return v
end
end
end
自己的一点学习心得:如果项目需要热更新,重要的是分辨出什么框架逻辑或者脚本内容是死的,如果是死的就用C#,然后活的东西可以热更新的,取用Lua写,例如配置变量都是死的,但是如何实现UI业务逻辑的部分是活的,全部由C#去调用Lua里面的函数。
补充:
看视频突然注意到UI界面选中悬停的时候,小箭头是晃动的,就用Lua补充了个协程把相关功能实现一下,嗯,对效果满意很多。。。。UI音效回头再加,没找到合适的音效。
![](https://i-blog.csdnimg.cn/blog_migrate/49bf801524332c5e4751ddd3edab86cd.gif)
UI = CS.UnityEngine.UI
local uiButtonName =
{
Continue = nil,
NewGame = nil,
PlayinMasterMode = nil,
Options = nil,
amiibo = nil,
DLCPurchased = nil
}
local uiArrowName =
{
Continue = nil,
NewGame = nil,
PlayinMasterMode = nil,
Options = nil,
amiibo = nil,
DLCPurchased = nil
}
local startCoroutine = false
local arrow = nil
local startPos = nil
function OnMenuUIUpdate()
if startCoroutine == true then
coroutine.resume(ArrowMove,arrow,startPos)
end
end
--第一次打开UI进行配置,等价于Awake
function OnMenuUIOpen()
uiButtonName.Continue = Continue_button;
uiArrowName.Continue = Continue_arrow;
uiButtonName.NewGame = NewGame_button;
uiArrowName.NewGame = NewGame_arrow;
uiButtonName.PlayinMasterMode = PlayinMasterMode_button;
uiArrowName.PlayinMasterMode = PlayinMasterMode_arrow;
uiButtonName.Options = Options_button;
uiArrowName.Options = Options_arrow;
uiButtonName.amiibo = amiibo_button;
uiArrowName.amiibo = amiibo_arrow;
uiButtonName.DLCPurchased = DLCPurchased_button;
uiArrowName.DLCPurchased = DLCPurchased_arrow;
end
--鼠标悬停执行的函数
function OnPointerClickOrHover(buttonName)
GetButtonInTable(buttonName).transform:GetChild(0).gameObject:GetComponent(typeof(UI.Text)).color = CS.UnityEngine.Color(238/255,237/255,228/255,255/255)
arrow = GetArrowInTable(buttonName)
arrow:SetActive(true)
startCoroutine = true;
startPos = GetArrowInTable(buttonName).transform.position
coroutine.resume(ArrowMove,arrow,startPos)
end
--鼠标移开执行的函数
function OnPointerExit(buttonName)
GetButtonInTable(buttonName).transform:GetChild(0).gameObject:GetComponent(typeof(UI.Text)).color = CS.UnityEngine.Color(188/255,185/255,169/255,255/255)
GetArrowInTable(buttonName).transform.position = startPos
GetArrowInTable(buttonName):SetActive(false)
startCoroutine = false;
end
function GetButtonInTable(name)
for k,v in pairs(uiButtonName) do
if k == name then
return v
end
end
end
function GetArrowInTable(name)
for k,v in pairs(uiArrowName) do
if k == name then
return v
end
end
end
--协程用来实现button下面的小箭头左右晃动,当OnPointerExit执行时,resume传入新arrow obj和arrow的pos进来,重新执行协程的移动功能
ArrowMove = coroutine.create(
function (obj, startPos)
::label:: print("coroutine")
while (obj.transform.position - (startPos + CS.UnityEngine.Vector3(7,0,0))).x >= 0.5 or
(obj.transform.position - (startPos + CS.UnityEngine.Vector3(7,0,0))).x <= -0.5 do
obj.transform.position = obj.transform.position + CS.UnityEngine.Vector3(7,0,0) * Time.deltaTime * 3
local newObj, newStartPos = coroutine.yield()
if newObj ~= obj then
obj = newObj
startPos = newStartPos
goto label
end
end
obj.transform.position = startPos + CS.UnityEngine.Vector3(7,0,0)
tempPos = obj.transform.position
while (obj.transform.position - (tempPos + CS.UnityEngine.Vector3(-7,0,0))).x >= 0.5 or
(obj.transform.position - (tempPos + CS.UnityEngine.Vector3(-7,0,0))).x <= -0.5 do
obj.transform.position = obj.transform.position + CS.UnityEngine.Vector3(-7,0,0) * Time.deltaTime * 3
local newObj, newStartPos = coroutine.yield()
if newObj ~= obj then
obj = newObj
startPos = newStartPos
goto label
end
end
obj.transform.position = startPos
if startCoroutine == false then
local newObj, newStartPos = coroutine.yield()
if newObj ~= obj then
obj = newObj
startPos = newStartPos
goto label
end
else
goto label
end
end
)