Unity开发游戏使用XLua的基础

Unity使用Xlua的常用编码方式,做一下记录

1、C#调用lua

1、Lua解析器

c# 复制代码
private LuaEnv env = new LuaEnv();//保持它的唯一性
    void Start()
    {
        env.DoString("print('你好lua')");
        //env.DoString("require('Main')");  默认在resources文件夹下面
        //帮助我们清除lua中我们没有手动释放的对象  垃圾回收
        env.Tick();
        //销毁lua解释器,用的较少
        env.Dispose();
    }

2、自定义loader

c# 复制代码
    private void Start()
    {
        //xlua路径重定向方法
        _env.AddLoader(MyLoader);
        _env.DoString("require('Main')");
    }
    private byte[] MyLoader(ref string filepath)
    {
         //传入的filepath是 require执行的lua脚本文件名,在这里是main
         string path = Application.dataPath + "/Lua/" + filepath + ".lua";
            //判断文件是否存在
         if (File.Exists(path))
         {
             return File.ReadAllBytes(path);
         }
         else
         {
             Debug.Log("重定向失败,文件名为"+filepath);
         }
         return null;
    }

3、Lua管理器

c# 复制代码
/// <summary>
/// lua管理器
/// 提供lua解析器
/// 保证解析器的唯一性
/// </summary>
public class LuaMgr
{
    private static LuaMgr _instance;
    public static LuaMgr Instance
    {
        get
        {
            if (_instance==null)
            {
                _instance = new LuaMgr();
            }

            return _instance;
        }
    }


    private LuaEnv _luaEnv;
    /// <summary>
    /// 得到lua中的_G
    /// </summary>
    public LuaTable Gloabl => _luaEnv.Global;


    /// <summary>
    /// 初始化
    /// </summary>
    public void Init()
    {
        if (_luaEnv!=null) return;
        _luaEnv = new LuaEnv();
        _luaEnv.AddLoader(MyLoader);
        _luaEnv.AddLoader(MyABLoader);
    }

