当前位置: 首页 > news >正文

xLua背包实践

主要学习目标

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("准备就绪")

vscode环境搭建

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.SpriteAtlasVector3 = 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。

Lua读取json表及准备玩家数据

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) doItemData[value.id] = value
endfor key,value in pairs(ItemData) doprint(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)endendfunction MainPanel:ShowMe()self:Init()self.panelObj:SetActive(true)
endfunction MainPanel:HideMe()self.panelObj:SetActive(false)
endfunction 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个togglelocal 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相关svBagself.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<bool>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
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<bool>

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua
{[CSharpCallLua]public static List<Type> cSharpCallLuaList = new List<Type>();//记得代码生成//目的是为了生成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 thenself:ChangeType(1)end
end...
--逻辑处理函数 用来切页签
--type 1装备 2道具 3宝石
function  BagPanel:ChangeType(type)--如果已经是该页,就不更新if self.nowType == type thenreturnend--切页 根据玩家信息 来进行格子创建--更新之前 把老的格子删掉 BagPanel.itemsfor i = 1, #self.items do--销毁格子对象GameObject.Destroy(self.items[i].obj)endself.items = {}--再根据当前选择的类型   来创建新的格子 BagPanel.items--要根据传入的type  来选择显示的数据local nowItems = nilif type == 1 thennowItems = PlayerData.equipselseif type == 2 thennowItems = PlayerData.itemselsenowItems = PlayerData.gemsend--创建格子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.itemsfor 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 = falsefunction 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控件都继承UIBehaviourlocal allControls = self.panelObj:GetComponentsInChildren(typeof(UIBehaviour))--如果存入没用的UI控件怎么办   --为了避免找 各种无用控件 我们定一个规则 拼面板时 控件名按一定规则来--Button btn名字--Toggle tog名字--Image img名字--ScrollRect sv名字for i = 0,  allControls.Length - 1 dolocal controlName = allControls[i].name--对应c#中的数组,从0开始if string.find(controlName,"btn") ~= nil or string.find(controlName,"tog")  or string.find(controlName,"img")  orstring.find(controlName,"sv")  orstring.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]}endendendend  
end--得到控件 根据 控件依附对象的名字 和 控件的类型字符串名字 Button Image Toggle
function BasePanel:GetControl(name,typeName)if self.controls[name] ~= nil thenlocal sameNameControls = self.controls[name]if sameNameControls[typeName] ~= nil thenreturn sameNameControls[typeName]endendreturn nilendfunction BasePanel:ShowMe(name)self:Init(name)self.panelObj:SetActive(true)
endfunction 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 thenbtnRole = self:GetControl("btnRole","Button")btnRole.onClick:AddListener(function ()self:BtnRoleClick()end)self.isInitEvent = trueend
endfunction 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<bool>local group = self.panelObj.transform:Find("Group")self:GetControl("togEquip","Toggle").onValueChanged:AddListener(function (value)if value == true thenself:ChangeType(1)endend)self:GetControl("togItem","Toggle").onValueChanged:AddListener(function (value)if value == true thenself:ChangeType(2)endend)self:GetControl("togGem","Toggle").onValueChanged:AddListener(function (value)if value == true thenself:ChangeType(3)endend)self.isInitEvent = trueendend
--显示隐藏
function BagPanel:ShowMe(name)self.base.ShowMe(self,name)if self.nowType == -1 thenself:ChangeType(1)end
end--逻辑处理函数 用来切页签
--type 1装备 2道具 3宝石
function  BagPanel:ChangeType(type)--如果已经是该页,就不更新if self.nowType == type thenreturnend--切页 根据玩家信息 来进行格子创建--更新之前 把老的格子删掉 BagPanel.itemsfor i = 1, #self.items do--销毁格子对象self.items[i]:Destroy()endself.items = {}--再根据当前选择的类型   来创建新的格子 BagPanel.items--要根据传入的type  来选择显示的数据local nowItems = nilif type == 1 thennowItems = PlayerData.equipselseif type == 2 thennowItems = PlayerData.itemselsenowItems = PlayerData.gemsend--创建格子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

lua文件迁移小工具

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代码

相关文章:

  • 电机控制系列模块解析(25)—— 过压抑制与欠压抑制
  • Github 2024-05-29 C开源项目日报 Top10
  • 如何防止重复提交请求?
  • 【Postman接口测试】第二节.Postman界面功能介绍(上)
  • leetcode热题100.完全平方数(动态规划进阶)
  • 如何找到docker的run(启动命令)
  • 什么是以太坊?
  • 多线程-线程池
  • Spring Boot中如何查询PGSQL分表后的数据
  • Pytorch 笔记
  • Linux入门攻坚——23、DNS和BIND基础入门2
  • 微信小程序开发(持续更新)
  • 实时合成 1 秒频订单簿快照:DolphinDB INSIGHT 行情插件与订单簿引擎应用
  • FaceChain-FACT:开源10秒写真生成,复用海量LoRa风格,基模友好型写真应用
  • 【链表】Leetcode 82. 删除排序链表中的重复元素 II【中等】
  • 230. Kth Smallest Element in a BST
  • Android 控件背景颜色处理
  • canvas 高仿 Apple Watch 表盘
  • css选择器
  • Git初体验
  • JS函数式编程 数组部分风格 ES6版
  • 计算机在识别图像时“看到”了什么?
  • 将回调地狱按在地上摩擦的Promise
  • 系统认识JavaScript正则表达式
  • 怎么把视频里的音乐提取出来
  • 阿里云重庆大学大数据训练营落地分享
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • (12)Hive调优——count distinct去重优化
  • (30)数组元素和与数字和的绝对差
  • (33)STM32——485实验笔记
  • (7)STL算法之交换赋值
  • (9)目标检测_SSD的原理
  • (C++20) consteval立即函数
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (附源码)计算机毕业设计大学生兼职系统
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • .NET Framework .NET Core与 .NET 的区别
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .vimrc 配置项
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • @Autowired注解的实现原理
  • @软考考生,这份软考高分攻略你须知道
  • [ Algorithm ] N次方算法 N Square 动态规划解决
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?