xLua背包实践

准备工作

环境,代码

在C#代码方面我们需要准备单例模式基类,AB包管理器,lua解析器管理器

详情请见AB包管理器 xlua详解

然后是Xlua包和AB包,具体导入方法也在上面的链接中

然后是lua的三个文件

具体代码:

JsonUtility.lua网上应该能找到

下面是Object.lua

这里实现了一个lua中new和继承的逻辑

lua 复制代码
--面向对象实现 
--万物之父 所有对象的基类 Object
--封装
Object = {}
--实例化方法
function Object:new()
	local obj = {}
	--给空对象设置元表 以及 __index
	self.__index = self
	setmetatable(obj, self)
	return obj
end
--继承
function Object:subClass(className)
	--根据名字生成一张表 就是一个类
	_G[className] = {}
	local obj = _G[className]
	--设置自己的"父类"
	obj.base = self
	--给子类设置元表 以及 __index
	self.__index = self
	setmetatable(obj, self)
end

然后是SplitTools.lua

这个函数实现了将一个字符串根据指定的分隔符进行分割的逻辑

lua 复制代码
function string.split(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter=='') then
        return false
    end
    local pos,arr = 0, {}

    local find = function() 
        return string.find(input, delimiter, pos, true) 
    end

    for st,sp in find do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

然后是Mian.csharp

使用luaMgr来重定向和执行lua脚本的

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour
{
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");
    }

}

接下来是VSCode下载安装

首先我们来到微软官网
微软

进去下载就行

下好VSCode后我们需要下载插件

  • Chinese
  • C#
  • C# XML Documentation Comments
  • Debugger for Unity
  • Unity Tools
  • UnitySnippets

依次下载安装即可

我们把Unity中的默认编译器改成VSCode

这里版本变了。要用的插件其实并不太准,建议大家去查最新版本的方法,这里仅供参考

经过一番折腾可以调试了

至于lua的调试我们下一个Emmylua ,但是需要JDK1.8

然后要配置下环境变量

然后在VSCode里添加配置,选择通过进程ID附加

资源导入与UI拼接

把准备好的UI资源导入,这里随便使用什么图标都行

接下来我们就开始复习UI相关的知识了

先创建一个UIpanel,把名字改成MainPanel,然后把panel自带的image删了

修改一下panel的父级canvas

然后在MainPanel下面新建一个按钮

找到锚点,按住shift和alt点击右下角,这里我说的是中间的3*3格子的右下角

给这个button添加一个image,再用同样的方法添加一个button

我们现在要把这个panel做成预制体,用作AB包打包,拖到Asset中就可以了

接下来我们来拼格子面板

再创建一个panel

这里我需要做一点半遮罩的效果,所以把底色改的黑一点

我们现在要创建一个背景图,新建一个image

我们改一下锚点,还是按住shift和alt,这次不选右下角,选中间第三个(也是那个3*3格子)

在这个panel下新建一个button,作为背景面板的关闭,然后把这个button也是和他的父image一样的锚点设置

然后我们加一个toggle(单选框),锚点也是中间靠右

我们来看一下这个单选框,选中Back和Check,调整锚点,注意这时要调整为右下角(不是3*3格子。而是整个右下角)

然后是Label,选中居中

这里有一个选中变色的逻辑,没选中是黄色,选中是绿色

所以back我们选择黄色图,checkmark选择绿色图

我们改一下参数,看起来差不多是这样

再加两个

现在我们要把这三个做成互斥的

我们新建一个空对象作为这三个按钮的父对象,依旧是锚点右对齐

给父对象加个组件

然后我们把这三个组件的group选择父组件新加的ToggleGroup

然后就互斥了

之后我们来做格子背景

然后我们新建一个scroll view锚点右对齐

我不需要背景图,所以把image移除。只留滑动条

先把滑动方式改成竖直,再把滑动条的联系置空

然后再把这俩删掉

最后把这一整个做成预制体

下面就是整个结构

然后是格子拼接逻辑

首先新建一个gameobject,宽和高改成170,170,锚点改成3*3中的左上角

在下面添加一个image,也改成170,170,这里的数值你可以随便改,这里image的锚点改成真右下角

再加个Image作为图标

这里还要有数量

Lua基本逻辑准备

别名

我们在lua文件夹下新建一个lua文件,这个文件写的是常用别名

lua 复制代码
--1.导入lua准备文件
require(Object)--调用Object.lua 这里面装的是一个面对对象的逻辑
require("SplitTools") --这里面装的是一个根据传入的字符进行字符串分割的函数
Json=require("JsonUtility") --这里面是一个Json解析逻辑