    #region 标准内容
    /// <summary>
    /// 传入文件名,执行脚本
    /// </summary>
    /// <param name="fileName"></param>
    public void DoLuaFile(string fileName)
    {
        string str = $"require('{fileName}')";
        DoString(str);
    }
    public void DoString(string str)
    {
        if(_luaEnv==null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        _luaEnv.DoString(str);
    }
    /// <summary>
    /// 释放lua垃圾
    /// </summary>
    public void Tick()
    {
        if(_luaEnv==null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        _luaEnv.Tick();
    }
    /// <summary>
    /// 销毁解析器
    /// </summary>
    public void Dispose()
    {
        if(_luaEnv==null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        _luaEnv.Dispose();
        _luaEnv = null;
    }
    //自动执行
    private byte[] MyLoader(ref string filepath)
    {
        //传入的filepath是 require执行的lua脚本文件名,在这里是main
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
        //判断文件是否存在
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("重定向失败,文件名为"+filepath);
        }
        return null;
    }
    //重定向加载AB包中的lua脚本
    private byte[] MyABLoader(ref string filepath)
    {
        //加载路径
        string path = Application.streamingAssetsPath + "/lua";
        //加载AB包
        AssetBundle ab=AssetBundle.LoadFromFile(path); 
        TextAsset tx = ab.LoadAsset<TextAsset>(filepath+".lua");//加载Lua文件返回
        //加载lua文件,byte数组
        return tx.bytes;
    }
    
    
    //Lua脚本最终会放在ab包中,
    #endregion
}

4、全局变量获取

c3 复制代码
 private void Start()
 {
        LuaMgr.Instance.Init();

        LuaMgr.Instance.DoLuaFile("Main");
        
        int i= LuaMgr.Instance.Gloabl.Get<int>("testNumber");
        var i2= LuaMgr.Instance.Gloabl.Get<bool>("testBool");
        var i3= LuaMgr.Instance.Gloabl.Get<float>("testFloat");
        var i4= LuaMgr.Instance.Gloabl.Get<string>("testString");
  		//值拷贝不会修改lua中的值,可以用set
  
  
        LuaMgr.Instance.Gloabl.Set("testNumber",1000);
        i= LuaMgr.Instance.Gloabl.Get<int>("testNumber");
        Debug.Log("testNumber"+i);
 }
   

虽然lua中只有number一种数值类型,但是我们可以根据它具体的值,用对应的C#变量类型来存储

5、全局函数的获取

c# 复制代码
public delegate void CustomCall();
public delegate void CustomCall2(int a);
//加一个特性,只有添加特性才能被调用,,加完特性后还要一部分工作,就是点击Xlua->Generatedate
[CSharpCallLua]
public delegate int CustomCall3(int a);
[CSharpCallLua]   //使用out来接
public delegate int CustomCall4(int a,out int b,out bool c,out string d,out int e);

[CSharpCallLua]   //使用ref来接
public delegate int CustomCall5(int a,ref int b,ref bool c,ref string d,ref int e);

//变长函数,如果边长函数中类型多种多样,那就使用object
[CSharpCallLua]
public delegate void CustomCall6(string a,params int[] args);
public class Lesson5_CallFunction : MonoBehaviour
{ 
    void Start()
    {
        LuaMgr.Instance.Init();
        // LuaMgr.Instance.DoString("require('Main')"); 
        LuaMgr.Instance.DoLuaFile("Main");
        //无参无返回值
        var call1= LuaMgr.Instance.Gloabl.Get<UnityAction>("testFun1");
        call1();
        //有参有返回值
        var call2 = LuaMgr.Instance.Gloabl.Get<CustomCall3>("testFun2");
       // Debug.Log(call2(2));
        
        var call3 = LuaMgr.Instance.Gloabl.Get<Func<int,int>>("testFun2");
       // Debug.Log("Func: "+call3(2));
        //有参多返回值
        var call4 = LuaMgr.Instance.Gloabl.Get<CustomCall4>("testFun3");
        int b;
        bool c;
        string d;
        int e;
      //  Debug.Log(call4(51,out b,out c,out d,out e));
        
        var call5 = LuaMgr.Instance.Gloabl.Get<CustomCall5>("testFun3");
        int b1 = 0;
        bool c1 = true;
        string d1="";
        int e1=0;
       // Debug.Log(call5(51,ref b1,ref c1,ref d1,ref e1));
   

        var call6 = LuaMgr.Instance.Gloabl.Get<CustomCall6>("testFun4");
        call6("你好",1,5,6,5,4);

        // LuaFunction lf = LuaMgr.Instance.Gloabl.Get<LuaFunction>("testFun4");
        // lf.Call("你好", 1, 5, 6, 5, 4);
    }
lua 复制代码
print("Variable启动")
-- 无参无返回
testFun1 = function()
    print("无参无返回")
end

-- 有参有返回
testFun2 = function(a)
    print("有参有返回")
    return a + 1
end
-- 多返回
testFun3 = function(a)
    print("多返回")
    return 1, 2, false, "123", a
end
-- 变长参数
testFun4 = function(a, ...)
    print("变长参数")
    print(a)
    arg = { ... }
    for k, v in pairs(arg) do
        print(k, v)
    end
end

6、List和Dictionary

c# 复制代码
 private void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");
        #region List
        List<int> list = LuaMgr.Instance.Gloabl.Get<List<int>>("testList");
        for (int i = 0; i < list.Count; i++)
        {
            Debug.Log("----"+list[i]);
        }
        //不确定类型用object
        List<object> list2 = LuaMgr.Instance.Gloabl.Get<List<object>>("testList2");
        for (int i = 0; i < list2.Count; i++)
        {
            Debug.Log("----"+list2[i]);
        }
        #endregion
        #region 字典
        Dictionary<string, int> dic = LuaMgr.Instance.Gloabl.Get<Dictionary<string, int>>("testDic");
        foreach (var item in dic)
        {
            Debug.Log(item.Key+"____"+item.Value);
        }
        Dictionary<object, object> dic2 = LuaMgr.Instance.Gloabl.Get<Dictionary<object, object>>("testDic2");
        foreach (var item in dic2)
        {
            Debug.Log(item.Key+"____"+item.Value);
        }
        #endregion
    }

7、类映射table

c# 复制代码
//Lua
testClas={
    testInt=2,
    testBool=true,
    testFloat=1.2,
    testString="123",
    testFun=function()
        print("NIHAO")
    end
}
//C#
public class CallLuaClass
{
    //在类中去生命成员变量,名字要和lua那边一样
    //注意,。一定是公有的,不然不能赋值。
    //这个自定义中的变量可以多也可以少
    public int testInt;
    public bool testBool;
    public float testFloat;
    public string testString;
    public UnityAction testFun;//方法
}
public class Lesson7_Table : MonoBehaviour
{
    private void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");
        CallLuaClass callLuaClass = LuaMgr.Instance.Gloabl.Get<CallLuaClass>("testClas");
        Debug.Log(callLuaClass.testString);
        callLuaClass.testFun();
    }
}

8、接口映射table

注意接口是引用拷贝。改了值后,lua值也会改变

c# 复制代码
//接口中不允许有成员变量
//我们用属性
[CSharpCallLua]
public interface ICSharpCallInterface
{
    int testInt { get; set; }
    bool testBool { get; set; }
    float testFloat { get; set; }
    string testString { get; set; }
    UnityAction testFun { get; set; }
}

public class Lesson_Interface : MonoBehaviour
{
    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");
        ICSharpCallInterface callInterface = LuaMgr.Instance.Gloabl.Get<ICSharpCallInterface>("testClas");
        Debug.Log(callInterface.testFloat);
    }   
}

