Unity中 Xlua使用整理(一)

1.安装:

从GitHub上下载Xlua源码

Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)

下载Xlua压缩包,并解压将Aseet文件夹中的Xlua和Plugins文件夹复制到Unity工程中

 

复制到工程之后,菜单栏就会出现Xlua项

新建脚本运行第一个程序:

public class Test : MonoBehaviour
{
    void Start()
    {
        LuaEnv luaenv = new LuaEnv();
        luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
        luaenv.Dispose();
    }
}

结果:

2.加载Lua脚本

1.读取字符串:

使用DoString函数,输入lua的字符串代码执行

LuaEnv luaenv = new LuaEnv();
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
luaenv.Dispose();

2.默认loader加载:

使用DoString函数,执行require函数加载Lua脚本

 LuaEnv luaenv = new LuaEnv();
 luaenv.DoString("require 'main'");
 luaenv.Dispose();

require加载的路径:Resources文件夹、StreamingAssets文件夹、CustomLoader自定义Loader加载。下图中用红色框选的都是require的加载路径

如果放在Resources文件夹,因为Resource只支持有限的后缀,放Resources下的lua文件得加上txt后缀

如果放在StreamingAssets文件夹或者放在与Asset同级目录以及编辑器安装目录,则可以使用.lua作为后缀。

3.自定义loader加载:

通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。

新建Lua文件夹,存放lua文件

加载代码:

void Start()
{
    LuaEnv luaenv = new LuaEnv();
    luaenv.AddLoader(customLoader);
    luaenv.DoString("require 'main'");
    luaenv.Dispose();
}

    public byte[] customLoader(ref string filepath)
    {
        /*加载Lua代码*/
#if UNITY_EDITOR
        return AssetDatabase.LoadAssetAtPath<TextAsset>($"Assets/Lua/{filepath}.lua.txt").bytes;
#endif
        return null;
    }

3.C#调用Lua:

1.使用LuaEnv.Global获取一个全局基本数据类型:

使用Get<T>(string name)方法获取全局变量

C#代码:

    int hp = luaenv.Global.Get<int>("hp");
    bool isPlay = luaenv.Global.Get<bool>("isPlay");
    string heroName = luaenv.Global.Get<string>("heroName");
    Debug.Log($"HP:{hp},isPlay:{isPlay},heroName:{heroName}");

Lua代码:

hp = 100
isPlay = false
heroName = "Owen"

 结果:

2.访问一个全局的table

1.映射到普通class或struct

注:这种方式下xLua会new一个实例,并把对应的字段赋值过去。

table的属性可以多于或者少于class的属性,可以嵌套其它复杂类型, 这个过程是值拷贝,如果class比较复杂代价会比较大,而且修改class的字段值不会同步到table,反过来也不会。这个功能可以通过把类型加到GCOptimize生成降低开销。

使用Get<T>(string name)方法获取全局变量

C#代码:class和struct中的字段访问都是公开的(publish)

 public class PlayerInfo
 {
      public int id;
      public string name;
      public int level;
 }

 public struct EventMsg
 {
      public int eventId;
      public string param;
 } 
 //映射到有对应字段的class,by value 
 PlayerInfo info = luaenv.Global.Get<PlayerInfo>("playerInfo");
 EventMsg msg = luaenv.Global.Get<EventMsg>("eventMsg");
 Debug.Log($"PlayerInfo:{info.id},{info.name},{info.level}");
 Debug.Log($"EventMsg:{msg.eventId},{msg.param}");

Lua代码:

playerInfo = {
    id = 1001,
    name = "Owen",
    level = 100
} 

eventMsg = {
    eventId = 101,
    param = "aaaaaaaaaaa"
}

结果:

2.映射到一个interface

需要先生成代码(如果没生成代码会抛InvalidCastException异常),

代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数

