面筋
春招
- 首先问一下学习unity 多久了
- C++ 在学校是怎么学习的 – oop 没有C11
- 那就问一个C#问题 List底层是什么
- 无序数组怎么增加增删的效率 –增:在最后增加 删:swap最后然后在最后删 (堆排序?)
- 聊天:lua现在好像淘汰 C# 技术栈就够 huatuo
- 场景题: 水果忍者 怎么随机生成水果和角度
- 场景题 : 水果忍者 怎么切割
- 首先问一下学习unity 多久了
回答:大一下暑假断断续续,也是一年多
介绍一下杀戮尖塔项目
热更做了吗(没有)那做了什么
回答: 做了一个lua xlua的背包系统 写了个ABmanager 然后写Panel 后面补充了BasePanel的框架
- 有使用过unity的性能工具吗?有遇到性能问题吗
没有,但是大概知道理论
- 在CPU 和 GPU 方面的优化 大概了解吗
CPU : 代码层面:框架,缓存池 批处理:静态批处理 空间换时间
GPU : 太卡没回答
- 说一下你关于状态机的使用(后面有说公司项目有多用状态机来实现框架)
- unity 的进程有多少个
- unity 生命周期
- 改变timeScale 不会改变生命周期的哪个函数
回答:update,就说每一帧更新就不会更改(我也不会)
- 自我介绍
- IOC容器的理解
- IOC和其他方式的区别以及项目中好处
- 洗牌算法
- 说一下单例模式
- 介绍一下我的项目wordJump
- 为什么用射线检测来实现爬墙跳,为什么不使用其他的
- 请介绍一下反射
- 为什么使用lua热更不使用C#
- 排序算法有哪些
- 请介绍一下快排
- 如果使用unity和py通信应该怎么使用通信协议
- 学过shader吗 介绍一下
- 学过计算机图形学吗,三维坐标怎么转为屏幕坐标
说是无人机的虚拟仿真,unity做一个页面端
1. 自我介绍
自我介绍是面试的第一步,通常包括你的姓名、教育背景、工作经验、技能特长以及为什么对这个职位感兴趣。简洁明了地展示你的优势和与职位的匹配度。
2. IOC容器的理解
- IOC容器:IOC(Inversion of Control,控制反转)容器是一种设计模式,用于管理对象的生命周期和依赖关系。它将对象的创建和依赖注入交给容器来处理,而不是由对象自己管理。
3. IOC和其他方式的区别以及项目中好处
- 区别:
- 传统方式:对象自己管理依赖关系,通常通过new关键字直接创建依赖对象。
- IOC方式:依赖关系由容器管理,对象通过构造函数、属性或方法注入依赖。
- 好处:
- 解耦:降低类之间的耦合度,提高代码的可维护性和可测试性。
- 灵活性:可以轻松替换依赖的实现,便于扩展和修改。
- 集中管理:统一管理对象的生命周期和依赖关系,减少重复代码。
4. 洗牌算法
- 洗牌算法:常用的洗牌算法是Fisher-Yates算法,通过随机交换数组中的元素来实现洗牌。具体步骤如下:
- 从最后一个元素开始,向前遍历数组。
- 对于每个元素,随机选择一个前面的元素(包括自己)进行交换。
- 重复上述步骤,直到遍历完所有元素。
5. 说一下单例模式
- 单例模式:单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。通常用于管理共享资源,如配置管理、日志记录等。
- 实现方式:
- 私有化构造函数,防止外部实例化。
- 提供一个静态方法或属性来获取唯一实例。
- 使用懒加载或双重检查锁定来确保线程安全。
6. 介绍一下我的项目wordJump
- wordJump:这是一个基于Unity的游戏项目,玩家通过跳跃单词来完成关卡。项目中使用射线检测来实现爬墙跳功能,确保玩家在接触到墙壁时可以执行跳跃动作。
7. 为什么用射线检测来实现爬墙跳,为什么不使用其他的
- 射线检测:射线检测是一种高效的方式,用于检测物体之间的碰撞。在爬墙跳功能中,射线检测可以快速判断玩家是否接触到墙壁,从而触发跳跃动作。
- 其他方式:如触发器检测或碰撞检测,可能会增加计算复杂度,尤其是在复杂场景中。射线检测简单直接,适合这种特定需求。
8. 请介绍一下反射
- 反射:反射是一种机制,允许程序在运行时获取类型信息并操作对象。通过反射,可以动态创建对象、调用方法、访问属性等。
- 应用场景:
- 动态加载程序集。
- 实现插件系统。
- 序列化和反序列化。
9. 为什么使用lua热更不使用C#
- Lua热更:Lua是一种脚本语言,可以在不重新编译和发布应用程序的情况下,动态更新代码。这使得Lua非常适合用于热更新。
- **C#**:C#是编译型语言,更新代码需要重新编译和发布应用程序,不适合热更新。
10. 排序算法有哪些
- 常见排序算法:
- 冒泡排序(Bubble Sort)
- 选择排序(Selection Sort)
- 插入排序(Insertion Sort)
- 快速排序(Quick Sort)
- 归并排序(Merge Sort)
- 堆排序(Heap Sort)
- 希尔排序(Shell Sort)
- 基数排序(Radix Sort)
11. 请介绍一下快排
- 快速排序:快速排序是一种分治算法,通过选择一个基准元素,将数组分为两部分,一部分小于基准,一部分大于基准,然后递归地对两部分进行排序。
- 步骤:
- 选择一个基准元素(通常为第一个或最后一个元素)。
- 将数组分为两部分,一部分小于基准,一部分大于基准。
- 递归地对两部分进行排序。
12. 如果使用unity和py通信应该怎么使用通信协议
通信协议:可以使用TCP/IP、UDP、HTTP等协议进行通信。
实现方式:
- TCP/IP:通过Socket编程实现Unity和Python之间的通信。
- HTTP:使用RESTful API或WebSocket进行通信。
- 共享文件:通过读写共享文件进行数据交换。
13. 学过shader吗 介绍一下
- Shader:Shader是一种用于图形渲染的程序,运行在GPU上。常见的Shader类型包括顶点着色器(Vertex Shader)、片段着色器(Fragment Shader)和几何着色器(Geometry Shader)。
- 应用:Shader用于实现各种视觉效果,如光照、阴影、纹理映射等。
14. 学过计算机图形学吗,三维坐标怎么转为屏幕坐标
- 三维坐标转屏幕坐标:这个过程通常称为投影变换,包括以下几个步骤:
- 模型变换:将物体从模型坐标系转换到世界坐标系。
- 视图变换:将物体从世界坐标系转换到相机坐标系。
- 投影变换:将物体从相机坐标系转换到裁剪坐标系。
- 视口变换:将物体从裁剪坐标系转换到屏幕坐标系。
自我介绍
C# 类与接口的区别
数组与链表的区别
数组和链表的使用场景(增删)
GC 产生的原因(就说了俩个www)
ArrayList和List区别(我就说了List是接口 ArrayList会封箱拆箱)
介绍一下委托
相机三种的渲染形式(没有回答出来)
物理更新在哪里(fixUpdate)
为什么用fixUpdate
Lua 类的实现
Lua ipair 和pair区别
卡牌组怎么实现的
洗牌算法
缓存池怎么实现的
自研引擎的看法
反问:
15.进去做什么 :做业务之类的,自研引擎用纯Lua
- 后续流程
- 表现 (lua要提高,基础还行,但是扩展要仔细看)
1. 自我介绍
自我介绍是面试的第一步,通常包括你的姓名、教育背景、工作经验、技能特长以及为什么对这个职位感兴趣。简洁明了地展示你的优势和与职位的匹配度。
2. C# 类与接口的区别
- 类:类是面向对象编程的基本单位,可以包含字段、属性、方法、事件等成员。类可以被实例化,支持继承和多态。
- 接口:接口是一种契约,定义了类或结构体应实现的成员(方法、属性等)。接口不能被实例化,类可以实现多个接口,实现多继承的效果。
3. 数组与链表的区别
- 数组:数组在内存中是连续存储的,支持随机访问,访问时间复杂度为O(1)。但插入和删除操作的时间复杂度为O(n),因为需要移动元素。
- 链表:链表在内存中是非连续存储的,每个元素包含数据和指向下一个元素的指针。插入和删除操作的时间复杂度为O(1),但访问元素的时间复杂度为O(n)。
4. 数组和链表的使用场景(增删)
- 数组:适合用于需要频繁访问元素的场景,如查找、遍历等。不适合频繁插入和删除操作的场景。
- 链表:适合用于需要频繁插入和删除操作的场景,如实现队列、栈等数据结构。
5. GC 产生的原因
- 内存管理:GC(垃圾回收)是为了自动管理内存,防止内存泄漏和内存溢出。
- 对象生命周期:GC会回收不再使用的对象,释放内存资源。
6. ArrayList和List区别
- ArrayList:是非泛型集合,可以存储任何类型的对象,但需要进行装箱和拆箱操作,影响性能。
- **List
**:是泛型集合,类型安全,不需要装箱和拆箱操作,性能更好。
7. 介绍一下委托
- 委托:委托是一种类型安全的函数指针,用于封装方法。可以用于实现回调函数、事件处理等。C#中的委托类型是
delegate
。
8. 相机三种的渲染形式
- 前向渲染(Forward Rendering):逐个光源计算光照,适合少量光源的场景。
- 延迟渲染(Deferred Rendering):先将几何信息渲染到G-Buffer,再计算光照,适合大量光源的场景。
- 延迟光照(Deferred Lighting):结合前向渲染和延迟渲染的优点,先渲染几何信息,再计算光照。
9. 物理更新在哪里(FixUpdate)
- FixUpdate:在Unity中,
FixedUpdate
用于处理物理更新,调用频率固定,通常与物理引擎的时间步长一致。
10. 为什么用FixUpdate
- 物理引擎的稳定性:
FixedUpdate
确保物理计算的稳定性,避免因帧率波动导致的物理模拟不一致。
11. Lua 类的实现
- Lua类:Lua中没有类的概念,但可以通过表和元表模拟类的行为。通常使用
setmetatable
和__index
元方法来实现继承和多态。
12. Lua ipair 和pair区别
- ipairs:用于遍历数组部分的表,按顺序遍历整数索引的元素。
- pairs:用于遍历表中的所有键值对,包括非整数索引的键。
13. 卡牌组怎么实现的
- 卡牌组:通常使用数组或链表来实现。可以使用数组存储卡牌,链表方便插入和删除操作。
14. 洗牌算法
- 洗牌算法:常用的洗牌算法是Fisher-Yates算法,通过随机交换数组中的元素来实现洗牌。
15. 缓存池怎么实现的
- 缓存池:缓存池用于管理对象的复用,减少频繁创建和销毁对象的开销。通常使用队列或栈来存储可复用的对象。
16. 自研引擎的看法
- 自研引擎:自研引擎可以根据项目需求定制功能,优化性能,但开发成本高,维护难度大。适合对性能和控制有极高要求的项目!
秋招
剑心 一面 寄
- 什么时候会形成多态,动态多态只要定义虚函数都会出现吗?只要调子类的函数就会实现多态?
答:多态分动态多态和静态多态,静态多态是通过重载和模板实现,动态多态通过继承和虚函数实现(第二个问题是引导要有继承前提),
仅定义虚函数或直接调用派生类函数并不会自动触发动态多态,必须通过基类指针或引用来调用。
- 编译器如何实现多态(虚函数实现方式)
答:虚函数是通过虚函数表来实现的,虚函数表包含了一个类(所有)的虚函数的地址,在有虚函数的类对象中,它内存空间的头部会有一个虚函数表指针(虚表指针),用来管理虚函数表。当子类对象对父类虚函数进行重写的时候,虚函数表的相应虚函数地址会发生改变,改写成这个虚函数的地址,当我们用一个父类的指针来操作子类对象的时候,它可以指明实际所调用的函数。
- 多重继承的时候 C继承与A和B虚函数表会发生什么变化
类 A
的虚表 vtable_A
只包含 funcA
。
类 B
的虚表 vtable_B
只包含 funcB
。
类 C
中有两个虚表指针,分别指向 vtable_A
和 vtable_B
:
vtable_A
中 funcA
的指针会指向 C::funcA
。
vtable_B
中 funcB
的指针会指向 C::funcB
。
- 局部变量int a 添加一个static 会出现哪些变化
答 :
内存存储区的变化 ,int a 在栈中 static 在全局数据区中
初始化: 局部变量进函数每次都会被初始化 ,静态量只会被初始化一次,会保留上一次的值不会被重新初始化
- 定义static前后初始值是多少
局部是垃圾值,全局是0
堆栈的区别
栈区的容量大小? 4MB
栈的容量改成堆的容量有什么后果?
栈溢出和安全隐患,系统限制和资源浪费, 内存耗尽风险
- malloc- free new-delete为什么要配套使用 ,new 加free一起用会有什么效果
- 什么时候会产生内存泄漏,野指针,举个例子
- c语言中free一个(void*) 指针 怎么知道这个指针多大呢
举个例子:假设你用malloc需要申请100字节,实际是申请了104个字节。把前4字节存成该块内存的实际大小,并把前4字节后的地址返回给你。 free释放的时候会根据传入的地址向前偏移4个字节 从这4字节获取具体的内存块大小并释放。(实际上的实现很可能使用8字节做为头部:其中每四个字节分别标记大小和是否正在使用)
实际的内存管理比这个要复杂的多,但一般手段都是如上通过记录头或者叫做cookie来存储大小以及是否正在使用来确定以后到底要回收多少和是否可以复用。
内存管理比较复杂的主要原因是内存管理程序会缓存和合并已经申请的内存块并且去复用,或者申请超大的一个块,自己省着点用 ,这样不必每次都想操纵系统要。
- C#中协程线程进程区别
- 什么是单例模式
唯一性:单例模式确保在整个应用程序的生命周期中只有一个实例。
全局访问:提供一个全局访问点,使得应用程序中的任何地方都能获取该实例。
懒加载(可选):可以选择在第一次使用时创建实例,节省资源
- 如何将一个类改成单例模式
私有构造函数:将类的构造函数设为私有,以防止外部代码通过构造函数直接创建实例。
静态私有实例:在类中声明一个静态的私有实例,用于保存唯一的实例。
公共静态方法:提供一个公共的静态方法来获取该实例,如果实例不存在,则创建它。
1 | public class Singleton |
- 静态类 和单例类 有什么区别
首先理解一下什么是静态类,静态类就是一个类里面都是静态方法和静态field,构造器被private修饰,因此不能被实例化。Math类就是一个静态类。
elex 一面 寄
C# 的struct 和class区别
拆箱和装箱
一个Object他是一个装箱过程 他在内存里面的具体操作是什么
字典的底层结构
mono的生命周期
unity 的协程
开启协程的每次返回的时间是什么,比如说返回一个null,在update之前还是之后
string是值类型还是引用类型
对一个已经初始化的string做修改内存会产生什么后果
讲一下CLA的GC
讲一下红点系统(实习)
介绍TCP和UDP区别
算法:合并俩个链表
渲染管线过程
反问
SLG 自走棋
技术栈: 纯C# huatuo URP
实习岗? 有转正名额
评价: 挺好的 有点紧张 双向选择
以下是针对这些问题的详细回答方向:
1. C# 的 struct 和 class 区别
struct
是值类型,存储在栈上,分配和回收效率高。class
是引用类型,存储在堆上,通过引用访问。struct
不支持继承,只能实现接口,而class
支持继承。struct
用于表示小型轻量数据,class
更适合复杂对象。
2. 拆箱和装箱
- 装箱:将值类型转换为引用类型(
ValueType → Object
),发生在将值类型存储到引用类型变量或集合中时。 - 拆箱:将引用类型还原为值类型(
Object → ValueType
),需要显式转换。
3. 一个 Object 是一个装箱过程,在内存里的具体操作是什么
- 装箱过程:
- 在堆上分配内存,用于存储值类型的拷贝和类型信息。
- 将值类型的值拷贝到新分配的堆内存中。
- 返回指向堆内存的引用。
- 拆箱过程:
- 检查引用的类型信息是否与目标值类型匹配。
- 如果匹配,将堆内存的值拷贝回栈上的值类型变量。
4. 字典的底层结构
- C# 字典 (
Dictionary<TKey, TValue>
) 的底层是哈希表。 - 通过哈希函数计算键的哈希值,确定存储的桶位置。
- 冲突解决机制:C# 字典使用 链地址法(每个桶对应一个链表或动态数组)。
5. Mono 的生命周期
Mono 的生命周期和 Unity 的
1
GameObject
生命周期紧密关联:
Awake()
: 对象初始化时调用。Start()
: 在第一次更新之前调用。Update()
: 每帧调用,用于逻辑更新。LateUpdate()
: 在所有更新完成后调用。OnDestroy()
: 对象销毁前调用。
6. Unity 的协程
协程 (
Coroutine
) 是一种能暂停和延续的函数。通过
1
yield
返回值控制暂停时机,例如:
yield return null
: 下一帧恢复。yield return new WaitForSeconds(2)
: 等待 2 秒。
7. 开启协程的每次返回的时间是什么
yield return null
会暂停到 下一帧的Update()
之前。- Unity 协程在
MonoBehaviour
的Update()
之后执行。
8. string 是值类型还是引用类型
string
是 引用类型,但它是 不可变的,每次修改都会创建一个新的实例。
9. 对一个已经初始化的 string 做修改,内存会产生什么后果
- 修改
string
会分配新的内存,用于存储新的字符串值,原来的内存保留或等待垃圾回收。
10. 讲一下 .NET 的 GC
.NET 的 GC 是
分代回收机制
:
- 第 0 代:短期存活对象,频繁回收。
- 第 1 代:中期存活对象。
- 第 2 代:长期存活对象,较少回收。
使用标记-清除、压缩和分代算法进行垃圾回收。
11. 讲一下红点系统(实习)
- 红点系统用于提示用户某些事件(如新消息、未读内容):
- 数据层:监听数据变化,更新状态。
- 逻辑层:定义红点规则和优先级。
- UI 层:根据红点状态显示或隐藏红点。
12. 介绍 TCP 和 UDP 区别
- TCP:
- 面向连接,提供可靠传输。
- 有三次握手和数据确认机制。
- 适用于数据完整性要求高的场景(如文件传输)。
- UDP:
- 面向无连接,不保证可靠性。
- 无需握手,传输速度快。
- 适用于实时性要求高的场景(如游戏、视频流)。
13. 算法:合并两个链表
代码示例(C#):
1 | public ListNode MergeTwoLists(ListNode l1, ListNode l2) { |
14. 渲染管线过程
- CPU 阶段:
- 加载场景数据和资源。
- 提交渲染指令。
- GPU 阶段:
- 顶点着色:将 3D 顶点映射到 2D 屏幕坐标。
- 光栅化:将多边形转换为像素。
- 片段着色:计算每个像素的颜色。
- 合成:叠加多个渲染结果,输出最终图像。
需要进一步扩展或补充某个问题,随时告诉我!
极致二面主管面 寄
- 有玩过哪些游戏
- 杀戮尖塔最有意思的点
- 作为一个程序出身,简单介绍一下你这个类肉鸽的杀戮尖塔游戏吗
- 做的时候有没有很多bug
- buff的bug原因
- 解决bug是换一种方式还是找到问题
- 你这个项目是几个人(策划和程序俩个人)
- 有上线吗(没有想过上线)
- 做了多久?(一两月框架,策划一个月)
- 回头看有哪些地方做的好哪些地方做的不好
- 再让你做一次,怎么做的更好,做多久
- 用户有几个人(找bug)
- bug数量
- 为什么要参加比赛
- 自己以后的发展?(这边需要定)
- 项目和公司,怎么判断项目好不好
- 自己的优势是什么
1. 有玩过哪些游戏
- 介绍自己玩过的游戏,尤其是与 roguelike 或卡牌类游戏相关的,例如《杀戮尖塔》、《炉石传说》、《暗黑地牢》等。
- 可以提到一些启发性的游戏元素,比如《杀戮尖塔》的随机性和策略深度。
2. 杀戮尖塔最有意思的点
- 随机性与策略性的结合:每次冒险都不同,提供丰富的重玩价值。
- 卡牌构筑的灵活性:根据随机事件和奖励调整策略。
- Roguelike 的高风险高回报机制:失败后重新开始但会有新的尝试方向。
3. 作为一个程序出身,简单介绍一下你这个类肉鸽的杀戮尖塔游戏吗
- 描述游戏的核心玩法:随机生成的关卡、策略卡牌对战、Buff/Debuff系统等。
- 提到实现的技术细节,比如基于 Unity 开发,使用 C# 编写卡牌逻辑、随机事件和 UI 系统。
4. 做的时候有没有很多 bug
- 可以坦诚表示,初期开发中确实遇到了很多 bug,尤其是在卡牌交互、Buff/Debuff 系统和随机事件逻辑中。
5. Buff 的 bug 原因
- Buff/Debuff 的叠加规则和移除条件是常见问题,可能因为状态未及时更新、边界条件未考虑全面等。
6. 解决 bug 是换一种方式还是找到问题
- 优先找到问题的根本原因,但如果问题涉及逻辑设计的根本错误,可能会考虑换一种实现方式。
7. 你这个项目是几个人(策划和程序俩个人)
- 明确是一个小团队项目,自己主要负责程序开发,队友负责策划和部分美术设计。
8. 有上线吗(没有想过上线)
- 可以说明这是一个学习性质的项目,目标是提升开发和设计能力,并没有商业化计划。
9. 做了多久?(一两月框架,策划一个月)
- 可以具体描述:两个月内完成基础框架和核心玩法开发,策划和内容补充花了一个月。
10. 回头看有哪些地方做的好哪些地方做的不好
- 做得好:架构设计较清晰,核心玩法完成度高,游戏性有一定体验基础。
- 不足:细节打磨不足,UI设计不够美观,缺乏长期平衡性测试。
11. 再让你做一次,怎么做的更好,做多久
- 可以提到加入更多内容(如丰富的事件、敌人设计),优化代码架构,提升用户体验,可能需要 3-4 个月。
12. 用户有几个人(找 bug)
- 说明初步测试邀请了几位朋友(5-10人)参与,集中测试游戏性和找 bug。
13. bug 数量
- 初期测试发现了几十个 bug,经过修复后大部分核心问题解决,但细节问题仍存在。
14. 为什么要参加比赛
- 表达参加比赛的目的是为了获取反馈、提升项目影响力,并检验自己的能力和团队协作成果。
15. 自己以后的发展?(这边需要定)
- 可以说未来想专注于游戏开发,尤其是独立游戏的设计与技术实现,或深入研究游戏引擎和技术方案。
16. 项目和公司,怎么判断项目好不好
- 好项目应该有明确的目标、合理的资源分配、创新点和用户价值。
- 公司是否专注于核心业务,有清晰的技术规划和发展方向。
17. 自己的优势是什么
- 逻辑思维强,善于发现和解决问题。
- 有多语言、多平台开发经验,熟悉游戏开发的技术栈。
- 具备独立完成项目的能力,同时有良好的团队协作经验。
你可以根据实际情况微调这些回答,以适应具体场景或个人风格。需要进一步细化或补充,请告诉我!
体会
完成一个类似于《杀戮尖塔》的 Roguelike 卡牌游戏 Demo,无论是作为练习还是正式项目,都会让你获得非常宝贵的经验。以下是可能的体会总结,结合你的开发背景和 Unity 的实践经验:
1. 游戏系统的设计与实现:
- 模块化开发的重要性:
开发过程中会发现,游戏系统(如卡牌逻辑、关卡生成、角色成长等)需要高度解耦和模块化设计,否则复杂的依赖关系容易导致维护困难。
体会:在游戏开发中,设计一个清晰的模块边界对后续的扩展和调试非常重要。 - 状态机与战斗逻辑:
卡牌游戏核心是战斗系统,涉及状态机(回合制)、效果触发器和事件机制。实现过程中可能深刻认识到状态管理的重要性,比如如何避免条件嵌套导致代码混乱。
体会:灵活的状态机设计和事件驱动是实现复杂战斗逻辑的关键。
2. 卡牌系统的核心构建:
- 数据驱动开发:
卡牌的技能效果、数值变化等可以通过数据驱动配置(如 ScriptableObject 或 JSON 文件)实现,减少硬编码逻辑,提高策划灵活性。
体会:将卡牌效果与代码逻辑解耦,不仅可以快速调整平衡,还方便复用,开发效率会有显著提升。 - 多样化与随机性:
实现卡牌的随机生成和升级机制,会让你对游戏的重玩性有更深的理解。过多的随机性可能让游戏失控,而缺少随机性则让游戏失去乐趣。
体会:随机与平衡的拿捏是游戏设计中最大的挑战之一。
3. 用户体验优化:
- 卡牌动画与反馈:
卡牌动画(如拖拽、攻击效果、技能特效等)是玩家体验的核心部分。开发过程中,你可能意识到细节决定体验,流畅的 UI 交互能让游戏整体提升一个档次。
体会:细节动画不仅是加分项,更是提升玩家沉浸感的必要环节。 - UI 系统的复杂性:
Unity 的 UGUI 系统在处理动态卡牌布局(如手牌排列、墓地展示)时,可能需要处理大量边界情况,例如屏幕适配、拖拽响应等。
体会:提前规划 UI 的动态调整逻辑能避免后期返工。
4. Roguelike 机制的实现:
- 关卡随机生成:
类似《杀戮尖塔》的关卡是随机生成的,需要结合权重和玩家选择来设计关卡树。
体会:通过实现路径选择机制,你会学到如何平衡玩家决策和系统随机性,既保证体验的多样性,又不至于让玩家感到“无脑”。 - 经济与成长曲线:
Roguelike 的核心是风险与奖励的动态平衡,卡牌、金币、遗物等成长资源需要设计合理的获取与分配机制。
体会:构建一个让玩家既有成就感又能持续探索的成长曲线,是设计成功的核心。
5. 技术实现的积累:
- 资源管理:
动态加载卡牌素材、特效、音效等资源时,你可能学会了 Unity 的资源加载优化(如 AssetBundle 或 Addressable)。
体会:资源管理和加载优化在复杂游戏中是不可忽视的部分。 - 代码优化与架构经验:
完成这个项目后,你可能会更熟悉 设计模式 的使用(如工厂模式、观察者模式)和项目架构的组织。
体会:良好的代码架构能够显著提升项目开发效率和协作体验。
6. 团队协作与迭代开发:
- 与策划/美术的协作:
游戏项目需要频繁与策划、美术沟通,完成策划的需求,同时让美术资源融入游戏效果。
体会:游戏开发是一个综合艺术,程序员不仅是实现者,还需要理解设计意图并提供技术支持。 - 版本控制与迭代:
在开发过程中,频繁修改功能和调整细节可能让你意识到版本控制(如 Git)和分支管理的重要性。
体会:保持代码整洁和明确的开发流程是团队合作的基石。
7. 心态上的成长:
- 从复杂到简单的过程:
一开始可能觉得游戏系统庞杂无比,但随着实现,你会逐步理解问题的本质,找到更简单高效的实现方式。
体会:复杂问题是由多个简单问题组成的,学会分解和迭代是开发能力的提升关键。 - 失败也是一种收获:
开发过程可能遇到技术困难(如性能瓶颈、机制逻辑冲突等),通过解决这些问题,你的解决问题能力会得到极大提高。
体会:每一次卡住或失败,都是成长的契机,程序员的价值体现在解决问题的能力上。
总结:作为一个程序的成长感悟
- 你会更加理解 从设计到实现的完整流程,尤其是在系统架构、模块化设计、用户体验、团队协作等方面收获良多。
- Unity 的实际使用经验(资源加载、动画效果、UI 布局等)会让你对引擎的能力和局限性有更深的认识。
- 更重要的是,这种从 0 到 1 的开发经历会让你意识到程序员的核心价值不仅在于写代码,而在于用代码创造一个能带给玩家乐趣的世界。
通过完成这个项目,你不仅收获了一个完整的游戏 Demo,更积累了一整套开发思维和方法,为未来更复杂的项目打下了坚实的基础。
真有趣一面 寄
- 迷你玩写UI是Lua还是C#
- UNITY掌握到什么程度?
- AB包热更实现要几步流程
- md5怎么增量对比
- 数据结构你熟悉哪些 (树图)
- 图的结点和树的结点差异在哪
- 请介绍一下QF
- 你了解UGUI吗
- UGUI点适应有几个点(锚点),哪几个
- rebulid?
- 重绘的损耗在哪里
- 动静分离后是不是不会rebulid
迷你玩写UI是Lua还是C#
迷你玩(Mini Play)通常使用Lua来编写UI逻辑,因为Lua在游戏开发中常用于热更新和脚本化UI。C#则更多用于Unity的核心逻辑。UNITY掌握到什么程度?
我对Unity的掌握程度较为深入,熟悉Unity的核心模块如UI系统(UGUI)、动画系统、物理引擎、资源管理(如AssetBundle)、脚本编写(C#)、以及性能优化等。能够独立开发中小型项目,并对大型项目的架构有一定的理解。AB包热更实现要几步流程
AB包(AssetBundle)热更的实现流程通常包括以下几步:- 打包AssetBundle:将资源打包成AB包。
- 上传服务器:将打包好的AB包上传到服务器。
- 客户端检测更新:客户端启动时检测服务器上的AB包版本,判断是否需要更新。
- 下载AB包:从服务器下载最新的AB包到本地。
- 加载AB包:客户端加载本地的AB包,替换旧资源。
- 版本管理:确保客户端和服务器的AB包版本一致。
md5怎么增量对比
MD5增量对比通常用于检测文件的变化。具体步骤:- 为每个文件生成MD5哈希值。
- 将新旧文件的MD5哈希值进行对比。
- 如果MD5值不同,说明文件内容有变化,需要进行更新。
- 只更新MD5值不同的文件,实现增量更新。
数据结构你熟悉哪些 (树图)
我熟悉的数据结构包括:- 树:二叉树、二叉搜索树、平衡树(AVL树、红黑树)、B树、堆等。
- 图:有向图、无向图、邻接表、邻接矩阵、最短路径算法(Dijkstra、Floyd)、最小生成树(Prim、Kruskal)等。
图的结点和树的结点差异在哪
- 树的结点:每个结点有且只有一个父结点(根结点除外),结点之间没有环路,具有层次结构。
- 图的结点:结点可以有多个父结点或没有父结点,结点之间可以有环路,结构更加灵活,没有严格的层次关系。
请介绍一下QF
QF(Quick Framework)是一个轻量级的Unity游戏开发框架,通常用于快速搭建游戏原型或中小型项目。它提供了一些常用的模块,如资源管理、UI管理、事件系统、对象池等,帮助开发者减少重复工作,提升开发效率。你了解UGUI吗
是的,我了解UGUI(Unity GUI)。UGUI是Unity内置的UI系统,提供了丰富的UI组件(如Button、Text、Image、Slider等)和布局工具(如锚点、网格布局等),支持灵活的UI设计和交互逻辑。UGUI点适应有几个点(锚点),哪几个
UGUI的锚点有9个点,分别是:- 左上角、中上、右上角
- 左中、中心、右中
- 左下角、中下、右下角
这些锚点用于控制UI元素相对于父容器的对齐和缩放行为。
rebulid?
在UGUI中,Rebuild
是指当UI元素的布局或内容发生变化时,Unity会重新计算并绘制UI。Rebuild
分为两种:
- Layout Rebuild:重新计算布局(如位置、大小)。
- Graphic Rebuild:重新绘制图像(如纹理、颜色)。
频繁的Rebuild
会导致性能问题,因此需要优化。
- 重绘的损耗在哪里
重绘的损耗主要在于:
- CPU消耗:频繁的布局计算和顶点数据更新会增加CPU负担。
- GPU消耗:每次重绘都会提交新的绘制指令到GPU,增加渲染压力。
- Draw Call增加:过多的重绘会导致Draw Call增加,影响渲染性能。
- 动静分离后是不是不会rebulid
动静分离后,静态UI元素不会频繁触发Rebuild
,因为它们的布局和内容不会变化。动态UI元素(如频繁更新的文本、图片等)仍然会触发Rebuild
。动静分离可以减少不必要的Rebuild
,但并不能完全避免。
如果有其他问题,欢迎继续提问!
春招
摩丰
生命周期?
碰撞器和触发器区别?
某一帧执行方法怎么办? 关键帧
人形模型 上下怎么分开动画
分层之后怎么办
FSM?
动态加载? Prefabs res AB
对象池
怎么实现FPS?
除了子弹以外有什么办法?
延时几秒再执行怎么做?
✨ Update Invoke 协程 DoTween 支持参数传入 ✖️ ✖️ ✔️ ✖️ 除了协程还有别的办法吗
介绍一下协程
俄罗斯方块怎么判断铺满?
1. 生命周期(Unity MonoBehaviour)
Unity的生命周期方法按顺序执行如下:
• 初始化阶段:
• Awake()
:脚本加载时调用(仅一次),适合初始化组件引用。
• OnEnable()
:对象激活时调用(可多次)。
• Start()
:在第一次 Update()
前调用(仅一次),适合初始化数据。
• 主循环阶段:
• FixedUpdate()
:按固定时间步长(默认0.02秒)执行物理计算。
• Update()
:每帧调用一次,处理游戏逻辑。
• LateUpdate()
:在 Update()
后调用,适合摄像机跟随等后处理。
• 销毁阶段:
• OnDisable()
:对象禁用时调用。
• OnDestroy()
:对象销毁时调用,释放资源。
2. 碰撞器和触发器区别
• 碰撞器(Collider):
• 触发物理碰撞(如反弹),需使用 OnCollisionEnter/Stay/Exit
。
• 不勾选 Is Trigger
。
• 触发器(Trigger):
• 不触发物理响应,仅检测重叠,需使用 OnTriggerEnter/Stay/Exit
。
• 勾选 Is Trigger
。
3. 某一帧执行方法(关键帧)
• Animation Event:在动画关键帧中添加事件,绑定方法。
• 代码中判断帧数:
1
2
3
4
5void Update() {
if (Time.frameCount == targetFrame) {
ExecuteMethod();
}
}
• 协程等待帧:
1
2
3
4
5IEnumerator WaitFrames() {
yield return new WaitForEndOfFrame(); // 等待当前帧结束
// 或
yield return new WaitForSeconds(1f); // 等待时间
}
4. 人形模型上下半身分开动画
• 步骤:
- 创建 Avatar Mask:在Project窗口右键 → Create → Avatar Mask,选择下半身骨骼(排除上半身)。
- 在 Animator Controller 中添加新层(Layer),设置 Mask 为创建的 Avatar Mask。
- 在该层中配置动画状态(如上半身攻击)。
• 代码调整层权重:1
animator.SetLayerWeight(1, 1f); // 第1层权重设为1
5. 分层之后的管理
• 混合权重:通过 Animator.SetLayerWeight
控制不同层的动画影响比例。
• 状态覆盖:上层动画可覆盖下层(如攻击动作覆盖行走的手臂动作)。
6. FSM(有限状态机)
• 定义:管理对象状态的系统,每个状态定义明确的行为和转换条件。
• Unity实现:
• 使用 Animator Controller 可视化配置状态和转换条件。
• 或自定义类实现,例如:
1
2
3
4
5
6
7
8
9public enum PlayerState { Idle, Run, Jump }
public PlayerState currentState;
void Update() {
switch (currentState) {
case PlayerState.Idle: HandleIdle(); break;
// 其他状态处理...
}
}
7. 动态加载资源
• Resources.Load:
1
2GameObject prefab = Resources.Load<GameObject>("Path/PrefabName");
Instantiate(prefab);
• 缺点:资源必须放在 Resources
文件夹,难以热更新。
• **AssetBundle (AB)**:
• 打包资源为AB文件,运行时加载:
1
2AssetBundle bundle = AssetBundle.LoadFromFile("path/to/bundle");
GameObject obj = bundle.LoadAsset<GameObject>("AssetName");
• 优点:支持热更新,灵活管理资源。
• Addressables:
Unity官方资源管理系统,通过地址异步加载资源:
1
2
3Addressables.LoadAssetAsync<GameObject>("AssetKey").Completed += handle => {
Instantiate(handle.Result);
};
8. 对象池
• 作用:复用对象以减少实例化开销和GC压力。
• 实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class ObjectPool {
private Queue<GameObject> pool = new Queue<GameObject>();
public GameObject GetObject(GameObject prefab) {
if (pool.Count > 0) {
GameObject obj = pool.Dequeue();
obj.SetActive(true);
return obj;
}
return Instantiate(prefab);
}
public void ReturnObject(GameObject obj) {
obj.SetActive(false);
pool.Enqueue(obj);
}
}
9. 实现FPS(第一人称射击)
• 核心要素:
- 移动控制:使用
CharacterController
或Rigidbody
处理移动。 - 摄像机控制:鼠标控制视角旋转(通过
Transform.Rotate
)。 - 射击机制:射线检测(Raycast)判断命中。
- 准星UI:在屏幕中心绘制准星图像。
• 射击代码示例:
1
2
3
4
5
6void Shoot() {
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out RaycastHit hit)) {
Debug.Log("击中:" + hit.collider.name);
}
}
10. 其他攻击检测方式(非子弹)
• OverlapSphere:检测球形范围内的所有碰撞体。
1
2
3
4Collider[] hits = Physics.OverlapSphere(transform.position, 5f);
foreach (var hit in hits) {
if (hit.CompareTag("Enemy")) { /* 处理伤害 */ }
}
• 触发器区域:设置一个带Trigger的Collider,通过 OnTriggerEnter
检测进入的物体。
11. 延时执行方法
• 协程:
1
2
3
4
5IEnumerator DelayMethod() {
yield return new WaitForSeconds(3f);
Debug.Log("3秒后执行");
}
StartCoroutine(DelayMethod());
• Invoke:
1
Invoke("MethodName", 3f); // 3秒后调用MethodName
• Update计时器:
1
2
3
4
5
6
7
8float timer = 0;
void Update() {
timer += Time.deltaTime;
if (timer >= 3f) {
ExecuteMethod();
timer = 0;
}
}
12. 其他延时方法(非协程)
• UniTask(第三方库):支持异步/等待语法。
• Dotween延时回调:
1
DOVirtual.DelayedCall(3f, () => { /* 执行代码 */ });
13. 协程(Coroutine)
• 定义:一种特殊函数,可分步执行,通过 yield
暂停并在后续帧恢复。
• 用途:延迟执行、分帧加载、异步操作。
• 示例:
1
2
3
4
5
6
7IEnumerator LoadData() {
yield return new WaitForSeconds(1f); // 等待1秒
Debug.Log("加载完成");
}
void Start() {
StartCoroutine(LoadData());
}
14. 俄罗斯方块铺满判断
• 实现逻辑:
- 使用二维数组
grid[,]
记录每个格子的填充状态。 - 遍历每一行,检查是否所有格子都被填充:
1
2
3
4
5
6bool IsLineFull(int row) {
for (int col = 0; col < gridWidth; col++) {
if (grid[row, col] == null) return false;
}
return true;
} - 消除满行,并让上方行下落:
1
2
3
4
5
6
7
8void ClearFullLines() {
for (int row = 0; row < gridHeight; row++) {
if (IsLineFull(row)) {
DestroyRow(row);
ShiftRowsDown(row);
}
}
}
以上解答涵盖了Unity开发中的常见问题,结合代码示例和实际应用场景,帮助您快速理解核心概念。
舟游 32min
C++ unity是自学的吗
OOP?
重载和重写
重写关键字? override
类和接口区别
public private protect?
list 字典 实现
C# 堆栈,栈溢出?
GC?
值类型引用类型有哪些?
int 字节大小?
int数量级左右
double是几个字节
int有多少位 short
unity灯光类型?
animation animator介绍一下(顺便讲了一下FSM)
状态直接怎么转换的?
怎么触发碰撞?
Mono 生命周期
update fixupdate lastupdate?
roglike攻击怎么实现的
常用排序算法
快排 插排?
TCP UDP HTTP?
背包系统?数据处理装UI层
浅拷贝和深拷贝?
代码浅拷贝和深拷贝怎么实现?
对象里面又有对象怎么拷贝?
背包 数据和UI表现怎么连接起来的?
无限列表每个物体怎么对应你每个对象?
职业规划?
哪里人?为什么想来成都?
1. C++和Unity是自学的吗?
是的,通常Unity开发使用C#语言,但如果您在学习C++和Unity的底层交互(如插件开发),可以通过自学掌握相关技术。
2. OOP(面向对象编程)的核心概念?
• 封装:隐藏内部实现,暴露接口(如类的私有字段和公有方法)。
• 继承:子类复用父类的属性和方法(如 class Dog : Animal
)。
• 多态:同一方法在不同对象中有不同实现(通过重写override
实现)。
• 抽象:定义接口或抽象类,约束子类行为。
3. 重载(Overload)和重写(Override)的区别?
• 重载:同一类中,方法名相同但参数不同(如 void Print(int a)
和 void Print(string s)
)。
• 重写:子类覆盖父类虚方法,使用 override
关键字(如 override void Move()
)。
4. 重写的关键字?
在C#中,使用 override
关键字重写父类标记为 virtual
或 abstract
的方法。
5. 类和接口的区别?
• 类:可包含字段、方法实现,支持单继承。
• 接口:仅定义契约(方法签名),无实现,支持多继承(C# 8+允许默认实现)。
6. 访问修饰符 public
、private
、protected
?
• public:所有代码可访问。
• private:仅类内部可访问。
• protected:类内部及子类可访问。
7. List 和 Dictionary 的实现原理?
• List:基于动态数组,自动扩容(如C#的 List<T>
)。
• Dictionary:基于哈希表(键值对),通过哈希函数快速查找(如C#的 Dictionary<TKey, TValue>
)。
8. C# 中的堆栈和栈溢出?
• 栈(Stack):存储值类型和方法调用帧,分配速度快但空间有限。
• 栈溢出:递归过深或大对象分配在栈上(如大型结构体)导致 StackOverflowException
。
9. GC(垃圾回收)机制?
• 分代回收:分三代(0/1/2),新对象在Gen0,存活对象晋升。
• 标记-清除:标记可达对象,清除未标记对象,压缩内存(减少碎片)。
10. 值类型和引用类型举例?
• 值类型:int
, float
, struct
, enum
(存储在栈或堆中,作为对象的一部分)。
• 引用类型:class
, string
, 数组
, 接口
(存储在堆中,栈存引用地址)。
11. int
的字节大小?
在C#中,**int
为4字节(32位)**,范围:-2,147,483,648 到 2,147,483,647。
12. int
的数量级?
约 ±20亿(即 (2^{31}-1))。
13. double
的字节大小?
8字节(64位),遵循IEEE 754标准。
14. short
的位数?
16位(2字节),范围:-32,768 到 32,767。
15. Unity中的灯光类型?
• Directional Light(平行光)
• Point Light(点光源)
• Spot Light(聚光灯)
• Area Light(区域光,仅烘焙)
16. Animation 和 Animator 的区别?
• Animation:旧版动画系统,直接控制单个动画片段。
• Animator:使用状态机(FSM)管理动画过渡,配合Animator Controller配置条件(如布尔参数)。
17. 状态间如何转换?
通过 Animator Controller 设置过渡条件(如 Trigger
、Bool
、Float
),代码中修改参数触发转换:
1 | animator.SetBool("IsRunning", true); |
18. 如何触发碰撞?
• 必要条件:两物体均需有碰撞器(Collider),至少一个带有刚体(Rigidbody)。
• 触发器:勾选 isTrigger
,通过 OnTriggerEnter()
方法检测。
19. MonoBehaviour 生命周期方法顺序?
Awake()
→ 对象初始化时调用(仅一次)。Start()
→ 在第一次Update()
前调用。FixedUpdate()
→ 按固定时间步长执行(物理计算)。Update()
→ 每帧执行。LateUpdate()
→ 在Update()
后执行(如摄像机跟随)。OnDestroy()
→ 对象销毁时调用。
20. Update、FixedUpdate、LateUpdate 的区别?
• FixedUpdate:固定时间间隔(默认0.02秒),用于物理计算。
• Update:每帧调用一次,时间间隔不固定。
• LateUpdate:所有 Update()
执行后调用,适合处理跟随逻辑。
21. Roguelike攻击的实现?
• 碰撞检测:使用 OnTriggerEnter()
或射线检测。
• 伤害计算:根据属性(攻击力、暴击率)随机生成数值。
• 状态同步:通过事件或UI更新玩家状态。
22. 常用排序算法?
• 快速排序:分治思想,平均O(n log n)。
• 插入排序:O(n²),适合小规模数据。
• 归并排序:稳定,O(n log n)。
• 堆排序:原地排序,O(n log n)。
23. 快排和插入排序的原理?
• 快排:选基准元素,分割为左右子数组,递归排序。
• 插入排序:将元素插入已排序序列的合适位置。
24. TCP、UDP、HTTP的区别?
• TCP:可靠、面向连接(三次握手),保证数据顺序(如文件传输)。
• UDP:无连接、高效,可能丢包(如实时音视频)。
• HTTP:应用层协议,基于TCP,用于Web通信(如REST API)。
25. 背包系统的数据与UI同步?
• 数据层:使用 InventoryItem
类管理物品属性。
• UI层:通过 InventoryUI
监听数据变化(如事件或观察者模式),动态生成或更新UI元素。
26. 浅拷贝与深拷贝的区别?
• 浅拷贝:复制对象及值类型字段,引用类型字段共享同一地址(如 MemberwiseClone()
)。
• 深拷贝:递归复制所有字段,包括引用类型的新实例。
27. 代码实现拷贝?
• 浅拷贝:实现 ICloneable
接口,调用 MemberwiseClone()
。
• 深拷贝:手动复制所有字段,或通过序列化/反序列化(如JSON、二进制)。
28. 对象嵌套时的深拷贝?
需递归复制所有层级:
1 | public class DeepCopyExample : ICloneable { |
29. 背包数据与UI的对应?
• 数据绑定:每个UI元素(如 InventorySlot
)对应一个数据项,通过索引或唯一ID关联。
• 更新机制:数据变更时触发事件,UI监听并刷新显示。
30. 无限列表的物体复用?
• 对象池技术:根据滚动位置动态加载/卸载数据,复用UI元素。
• 虚拟化:仅渲染可视区域内的项(如Unity的 ScrollRect
扩展插件)。
31. 职业规划?
短期深入游戏开发技术(如Unity引擎优化),中期向技术管理或架构师发展,长期目标为带领团队完成高质量项目。
32. 为何选择成都?
成都是中国西部科技与游戏产业中心,拥有良好的发展机遇和生活环境,符合个人职业与生活平衡的需求。
希望这些回答能帮助您应对面试或技术讨论!