========================================================================
HOW I CREATE ANIMATIONS FOR TENNIS ELBOW 4
A complete workflow
As i will enter in med school in a few weeks i will not have the time to do animations anymore and i feel the need to share this, so as many people as possible
can also make animations.
========================================================================
By Pulsar
I'm not a programmer. To make custom player animations, I built a
semi-automated pipeline around Manutoo's Anim Modding SDK: the creative
work stays in 3ds Max, and custom scripts handle the repetitive technical
steps (Unity build, install, packaging) so I never have to touch code.
Tools used: After Effects (or any video editor), QuickMagic (AI mocap),
3ds Max, Unity 2019.4.27f1 (SDK), + my own one-click scripts by Claude.
########################################################################
STEP 1 — VIDEO REFERENCE
########################################################################
- Find a clip of the real player (slow motion or real time): full body in
frame, complete gesture (preparation -> contact -> follow-through).
- if the clip is in slow motion , speed it up to real time (i use After effect --->Time Stretch). If you skip
this, the mocap comes out in slow motion. A real forehand lasts ~1 second.
- Trim to just the stroke. Shorter clip = faster processing, cleaner result.
########################################################################
STEP 2 — QUICKMAGIC (AI motion capture)
########################################################################
Settings:
- Export format : BIP
- Frame rate : 30 FPS
- "In-place" : ON
- Features : Full Body + Hand
- Physics optimisation : 1x (raise it if the feet jitter)
=> download the .bip
Note: no AI mocap captures the racket — it isn't even in the skeleton.
That part is always done by hand (see step 4). This is normal; every
modder does it.
########################################################################
STEP 3 — LOAD ONTO THE GAME RIG IN 3DS MAX (zero retargeting)
########################################################################
The TE4 player rig IS a 3ds Max Biped, so a .bip drops straight onto it.
What you see in Max is what you get in game.
- Open <AnimSDK>\Player Models [Max]\PlayerMale-TE4.max
- SAVE AS a working copy immediately, so you never overwrite the base rig
- Select any Biped bone (press H -> Bip01)
- MOTION panel -> Biped rollout -> Figure Mode OFF -> Load File -> your .bip
- Press Play: the game character performs the motion
########################################################################
STEP 4 — REFINE (the actual craft)
########################################################################
Setup: 30 FPS, Real Time playback. Auto Key (N) must be ON (red border)
or your edits are not recorded.
ANIMATION LAYERS
Motion panel -> Layers rollout -> Create Layer.
Corrections sit on top of the mocap without destroying it, and you can
toggle them to compare.
/!\ COLLAPSE the layer BEFORE exporting, or the edits are lost.
KEY REDUCTION
Raw mocap has a key on every frame (jittery, and impossible to edit
cleanly). Keeping roughly one key every 5-10 frames makes the motion
smooth and your corrections actually interpolate.
FEET ON THE GROUND (leg IK)
Select a foot -> Key Info -> "Set Planted Key". The foot locks to the
floor, so lowering Bip01 makes the knees bend naturally.
WRIST / RACKET
For strokes, correct the RIGHT HAND (Bip01 R Hand) — not the racket.
Bone_rocket (the racket's parent bone, select the racket then press
Page Up) is your visual guide: it's parented to the hand, so it shows
exactly what the racket will do in game.
Per Manutoo's SDK notes, Bone_rocket itself is only meant to be animated
for gestures and return-waiting animations, where the racket spins in
the hand. Note the .bip never contains racket animation, so that has to
be keyed by hand.
/!\ IMPORTANT — HUMANOID CLAMPING: the engine caps total wrist excursion
at roughly 110 degrees. I measured a capture asking for 146 deg at
contact: 37 deg were silently clipped in game.
=> Put most of the racket rotation into FOREARM PRONATION (twist around
the arm's own axis), and keep wrist break under ~40-50 deg. Rule of
thumb: if the pose looks anatomically impossible in Max, the engine
will refuse it.
SNAP AT CONTACT
AI mocap smooths the fastest part of the swing. Cutting 2-3 frames just
BEFORE contact restores the whip. Don't touch the take-back or the end
of the follow-through — their relative slowness is what makes the
contact look fast.
ORIENTATION
If the character faces the wrong way: Motion -> "Move All Mode" -> 180 on Z.
(Never rotate the pelvis with Auto Key — that keys a single frame and the
character spins mid-animation.)
RE-BASING THE TIMELINE (after cutting frames at the start)
To bring [97..181] back to [0..84]:
Ctrl+A -> Dope Sheet -> Edit > Select All -> Time menu -> Select Time
(0..96) -> Delete Time. Then set timeline End = 84, and RECALCULATE the
contact frame (-97).
Before exporting, write down:
- the CONTACT frame (racket meets ball)
- for one-handed strokes, the frame where the LEFT HAND RELEASES the racket
########################################################################
STEP 5 — EXPORT THE FBX
########################################################################
- Make sure layers are collapsed (you're on "Original")
- File -> Export -> .FBX
- In the FBX dialog:
* Animation : checked
* Bake Animation : checked -> Start/End = the stroke range
* Resample All : checked
- Always export starting at frame 0, otherwise the contact frame shifts.
########################################################################
STEP 6 — INTO THE GAME (my one-click tools)
########################################################################
(I'm not shipping these — see the BONUS section at the end to build your
own in minutes with claude code.)
>>> EXPORT TO TE4
Takes the newest FBX and asks for:
- display name
- STROKE TYPE (28 options: forehand, slice, one/two-handed backhand,
volleys, smashes, serve, waiting poses, and all Slide-* / Stretch-*
variants)
- slow-motion factor (1 if already real time)
- CONTACT frame
- for one-handed strokes: the frame where the left hand releases
It then builds the Unity AssetBundle the way the SDK expects (Humanoid
import, "Bake Into Pose" so the player doesn't slide, avatar copied from
the game model, LZ4 compression) and installs it into the mod folder.
>>> LINK ACCELERATION
Links a "regular" stroke to a dedicated power-shot animation, played
when the player accelerates. Pick base + acceleration by number or by
name. (This writes the m_Acceleration field; both animations stay
selectable on their own.)
>>> CHECK COMPRESSION (run before every release)
Verifies the bundles are LZ4 — the format consoles require. It asks
Unity to recompress to LZ4: if the file size doesn't change, it was
already correct.
Relaunch TE4 after a build, then pick the animation in Customize Character.
########################################################################
STEP 7 — PUBLISHING ON MOD.IO
########################################################################
Mod structure (same as Nard's):
<Mod Name>\
Mod.ini -> [Mod] then "Name= <Mod Name>"
AnimationBundles\
<bundle> + <bundle>.manifest (one pair per animation)
- The FOLDER NAME must match the Name in Mod.ini. (The mod.io page title
can be different.)
- Do NOT ship a CachedAnimList.ini — the game generates it. Shipping one
breaks registration on Mac.
- ZIP IT with Windows' own compressed folder:
right-click -> Show more options -> Send to -> Compressed folder.
- On mod.io: tag "Animation", platforms Windows + Mac + Linux + Xbox One
+ PlayStation 4, no dependencies.
########################################################################
THREE TRAPS THAT COST ME DAYS (thanks Manutoo for the first one)
########################################################################
1. BUNDLE COMPRESSION — bundles must be built with
BuildAssetBundleOptions.ChunkBasedCompression (LZ4), not None.
Symptom if wrong: works on PC and Mac; on console the mod downloads and
shows as "Installed", but the animation NEVER appears in the list.
Visible sign of a correct build: the bundle gets ~20% BIGGER.
Note: the UnityFS header flag reads 0x43 (LZ4HC) on everyone's bundles,
including working ones — it describes the blocksInfo, not the data.
Don't diagnose from it; recompress-test instead.
2. ZIP FORMAT — never PowerShell's Compress-Archive (writes backslash path
separators) and never 7-Zip (Unix host + data descriptors). Consoles
reject both. Use Windows' "Send to -> Compressed folder".
A working zip has: FAT/DOS host, no data descriptor, DOS attributes
(0x10 dirs / 0x20 files), no extra fields, forward-slash paths.
3. CACHEDANIMLIST — never ship it inside the zip (breaks Mac registration).
########################################################################
MODDABLE STROKE TYPES (what the game actually supports)
########################################################################
Baseline : Forehand, Forehand-Slice, Forehand-2H,
Backhand-1H, Backhand-2H, Backhand-Slice
Net : VolleyFH, VolleyBH, Smash, SmashBH
Serve : Service, ServiceWaiting, ReturnWaiting, ReturnWaiting&FlipR
Slide : Slide-Forehand, Slide-Forehand-Slice, Slide-Forehand-2H,
Slide-Backhand-1H, Slide-Backhand-2H, Slide-Backhand-Slice
Stretch : Stretch-Forehand, Stretch-Forehand-Slice, Stretch-Forehand-2H,
Stretch-Backhand-1H, Stretch-Backhand-2H, Stretch-Backhand-Slice,
Stretch-VolleyFH, Stretch-VolleyBH
Worth knowing:
- There is no separate "Lift/topspin" animation — the Lift button changes
ball physics and replays the normal Forehand. (Topspin in the ini files
is a player STAT, not an animation.)
- The power-shot variant is not a slot either: it's the m_Acceleration
field pointing at another animation by name.
- OffHandTarget values: "Free" (never held), "Reg" (held the whole
animation, for two-handed strokes), "Alt <frame> ; Free" (held until
<frame> then released — the correct format for one-handed backhands;
release ~3 frames before contact).
########################################################################
PHILOSOPHY
########################################################################
Let the software handle the plumbing, so the time goes where it matters:
making the gesture actually look like the real player.
========================================================================
########################################################################
BONUS — BUILD THESE TOOLS YOURSELF (with an AI coding assistant, ex : claude code for me)
########################################################################
I'm not shipping my scripts: they're wired to my own folder layout and
they'd rot the day the SDK changes. What I can give you is better — the
recipe. I built every one of them with Claude Code without writing a
single line myself, and any capable coding AI can do the same, PROVIDED
you hand it the constraints below. Those constraints are what took me
days to find; the AI has no way to know them on its own.
--- WHAT TO TELL IT FIRST ---
Point the assistant at your SDK folder and give it the context:
- Unity version: 2019.4.27f1 (not 2020+)
- The Anim Modding SDK project path
- Where the game loads mods from:
%USERPROFILE%\AppData\LocalLow\Mana Games\Tennis Elbow 4\Mods\<YourMod>\AnimationBundles
- The player rig is a 3ds Max Biped (Bip01) and clips are imported as
Humanoid
--- THE PROMPT THAT MATTERS (the build script) ---
"Write a Unity Editor C# script for this project, callable in batch mode,
that turns an FBX animation into a Tennis Elbow 4 mod AssetBundle.
Requirements:
- Import the FBX with animationType = Human (Humanoid)
- avatarSetup = CopyFromOther, sourceAvatar = the Avatar inside
Assets/Players/TEM2 [TE2013]/Model/PlayerMale [TEM2].FBX.
Do NOT use CreateFromThisModel: a hand-exported FBX can come out at a
wrong scale/offset, the avatar then fails to build and the player is
invisible in game.
- Do NOT hardcode the take name. Read Importer.importedTakeInfos[0] and
compute firstFrame/lastFrame from startTime/stopTime * sampleRate
(exporters name the take differently and the real range varies).
- On the ModelImporterClipAnimation set lockRootRotation,
lockRootHeightY and lockRootPositionXZ to true ("Bake Into Pose").
Without this Unity extracts root motion from the centre of mass and
the player slides across the court / desyncs online.
- animationCompression = KeyframeReduction
- Duplicate the imported clip into a twin .anim asset named
<fbxFileName>~<clipName>.anim, next to the FBX
- Create a prefab named !List carrying a CAnimList component, with one
CAnimInfo: m_Name, m_Strike (the twin clip), m_Info ("<contact>
<contact>"), m_Author, and optionally m_Acceleration
- Write a sidecar text file <fbxFileName>~<clipName>.txt containing
'OffHandTarget: <value>'
- Put the prefab and the sidecar under
Assets/Animation/<Name>/<StrokeType>/ and give both the same
assetBundleName (lowercase)
- Build with BuildPipeline.BuildAssetBundles(...,
BuildAssetBundleOptions.ChunkBasedCompression,
BuildTarget.StandaloneWindows64)
ChunkBasedCompression = LZ4 and is MANDATORY: with any other option
the mod installs on consoles but the animation never registers.
- Copy the bundle and its .manifest into the game's mod folder, then
delete CachedAnimList.ini so the game rescans on next launch.
Read all parameters from a simple key=value text file so a .bat can
drive it."
--- THEN THE LAUNCHER ---
"Now write a .bat + PowerShell script that takes the newest FBX in
<my export folder>, asks me for the display name, the stroke type, the
contact frame (and for one-handed strokes, the frame where the left hand
releases the racket), writes the parameter file, and runs:
Unity.exe -batchmode -quit -projectPath <SDK> -executeMethod <Class>.<Method> -logFile <log>
then reads the log and tells me whether it worked."
--- HOW TO ACTUALLY GET IT WORKING ---
- Make it MEASURE, never assume. The single most productive instruction:
"compare my bundle with this one from a working mod and list every
difference". That is how the compression problem was finally found,
after days of wrong guesses.
- Give it real reference material. Point it at a working mod's bundle,
its Mod.ini and its .zip — it can read binary headers and diff them.
- Describe symptoms precisely when something fails: "the mod installs and
shows as Installed on PS5, but the animation never appears in the list,
while PC and Mac are fine" is a usable bug report. "It doesn't work" is
not.
- Ask it to write small throwaway probes: dump a clip's properties, dump
the CAnimList fields of a published bundle, parse a zip's central
directory. Those turn debugging from guesswork into measurement.
- Be sceptical of confident answers, including mine and the AI's. Several
plausible theories in my own debugging turned out to be wrong; only the
measurements held up.
- And when you are truly stuck: ask ManuTOO on the forum. He has the
console-side logs, and no amount of local analysis replaces that. He
solved in one line what I had been chasing for days.
########################################################################
For reference , i used claude code to make this entire pipeline , in only one context window , around 800k tokens burnt using opus5/fable 5