C#代码:

    [CSharpCallLua]
    public interface IPlayerPosition
    {
        int x { get; set; }
        int y { get; set; }
        int z { get; set; }

        void add(int x, int y, int z);
        void sub(int x,int y ,int z);
    }

    public void GetInterface()
    {
        //映射到interface实例,by ref,这个要求interface加到生成列表,否则会返回null,建议用法
        IPlayerPosition pPos = luaenv.Global.Get<IPlayerPosition>("playerPosition");
        Debug.Log($"{pPos.x},{pPos.y},{pPos.z}");
        pPos.add(10,1,23);
        Debug.Log($"{pPos.x},{pPos.y},{pPos.z}");
        pPos.sub(1,-1,11);
        Debug.Log($"{pPos.x},{pPos.y},{pPos.z}");
    }

Lua代码:

playerPosition = {
   x = 10,
   y = 0,
   z = 10
}

function playerPosition:add(x0,y0,z0)
       self.x = self.x + x0
       self.y = self.y + y0
       self.z = self.z + z0
end

function playerPosition:sub(x0,y0,z0)
       self.x = self.x - x0
       self.y = self.y - y0
       self.z = self.z - z0
end

结果:

注意:

C#侧和Lua侧的属性、方法、字段必须名字一样大小写一致。如果将C#侧的add方法写成Add,就会报错,如下:

3.映射到Dictionary<>,List<>

lua侧table和C#侧的key和value类型必须一致,List只会映射table的数组部分,Dictionary只会映射非数组部分。

C#代码:

      //映射到Dictionary<string, double>,by value
      Dictionary<string, double> d = luaenv.Global.Get<Dictionary<string, double>>("Item");
      Debug.Log(d.Count);

      //映射到List<double>,by value
      List<double> l = luaenv.Global.Get<List<double>>("Item"); 
      Debug.Log(l.Count);

Lua代码:

Item = {
  10001,1002,content = 10000,20001,3300,
}

 结果:

4.映射到LuaTable类

这种方式好处是不需要生成代码,但也有一些问题,慢,比2要慢一个数量级,没有类型检查。

访问字段属性时需要用Get<T>(string name)方法访问。

C#侧代码:

//映射到LuaTable,by ref
LuaTable info= luaenv.Global.Get<LuaTable>("playerInfo");
Debug.Log($"PlayerInfo:{info.Get<int>("id")},{info.Get<string>("name")},{info.Get<int>("level")}");
  [CSharpCallLua]
  public delegate void AddMethod(LuaTable self,int x,int y, int z);

  [CSharpCallLua]
  public delegate Action addAc(LuaTable t, int x, int y, int z);

  //映射到LuaTable,by ref
  LuaTable info= luaenv.Global.Get<LuaTable>("playerPosition");

  AddMethod LD = info.Get<AddMethod>("add");
  LD(info,2, 3, 4);
  Debug.Log($"playerPosition :{info.Get<int>("x")},{info.Get<int>("y")},{info.Get<int>("z")}");

  var ac = info.Get<Action<LuaTable,int,int,int>>("add");
  ac(info, 2, 3, 4);
  Debug.Log($"playerPosition :{info.Get<int>("x")},{info.Get<int>("y")},{info.Get<int>("z")}");

  var aac = info.Get<addAc>("add");
  aac(info, 2, 3, 4);
  Debug.Log($"playerPosition :{info.Get<int>("x")},{info.Get<int>("y")},{info.Get<int>("z")}");
 //映射到LuaTable,by ref
 LuaTable info= luaenv.Global.Get<LuaTable>("playerPosition");
 var LF = info.Get<LuaFunction>("add");
 LF.Call(info,1,2,3);
 Debug.Log($"{info.Get<int>("x")},{info.Get<int>("y")},{info.Get<int>("z")}");

结果:

 

注意:在LuaTable中获取对象中方法需要用.Get<LuaFunction>(""),或者.Get<Action<type...>>("")或者使用.Get<delegate>("")。

3.访问一个全局的function