9、luatable映射到table

C# 复制代码
    //不过官方不建议使用luaTable和luaFunction
    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");
        LuaTable table = LuaMgr.Instance.Gloabl.Get<LuaTable>("testClas");
        Debug.Log(table.Get<int>("testInt"));
        table.Get<LuaFunction>("testFun").Call();
        table.Dispose();//使用完了dispose 
    }

2、lua调用C#(重点)

用的较多。

1、Lua使用C#类

Lua 复制代码
print("************Lua调用C#相关知识点*************")
-- lua中使用C#的类非常简单
-- 固定套路
-- Cs.命名空间.类名
-- Unity的类,比如GameObject Transform等等  ---CS.UnityEngine.类名

-- 通过C#中的类,实例化一个对象,注意lua中没有new
-- 默认调用的相当于无参构造
local obj1 = CS.UnityEngine.GameObject()
local obj2 = CS.UnityEngine.GameObject("郑毅")

-- 为了方便使用,并且节约性能,定义全局变量储存C#中的类
-- 相当于取了一个别名,这也是一种优化方式
GameObject = CS.UnityEngine.GameObject
local obj3 = GameObject("第三个")

-- 类中的静态对象可以直接使用 .来调用
local obj4 = GameObject.Find("郑毅")
--得到对象中的成员变量,直接对象.就可以了
print(obj4.transform.position)
Vector3 = CS.UnityEngine.Vector3;
--使用对象中的成员方法,一定要加:
obj4.transform:Translate(Vector3.right)
print(obj4.transform.position)

-- 调用自定义的类
local t = CS.Test()
t:Speak("你好")
--有命名空间的
local t = CS.MyClass.Test2()
t:Speak("你好")

--继承mono的类
--继承mono的类是不能直接new的
--lua不支持使用无惨的泛型的
local obj5 = GameObject("加脚本测试")
-- Xlua提供了一个重要防范typeof 可以得到类的类型
obj5:AddComponent(typeof(CS.LuaCallC))

---- c#

C# 复制代码
public class Test
{
    public void Speak(string str)
    {
        Debug.Log("test1"+str);
    }
}

namespace MyClass
{
    public class Test2
    {
        public void Speak(string str)
        {
            Debug.Log("test2"+str);
        }
    }
}
public class LuaCallC : MonoBehaviour
{
   
}

2、枚举

Lua 复制代码
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)

---获取自定义美剧
myEnum=CS.MyEnum
local c=myEnum.Idle
print(c)
--枚举转换
--数值转枚举
local a=myEnum.__CastFrom(1)
print(a)
--字符串转枚举
local b=myEnum.__CastFrom("Atk ")
print(b)

