[55] Motion And Emotion
Antorum
Players can now express themselves with a variety of emotes, like dancing, waving, flirting, etc.
It's an MMO staple, so we had to have it. Emotes are triggered via chat commands like /wave, /dance,
and /cheer, but there's also a little window for browsing them.
A character performing an iconic dance.
There are currently eight emotes: Wave, Cheer, Flex, Cry, Flirt, and three Dance variants. Each emote has one of two lifecycle types: one-shot or loop. One-shot emotes like Wave or Flex play once and then automatically stop after a set duration. Looping emotes like the dances keep playing until the player moves or performs another action.
Emotes are data-driven on the server side, defined in TOML just like everything else:
id = 3
name = "Wave"
command = "wave"
lifecycle = "one_shot"
duration_ticks = 180Adding new emotes is straightforward, aside from the animation issue I'll get to later. The client can request a list of all available emotes from the server, so the UI stays in sync automatically.
The whole thing is built on top of the activity system, which is the same bit of code that powers sitting, crafting, mining, and fishing. When an emote is triggered, the server sets the player's activity state and broadcasts the change. For one-shot emotes, it also schedules an automatic stop:
let state = ActivityState::Emote(EmoteActivityState { emote_id: emote.id });
activity_performer.set_state(state.clone());
if matches!(&emote.lifecycle, EmoteLifecycle::OneShot) {
let duration_ticks = emote
.duration_ticks
.unwrap_or(DEFAULT_ONE_SHOT_EMOTE_DURATION_TICKS);
pending_one_shot_stops
.entries
.insert(ent, current_tick + duration_ticks as u128);
}Looping emotes are instead cleared when the player's current interaction is cancelled, like by moving or starting a new action.
On the client side, when the activity state update arrives, the emote ID gets passed into the Unity Animator as a float parameter:
EmoteActivityState emoteActivityState = (EmoteActivityState)activity;
animationId = "IsEmoting";
emptyHandsForActivity = true;
emoteId = ConvertResourceIdToAnimatorFloat(emoteActivityState.EmoteId);This works, but it's not ideal. The animation controller is fairly static, so each emote clip has to be wired up manually through blend tree states keyed on that float value. There's no way to look up and play a clip by ID at runtime yet, so every new emote means another manual entry in the animator controller. I'd like to rework the character animation system to support that, but it hasn't happened yet.
Emotes also don't have any sound effects yet. When they do, the audio will most likely be driven by animation events on the clips themselves, so it shouldn't require any server-side changes.
The Cry emote in action.
In the future there will be plenty more emotes. Some cool ones will even be unlockable via quests.
Thanks for reading.
- Declan (@dooskington)