--2.准备Unity别名
GameObject=CS.UnityEngine.GameObject
Resources=CS.UnityEngine.Resources
Transfrom=CS.UnityEngine.Transfrom
RectTransform=CS.UnityEngine.RectTransform
TextAsset=CS.UnityEngine.TextAsset
--2.1图集相关
SpriteAtlas=CS.UnityEngine.U2D.SpriteAtlas

Vector3=CS.UnityEngine.Vector3
Vector2=CS.UnityEngine.Vector2

--2.2UI相关

UI=CS.UnityEngine.UI
Image=UI.Image
Text=UI.Text
Button=UI.Button
Toggle=UI.Toggle
ScrollRect=UI.ScrollRect

--3.自己写的C#脚本相关

AbMgr=CS.AbMgr.GetInstance()--得到AB包管理的单例对象

--4.找到Canvas,方便后期lua脚本操作
Canvas=GameObject.Find("Canvas").transform

然后在Main.lua文件中引用initClass

lua 复制代码
require(initClass)

数据准备

道具表准备

道具表我们使用excel

先写几个基本属性

现在icon还没有,我们处理icon

首先做如下的文件结构目录

在SpriteAltas中新建一个SpriteAltas

我们在图集下面加图,注意一定要加sprite类型的,如果不是还要转换

点击pack preview

把这里取消

可以发现图打的比较整齐了

我们把它打到UI的AB包里

我们现在就可以在excel表里写信息了,这里格式是图集名+下划线+序号,后续我们根据这个规则进行分割来找图

然后我们把这个excel转成json
bejson

这里把生成的空行删掉,再把倒数第二行多的逗号删掉

我们在ABRes新建一个Json文件夹,然后一个json文件,把刚才的内容复制粘贴进去

把json打到jsonAB包里

然后把我们之前创建的三个预制体面板和图集都打到AB包里

然后打包

Lua读取Json表

我们现在要做的就是把我们上面准备好的json解析一下

新建一个ItemData.lua

我们先通过ABMgr取到我们加载的AB包,然后调用Json库把文本解码到ItemList

lua 复制代码
local txt=ABMgr:LoadRes("json","ItemData",typeof(TextAsset))
local itemList =Json.decode(txt.text)
print(txt.text)

我们把itemList打印出来看看

lua 复制代码
print(itemList[1])

发现是个表

lua 复制代码
print(itemList[1].name.."id is"..itemList[1].id)

这里只是id序号和索引恰巧重合了而已,如果我不想通过索引(因为我并不知道每个索引的id是多少),而是想通过id来查询的话,这个表就不像键值对那样那么好查询,所以我们用个新表实现键值对的逻辑

我来解释一下这段代码,首先我们建一个新表,然后使用pairs遍历这个表,索引使用_表示我不关注索引

ItemData[value.id]=value这里我是使用自定义索引

这行代码执行完毕后可以说数据就与id相关联了,我们可以通过id来取到相应的表

lua 复制代码
ItemData={}
for _,value in pairs(itemList) do
    ItemData[value.id]=value
end

比如说我可以通过id访问icon

lua 复制代码
print(ItemData[2].icon)

然后我们也可以做一个玩家信息

我们之前做的只是一共拥有的物品数量,而现在我需要存储具体的物体

lua 复制代码
PlayerData.equips={}
PlayerData.items={}
PlayerData.gems={}

function PlayerData:Init()

    table.insert(self.equips,{id=1,num=1})
    table.insert(self.equips,{id=2,num=1})

    table.insert(self.items,{id=3,num=50})
    table.insert(self.items,{id=4,num=20})

    table.insert(self.gems,{id=5,num=99})
    table.insert(self.gems,{id=6,num=88})
end

PlayerData:Init()
print(PlayerData.equips[1].id)

主面板逻辑

接下来我们要用lua控制MainPanel

来到vscode新建一个MainPanel.lua

我们来解释一下下面代码,首先我创建个空表,用来模拟Mainpanel对象,这里相当于写了一个 ,panelObj是主面板的实例化对象,btnRole和btnSkill是两个控件

然后我们写一个初始化方法Init

从我们打包的AB包中取面板,然后放到Canva下面

lua 复制代码
MainPanel={}

MainPanel.panelObj=nil
MainPanel.btnRole=nil
MainPanel.btnSkill=nil

function MainPanel:Init()
    --1.实例化面板对象
    self.panelObj=ABMgr:LoadRes("ui","MainPanel",typeof(GameObject))

    self.panelObj.transform:SetParent(Canvas,false)--false表明保持原有缩放比例

end

执行后发现成功加载

然后我们从主界面找到它的子物体button

