基础知识大串讲

这里会记录可能问到的基础知识,也是对我所学的一个复习回顾。

C#相关

委托和事件

  1. 委托和事件是什么?使用事件对比直接使用函数调用有什么优势?
  • 委托和事件:
    • 委托和事件是存有方法引用的类型,他们的区别在于事件是委托的安全包裹,只能在声明它的类内部执行Invoke();外部仅可+=、-=函数。
  • 优势:
    • 降低耦合度。使用事件实际上是设计模式中观察者模式的体现,发布者不必关注订阅者的逻辑行为,订阅者在换逻辑时也只需注意加减函数,不必修改大量逻辑。
    • 访问更安全。外部不可Invoke()
    • 支持一对一广播,触发式全部自动执行。

泛型

  1. 泛型的作用,举例说明。
  • 泛型的作用:
    • 复用代码逻辑
    • 避免装箱拆箱
  • 例子:List<T>

接口和抽象类

  1. 接口和抽象类的核心区别?
  • 关键字:接口interface,抽象类abstract class
  • 成员:接口默认无字段、不能有实例变量;抽象类可以有字段、属性、构造函数
  • 继承:一个类只能继承一个抽象类,但是可以实现多个接口
  • 设计:接口强调能做什么、抽象类强调是什么

Unity相关

生命周期

  1. MonoBehaviour的完整生命周期,说说Awake()和Start()的区别,Update()、FixedUpdate()和LateUpdate的区别。
  • 生命周期:Awake -> OnEnable -> Start -> FixedUpdate -> Update -> LateUpdate -> OnDisable -> OnDestory
  • Awake()和Start()的区别:
    • Awake():在物体被实例化时执行,即使脚本失活也能执行。用于初始化赋值。所有物体的Awake()执行完后,才会开始执行Start()。
    • Start():在Awake()之后,Update()第一帧开始前执行,必须当脚本被激活时才能执行。常用于需要等待其他数据的逻辑上。
  • Update()和FixedUpdate()和LateUpdate()的区别:
    • Update():每帧执行,帧间隔不固定。用于处理普通业务逻辑。比如物体移动、摄像机移动
    • FixedUpdate():按照固定时间间隔执行,不受帧率影响。用于处理与物理相关的逻辑。
    • LateUpdate():所有Update()执行完毕后才运行。常用于跟随逻辑(相机跟随角色、挂件跟随主体),避免Update物体移动完相机滞后错位。

方向向量和位置向量

  1. 方向向量和位置向量的区别
  • 方向向量:只关心方向,不关心起点终点。可以用两点坐标相减得到。
  • 位置向量:是从世界原点指向空间某物体位置的向量,用来记录一个点在哪。是物体的transform.position

工程文件夹

常见的工程文件夹及作用

  1. 常见的工程文件夹有哪些?分别有什么作用?
  • Assets:工程资源文件夹,存放美术资源、预制体、脚本等等
  • ProjectSettings:工程设置文件夹,有工程的设置信息
  • Packages:包管理器的设置信息
  • Library:库文件夹
  • Logs:日志文件夹
  • Resources:资源文件夹
  • StreamingAssets:流动资源文件夹
  • Editor:编辑器文件夹,写编辑器扩展

编辑器扩展实例

  1. 写过哪些编辑器扩展?有什么作用?

在做一个《宝石填色》的小游戏时,我写了一个解析图片生成为棋盘的编辑器扩展:

宝石填色示例图

首先我们要知道,Editor文件夹下的东西在打包时都不会被Unity打包出去,因此,我们可以给整个脚本的头部和尾部加上条件编译预处理器指令:#if #endif,编译前处理,不满足直接从源码删掉。

1
[CustomEditor(目标组件类型)]

这个编辑器特性就是告诉Unity当检视目标组件时,不用系统默认的Inspector,改用这个类来绘制面板。

其中,目标组件类型可以用typeof(类名)求得。

然后将这个类中Inspector的某一个按钮和LevelImageParser自己写的解析图片颜色转为棋盘的类关联即可实现这个功能。

