跳到主要内容

训练师背面精灵与投球

2D 战斗开始时,玩家角色(双打战斗中还有盟友训练师)背对镜头站在屏幕一角,投出球,然后滑出画面。这一切由一个组件绘制:BattleTrainerSprite,战斗场景中每个训练师槽位一个实例(AllyTrainerSprites、EnemyTrainerSprites)。

本页说明该精灵是如何制作的,以及组件为你做了什么,让投球在任何屏幕上都看起来正确。

什么是背面精灵​

角色的背面存储在其 CharacterType 资产上:

字段类型含义
BackSpriteMaterial角色的动画背面:一个包含帧图集(_SpriteSheet)的材质,按列乘行(_Flipbook)排列,由 MCE 的翻页动画着色器绘制
BackSpriteTimelineFlipbookTimeline投球的单元格及每个单元格持续的时间。引擎自带精灵为空

没有背面材质的角色类型(例如导入游戏的玩家)根本不会被绘制:渲染器会被关闭,而不是显示占位方块。

翻页动画及其时间线​

默认情况下,投球以均匀的节奏遍历图集的单元格。组件把 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 的子对象,以着色器的阴影灰色绘制,平躺在地面上,中心位于图形的脚下,图像和镜像与绘制对象相同。

设置​

  1. 使用翻页动画着色器创建背面材质,把图集赋给 _SpriteSheet,并把 _Flipbook 设置为图集的列数和行数。像素画图集必须使用 Point 过滤。
  2. 把它赋给 CharacterType 的 Back Sprite 字段。
  3. 可选,若要自定义计时的投球:填写 Back Sprite Timeline —— 单元格列表、每个单元格的秒数以及释放条目。留空则使用均匀投球。
  4. 在战斗中,BattleManagerCharactersModule 把材质和时间线交给该槽位的 BattleTrainerSprite(SetMaterial(material, timeline))。

从卡带导入的游戏无需这些:导入器会根据卡带数据为每个玩家角色设置背面材质和计时表(参见导入的游戏与游戏配置文件)。

参考​

类型命名空间作用
BattleTrainerSpriteOpenMon.MCE.Battle在战斗中绘制训练师并播放动画:投球、滑出、像素完美绘制、阴影
FlipbookTimelineOpenMon.MCE.Battle投球的单元格、时间和释放条目
CharacterTypeOpenMon.MCE.Characters持有 BackSprite 和 BackSpriteTimeline