Skip to main content

Trainer Back Sprites and the Throw

At the start of a 2D battle the player's character (and an ally trainer, in a double battle) stands in the corner of the screen with its back to the camera, throws the ball, and slides out of the picture. One component draws all of it: BattleTrainerSprite, one instance per trainer slot in the battle scene (AllyTrainerSprites, EnemyTrainerSprites).

This page explains how that sprite is authored and what the component does for you, so the throw looks right on any screen.

What a Back Sprite Is​

A character's back is stored on its CharacterType asset:

FieldTypeMeaning
BackSpriteMaterialThe animated back of the character: a material that holds a sheet of frames (_SpriteSheet) laid out in columns by rows (_Flipbook) and is drawn by MCE's flipbook shader
BackSpriteTimelineFlipbookTimelineThe cells of the throw and how long each one stays. Empty for the engine's own sprites

A character type with no back material (an imported game's player, for example) is simply not drawn: the renderer is switched off instead of showing the placeholder square.

The Flipbook and Its Timeline​

By default the throw walks the sheet's cells at an even pace. The component divides AnimationDuration (1 second at speed 1) evenly between the cells the material declares in _Flipbook (if it declares one cell or none, the serialized FlipbookSteps, 5 by default, is used), stops on the last frame instead of wrapping, and lets the ball go at 0.4 seconds.

That is fine for a sprite you draw yourself. A cartridge, though, holds some cells for a couple of ticks and one for fifty. A FlipbookTimeline records exactly that:

FieldMeaning
cellsThe cell to show in each entry
secondsHow long each entry lasts, at speed 1
releaseEntryThe entry in which the ball leaves the hand (negative for none)

The table is usable only when it has at least one entry, one time for every entry and every time above zero. Anything else (including the empty table a scene creates by default) is "not usable", and the trainer keeps the even flipbook.

With a usable table:

  • each cell is shown for its own time (the cell shown at second t is CellAt(t));
  • the ball is released when the release entry starts (ReleaseSeconds: the sum of the entries before it);
  • the whole throw lasts TotalSeconds, and the last cell is held after it.

You can build a table from a cartridge's numbers with FlipbookTimeline.FromTicks(cells, ticks, ticksPerSecond, releaseEntry). As an example, the HeartGold throw is 8, 16, 6, 2, 4, 2, 2, 52 and 2 ticks of 1/60 s, and the ball leaves when the fourth entry starts -- after 8 + 16 + 6 = 30 ticks, half a second in.

The Throw Runs on Its Own Clock​

The throw is not tied to the slide. ThrowAndSlide(speed, callback) starts both, but the throw advances in the component's own Update (TickThrow): it shows the table's cell for the elapsed time, calls your callback once, when the ball leaves, and ends when the table is over. The speed of the battle scales both.

That separation matters. At the original's slide speed the slide (0.45 s) is shorter than the throw (the ball only leaves at 0.5 s), so a throw driven by the slide's completion released the ball early and cut the animation. Other guarantees:

  • a throw that starts while another is running replaces it, and the replaced one never lets go of its ball;
  • if the trainer object is inactive and cannot update, the end of the slide releases the ball as a last resort, so the battle never waits forever.

The Slide Always Ends Just Off Screen​

After the throw the trainer slides out of the picture. Two things are worked out for you:

Speed. The slide is linear at SlideUnitsPerSecond world units a second (3.125 by default: the original's 5 DS pixels per frame). Set it to 0 to use the fixed SlideDuration (2 seconds at speed 1) instead.

Destination. The battle scene has a marker (SlideTarget) at one fixed x. A fixed x cannot be right for every screen: on a 16:9 picture the edge is much closer than on 21:9 or 32:9, where a trainer sliding to a fixed marker would stop inside the picture. So the destination is computed for the camera that draws the trainer: just past the edge of the screen on the marker's side, the figure's own reach (how far its drawing extends past its pivot toward that edge) plus a small margin (SlideExitMargin, 0.1 units) beyond it. Height and depth are the marker's. With a camera that is not orthographic, or on the 3D stage, the scene's marker decides.

Pixel-Perfect Drawing​

A back sheet made of pixel art, point-filtered and drawn at the size it was authored with, shows its pixels in two different widths (some take one more screen pixel than others), and the figure looks rough. For these sheets BattleTrainerSprite sets the size so that every pixel of the sheet covers a whole number of screen pixels (never less than one), and puts the figure's feet where the original had them:

  • the size is the nearest whole-pixel scale to the authored one (PixelPerfectHeight);
  • the height is chosen so that the last drawn row of the standing figure sits on the bottom edge of the screen (the empty rows under the first frame are counted from the texture and go below the edge, as the original's body runs off the bottom of the screen). A sheet whose texture cannot be read keeps the height the scene gave it;
  • the horizontal position is not touched -- the slide owns it.

The rule only applies when it makes sense: a point-filtered sheet, an orthographic camera and the 2D battle. Drawn art with bilinear filtering (a large illustrated back) has no pixels to keep whole and is left exactly as authored; so is the 3D stage. If the sheet goes away, the authored size comes back.

The calculation runs right before each camera draws, hooked to the render pipeline's own event (beginCameraRendering in URP), so it uses the camera's final zoom and not the value of an earlier frame.

On the 3D Stage​

When the battle uses the 3D stage, the trainer stands where the original puts it for the whole battle: its slides keep it in place, its idle frames loop on the game's clock, and it plays the throw when it sends out a monster (ThrowOnStage). The drawing is scaled so that one of its pixels is the stage's pixel size.

The Shadow​

The trainer gets a shadow like the monsters' have: a child of the drawing named Shadow, painted in the shader's shadow grey, lying on the ground with its center at the figure's feet, with the same image and mirroring as the drawing.

Setting It Up​

  1. Create the back Material with the flipbook shader, assign the sheet to _SpriteSheet, and set _Flipbook to the sheet's columns and rows. Pixel-art sheets must use Point filtering.
  2. Assign it to the Back Sprite field of the CharacterType.
  3. Optional, for a throw with its own timing: fill Back Sprite Timeline -- the list of cells, the seconds of each, and the release entry. Leave it empty for the even throw.
  4. In the battle, the BattleManagerCharactersModule hands the material and the timeline to the BattleTrainerSprite of the slot (SetMaterial(material, timeline)).

Games imported from a cartridge need none of this: the importers set the back material and the timing table of every player character from the cartridge data (see Imported Games and Game Profiles).

Reference​

TypeNamespaceRole
BattleTrainerSpriteOpenMon.MCE.BattleDraws and animates a trainer in battle: throw, slide, pixel-perfect drawing, shadow
FlipbookTimelineOpenMon.MCE.BattleCells, times and release entry of a throw
CharacterTypeOpenMon.MCE.CharactersHolds BackSprite and BackSpriteTimeline