注意:用该类访问Lua函数会有boxing,unboxing的开销,为了性能考虑,需要频繁调用的地方不要用该类。建议通过 table.Get<ABCDelegate> 获取一个 delegate 再调用(假设 ABCDelegate 是 C# 的一个 delegate)。在使用使用 table.Get<ABCDelegate> 之前,请先把ABCDelegate加到代码生成列表。

1.映射到delegate:

使用delegate获取lua方法时需要先生成代码

  [CSharpCallLua]
  public delegate void test1(int x);

  [CSharpCallLua] 
  public delegate Action test2(int x);
  
  test1 LD = luaenv.Global.Get<test1>("test");
  LD(100);

  var ac = luaenv.Global.Get<Action<int>>("test");
  ac(0);

  var aac = luaenv.Global.Get<test2>("test");
  aac(19);

Lua代码:

function test(a)
    print(a)
end

结果: 

2.映射到LuaFunction

var lf = luaenv.Global.Get<LuaFunction>("test");
lf.Call(10);

结果:

4.使用建议(官方手册)

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。

  2. 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。

4.Lua调用C#:

1.new C#对象

lua里头没有new关键字;所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法

使用lua new一个C#侧的GameObject对象

Lua代码:

local newGameObj = CS.UnityEngine.GameObject()

结果:

xlua支持重载,使用重载new一个构造函数带参的GameObject

Lua代码:

local newGameObj = CS.UnityEngine.GameObject("test")

结果: 

2.访问C#静态属性,方法

c#代码:使用打标签(LuaCallCSharp)需要先生成代码再使用,如果不生成代码会使用性能较低的反射方式来访问。

    //此类是否是static都可以
    [LuaCallCSharp]  
    public static class GameCfg
    {
        public static int times = 0;
        public static string url = "www.baidu.com";

        public static void CalcValue()
        {
            Debug.Log("cccccccc");
        }
    }

1.获取静态属性:

 Lua代码:

local url = CS.GameCfg.url
print(url)

结果:

 

2.写入静态属性:

Lua代码:

CS.GameCfg.url = "zzzzzz"
print(CS.GameCfg.url)

结果:

3.调用静态方法:

Lua代码:

CS.Test.GameCfg.CalcValue()

结果:

 

3.访问C#成员属性,方法

C#侧代码: 

    [LuaCallCSharp]
    public class Actor
    {
        public int id;
        public float hp;
        public string name;
        public float baseAtk;
        public float baseDef;

        public virtual void callAtk()
        {
            Debug.Log("atk");
        }

        public virtual void callWalk()
        {
            Debug.Log("walk");
        }
     
        public void PrintActorInfo()
        {
            Debug.Log($"ActorInfo:{id},{hp},{name},{baseAtk},{baseDef}");
        }

        public void Test2(int v, ref int c, out int d)
        {
            d = v + c;
            c++;
        }

        public void Test2(int v, ref int c)
        {
            c += v;
        }

        public void Test(int v , Action action,ref int c,out int d,out Action<int,int> testFunc)
        {
            d = v + c;
            c++;
            action();
            testFunc = (c,d) => { Debug.Log($"{c},{d}"); };
        }
    }

    [LuaCallCSharp]
    public class Player : Actor
    {
        public float Atk;
        public float Def;
        
        public delegate void testDelegate();

        public override void callAtk()
        {
            Debug.Log("Player Atk");
        }

        public override void callWalk()
        {
            Debug.Log("Player walk");
        }
     
        public bool testPass;

        public static Player operator +(Player a, Player b)
        {
            Player player = new Player();
            player.Atk = a.Atk + b.Atk;
            return player;
        }
     
        public void TestParam(int a, params int[] p)
        {
           Debug.Log($"a:{a},param len:{p.Length}");
        }

        public void TestDefualtValue(int a, string b ,bool c,Player p)
        {
           Debug.Log($"a:{a},b:{b},c:{c},p:{p}");
        }
    }

   [LuaCallCSharp]
   public class Monster : Actor
   {
     public event Action<string> testEvent;

     public override void callAtk()
     {
         Debug.Log("Monster atk");
     }

     public override void callWalk()
     {
         Debug.Log("Monster walk");
     }

     public MonsterType getMonsterType(int value)
     {
         value = Mathf.Min(++value, 3);
         return (MonsterType)value;
     }
   }

1.调用成员方法:

调用成员方法,第一个参数需要传该对象,建议用冒号语法糖。

Lua代码:

local actorObj = CS.Test.Actor()

actorObj:callAtk()
actorObj:callWalk()

--或者使用如下方式执行成员方法
--actorObj.callAtk(actorObj)
--actorObj.callWalk(actorObj)

结果:

2.写入成员属性:

Lua代码:

local actorObj = CS.Test.Actor()

actorObj:PrintActorInfo()

actorObj.id = 1001
actorObj.hp = 100
actorObj.name = "wukong"
actorObj.baseAtk = 500.6
actorObj.baseDef = 800

actorObj:PrintActorInfo()

结果:

3.获取成员属性:

Lua代码:

local actorObj = CS.Test.Actor()

actorObj.id = 1001
actorObj.hp = 100
actorObj.name = "wukong"
actorObj.baseAtk = 500.6
actorObj.baseDef = 800

print(actorObj.id)
print(actorObj.hp)
print(actorObj.name)
print(actorObj.baseAtk)
print(actorObj.baseDef)

actorObj:PrintActorInfo()

结果:

4.父类属性,方法

Xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法。

Lua代码:

local playerObj = CS.Test.Player()

playerObj.id = 1001
playerObj.hp = 100
playerObj.name = "wukong"
playerObj.baseAtk = 500.6
playerObj.baseDef = 800

playerObj:PrintActorInfo()

结果:

5.参数的输入输出属性(out,ref)

Lua调用侧的参数处理规则:C#的普通参数和带有ref参数算输入形参,带有out的参数不算形参,然后从左往右对应lua 调用侧的实参列表;

Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)、带有ref的参数和带有out参数算返回值,然后从左往右对应lua的多返回值。