3、调用C#数组和list和字典

c# 复制代码
public class Lesson
{
    public int[] array = new int[5] { 1, 2, 3, 4, 5 };
    public List<int> list = new List<int>();
    public Dictionary<int, string> dic = new Dictionary<int, string>();
}
lua 复制代码
local obj = CS.Lesson()
-- Lua使用数组相关知识
-- 长度
print(obj.array.Length)
print(obj.array[2])
--遍历,虽然lua索引是从1开始
--但是要按照C#来,从0开始
for i = 0, obj.array.Length - 1 do
    print(obj.array[i])
end

--创建数组   数组的底层是array类,使用静态方法CreateInstance
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)
print(array2.Length)

---list
obj.list:Add(10)
obj.list:Add(20)
obj.list:Add(30)
obj.list:Add(40)

for i = 0, obj.list.Count - 1 do
    print(obj.list[i])
end
--创建list  相当于得到一个List<int>的一个类的别名,需要再实例化
local list_string = CS.System.Collections.Generic.List(CS.System.Int32)
local list3 = list_string()
list3:Add(50)
print(list3.Count)
print(list3[0])

--- 字典
obj.dic:Add(1, "你好")
print(obj.dic[1])

for k, v in pairs(obj.dic) do
    print(k, v)
end

--创建字典
local dicString=CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2=dicString()
dic2:Add("001",CS.UnityEngine.Vector3.right)

print(dic2["001"])---这里有个坑,自己创建的字典,这样是访问不到的
print(dic2:get_Item("001")) --只有通过这种方法才可以
dic2:set_Item("001",CS.UnityEngine.Vector3.up)
print(dic2:get_Item("001")) --只有通过这种方法才可以
print(dic2:TryGetValue("001"))--多返回值

4、拓展方法

c# 复制代码
//拓展方法
//拓展方法的写法
//想要在lua中使用拓展方法,一定要在工具类前面加上特性
//如果不加该特性,除了拓展方法以外,都不会报错
//但是lua是通过反射机制去调用C#类的,效率较低
//加上可提高性能
[LuaCallCSharp]
public static class Tools
{
    public static void Move(this Lesson4 obj)
    {
        Debug.Log(obj.name+"移动");
    }
}
public class Lesson4
{
    public string name = "名称";

    public void Speak(string str)
    {
        Debug.Log(str);
    }

    public static void Eat()
    {
        Debug.Log("吃东西");
    }
}
public class LuaCallC : MonoBehaviour
{
    private void Start()
    {
        Lesson4 lesson4 = new Lesson4();
        lesson4.Move();//拓展方法,对象可以直接调用
    }
}
lua 复制代码
Lesson4 = CS.Lesson4
Lesson4.Eat()
--成员方法
local a = Lesson4()
a:Speak("说话了啊")
--使用拓展方法
a:Move()

5、ref out

c# 复制代码
public class Lesson5
{
    public int RefFun(int a,ref int b,ref int c,int d)
    {
        b = a + d;
        c = a - d;
        return 100;
    }

    public int OutFun(int a,out int b,out int c,int d)
    {
        b = a;
        c = d;
        return 200;
    }

    public int RefOutFun(int a,out int b,ref int c)
    {
        b = a * 10;
        c = a * 20;
        return 300;
    }
}
lua 复制代码
Lesson5 = CS.Lesson5
local obj = Lesson5()

--ref 参数会以多返回值的形式返回给lua
local a, b, c = obj:RefFun(1, 0, 0, 1)
print(a)
print(b)
print(c)

--out 参数会以多返回值的形式返回给lua
--out参数不需要传占位置的值
local a, b, c = obj:OutFun(1, 30)
print(a)
print(b)
print(c)

--综合起来
-- out不用占位值 ref需要占位
local a, b, c = obj:OutFun(1, 30)
print(a)
print(b)
print(c)

6、C#函数重载

C# 复制代码
public class Lesson6
{
    public int Cal()
    {
        return 100;
        
    }
    public int Cal(int a,int b)
    {
        return a + b;
    }