为其添加一个事件监听

lua 复制代码
function MainPanel:Init()
    --1.实例化面板对象
    self.panelObj=ABMgr:LoadRes("ui","MainPanel",typeof(GameObject))

    self.panelObj.transform:SetParent(Canvas,false)--false表明保持原有缩放比例

    self.btnRole= self.panelObj.transform:Find("btnRole"):GetComponent(typeof(Button))


    self.btnRole.onClick:AddListener(function()
    self:BtnRoleClick()
    end)

end

function MainPanel:BtnRoleClick()
    print(123123)
end

现在点击图标之后就会打印123了

我们再为其添加一个激活和隐藏的函数

最后完整代码:Main中直接调用showme即可

lua 复制代码
MainPanel={}

MainPanel.panelObj=nil
MainPanel.btnRole=nil
MainPanel.btnSkill=nil

function MainPanel:Init()

    if self.panelObj==nil then
    --1.实例化面板对象
    self.panelObj=ABMgr:LoadRes("ui","MainPanel",typeof(GameObject))

    self.panelObj.transform:SetParent(Canvas,false)--false表明保持原有缩放比例

    self.btnRole= self.panelObj.transform:Find("btnRole"):GetComponent(typeof(Button))


    self.btnRole.onClick:AddListener(function()
    self:BtnRoleClick()
    end)
end

end

function MainPanel:ShowMe()
    self:Init()
    self.panelObj:SetActive(true)
end

function MainPanel:HideMe()
    self.panelObj:SetActive(false)
end

function MainPanel:BtnRoleClick()
    print(123123)
end

背包面板

新建一个BagPanel.lua

核心逻辑还是按照我们之前想法

lua 复制代码
BagPanel={}

BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nil

function BagPanel:Init()
    if self.panelObj==nil then
    self.panelObj=ABMgr:LoadRes("ui","BagPanel",typeof(GameObject))
    self.panelObj.transform:SetParent(Canvas,false)
    end
end

function BagPanel:ShowMe()

    self:Init()
    self.panelObj:SetActive(true)
end

function BagPanel:HideMe()

    self.panelObj:SetActive(false)
end

然后在MainPanel中的显示中调用背包格子的显示

lua 复制代码
function MainPanel:BtnRoleClick()
    BagPanel:ShowMe()
end

就可以显示了

接着我们要实现一个选择单选框切换的逻辑,但是toggle使用的逻辑本质上是Action委托,是只读的,所以要用到我们之前的方法,也就是自己写一个静态类,新建一个列表

csharp 复制代码
using XLua;

public static class CSharpCallLuaList
{
    [CSharpCallLua]
    public static List<Type> csharpCallLuaList=new List<Type>();
}

下面是背包类全部代码

lua 复制代码
BagPanel={}

BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togItem=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nil

function BagPanel:Init()
    if self.panelObj==nil then
    self.panelObj=ABMgr:LoadRes("ui","BagPanel",typeof(GameObject))
    self.panelObj.transform:SetParent(Canvas,false)

    self.btnClose=self.panelObj.transform:Find("btClose"):GetComponent(typeof(Button))
    local group=self.panelObj.transform:Find("Group")
    self.togEquip=group:Find("togEquip"):GetComponent(typeof(Toggle))
    self.togItem=group:Find("togItem"):GetComponent(typeof(Toggle))
    self.togGem=group:Find("togGem"):GetComponent(typeof(Toggle))

    self.svBag=self.panelObj.transform:Find("svBag"):GetComponent(typeof(ScrollRect))
    self.Content=self.svBag.transform:Find("Viewport"):Find("Content")

    self.btnClose.onClick:AddListener(function()
    self:HideMe()
    end)

    self.togEquip.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(1)
        end
    end)
    self.togEquip.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(2)
        end
    end)
    self.togEquip.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(3)
        end
    end)

end
end

function BagPanel:ShowMe()

    self:Init()
    self.panelObj:SetActive(true)
end

function BagPanel:HideMe()

    self.panelObj:SetActive(false)
end

--1.装备 2.道具 3.宝石
function BagPanel:ChangeType(type)
    print("当前类型为"..type)
end

格子逻辑

格子逻辑有两种实现方法,一种是比较笨的,我们每次点击单选框后生成多个格子对象(不使用面对对象思想)

直接在changeType中实现

我们先创建一个nowItem存相应的单选框存的数据,这个数据来自于PlayerData,是我们自己赋值的,正常情况应该是从服务器或从本地读取的

然后我们遍历nowItem,要做的事情是我们先把需要的素材准备好,然后把图标,文本,生成位置这些对象准备好,最后再赋值

