准备工作
环境,代码
在C#代码方面我们需要准备单例模式基类,AB包管理器,lua解析器管理器
详情请见AB包管理器 xlua详解
然后是Xlua包和AB包,具体导入方法也在上面的链接中
然后是lua的三个文件
具体代码:
JsonUtility.lua网上应该能找到
下面是Object.lua
这里实现了一个lua中new和继承的逻辑
--面向对象实现
--万物之父 所有对象的基类 Object
--封装
Object = {}
--实例化方法
function Object:new()local obj = {}--给空对象设置元表 以及 __indexself.__index = selfsetmetatable(obj, self)return obj
end
--继承
function Object:subClass(className)--根据名字生成一张表 就是一个类_G[className] = {}local obj = _G[className]--设置自己的“父类”obj.base = self--给子类设置元表 以及 __indexself.__index = selfsetmetatable(obj, self)
end
然后是SplitTools.lua
这个函数实现了将一个字符串根据指定的分隔符进行分割的逻辑
function string.split(input, delimiter)input = tostring(input)delimiter = tostring(delimiter)if (delimiter=='') thenreturn falseendlocal pos,arr = 0, {}local find = function() return string.find(input, delimiter, pos, true) endfor st,sp in find dotable.insert(arr, string.sub(input, pos, st - 1))pos = sp + 1endtable.insert(arr, string.sub(input, pos))return arr
end
然后是Mian.csharp
使用luaMgr来重定向和执行lua脚本的
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文件,这个文件写的是常用别名
--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.SpriteAtlasVector3=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
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
local txt=ABMgr:LoadRes("json","ItemData",typeof(TextAsset))
local itemList =Json.decode(txt.text)
print(txt.text)
我们把itemList打印出来看看
print(itemList[1])
发现是个表
print(itemList[1].name.."id is"..itemList[1].id)
这里只是id序号和索引恰巧重合了而已,如果我不想通过索引(因为我并不知道每个索引的id是多少),而是想通过id来查询的话,这个表就不像键值对那样那么好查询,所以我们用个新表实现键值对的逻辑
我来解释一下这段代码,首先我们建一个新表,然后使用pairs遍历这个表,索引使用_表示我不关注索引
ItemData[value.id]=value这里我是使用自定义索引
这行代码执行完毕后可以说数据就与id相关联了,我们可以通过id来取到相应的表
ItemData={}
for _,value in pairs(itemList) doItemData[value.id]=value
end
比如说我可以通过id访问icon
print(ItemData[2].icon)
然后我们也可以做一个玩家信息
我们之前做的只是一共拥有的物品数量,而现在我需要存储具体的物体
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})
endPlayerData:Init()
print(PlayerData.equips[1].id)
主面板逻辑
接下来我们要用lua控制MainPanel
来到vscode新建一个MainPanel.lua
我们来解释一下下面代码,首先我创建个空表,用来模拟Mainpanel对象,这里相当于写了一个类,panelObj是主面板的实例化对象,btnRole和btnSkill是两个控件
然后我们写一个初始化方法Init
从我们打包的AB包中取面板,然后放到Canva下面
MainPanel={}MainPanel.panelObj=nil
MainPanel.btnRole=nil
MainPanel.btnSkill=nilfunction MainPanel:Init()--1.实例化面板对象self.panelObj=ABMgr:LoadRes("ui","MainPanel",typeof(GameObject))self.panelObj.transform:SetParent(Canvas,false)--false表明保持原有缩放比例end
执行后发现成功加载
然后我们从主界面找到它的子物体button
为其添加一个事件监听
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)endfunction MainPanel:BtnRoleClick()print(123123)
end
现在点击图标之后就会打印123了
我们再为其添加一个激活和隐藏的函数
最后完整代码:Main中直接调用showme即可
MainPanel={}MainPanel.panelObj=nil
MainPanel.btnRole=nil
MainPanel.btnSkill=nilfunction 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)
endendfunction MainPanel:ShowMe()self:Init()self.panelObj:SetActive(true)
endfunction MainPanel:HideMe()self.panelObj:SetActive(false)
endfunction MainPanel:BtnRoleClick()print(123123)
end
背包面板
新建一个BagPanel.lua
核心逻辑还是按照我们之前想法
BagPanel={}BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nilfunction BagPanel:Init()if self.panelObj==nil thenself.panelObj=ABMgr:LoadRes("ui","BagPanel",typeof(GameObject))self.panelObj.transform:SetParent(Canvas,false)end
endfunction BagPanel:ShowMe()self:Init()self.panelObj:SetActive(true)
endfunction BagPanel:HideMe()self.panelObj:SetActive(false)
end
然后在MainPanel中的显示中调用背包格子的显示
function MainPanel:BtnRoleClick()BagPanel:ShowMe()
end
就可以显示了
接着我们要实现一个选择单选框切换的逻辑,但是toggle使用的逻辑本质上是Action委托,是只读的,所以要用到我们之前的方法,也就是自己写一个静态类,新建一个列表
using XLua;public static class CSharpCallLuaList
{[CSharpCallLua]public static List<Type> csharpCallLuaList=new List<Type>();
}
下面是背包类全部代码
BagPanel={}BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togItem=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nilfunction BagPanel:Init()if self.panelObj==nil thenself.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 thenself:ChangeType(1)endend)self.togEquip.onValueChanged:AddListener(function(value) if value==true thenself:ChangeType(2)endend)self.togEquip.onValueChanged:AddListener(function(value) if value==true thenself:ChangeType(3)endend)end
endfunction BagPanel:ShowMe()self:Init()self.panelObj:SetActive(true)
endfunction BagPanel:HideMe()self.panelObj:SetActive(false)
end--1.装备 2.道具 3.宝石
function BagPanel:ChangeType(type)print("当前类型为"..type)
end
格子逻辑
格子逻辑有两种实现方法,一种是比较笨的,我们每次点击单选框后生成多个格子对象(不使用面对对象思想)
直接在changeType中实现
我们先创建一个nowItem存相应的单选框存的数据,这个数据来自于PlayerData,是我们自己赋值的,正常情况应该是从服务器或从本地读取的
然后我们遍历nowItem,要做的事情是我们先把需要的素材准备好,然后把图标,文本,生成位置这些对象准备好,最后再赋值
function BagPanel:ChangeType(type)local nowItem=nilif(type==1)then print("type is"..type)nowItem=PlayerData.equipselseif(type ==2)thenprint("type is"..type)nowItem=PlayerData.itemselseprint("type is"..type)nowItem=PlayerData.gemsend--创建格子for i=1,#nowItem dolocal 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].numtable.insert(self.items,grid) end
但是现在有个问题,我并没有删除不用的itemGrid,这个itemGrid是格子对象
所以我们在ChangeType进来的时候就把原有的逻辑置空了
function BagPanel:ChangeType(type)local nowItem=nil
for i=1,#self.items doGameObject.Destroy(self.items[i].obj)
endself.items={}
整体代码
不过现在还有个问题,我重复点同一个单选框时会浪费性能
所以记录一下当前类型,如果是就不改变了
BagPanel.nowType=-1
function BagPanel:ChangeType(type)if self.nowType==type thenreturn
elseself.nowType=type
end
最后一个小问题是第一次进入的时候没有刷新
可以直接设置ChangeType(1)
function BagPanel:ShowMe()self:Init()self.panelObj:SetActive(true)if self.nowType==-1 thenself:ChangeType(1)end
end
最终逻辑
BagPanel={}BagPanel.panelObj=nil
BagPanel.btnClose=nil
BagPanel.togEquip=nil
BagPanel.togItem=nil
BagPanel.togGem=nil
BagPanel.svBag=nil
BagPanel.Content=nilBagPanel.items={}BagPanel.nowType=-1function BagPanel:Init()if self.panelObj==nil thenself.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 thenself:ChangeType(1)endend)self.togItem.onValueChanged:AddListener(function(value) if value==true thenself:ChangeType(2)endend)self.togGem.onValueChanged:AddListener(function(value) if value==true thenself:ChangeType(3)endend)end
endfunction BagPanel:ShowMe()self:Init()self.panelObj:SetActive(true)if self.nowType==-1 thenself:ChangeType(1)end
endfunction BagPanel:HideMe()self.panelObj:SetActive(false)
end--1.装备 2.道具 3.宝石
function BagPanel:ChangeType(type)if self.nowType==type thenreturn
elseself.nowType=type
endlocal nowItem=nilfor i=1,#self.items doGameObject.Destroy(self.items[i].obj)
endself.items={}if(type==1)then print("type is"..type)nowItem=PlayerData.equipselseif(type ==2)thenprint("type is"..type)nowItem=PlayerData.itemselseprint("type is"..type)nowItem=PlayerData.gemsend--创建格子for i=1,#nowItem dolocal 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].numtable.insert(self.items,grid) end
end
我们回顾一下实现的逻辑,其实格子的逻辑不应该写在背包里的,如果我加什么别的操作,就要一直在背包这里设置格子逻辑,很冗余
所以我们应该把格子写成一个类
所以我们写一个itemGrid.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,位置,这里都可以传进去(位置逻辑在外面计算)
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
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
我们来改背包逻辑
--创建格子for i=1,#nowItem dolocal 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逻辑
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