Lua代码:

local actorObj = CS.Test.Actor()
local c,d = actorObj:Test2(10,1)
print(c,d)

结果:

6.重载方法

直接通过不同的参数类型进行重载函数的访问,Xlua只一定程度上支持重载函数的调用,调用顺序是生成代码中排前面的那个最先调用,也就是同样参数数量的方法,在生成代码中最先生成的符合参数数量的函数被执行。

Lua代码:

local actorObj = CS.Test.Actor()
local c,d = actorObj:Test2(10,1)
print(c,d)

结果:

因为C#侧的两个参数的Test2的重载方法最先生成代码,所以执行的是两个参数的Test2

7.操作符

支持的操作符有:+,-,*,/,==,unary-(++,--,+,-,!,~,(T)x,await,&x *x),<,<=, %,[]

C#代码:

 public static Player operator +(Player a,Player b)
 {
     Player player = new Player();
     player.Atk = a.Atk + b.Atk;
     return player;
 }

Lua代码:

--操作符
local player1 = CS.Test.Player()
player1.Atk = 100
local player2 = CS.Test.Player()
player2.Atk = 210
print((player1+player2).Atk)

结果:

8.参数带默认值的方法

和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。

C#代码:

 public void TestDefualtValue(int a, string b ,bool c,Player p)
 {
     Debug.Log($"a:{a},b:{b},c:{c},p:{p}");
 }

Lua代码:

local p = CS.Test.Player()
p:TestDefualtValue(2,"aaaaaaaa",false)
p:TestDefualtValue(2,"aaaaaaaa")
p:TestDefualtValue(2)

结果:

9.可变参数方法

C#代码:

 public void TestParam(int a, params int[] p)
 {
     Debug.Log($"a:{a},param len:{p.Length}");
 }

Lua代码:

local p = CS.Test.Player()
p:TestParam(10,"hp",false,{"ccc",1,2})
p:TestParam(10,"hp",false)

结果:

10.使用Extension methods,泛型(模版)方法(本身不支持,使用扩展方法实现)

扩展方法:扩展方法 - C# | Microsoft Learn

C#代码:

public static class ActorExtendMethod
{
    public static void TestExtend(this Player p)
    {
        Debug.Log("这是Player的扩展方法");
    }

    public static void TestExtend(this Actor a)
    {
        Debug.Log("这是Actor的扩展方法");
    }

    public static void TestExtend(this Monster m)
    {
        Debug.Log("这是Monster的扩展方法");
    }
}

Lua代码:

local c1 = CS.Test.Actor()
local c2 = CS.Test.Player()
local c3 = CS.Test.Monster()

c1.TestExtend()
c2.TestExtend()
c3.TestExtend()

结果:

11.枚举类型

枚举类支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换

C#代码:

[LuaCallCSharp]
public enum MonsterType
{
    None,
    Normal,
    melee,
    remote
}

Lua代码:

local m = CS.Test.Monster()
print(m:getMonsterType(2))
print(CS.MonsterType.__CastFrom(1))

结果:

12.delegate使用(调用,+,-)

C#的delegate调用:和调用普通lua函数一样,+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。-操作符:和+相反,把一个delegate从调用链中移除。

C#代码:

  public delegate void testDelegate2(string content);
  public testDelegate2 test2 = (string content) =>
  {
      Debug.Log(content);
  };

Lua代码:

local p = CS.Test.Player()

p.test2 = p.test2 + testD 
p.test2("add")

p.test2 = p.test2 - testD
p.test2("remove")

结果:

注:由于使用的是多播委托,所以初始时需要给多播委托一个值,后续增加委托才能用”+“/”-“,如果直接使用”+“,

 public delegate void testDelegate2(string content);
 public testDelegate2 test2;

会报如下的错误

13.event

C#代码:

public event Action<string> testEvent;

public void testCALL(string content)
{
     testEvent?.Invoke(content);
}

Lua代码:

local function testD(content)
   print("LUA TESTD...."..content)
end

local m = CS.Test.Monster()

m:testEvent('+', testD)
m:testCALL("add");


m:testCALL("remove")
m:testEvent('-', testD)

结果:

注:

event使用 对象:事件名(+,方法),事件名(-,方法)来注册事件,所以在调用的时候不能使用对象:事件名()来执行事件,如果使用就会报下面的错,

因为使用对象:事件名()相当于参数只传了一个对象自身,"+"/"-"和方法并没有传入,得到的gen_param_count就是1,然后gen_delegate就是null,然后就会出现上边的报错。

由上,要执行事件可以再写一个函数来执行事件函数。

Lua代码中不要使用中文,

会报以下错:

14.64位整数支持

Lua53版本64位整数(long,ulong)映射到原生的64位整数,而luajit版本,相当于lua5.1的标准,本身不支持64位,xlua做了个64位支持的扩展库,C#的long和ulong都将映射到userdata,支持在lua里头进行64位的运算、比较、打印,支持和lua number的运算、比较,在64扩展库中,实际上只有int64,ulong也会先强转成long再传递到lua,而对ulong的一些运算,比较,采取和java一样的支持方式。

15.C#复杂类型和table的自动转换

对于一个有无参构造函数的C#复杂类型,在lua侧可以直接用一个table来代替,该table对应复杂类型的public字段有相应字段即可,支持函数参数传递,属性赋值等。

C#代码:

[LuaCallCSharp]
public class TestObj
{
    public void test(testStruct t)
    {
        Debug.Log($"{t.a},{t.c}");
    }
}

public struct testStruct
{
    public int a;
    public string c;
}

Lua代码:

local m = CS.TestObj()
m:test({a=10,c="ccccccc"})

结果:

16.获取类型

Lua代码:

print(typeof(CS.Test.Monster))

结果:

17.强制类型转换

lua没类型,所以不会有强类型语言的“强转”。

Lua代码:

cast(calc, typeof(CS.Tutorial.Calc))
什么时候用到?

有的时候第三方库对外暴露的是一个interface或者抽象类,实现类是隐藏的,这样我们无法对实现类进行代码生成。该实现类将会被xlua识别为未生成代码而用反射来访问,如果这个调用是很频繁的话还是很影响性能的,这时我们就可以把这个interface或者抽象类加到生成代码,然后指定用该生成代码来访问。