    public int Cal(int a)
    {
        return a;
    }

    public float Cal(float a)
    {
        return a;
    }
}
lua 复制代码
Lesson6 = CS.Lesson6
local obj = Lesson6()

print(obj:Cal())
print(obj:Cal(1))
print(obj:Cal(15,20))
--对于C#中多精度的重载函数支持不好
print(obj:Cal(1.25))

--如果要解决重载函数模糊不清的问题
--xlua提供了解决方案,反射机制,但这种方法只做了解
--另外,能不用就不用
local m2 = typeof(CS.Lesson6):GetMethod("Cal", { typeof(CS.System.Single) })
--通过xlua提供的一个方法,把它转换成lua函数来使用。
--一般我们转一次,然后重复使用
local f2 = xlua.tofunction(m2)
print(f2(obj,10.2))

7、委托 事件

c3 复制代码
public class Lesson8
{
    public UnityAction del;
    public event UnityAction eventAction;

    public void DoEvent()
    {
        if (eventAction != null) eventAction();
    }
}
lua 复制代码
Lesson8 = CS.Lesson8
local obj = Lesson8()

-- 委托
local func=function()
    print("这是lua函数")
end
-- 注意lua没有复合运算符,没有+=
-- 如果第一次往委托中加函数,因为是niu 不能直接+
-- 所以第一次  要先等=
obj.del=func
--第二次可以
obj.del=obj.del+func
obj.del()
--委托清除
obj.del=nil

---事件,事件和委托特别不一样
---有点类似于成员方法 
obj:eventAction("+",func)
obj:DoEvent()
--事件不能直接设置为空

8、二维数组

使用GetValue获取

c3 复制代码
[LuaCallCSharp]
public class Book : MonoBehaviour
{
    public int[,] INTArray;
    void Awake()
    {
        INTArray = new int[3, 3]
        {
            {1,2,3},{2,4,5},{6,7,8}
        };
    }

    public int GetArrayValue(int x,int y)
    {
        
        return INTArray[x, y];
    }

    public void SetArrayValue(int x,int y,int value)
    {
        INTArray[x, y] = value;
    }
}

local book = CS.Book
local go = GameObject("二维数组")


local obj = go:AddComponent(typeof(book))

-- 调用 GetArrayValue 方法
local c = obj:GetArrayValue(1, 1)
print(c)

9、nil

lua 复制代码
PrimitiveType=CS.UnityEngine.PrimitiveType
Rigidbody=CS.UnityEngine.Rigidbody
local GameObject = CS.UnityEngine.GameObject;
local obj = GameObject("Cube")
local obj2 = GameObject.CreatePrimitive(PrimitiveType.Cube)
local rig = obj2:GetComponent(typeof(Rigidbody))
-- nil和Null没法进行 == 比较
if rig == nil then
    print("空啊")
end
print("不为空吗")

--可用这样
if rig:Equals(nil) then
    obj2:AddComponent(typeof(Rigidbody))
end
-- 全局函数
print(IsNull(rig))
-- 在C#中
print(rig:Equ())
lua 复制代码
-- 判断全局函数
function IsNull(obj)
    if obj==nil or obj:Equals(nil) then
        return true
    end
    return false
end

10、C# 扩展方法

C# 复制代码
[LuaCallCSharp]
public static class ObjTest
{
    public static bool Equ(this Object obj)
    {
        return obj==null;
    }
}

11、特殊问题

CSharpCallLua :委托、接口

LuaCallCSharp:拓展方法时、建议每个类都加

无法为系统类或者第三方库代码加上这两个特性

Lua 复制代码
Obj = CS.UnityEngine.GameObject
Ui = CS.UnityEngine.UI
local sid=Obj.Find("Slider")
local sliders=sid:GetComponent(typeof(Ui.Slider))
sliders.onValueChanged:AddListener(function(f)
    print("你好")
end)