lua 复制代码
function BagPanel:ChangeType(type)

local nowItem=nil

 if(type==1)then 
    print("type is"..type)
    nowItem=PlayerData.equips
 elseif(type ==2)then
    print("type is"..type)
    nowItem=PlayerData.items
 else
    print("type is"..type)
    nowItem=PlayerData.gems
 end

 --创建格子

  for i=1,#nowItem do
    local grid={}
    grid.obj=ABMgr:LoadRes("ui","ItemGrid")
    grid.obj.transform:SetParent(self.Content,false)
    grid.obj.transform.localPosition = Vector3((i - 1) % 4 * 175, math.floor((i - 1) / 4) * 175, 0)
    grid.imgIcon=grid.obj.transform:Find("imageIcon"):GetComponent(typeof(Image))
    grid.Text=grid.obj.transform:Find("Text"):GetComponent(typeof(Text))
   
    local data=ItemData[nowItem[i].id]

    local strs=string.split(data.icon,"_")
    local spriteAtlas=ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
    grid.imgIcon.sprite=spriteAtlas:GetSprite(strs[2])
    print(nowItem[i].num)
    grid.Text.text=nowItem[i].num
    table.insert(self.items,grid)  
 end 


但是现在有个问题,我并没有删除不用的itemGrid,这个itemGrid是格子对象

所以我们在ChangeType进来的时候就把原有的逻辑置空了

lua 复制代码
function BagPanel:ChangeType(type)

local nowItem=nil
for i=1,#self.items do
    GameObject.Destroy(self.items[i].obj)
end

self.items={}

整体代码

不过现在还有个问题,我重复点同一个单选框时会浪费性能

所以记录一下当前类型,如果是就不改变了

lua 复制代码
BagPanel.nowType=-1
function BagPanel:ChangeType(type)

    if self.nowType==type then
        return
else
    self.nowType=type
end

最后一个小问题是第一次进入的时候没有刷新

可以直接设置ChangeType(1)

lua 复制代码
function BagPanel:ShowMe()

    self:Init()
    self.panelObj:SetActive(true)
    if self.nowType==-1 then
        self:ChangeType(1)
        end
end

最终逻辑

lua 复制代码
BagPanel={}

BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togItem=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nil

BagPanel.items={}

BagPanel.nowType=-1

function BagPanel:Init()
    if self.panelObj==nil then
    self.panelObj=ABMgr:LoadRes("ui","BagPanel",typeof(GameObject))
    self.panelObj.transform:SetParent(Canvas,false)

    self.btnClose=self.panelObj.transform:Find("btClose"):GetComponent(typeof(Button))
    local group=self.panelObj.transform:Find("Group")
    self.togEquip=group:Find("togEquip"):GetComponent(typeof(Toggle))
    self.togItem=group:Find("togItem"):GetComponent(typeof(Toggle))
    self.togGem=group:Find("togGem"):GetComponent(typeof(Toggle))

    self.svBag=self.panelObj.transform:Find("svBag"):GetComponent(typeof(ScrollRect))
    self.Content=self.svBag.transform:Find("Viewport"):Find("Content")

    self.btnClose.onClick:AddListener(function()
    self:HideMe()
    end)

    self.togEquip.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(1)
        end
    end)
    self.togItem.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(2)
        end
    end)
    self.togGem.onValueChanged:AddListener(function(value) 
        if value==true then
            self:ChangeType(3)
        end
    end)

end
end

function BagPanel:ShowMe()

    self:Init()
    self.panelObj:SetActive(true)
    if self.nowType==-1 then
        self:ChangeType(1)
        end
end

function BagPanel:HideMe()

    self.panelObj:SetActive(false)
end

--1.装备 2.道具 3.宝石
function BagPanel:ChangeType(type)

    if self.nowType==type then
        return
else
    self.nowType=type
end

local nowItem=nil

for i=1,#self.items do
    GameObject.Destroy(self.items[i].obj)
end

self.items={}

 if(type==1)then 
    print("type is"..type)
    nowItem=PlayerData.equips
 elseif(type ==2)then
    print("type is"..type)
    nowItem=PlayerData.items
 else
    print("type is"..type)
    nowItem=PlayerData.gems
 end

 --创建格子

  for i=1,#nowItem do
    local grid={}
    grid.obj=ABMgr:LoadRes("ui","ItemGrid")
    grid.obj.transform:SetParent(self.Content,false)
    grid.obj.transform.localPosition = Vector3((i - 1) % 4 * 175, math.floor((i - 1) / 4) * 175, 0)
    grid.imgIcon=grid.obj.transform:Find("imageIcon"):GetComponent(typeof(Image))
    grid.Text=grid.obj.transform:Find("Text"):GetComponent(typeof(Text))
   
    local data=ItemData[nowItem[i].id]

    local strs=string.split(data.icon,"_")
    local spriteAtlas=ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
    grid.imgIcon.sprite=spriteAtlas:GetSprite(strs[2])
    print(nowItem[i].num)
    grid.Text.text=nowItem[i].num
    table.insert(self.items,grid)  
 end 