注意:

1.如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能。

local GameObject = CS.UnityEngine.GameObject
GameObject.Find('helloworld')

2. 像上边这种Lua调用C#中某个类中的属性、字段、方法,调用方式是CS.命名空间.类名,如果没有命名空间则就是CS.类名,要注意的是,如果这个类包含在一个类中,就需要CS.类名.类名,比如

 GameCfg包含在Test类中,如果用如下Lua代码,就会出现这个类的获取错误的问题

print(CS.GameCfg.url)
CS.GameCfg.url = "zzzzzz"
print(CS.GameCfg.url)
CS.GameCfg.CalcValue()

结果:

查看GameCfg生成的代码如下:

是通过获取Test.GameCfg的类型来注册的,所以Lua代码需要修改为:

print(CS.Test.GameCfg.url)
CS.Test.GameCfg.url = "zzzzzz"
print(CS.Test.GameCfg.url)
CS.Test.GameCfg.CalcValue()

结果:

5.HotFix:

使用xLua 的代码逻辑替换掉原有的 C# 程序逻辑, 以实现热补丁。

未完待续。。。

参考链接:

介绍 — XLua (tencent.github.io)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949417.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Matlab仿真径向受压圆盘光弹图像

Matlab仿真径向受压圆盘光弹图像-十步相移法 主要参数 % 定义圆盘参数 R 15; % 圆盘半径&#xff0c;单位&#xff1a;mm h 5; % 圆盘厚度&#xff0c;单位&#xff1a;mm P 300; % 径向受压载荷大小&#xff0c;单位&#xff…

基于Django的学校智能图书馆借书归还订阅管理系统

完整源码项目包获取→点击文章末尾名片&#xff01;

【设计模式-2】23 种设计模式的分类和功能

在软件工程领域&#xff0c;设计模式是解决常见设计问题的经典方案。1994 年&#xff0c;Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides&#xff08;四人帮&#xff0c;GoF&#xff09;在《设计模式&#xff1a;可复用面向对象软件的基础》一书中系统性地总结了…

阿里云代理商热销产品推荐

在数字化浪潮的推动下&#xff0c;企业对于云计算的依赖日益加深。阿里云&#xff0c;作为中国领先的云计算服务提供商&#xff0c;为企业提供了丰富多样的云产品和服务。本文将聚焦于阿里云代理商热销产品推荐&#xff0c;探讨其如何帮助企业高效利用云资源&#xff0c;加速数…

[redux] 异步逻辑的两种写法

