Unity、C#、Lua面试题整理二、(持续更新)

二十四:C#,GC的原理

答:     1.被分配内存空间的对象最有可能被释放。在方法执行时,就需要为该方法的对象分配内存空间,搜索最近分配的对象集合有助于花费最少的代价来尽可能多地释放内存空间。
2.生命期最长的对象释放的可能性最小,经过几轮垃圾回收后,对象仍然存在,搜索它时就需要进行大量的工作,却只能释放很小的一部分空间。
3.同时被分配内存的对象通常是同时使用,将它们彼此相连有助于提高缓存性能和回收效率。
C#中的回收器是分代的垃圾回收器(Gererational GarbageCollector) 它将分配的对象分为3个类别或代。(可用GC.GetGeneration方法返回任意作为参数的对象当前所处的代)最近被分配内存的对象被放置于第0代,因为第0代很小,小到足以放进处理器的二级(L2)缓存,所以它能够提供对对象的快速存取。经过一轮垃圾回收后,仍然保留在第0代中的对象被移进第1代中,再经过一轮垃圾内存回收后,仍然保留在第1代中的对象则被移进第2代中,第2代中包含了生存期较长的对象。
      在C#中值类型是在堆栈中分配内存,它们有自身的生命周期,所以不用对它们进行管理,会自动分配和释放。而引用类型是在堆中分配内存的。所以它的分配和释放就需要像回收机制来管理。C#为一个对象分配内存时,托管堆可以立即返回新对象所需的内存,因为托管堆类似于简单的字节数组,有一个指向第一个可用内存空间的指针,指针像游标一样向后移动,一段段内存就分配给了正在运行的程序的对象。在不需要太多垃圾回收的程序小,托管堆性能优于传统的堆。
      当第0代中没有可以分配的有效内存时,就触发了第0代中的一轮垃圾回收,它将删除那些不再被引用的对象,并将当前正在使用的对象移至第1代。而当第0代垃圾回收后依然不能请求到充足的内存时,就启动第1代垃圾回收。如果对各代都进行了垃圾回收后仍没有可用的内存就会引发一个OutOfMemoryException异常。

      在垃圾回收时尽量避免使用finallize来回收资源,这样会造成两车垃圾回收,影响效率

      垃圾回收器使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象。每次您的应用程序创建具有 Finalize 方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。托管堆中所有需要在垃圾回收器回收其内存之前调用它们的终止代码的对象都在终止队列中含有项。(实现 Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用 Finalize 方法,然后,将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。)

二十五:FSM(状态机具体怎么实现):

答: 初始化各状态机的名称或ID(一般使用枚举),设定每种状态人物对应的行为(编写相应的脚本),状态切换(切换条件,切换回调)

二十六:Lua如何与C#进行交互的