我有一个类LevelImageParser,它的结构如下:

  • struct PalleteSample:(结构体)颜色样本
    • int id
    • Color baseColor
    • Color gemColor

它的作用是告诉解析器,哪个颜色对应哪个宝石id

  • int[] Parse(...):将图片解析为棋盘的核心方法
    • 参数:Texture 2D source、棋盘宽高、int pixelsPerCell 每格多少像素、IReadOnlyList<PaletteSample> palette
    • 返回值:一个长度为 width * height 的 int[]
    • 逻辑:
1
2
3
4
int pixelX = Mathf.Clamp(x * pixelsPerCell + pixelsPerCell / 2, 0, source.width - 1);
int pixelYFromTop = y * pixelsPerCell + pixelsPerCell / 2;
int pixelY = Mathf.Clamp(source.height - 1 - pixelYFromTop, 0, source.height - 1);
Color sampledColor = SampleAverage(source, pixelX, pixelY, sampleRadius);

意思就是:

  • 每个格子都取“中心点”
  • 因为图像坐标和 Unity 的格子坐标上下方向相反,所以 y 要翻转
  • 再对中心点附近做一次平均采样,减少噪点影响

然后:

1
2
3
4
5
6
7
if (IsEmptyColor(sampledColor))
{
pattern[index] = BoardCellView.EmptyJewelId;
continue;
}

pattern[index] = FindNearestPaletteId(sampledColor, palette, useBaseColor);

如果这格是透明或空白,就记成空位。
如果不是,就拿采样颜色去和调色板比,找最近的宝石 id。

资源优化

移动端需要注意的坑

  1. 移动端输入处理需要注意哪些坑?
  • 触摸点击事件有300ms延时
  • 多点冲突、滑动和点击事件冲突
  • 手指越界,手指移出按钮后要正确结束输入
  • 多分辨率适配
  • 坐标转换,屏幕坐标要转为游戏坐标

资源优化的方法

  1. 资源优化有哪些方法?
  • 打图集减少drawcall
  • 压缩图片纹理
  • 不用的物体及时销毁
  • 对于需要频繁创建销毁的物体,采用对象池复用
  • 避免每帧进行大量计算
  1. 如果游戏卡帧、掉帧,检查思路是什么?
  • 看Unity性能面板,检查drawcall
  • 看帧更新逻辑,是否有大量循环、实例化
  • 检查资源:图片过大、没打图集
  • 检查内存:是否有对象没有销毁,频繁GC

Playable Ads 试玩广告

Playable可玩广告的定义

  1. 什么是Playable可玩广告?它与普通小游戏有什么区别?

Playable可玩广告就是交互式试玩广告,用户不用下载APP,直接在广告位体验精简玩法,完成后跳转下载引导页。

它与普通小游戏的区别是:

  • 时长严格限制
  • 性能要求极高
  • 目标是广告转化,不是完整的游戏体验
  • 生命周期受广告SDK管控,有暂停、销毁、回调,不能常驻后台

游戏状态机与Playable

  1. 解释游戏状态机是什么,举一个在Playable广告中的使用场景。

游戏状态机可以理解为把各个状态封装成一个个不同的类,状态机持有当前的状态,当条件改变时,切换状态。
Playable中可能有加载状态、教程引导状态、游玩状态、胜利失败状态、引导下载状态。

GIT相关

  1. 简单说下使用Git的合作流程,遇到冲突时怎么解决?
  • 首次拿到项目,克隆仓库:git clone
  • 开发新功能,创建自己的分支:git checkout -b feature/功能描述
  • 本地写代码,提交到本地仓库:
    • 把改动加到暂存区:git add .
    • 提交到本地仓库,写备注:git commit -m ""
  • 推送本地分支到远程仓库:git push origin feature/功能描述
  • 发送合并请求PR(Pull Request):在GitHub上新建PR,请求把feature/功能描述合并到main主分支,再交给同事审评
  • 审评通过,合并分支:main中有我写的代码
  • 所有人同步最新主分支
    • git checkout main
    • git pull origin main

当遇到冲突时找到冲突标记,人工选择/合并两边代码逻辑。git add、git commit完成冲突提交。