end

我们回顾一下实现的逻辑,其实格子的逻辑不应该写在背包里的,如果我加什么别的操作,就要一直在背包这里设置格子逻辑,很冗余

所以我们应该把格子写成一个类

所以我们写一个itemGrid.lua

lua 复制代码
Object:subClass("ItemGrid")

ItemGrid.obj=nil
ItemGrid.imgIcon=nil
ItemGrid.Text=nil

--初始化格子对象
function ItemGrid:Init() 
end

--初始化格子信息
--这里是根据PlayerData传进来的id和num进行操作
function ItemGrid:InitData(data)
end

我们先写Init

首先自己的属性就要用self来调用了,其次father,位置,这里都可以传进去(位置逻辑在外面计算)

lua 复制代码
function ItemGrid:Init(father,posX,posY) 
    self.obj=ABMgr:LoadRes("ui","ItemGrid")
    self.obj.transform:SetParent(father,false)
    self.obj.transform.localPosition = Vector3(posX,posY,0)
    self.imgIcon=self.obj.transform:Find("imageIcon"):GetComponent(typeof(Image))
    self.Text=self.obj.transform:Find("Text"):GetComponent(typeof(Text))
end

然后是InitData

lua 复制代码
function ItemGrid:InitData(data)
    local data_keep=ItemData[data.id]

    local strs=string.split(data_keep.icon,"_")
    local spriteAtlas=ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
    self.imgIcon.sprite=spriteAtlas:GetSprite(strs[2])
    --print(data.num)
    self.Text.text=data.num
end

我们来改背包逻辑

lua 复制代码
 --创建格子

    for i=1,#nowItem do
    local grid=ItemGrid:new()

    grid:Init(self.Content, (i-1) % 4 * 175, math.floor((i - 1) / 4) * 175)

    grid:InitData(nowItem[i])

    table.insert(self.items,grid)  

itemData逻辑

lua 复制代码
Object:subClass("ItemGrid")

ItemGrid.obj=nil
ItemGrid.imgIcon=nil
ItemGrid.Text=nil

--初始化格子对象
function ItemGrid:Init(father,posX,posY) 
    self.obj=ABMgr:LoadRes("ui","ItemGrid")
    self.obj.transform:SetParent(father,false)
    self.obj.transform.localPosition = Vector3(posX,posY,0)
    self.imgIcon=self.obj.transform:Find("imageIcon"):GetComponent(typeof(Image))
    self.Text=self.obj.transform:Find("Text"):GetComponent(typeof(Text))
end


--初始化格子信息
--这里是根据PlayerData传进来的id和num进行操作
function ItemGrid:InitData(data)
    local data_keep=ItemData[data.id]

    local strs=string.split(data_keep.icon,"_")
    local spriteAtlas=ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
    self.imgIcon.sprite=spriteAtlas:GetSprite(strs[2])
    --print(data.num)
    self.Text.text=data.num
end
相关推荐
风霜不见闲沉月3 天前
kong网关的使用
junit·kong
一名技术极客4 天前
Nginx 实现动态封禁IP,详细教程来了
tcp/ip·nginx·junit
菠萝地亚狂想曲4 天前
优雅的LUA数据记录方法-serpent序列化+LUA Table
开发语言·junit·lua
硬汉嵌入式5 天前
H7-TOOL的LUA小程序教程第17期:扩展驱动AD7606, ADS1256,MCP3421, 8路继电器和5路DS18B20(2024-11-01)
junit·小程序·lua
程序猿小D6 天前
第三百零六节 Log4j教程 - Log4j日志级别
junit·单元测试·log4j
A尘埃6 天前
单元测试(Junit)
junit·单元测试·log4j
lihan_freak7 天前
Spring框架---IOC注解方式,Spring整合Junit单元测试
spring·junit·单元测试
程序猿小D7 天前
第三百零五节 Log4j教程 - Log4j日志记录方法
java·sql·junit·单元测试·log4j·mybatis·lucene
又菜又爱玩的晴晴7 天前
mockito+junit完成单元测试
junit·单元测试·mockito
板子小哥8 天前
LuatOS学习指南:开启物联网开发之旅
运维·服务器·开发语言·人工智能·物联网·junit·lua