1.巩固学习的AB包+Lua语法+xLua解决方案的3部分知识
2.学会Unity+Lua+VSCode环境调试
3.学会Unity结合xLua进行游戏功能开发
4.学会制作Lua文件迁徙小工具
1.导入xlua
将xlua文件夹下的Assets中的Plugins和XLua文件夹导入Unity
2.ab包导入
导入asset Bundle Browser,新版本中已经下架,可以在官方手册中通过git下载。
设置如图
3.导入ProjectBase
4.导入相关lua文件
5.C#Main以及Lua 的Main
- void Start()
- {
- LuaMgr.GetInstance().Init();
- LuaMgr.GetInstance().DoLuaFile("Main");
- }
print("准备就绪")
1.下载扩展
(///快捷注释)
(debugger for Unity)
2.改变Unity启动的编辑器
3.验证C#环境搭建成功
跳出transfrom说明成功
选择Attach to Unity即可开始调试
4.lua环境搭建
下载Emmylua扩展
添加新的调试配置
需要jdk1.8以上并设置环境变量
设置好后就可以正常调试了。
Canvas设置
加上toggleGroup组件
记得给每个tog的group指定
左对齐
效果
预设体图样
新建InitClass.lua
- --常用别名都在这里定位
- --准备我们之前导入的脚本
- --面向对象
- require("Object")
- --字符串拆分
- require("SplitTools")
- --Json解析
- Json = require("JsonUtility")
-
- --Unity相关的
- GameObject = CS.UnityEngine.GameObject
- Resources = CS.UnityEngine.Resources
- Transform = CS.UnityEngine.Transform
- RectTransform = CS.UnityEngine.RectTransform
- --图集对象类
- SpriteAtlas = CS.UnityEngine.U2D.SpriteAtlas
-
- Vector3 = CS.UnityEngine.Vector3
- Vector2 = CS.UnityEngine.Vector2
-
- --UI相关的
- UI = CS.UnityEngine.UI
- Image = UI.Image
- Button = UI.Button
- Text = UI.Text
- Toggle = UI.Toggle
- ScrollRect = UI.ScrollRect
-
- --自己写的C#脚本相关
- --直接得到AB包资源管理器的单例对象
- ABMgr = CS.ABMgr.GetInstance()
-
-
Main.lua
- print("准备就绪")
- --初始化所有准备好的类别名
- require("InitClass")
生成json表,打包进AB包
先编辑excel表
icon命名参考下面图集,加Icon是为了区分其它图集,表明是Icon图集中的资源
图集设置
使用转json工具将excel表转为json(推荐bejson网站)
注意有的转json网站会将数字用字符串表示,同时最后一行可能多了逗号或者多空一行。
将json文件打成json ab包中,之前的预设体和icon图集打到ui包中
之后就build。
Main.lua修改
- print("准备就绪")
- --初始化所有准备好的类别名
- require("InitClass")
- --初始化道具表信息
- require("ItemData")
- --玩家信息
- --1.从本地读取 本地存储 有PlayerPrefs和json或者二进制
- --2.网络游戏 从服务器读取
- require("PlayerData")
- PlayerData:Init()
新建ItemData.lua
-
- --将json数据读取道lua中的表中进行存储
-
- --首先应该先把Json表 从AB包中加载出来
- --TextAsset 是InitClass中定义的TextAsset = CS.UnityEngine.TextAsset
- local txt = ABMgr:LoadRes("json","ItemData",typeof(TextAsset))
- --获取它的文本信息 进行json解析
- local itemList = Json.decode(txt.text)
- print(itemList[1]) --打印出一个table
- print(itemList[1].id .. itemList[1].name)
-
- --加载出来是一个像数组结构的数据
- --不方便我们通过 id 来获取里面的内容 所以 我们用一张新表 转存一次
- --而且这张表 在任何地方 都能被使用
- -- 一张用来存储道具信息的表
- -- 键值对形式 键是道具ID 值是道具表一行信息
- ItemData = {}
- for _, value in pairs(itemList) do
- ItemData[value.id] = value
- end
-
- for key,value in pairs(ItemData) do
- print(key,value.tips)
- end
PalyerData.lua
- PlayerData = {}
- --目前只做背包功能 所以只需要它们的道具信息
-
- PlayerData.equips = {}
- PlayerData.items = {}
- PlayerData.gems = {}
-
- --为玩家数据写一个 初始化方法后 以后直接改这里的数据来源即可
- function PlayerData:Init()
- --道具信息 不管存本地 还是服务器 都不会把道具的所有信息存起来
- --道具ID和数量
-
- --目前因为没有服务器 为了测试 就写死道具数据作为玩家数据
- 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 = 30})
-
- table.insert(self.gems,{id = 5, num = 99})
- table.insert(self.gems,{id = 6, num = 88})
- end
编写MainPanel.lua
- --只要是一个新的对象(面板) 我们就新建一张表
- MainPanel = {}
-
- --不是必须写 因为lua的特性 不存在声明变量的概念
- --这样写的目的 是当别人看到这个lua代码时 知道这个表(对象)有什么变量很重要
- --关联的面板对象
- MainPanel.panelObj = nil
- --对应的面板控件
- MainPanel.btnRole = nil
- MainPanel.btnSkill = nil
-
- --需要做 实例化面板对象
- --为这个面板 处理对应的逻辑 比如按钮点击等等
-
- --初始化该面板 实例化对象 控件事件监听
- function MainPanel:Init()
- --面板对象没有实例化过 才去实例化
- if self.panelObj == nil then
- --1.实例化面板对象 ABMgr + 设置父对象
- self.panelObj = ABMgr:LoadRes("ui","MainPanel",typeof(GameObject))
- self.panelObj.transform:SetParent(Canvas,false)
- --2.找到对应控件
- --找到子对象 再找到身上挂载的 想要的脚本
- self.btnRole = self.panelObj.transform:Find("btnRole"):GetComponent(typeof(Button))
- print(self.btnRole)
- --3.为控件加上监听事件 进行点击等等逻辑处理
- --以下方法,如果直接传入自己的函数 那么在函数内部 没办法用self获取内容
- --self.btnRole.onClick:AddListener(self.BtnRoleClick)
- 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)
- --print(self.panelObj)
- --等写了背包面板
- --在这写显示背包
- end
更新Main.lua
- print("准备就绪")
- --初始化所有准备好的类别名
- require("InitClass")
- --初始化道具表信息
- require("ItemData")
- --玩家信息
- --1.从本地读取 本地存储 有PlayerPrefs和json或者二进制
- --2.网络游戏 从服务器读取
- require("PlayerData")
- PlayerData:Init()
-
- --之后的逻辑
- require("MainPanel")
- MainPanel:ShowMe()
新建BagPanel.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
- --1.实例化面板对象 ABMgr + 设置父对象
- self.panelObj = ABMgr:LoadRes("ui","BagPanel",typeof(GameObject))
- self.panelObj.transform:SetParent(Canvas,false)
- --2.找到对应控件
- --找到子对象 再找到身上挂载的 想要的脚本
- --关闭按钮
- self.btnClose = self.panelObj.transform:Find("btnClose"):GetComponent(typeof(Button))
- --3个toggle
- 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))
- --sv相关svBag
- self.svBag = self.panelObj.transform:Find("svBag"):GetComponent(typeof(ScrollRect))
- self.Content = self.svBag.transform:Find("Viewport"):Find("Content")
- --3.为控件加上监听事件 进行点击等等逻辑处理
- --以下方法,如果直接传入自己的函数 那么在函数内部 没办法用self获取内容
- --self.btnRole.onClick:AddListener(self.BtnRoleClick)
- --关闭按钮
- self.btnClose.onClick:AddListener(function ()
- self:HideMe()
- end)
- --单选框事件
- --切页签
- --toggle 对应委托 是UnityAction
- 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)
- end
- function BagPanel:HideMe()
- self.panelObj:SetActive(false)
- end
-
- --逻辑处理函数 用来切页签
- --type 1装备 2道具 3宝石
- function BagPanel:ChangeType(type)
- print("当前类型为".. type)
- --切页 根据玩家信息 来进行格子创建
- end
新增内容MainPanel
- function MainPanel:BtnRoleClick()
- BagPanel:ShowMe()
- --print(123123)
- --print(self.panelObj)
- --等写了背包面板
- --在这写显示背包
- end
新建CSharpCallLua.cs
为了能xlua调用UnityAction
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using XLua;
- public class CSharpCallLua
- {
- [CSharpCallLua]
- public static List
cSharpCallLuaList = new List(); - //记得代码生成
- //目的是为了生成xlua代码 ???
- }
可以参考https://blog.csdn.net/woodengm/article/details/112614506
之前的MainPanel和BagPanel每次初始化都是一个表,只能表示一个对象
而格子会有很多个则没法用这种做法做。
粗暴的方法(后面有面向对象的方法)
BagPanel更新
- ...
- --用来存储当前显示的格子
- BagPanel.items = {}
-
- BagPanel.nowType = -1
-
- ....
-
- --显示隐藏
- function BagPanel:ShowMe()
- self:Init()
- self.panelObj:SetActive(true)
- if self.nowType == -1 then
- self:ChangeType(1)
- end
- end
-
- ...
- --逻辑处理函数 用来切页签
- --type 1装备 2道具 3宝石
- function BagPanel:ChangeType(type)
- --如果已经是该页,就不更新
- if self.nowType == type then
- return
- end
- --切页 根据玩家信息 来进行格子创建
-
- --更新之前 把老的格子删掉 BagPanel.items
- for i = 1, #self.items do
- --销毁格子对象
- GameObject.Destroy(self.items[i].obj)
- end
- self.items = {}
- --再根据当前选择的类型 来创建新的格子 BagPanel.items
- --要根据传入的type 来选择显示的数据
- local nowItems = nil
- if type == 1 then
- nowItems = PlayerData.equips
- elseif type == 2 then
- nowItems = PlayerData.items
- else
- nowItems = PlayerData.gems
- end
-
- --创建格子
- for i = 1, #nowItems 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("imgIcon"):GetComponent(typeof(Image))
- grid.Text = grid.obj.transform:Find("num"):GetComponent(typeof(Text))
- --设置它的图标
- --通过 道具id 去读取 道具配置表 得到图标信息
- local data = ItemData[nowItems[i].id]
- --想要的是data中的图标信息
- --根据名字 先加载图集 再加载图集中的 图标信息
- local strs = string.split(data.icon, "_")
- --加载图集
- local spritAtlas = ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
- --加载图标
- grid.imgIcon.sprite = spritAtlas:GetSprite(strs[2])
- --设置它的数量
- grid.Text.text = nowItems[i].num
- --把他存起来
- table.insert(self.items,grid)
- --这里实现了显示逻辑,但每次切换type要记得把老格子删除,逻辑在上面
- end
- end
前面虽然实现了格子的基本逻辑,但因为不是面向对象的实现方式,不能实现格子的各种功能,如果需要与格子进行交互,就只能再bagPanel里实现前面的方法就不可行。
创建ItemGrid.lua
- --用到之前讲过的 GameObject
- --生成一个table 继承Object 主要目的是要它里面实现的 继承方法 subClass 和 new
- 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("imgIcon"):GetComponent(typeof(Image))
- self.Text = self.obj.transform:Find("num"):GetComponent(typeof(Text))
- end
-
-
- --实例化格子对象
- --data 是外面传入的 道具信息 里面包含了 id 和 num
- function ItemGrid:InitData(data)
- --通过 道具id 去读取 道具配置表 得到图标信息
- local itemData = ItemData[data.id]
- --想要的是data中的图标信息
- --根据名字 先加载图集 再加载图集中的 图标信息
- local strs = string.split(itemData.icon, "_")
- --加载图集
- local spritAtlas = ABMgr:LoadRes("ui",strs[1],typeof(SpriteAtlas))
- --加载图标
- self.imgIcon.sprite = spritAtlas:GetSprite(strs[2])
- --设置它的数量
- self.Text.text = data.num
- end
- --初始化格子信息
-
- --加自己的逻辑
- function ItemGrid:Destroy()
- GameObject.Destroy(self.obj)
- self.obj = nil
- end
修改BagPanel
- --更新之前 把老的格子删掉 BagPanel.items
- for i = 1, #self.items do
- --销毁格子对象
- self.items[i]:Destroy()
- end
-
- ...
-
- --创建格子
- for i = 1, #nowItems do
- --根据数据 创建一个格子对象
- local grid = ItemGrid:new()
- --要实例化对象 设置位置
- grid:Init(self.Content,(i-1)%4*175,math.floor((i-1)/4)*175)
- --初始化它的信息 数量 和 图标
- grid:InitData(nowItems[i])
- --把他存起来
- table.insert(self.items,grid)
- --这里实现了显示逻辑,但每次切换type要记得把老格子删除,逻辑在上面
- end
- end
修改Main.lua
- ...
- --之后的逻辑
- require("MainPanel")
- MainPanel:ShowMe()
- require("BagPanel")
- require("ItemGrid")
面板里有Init()等相同的函数或变量,可以创建面板基类
新建BasePanel.lua
- --利用面向对象
- Object:subClass("BasePanel")
-
- BasePanel.panelObj = nil
- --相当于模拟一个字典 键为 控件名 值为控件本身
- BasePanel.controls = {}
- --用来判断是否已经初始化过了,因为父类的方法,子类不能用self.panelObj == nil 来判断
- --所以用这个变量来判断
- --作为事件监听标识
- BasePanel.isInitEvent = false
-
- function BasePanel:Init(name)
- if self.panelObj == nil then
- --公共的实例化对象的方法
- self.panelObj = ABMgr:LoadRes("ui",name,typeof(GameObject))
- self.panelObj.transform:SetParent(Canvas,false)
- --GetComponentsInChildren() 得到所有挂载的
- --找所有UI控件 存起来
- --所有UI控件都继承UIBehaviour
- local allControls = self.panelObj:GetComponentsInChildren(typeof(UIBehaviour))
- --如果存入没用的UI控件怎么办
- --为了避免找 各种无用控件 我们定一个规则 拼面板时 控件名按一定规则来
- --Button btn名字
- --Toggle tog名字
- --Image img名字
- --ScrollRect sv名字
- for i = 0, allControls.Length - 1 do
- local controlName = allControls[i].name
- --对应c#中的数组,从0开始
- if string.find(controlName,"btn") ~= nil or
- string.find(controlName,"tog") or
- string.find(controlName,"img") or
- string.find(controlName,"sv") or
- string.find(controlName,"txt") then
- --为了让我们在得的时候 能够确定控件类型 我们需要存储类型
- --利用反射 Type 得到 控件的类名
- local typeName = allControls[i]:GetType().Name
- --一个对象可能有多个ui组件 如同时又img txt,因此用表来存组件,名字为键
-
- --最终存储形式
- --{btnRole = {Image = 控件, Button = 控件 } ,
- -- toggle = {Toggle = 控件 } }
- if self.controls[allControls[i].name] ~= nil then
- --table.insert(self.controls[allControls[i].name],allControls[i])
- self.controls[controlName][typeName] = allControls[i]
- else
- --self.controls[allControls[i].name] = {allControls[i]}
- --这仍有点问题,我们该怎么区分表里的btn和img这些类别呢,因此要用到上面存储的类型
- --以下是正确的
- self.controls[controlName] = {[typeName] = allControls[i]}
- end
- end
- end
- end
- end
-
- --得到控件 根据 控件依附对象的名字 和 控件的类型字符串名字 Button Image Toggle
- function BasePanel:GetControl(name,typeName)
- if self.controls[name] ~= nil then
- local sameNameControls = self.controls[name]
- if sameNameControls[typeName] ~= nil then
- return sameNameControls[typeName]
- end
- end
- return nil
-
- end
-
-
- function BasePanel:ShowMe(name)
- self:Init(name)
- self.panelObj:SetActive(true)
- end
-
- function BasePanel:HideMe()
- self.panelObj:SetActive(false)
- end
修改MainPanel.lua和BagPanel.lua
-
- BasePanel:subClass("MainPanel")
-
- function MainPanel:Init(name)
- --使用父类的方法,但用 . 而不是 :
- --要传入自己
- --里面已经有判空
- self.base.Init(self,name)
- --为了只添加一次事件监听
- if self.isInitEvent == false then
- btnRole = self:GetControl("btnRole","Button")
- btnRole.onClick:AddListener(function ()
- self:BtnRoleClick()
- end)
- self.isInitEvent = true
- end
- end
-
- function MainPanel:BtnRoleClick()
- BagPanel:ShowMe("BagPanel")
- end
-
-
- BasePanel:subClass("BagPanel")
-
- BagPanel.Content = nil
- --用来存储当前显示的格子
- BagPanel.items = {}
-
- BagPanel.nowType = -1
- --“成员方法"
- --初始化方法
- function BagPanel:Init(name)
- self.base.Init(self,name)
- --2.找到对应控件
- --找到子对象 再找到身上挂载的 想要的脚本
- --关闭按钮
- if self.isInitEvent == false then
- --找到没有挂载UI控件的对象还是需要手动去找
- self.Content = self:GetControl("svBag","ScrollRect").transform:Find("Viewport"):Find("Content")
- local group = self.panelObj.transform:Find("Group")
-
- self:GetControl("btnClose","Button").onClick:AddListener(function ()
- self:HideMe()
- end)
-
- --单选框事件
- --切页签
- --toggle 对应委托 是UnityAction
- local group = self.panelObj.transform:Find("Group")
- self:GetControl("togEquip","Toggle").onValueChanged:AddListener(function (value)
- if value == true then
- self:ChangeType(1)
- end
- end)
- self:GetControl("togItem","Toggle").onValueChanged:AddListener(function (value)
- if value == true then
- self:ChangeType(2)
- end
- end)
- self:GetControl("togGem","Toggle").onValueChanged:AddListener(function (value)
- if value == true then
- self:ChangeType(3)
- end
- end)
- self.isInitEvent = true
-
- end
-
- end
- --显示隐藏
- function BagPanel:ShowMe(name)
- self.base.ShowMe(self,name)
- if self.nowType == -1 then
- self:ChangeType(1)
- end
- end
-
-
- --逻辑处理函数 用来切页签
- --type 1装备 2道具 3宝石
- function BagPanel:ChangeType(type)
- --如果已经是该页,就不更新
- if self.nowType == type then
- return
- end
- --切页 根据玩家信息 来进行格子创建
-
- --更新之前 把老的格子删掉 BagPanel.items
- for i = 1, #self.items do
- --销毁格子对象
- self.items[i]:Destroy()
- end
- self.items = {}
- --再根据当前选择的类型 来创建新的格子 BagPanel.items
- --要根据传入的type 来选择显示的数据
- local nowItems = nil
- if type == 1 then
- nowItems = PlayerData.equips
- elseif type == 2 then
- nowItems = PlayerData.items
- else
- nowItems = PlayerData.gems
- end
-
- --创建格子
- for i = 1, #nowItems do
- --根据数据 创建一个格子对象
- local grid = ItemGrid:new()
- --要实例化对象 设置位置
- grid:Init(self.Content,(i-1)%4*175,math.floor((i-1)/4)*175)
- --初始化它的信息 数量 和 图标
- grid:InitData(nowItems[i])
- --把他存起来
- table.insert(self.items,grid)
- --这里实现了显示逻辑,但每次切换type要记得把老格子删除,逻辑在上面
- end
- end
LuaCopyEditor.cs
- using System.Collections;
- using System.Collections.Generic;
- using System.IO;
- using System.IO.Enumeration;
- using Unity.VisualScripting;
- using UnityEditor;
- using UnityEngine;
-
- public class LuaCopyEditor : Editor
- {
- [MenuItem("XLua/自动生成txt后缀的lua")]
- public static void CopyLuaToTxt(){
- //找到所有lua文件
- string path = Application.dataPath + "/Lua/";
- if(!Directory.Exists(path))
- return;
- //得到每一个lua文件的路径 才能迁移拷贝
- //得到.lua文件路径
- string[] strs = Directory.GetFiles(path,"*.lua");
- //把lua文件拷贝到新的文件夹中
- //首先 定一个新路径
- string newPath = Application.dataPath + "/LuaTxt/";
-
- //为了避免一些被删除的lua文件 不再使用 我们应该先清空目标路径
-
- //判断新路径文件夹是否存在
- if(!Directory.Exists(newPath))
- Directory.CreateDirectory(newPath);
- else{
- //得到该路径中 所有后缀.txt的文件 把他们全部删除
- string[] oldFileStrs = Directory.GetFiles(newPath,"*.txt");
- for(int i = 0; i < oldFileStrs.Length; i++){
- File.Delete(oldFileStrs[i]);
- }
- }
- List<string> newFileNames = new List<string>();
- string fileName;
- for(int i = 0; i < strs.Length; i++){
- //得到新的文件路径 用于拷贝
- fileName = newPath + strs[i].Substring(strs[i].LastIndexOf("/") + 1) + ".txt";
- newFileNames.Add(fileName);
- File.Copy(strs[i],fileName);
- }
-
- AssetDatabase.Refresh();
-
- //刷新过后再来改指定AB包 如果不刷新 第一次改变 会没用
- for(int i = 0; i < newFileNames.Count; i++){
- //Unity API
- //该API传入的路径 必须是 相对Assets文件夹的 Assets/.../...
- AssetImporter importer = AssetImporter.GetAtPath( newFileNames[i].Substring(newFileNames[i].IndexOf("Assets")));
- if(importer != null)
- importer.assetBundleName = "lua";
- }
- }
- }
LuaMgr中可以注释掉
//luaEnv.AddLoader(MyCustomLoader);
只使用
luaEnv.AddLoader(MyCustomLoaderFormAB);
即从AB包中加载
每次ab包打包时,要将xlua代码清楚后打包,不然会报错
记得打包完后重新生成xlua代码