答: Lua调用C#:

  1.打标签[LuaCallC#]:首先生成C#源文件所对应的Wrap文件,由Lua文件调用Wrap文件,再由Wrap文件调用C#文件;
   
  2.不打标签:通过反射方式当索引系统API、dll库或者第三方库时,如果无法将代码的具体实现进行代码生成,可采用此方式实现交互。缺点:执行效率低。

C#调用Lua:打标签[C#CallLua]:由C#先将数据放入栈中,由lua去栈中获取数据,然后返回数据对应的值到栈顶,再由栈顶返回至C#

C#生成Bridge文件,Bridge调dll文件(dll是用C写的库),先调用lua中dll文件,由dll文件执行lua代码
C#->Bridge->dll->Lua  OR   C#->dll->Lua

二十七:Lua如何在项目中使用:

答:通过一个C#脚本调用 Lua入口文件(Main.lua),加载全局模块,比如UI模块,事件模块,table工具类模块,class类模块,单例管理类模块等等,部分模块用C#实现,Lua调用C#

二十八:Lua实现单例模式:

Singleton={}
function Singleton:new(o)
    o=o or {}
    setmetatable(o,self)
    self.__index=self
    return o
end

function Singleton:Instance()
    if self.instance == nil then
    self.instance = self:new()
    end
    return self.instance
end

--[[
    优点:确保所有对象都访问唯一实例
    缺点:每次对象请求引用时都要检查是否存在类的实例。  必须记住自己不能使用new关键字实例化对象。
--]]
	

二十九:内存是如何优化的:

答:1.压缩自带类库;

2.将暂时不用的以后还需要使用的物体影藏起来,不要直接Destroy掉,频繁调用销毁的物体使用对象池

3.释放AssetBundle占用的资源

4.降低模型的片面数,降低模型的骨骼数量,降低贴图大小,尽量使用图集

5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设体(Prefabs)

6.尽量减少unity的回调函数,即使空着的Update,Fixupdate函数也不要留着

三十:SDK接入怎么接入的?Plugins目录的作用:

答: 新建一个Android library工程,按照平台给的SDK文档配置AndroidManifest.xml,然后添加SDK的aar或者jar库到libs目录下,不要忘了去Unity安装目录找到classes.jar,放入libs目录下,然后配置gradle。

 接着就开始写SDK代码。写完之后直接生成aar库,直接导入到Unity就可以用了。

Plugins目录:Plugins里存放的SDK,库文件能够优先被编译

Windows     .dll 动态链接库

Linux Mac Android IOS   .so 共享函数库

三十一:地形优化插件

答:T4M插件

三十二:对象池的好处是啥?真的会减少内存占用嘛

答:优缺点:空间换时间,用额外的内存消耗减少创建物体的时间,保证运行的流畅度

三十三:C#索引器的实现

答:索引器允许类和结构的实例就像数组一样,直观的引用类中的成员。一索引器可以重载,一个类可以有多个索引器,索引的参数可以是任何类型,而不像整数参数类型只能是整数

 

private string[] name = new string[10];

//定义索引器,必须以this关键字定义,设置name字段的索引值为0,address字段的索引值为

public string this[int index]{

get{return name[index];}

set{this.name[index] = value;}

}

三十四:相机移动应该放在哪个函数里:

答:LateUpdate,是在所有的Update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的 跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。   

三十五:矩阵相乘的意义及注意点

答:用于表示线性的变换:旋转,缩放,投影,平移,仿射

注意矩阵的蠕变:误差的积累

 

 

 

 

 

### 关于 UnityLua面试题整理 #### 一、基础概念类问题 1. **Unity 中支持哪些脚本语言?** Unity 支持 C# 脚本作为主要开发语言,同时也允许通过插件扩展其他脚本语言的支持,比如 Lua。由于 Unity 使用 Mono .NET 平台运行脚本,因此可以通过绑定的方式让 LuaC# 进行交互[^1]。 2. **LuaUnity 开发中的作用是什么?** Lua 是一种轻量级嵌入式脚本语言,在 Unity 游戏开发中通常用于逻辑控制、配置管理以及动态加载资源等功能。它能够显著降低项目复杂度并提高灵活性[^2]。 3. **如何在 Unity 中集成 Lua?** 可以借助第三方工具包(如 ToLua 或 SLua),这些工具会自动生成 Wrapper 文件来桥接 LuaC#之间的调用关系。另一种方法则是利用反射机制完成无标记式的 API 访问,不过这种方式性能较差。 #### 、技术实现细节 4. **解释一下[LuaCallC#]的工作原理及其优劣势。** 当我们给某个功能打上[LuaCallC#]标签时,意味着该部分代码会被转换成特定格式的 Wrap 文件供 Lua 解析器读取并执行相应操作。这种方法的优点在于编译期即可确定接口定义从而提升运行速度;然而其缺点也很明显——增加了额外构建流程并且维护成本较高。 5. **如果不使用[LuaCallC#]标签,则应采取何种替代方案?** 对于未被打标的函数或类成员访问需求,可以考虑运用.NET框架下的System.Reflection命名空间所提供的能力来进行动态类型实例化与方法调用处理。尽管如此做可能会带来一定的性能开销,但在某些特殊场景下不失为一种可行的选择。 6. **描述一个完整的从编写Lua脚本到实际影响游戏对象的过程。** 假设我们需要创建一个新的敌人单位: - 首先在编辑器内拖拽预制体至Scene视图; - 接着为其附加行为组件(假设名称叫EnemyBehavior),并将其实现改为继承自MonoBehaviour的基础结构; - 然后切换到外部文本编辑软件撰写一段简单的lua脚本来改变这个敌人的移动方向或者攻击模式等等属性设置; - 最终回到unity工程目录找到对应路径保存上述更改后的lua文件,并确保已经正确设置了自动刷新检测选项以便即时反映改动效果^。 7. ```csharp public class EnemyBehavior : MonoBehaviour { private ILuaScript luaInstance; void Start() { string scriptPath = Application.dataPath + "/Scripts/enemy.lua"; luaInstance = new LuaInterpreter().DoFile(scriptPath); Vector3 initialPosition = transform.position; float speedFactor = (float)luaInstance.CallFunction("getSpeed", null)[0]; StartCoroutine(MoveOverTime(initialPosition, speedFactor)); } IEnumerator MoveOverTime(Vector3 startPosition, float duration){ while(true){ yield return new WaitForSeconds(duration / 10f); transform.Translate(new Vector3((float)(luaInstance.CallFunction("getXVelocity",null)[0]), (float)(luaInstance.CallFunction("getYVelocity",null)[0]), Time.deltaTime * Random.Range(-duration,duration))); } } } ``` 8. ```lua -- enemy.lua contents below: function getSpeed() return math.random()*5+1 -- returns a value between ~[1..6] end function getXVelocity() local randXDir = love.math.randomNormal()*.2-.1; -- some pseudo-random generator logic here... return randXDir*love.timer.getDelta(); end function getYVelocity() return -(math.abs(math.sin(os.clock()))+.3)*love.graphics.getHeight()/90; end ``` #### 三、高级应用案例分析 9. **讨论多线程环境下Lua-C#互操作需要注意的地方有哪些?** 因为两者本质上属于不同进程模型范畴内的事物所以必然存在同步异步协调方面的问题待解决。具体来说就是必须小心避免竞态条件(race condition),死锁(deadlock)等情况发生的同时还要兼顾整体程序架构设计上的清晰简洁程度. 10. **列举几个常见的错误实践例子并给出改正建议。** 错误示范之一可能是频繁地来回跨越边界层去做简单数值运算之类的琐碎事务而不是集中精力放在高层业务抽象层面之上。针对这种情况应该尽量减少不必要的跨平台通信次数并通过预计算等方式优化性能表现.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值