createAsyncThunk | Redux Toolkit 第一种, extraReducers 普通的reducers只能写同步代码 异步必须得用中间件的形式,就是异步代码调用完有结果了, 再调用同步的reducer, 大概这么理解, 第一种怎么用呢? 先用一个异步函数 const fetchUserById createAsyncThunk(users/fet…

在Java中使用有符号类型模拟无符号整数的技巧

有符号整数和无符号整数 有符号整数&#xff1a;可以表示正数、负数和零。例如&#xff0c;Java中的 byte 类型是有符号的&#xff0c;其范围是 -128 到 127.无符号整数&#xff1a;只能表示非负数&#xff08;即零和正数&#xff09;。例如&#xff0c;无符号 byte 应该表示的…

51单片机——8*8LED点阵

LED 点阵的行则为发光二极管的阳极&#xff0c;LED 点阵的列则为发光二极管的阴极 根据 LED 发光二极管导通原理&#xff0c;当阳极为高电平&#xff0c;阴极为低电平则点亮&#xff0c;否则熄灭。 因此通过单片机P0口可控制点阵列&#xff0c;74HC595可控制点阵行 11 脚 SR…

Flutter:邀请海报,Widget转图片,保存相册

记录下&#xff0c;把页面红色区域内的内容&#xff0c;转成图片后保存到相册的功能 依赖 # 生成二维码 qr_flutter: ^4.1.0 # 保存图片 image_gallery_saver_plus: ^3.0.5view import package:demo/common/index.dart; import package:ducafe_ui_core/ducafe_ui_core.dart; i…

C++Primer const限定符

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

C语言 游动的小球

代码如下&#xff1a; 在这里插入代码片#include<stdio.h> #include<stdlib.h> #include<windows.h>int main() {int i,j;int x 5;int y 10;int height 20;int velocity_x 1;int velocity_y 1;int left 0;int right 20;int top 0;int bottom 10;while(1){…

动漫推荐系统django+vue前台后台完整源码

完整源码项目包获取→点击文章末尾名片&#xff01;

Chapter 1 Understanding Large Language Models

文章目录 Understanding Large Language ModelsWhat is an LLM?Applications of LLMSStages of building and using LLMsUsing LLMS for different tasksA closer look at the GPT architectureBuilding a large language modelSummary Understanding Large Language Models …

什么是VLAN?

VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;是一种将物理局域网划分成多个逻辑上独立的虚拟网络的技术。VLAN不依赖于设备的物理位置&#xff0c;而是通过逻辑划分&#xff0c;将局域网内的设备虚拟地组织到同一组。这种技术允许网络管理员…

【君正T31开发记录】12.编译工具相关总结及介绍

移植交叉工具包的时候&#xff0c;发现这是很多工具的集合包&#xff1b;以及写makefile的时候&#xff0c;也需要了解下这些工具的作用及用法&#xff0c;这里总结记录一下常见的工具及相关用法。 g C编译器&#xff0c;用于编译C源代码文件&#xff0c;这个很常见&#xff0…

Appium(一)--- 环境搭建

一、Android自动化环境搭建 1、JDK 必须1.8及以上(1) 安装&#xff1a;默认安装(2) 环境变量配置新建JAVA_HOME:安装路径新建CLASSPath%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar在path中增加&#xff1a;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin&#xff1b;(3) 验证…

猫的眼睛有几种颜色?

在猫咪神秘而迷人的世界里&#xff0c;它们的眼睛犹如璀璨星辰&#xff0c;闪烁着各异的光芒&#xff0c;颜色丰富多样&#xff0c;令人着迷。 猫眼睛的颜色&#xff0c;粗略一数&#xff0c;常见的便有黄色、蓝色、绿色、棕色&#xff0c;还有那神秘的异瞳。这些色彩并非无端生…

PHP框架+gatewayworker实现在线1对1聊天--接收消息(7)

文章目录 接收消息的原理接收消息JavaScript代码 接收消息的原理 接收消息&#xff0c;就是接受服务器转发的客户端消息。并不需要单独创建函数&#xff0c;因为 ws.onmessage会自动接收消息。我们需要在这个函数里进行处理。因为初始化的时候&#xff0c;已经处理的init类型的…

校园周边美食探索及分享平台的设计与实现(源码+数据库+文档)

亲测完美运行带论文&#xff1a;文末获取源码 文章目录 项目简介&#xff08;论文摘要&#xff09;运行视频包含的文件列表&#xff08;含论文&#xff09;前台运行截图后台运行截图 项目简介&#xff08;论文摘要&#xff09; &#xff1a; 美食一直是与人们日常生活息息相关…

基于深度学习的视觉检测小项目(七) 开始组态界面

开始设计和组态画面。 • 关于背景和配色 在组态画面之前&#xff0c;先要确定好画面的风格和色系。如果有前端经验和美术功底&#xff0c;可以建立自己的配色体系。像我这种工科男&#xff0c;就只能从网络上下载一些别人做好的优秀界面&#xff0c;然后在photo shop中抠取色…

wps版excel中如何快速生成倒序序号?

使用wps办公软件打开的excel文件&#xff1a; 效果如下&#xff1a; 方法&#xff1a; 如&#xff1a;想生成此列序号从101~13序号&#xff0c;倒序排列。 在第1个格子中输入开头的最小数字&#xff1a;13 点击一下【13】这个单元格&#xff0c;然后鼠标放在右下角&#xff…