训练师背面精灵与投球
2D 战斗开始时,玩家角色(双打战斗中还有盟友训练师)背对镜头站在屏幕一角,投出球,然后滑出画面。这一切由一个组件绘制:BattleTrainerSprite,战斗场景中每个训练师槽位一个实例(AllyTrainerSprites、EnemyTrainerSprites)。
本页说明该精灵是如何制作的,以及组件为你做了什么,让投球在任何屏幕上都看起来正确。
什么是背面精灵
角色的背面存储在其 CharacterType 资产上:
| 字段 | 类型 | 含义 |
|---|---|---|
BackSprite | Material | 角色的动画背面:一个包含帧图集(_SpriteSheet)的材质,按列乘行(_Flipbook)排列,由 MCE 的翻页动画着色器绘制 |
BackSpriteTimeline | FlipbookTimeline | 投球的单元格及每个单元格持续的时间。引擎自带精灵为空 |
没有背面材质的角色类型(例如导入游戏的玩家)根本不会被绘制:渲染器会被关闭,而不是显示占位方块。
翻页动画及其时间线
默认情况下,投球以均匀的节奏遍历图集的单元格。组件把 AnimationDuration(速度为 1 时为 1 秒)平均分给材质在 _Flipbook 中声明的单元格(如果声明了一个单元格或没有声明,则使用序列化的 FlipbookSteps,默认为 5),停在最后一帧而不是循环,并在 0.4 秒时放出球。
对于你自己绘制的精灵,这就够了。但卡带中有些单元格只保持几个 tick,而有一个要保持五十个。FlipbookTimeline 记录的正是这些:
| 字段 | 含义 |
|---|---|
cells | 每个条目显示的单元格 |
seconds | 每个条目持续多久(速度为 1 时) |
releaseEntry | 球离手的条目(没有则为负数) |
只有当表至少有一个条目、每个条目都有时间且所有时间都大于零时,该表才是可用的。其他任何情况(包括场景默认创建的空表)都是“不可用”,训练师保持均匀的翻页动画。
使用可用的表时:
- 每个单元格按各自的时间显示(第 t 秒显示的单元格是
CellAt(t)); - 球在释放条目开始时放出(
ReleaseSeconds:它之前各条目之和); - 整个投球持续
TotalSeconds,之后会保持最后一个单元格。
你可以用 FlipbookTimeline.FromTicks(cells, ticks, ticksPerSecond, releaseEntry) 根据卡带的数值构建表。例如,HeartGold 的投球是 8、16、6、2、4、2、2、52 和 2 个 1/60 秒的 tick,球在第四个条目开始时离手 —— 即 8 + 16 + 6 = 30 个 tick 之后,也就是半秒时。
投球使用自己的时钟
投球不与滑出绑定。ThrowAndSlide(speed, callback) 同时启动两者,但投球在组件自己的 Update(TickThrow)中推进:它按已过的时间显示表中的单元格,在球离手时调用你的 callback 一次,并在表结束时终止。战斗速度对 两者都起缩放作用。
这种分离很重要。在原版的滑出速度下,滑出(0.45 秒)比投球更短(球要到 0.5 秒才离手),所以由滑出完成来驱动的投球会提前放球并截断动画。其他保证:
- 在另一次投球进行时开始的投球会替换它,被替换的那次永远不会放出它的球;
- 如果训练师对象处于非激活状态而无法更新,滑出结束时会作为最后手段放出球,因此战斗绝不会无限等待。
滑出始终在屏幕外恰好结束
投球之后,训练师滑出画面。两件事会为你计算好:
速度。 滑出是线性的,速度为每秒 SlideUnitsPerSecond 个世界单位(默认 3.125:原版每帧 5 个 DS 像素)。设为 0 则改用固定的 SlideDuration(速度为 1 时 2 秒)。
终点。 战斗场景在一个固定的 x 位置有一个标记(SlideTarget)。固定的 x 不可能适用于每种屏幕:在 16:9 的画面上,边缘比 21:9 或 32:9 近得多,在后者上滑向固定标记的训练师会停在画面之内。因此终点是针对绘制训练师的相机计算的:恰好越过标记一侧的屏幕边缘,距离为图形自身的延伸量(其绘制内容越过枢轴向该边缘延伸的距离)再加一个小边距(SlideExitMargin,0.1 单位)。高度和深度取自标记。对于非正交相机,或在 3D 舞台上,由场景的标记决定。
像素完美绘制
由像素画制成、使用点过滤并按创作尺寸绘制的背面图集,其像素会以两种不同宽度显示(有些比其他的多占一个屏幕像素),图形看起来粗糙。对于这类图集,BattleTrainerSprite 设置尺寸,使图集的每个像素都覆盖整数个屏幕像素(绝不少于一个),并把图形的脚放在原版所在的位置:
- 尺寸是最接近创作尺寸的整像素缩放(
PixelPerfectHeight); - 高度的选择使站立图形最后绘制的一行落在屏幕下边缘上(第一帧下方的空行从纹理中计算,位于边缘之下,正如原版的身体延伸出屏幕底部)。纹理无法读取的图集保留场景赋予它的高度;
- 水平位置不处理 —— 它归滑出管理。
该规则仅在有意义时才适用:点过滤的图集、正交相机和 2D 战斗。使用双线性过滤的手绘美术(大幅插画背面)没有需要保持整数的像素,会保持创作时的样子;3D 舞台同理。如果图集消失,创作尺寸会恢复。
计算在每个相机绘制之前立即运行,挂接到渲染管线自己的事件(URP 中的 beginCameraRendering),因此使用的是相机最终的缩放,而不是之前某一帧的值。
在 3D 舞台上
当战斗使用 3D 舞台时,训练师在整场战斗中站在原版放置的位置:它的滑出会让它留在原地,待机帧按游戏时钟循环,并在派出怪物时播放投球(ThrowOnStage)。绘制内容会被缩放,使其一个像素等于舞台的像素大小。
阴影
训练师会获得与怪物一样的阴影:绘制对象的一个名为 Shadow 的子对象,以着色器的阴影灰色绘制,平躺在地面上,中心位于图形的脚下,图像和镜像与绘制对象相同。
设置
- 使用翻页动画着色器创建背面材质,把图集赋给
_SpriteSheet,并把_Flipbook设置为图集的列数和行数。像素画图集必须使用 Point 过滤。 - 把它赋给
CharacterType的 Back Sprite 字段。 - 可选,若要自定义计时的投球:填写 Back Sprite Timeline —— 单元格列表、每个单元格的秒数以及释放条目。留空则使用均匀投球。
- 在战斗中,
BattleManagerCharactersModule把材质和时间线交给该槽位的BattleTrainerSprite(SetMaterial(material, timeline))。
从卡带导入的游戏无需这些:导入器会根据卡带数据为每个玩家角色设置背面材质和计时表(参见导入的游戏与游戏配置文件)。
参考
| 类型 | 命名空间 | 作用 |
|---|---|---|
BattleTrainerSprite | OpenMon.MCE.Battle | 在战斗中绘制训练师并播放动画:投球、滑出、像素完美绘制、阴影 |
FlipbookTimeline | OpenMon.MCE.Battle | 投球的单元格、时间和释放条目 |
CharacterType | OpenMon.MCE.Characters | 持有 BackSprite 和 BackSpriteTimeline |