---------------------------------------------------------------
public static class Lesson10
{
    [CSharpCallLua]
    public static List<Type> cSharpCallLua = new List<Type>()
    {
        typeof(UnityAction<float>)
    };
    [LuaCallCSharp]
    public static List<Type> luaCallCSharp=new List<Type>(){
        typeof(GameObject)
    };
}
----------------------------------------------------------------
lua 复制代码
local ani=Obj.Find("Btn")
local btn=ani:GetComponent(typeof(Ui.Button))
btn.onClick:AddListener(function()
    print("鼠标点击了")
end)

---还可以这样
local c=CS.UnityEngine.Events.UnityAction(function()
    print("鼠标点击了")
end)

12、协程

lua 复制代码
--xlua提供的一个工具表,一定要通过require调用后才能用
local util=require('xlua.util')
GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds
--在场景中创建一个空物体,然后添加一个脚本
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallC))
--希望用来被开启的协程函数
fun = function()
    local a = 1
    while true do
        coroutine.yield(WaitForSeconds(1))
        print(a)
        a = a + 1
        if a>10 then
            --关闭协程
            mono:StopCoroutine(b)
        end
    end
end
--我们不能直接将lua函数传入到开启协程中
--如果要把lua函数当做协程函数传入
--必须先调用xlua.util 中的cs_generator(fun)
b=mono:StartCoroutine(util.cs_generator(fun))

13、泛型

支持有约束有参数的泛型函数

不支持没有约束的泛型番薯

不支持有约束,但是没有参数的泛型函数

不支持非Class的约束

C# 复制代码
public class Lesson12
{
    public class TestFather
    {
        
    }
    public class TestChild:TestFather
    {
        
    }
    public void TestFun1<T>(T a,T b) where T:TestFather
    {
        Debug.Log("有参数有约束的泛型方法");
    }
    public void TestFun2<T>(T a,T b) 
    {
        Debug.Log("有参数无约束的泛型方法");
    }
    public void TestFun3<T>(T a) 
    {
        Debug.Log("有参数");
    }
    public void TestFun3<T>() where T:TestFather
    {
        Debug.Log("有参数");
    }  
    
}
lua{​ 复制代码
local obj=CS.Lesson12()
local child=CS.Lesson12.TestChild()
local father=CS.Lesson12.TestFather()

------------默认情况下支持----------
--支持有约束有参数的泛型函数
obj:TestFun1(child,father)
----------------------------------

-注意 如果是mono 这种方式支持使用
--但是如果是iLcpp打包 泛型参数是引用类型才可以

--要使得可用用
--设置泛型类型再使用
local testFun2=xlua.get_generic_method(CS.Lesson12,"TestFun3")
local testFun2_R=testFun2(CS.System.Int32)
--调用
testFun2_R(obj,10)

--设置泛型类型再使用
local testFun3=xlua.get_generic_method(CS.Lesson12,"TestFun2")
local testFun3_R=testFun3(CS.System.Int32)
--调用
testFun3_R(obj,10,20)
相关推荐
AllBlue1 小时前
unity调用安卓方法
android·unity·游戏引擎
郝学胜-神的一滴1 小时前
Horse3D游戏引擎研发笔记(十):在QtOpenGL环境下,视图矩阵与投影矩阵(摄像机)带你正式进入三维世界
c++·3d·unity·游戏引擎·godot·图形渲染·unreal engine
geekmice2 小时前
thymeleaf处理参数传递问题
开发语言·lua
wanhengidc4 小时前
云手机 多样化的云服务产品
运维·服务器·科技·游戏·智能手机
AllBlue4 小时前
unity导出成安卓工程,集成到安卓显示
android·unity·游戏引擎
春卷同学5 小时前
Electron for鸿蒙PC开发的骰子游戏应用
游戏·electron·harmonyos
春卷同学5 小时前
Electron for 鸿蒙pc开发的二十一点游戏
游戏·electron·harmonyos
Sator17 小时前
Unity的FishNet相关知识
网络·unity·游戏引擎
AllBlue7 小时前
安卓调用unity中的方法
android·unity·游戏引擎
春卷同学7 小时前
基于 Electron 开发的跨平台鸿蒙PC端数字猜谜游戏桌面应用
游戏·electron·harmonyos