The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
animated.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2004 - 2025
3 by Philippe Plantier <ayin@anathas.org>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16/** @file animated.hpp
17 * Frame-based animation system with support for looping, acceleration, and time manipulation.
18 * The animation system uses two parallel timelines:
19 * - Normal timeline: Real wall-clock time for UI animations, effects, etc.
20 * - Accelerated timeline: Can be sped up/slowed down for unit movement, attacks, etc. */
21
22#pragma once
23
24#include <algorithm>
25#include <chrono>
26#include <vector>
27
28// ============================================================================
29// Global Animation Timeline Management
30// ============================================================================
31
32/** Updates both animation timelines.
33 * Should be called once per frame before advancing any animations.
34 * @param acceleration Multiplier for the accelerated timeline (1.0 = normal speed) */
35void update_animation_timers(double acceleration = 1.0);
36
37/** Gets the current time point for animations.
38 * @param uses_acceleration If true, returns accelerated timeline; otherwise normal timeline
39 * @return Current time point on the selected timeline */
40std::chrono::steady_clock::time_point get_current_animation_tick(bool uses_acceleration);
41
42
43// ============================================================================
44// Frame-Based Animation Template
45// ============================================================================
46
47template<typename T>
49{
50public:
51 typedef std::pair<std::chrono::milliseconds, T> frame_description;
52 typedef std::vector<frame_description> anim_description;
53
54 // ========================================================================
55 // Construction
56 // ========================================================================
57
58 /** Creates an animation with no frames; add_frame() populates it afterward.
59 * @param start_time Logical start time of the animation timeline (default 0ms) */
60 animated(const std::chrono::milliseconds& start_time = std::chrono::milliseconds{0});
61
62 /** Creates an animation with its frame list already built from cfg.
63 * @param cfg Sequence of (duration, value) pairs to build the frame list from
64 * @param start_time Logical start time of the animation timeline
65 * @param force_change See add_frame(). Applies to the whole sequence built from cfg. */
66 explicit animated(const anim_description& cfg,
67 const std::chrono::milliseconds& start_time = std::chrono::milliseconds{0},
68 bool force_change = false);
69
70 virtual ~animated() = default;
71
72 /** Appends a frame, starting where the previous one ends (or at start_time, if first).
73 * @param duration How long this frame should be displayed
74 * @param value The data/state for this frame
75 * @param force_change Forces is_static() false even if this is the only frame added so
76 * far; use when the frame's own value can change over time despite the frame count. */
77 void add_frame(const std::chrono::milliseconds& duration, const T& value, bool force_change = false);
78
79 // ========================================================================
80 // Playback Control
81 // ========================================================================
82
83 /** Anchors the timeline to the current tick, then begins playback.
84 * @param start_time_offset How far into the animation to start (0 = from beginning)
85 * @param cycles If true, loop back to the first frame on reaching the end; if false, stop there */
86 void start_animation(const std::chrono::milliseconds& start_time_offset, bool cycles = false);
87
88 /** Freezes the timeline in place. Playback continues from the same point via resume_animation(). */
90
91 /** Shifts the timeline forward by the time spent paused, then continues playback. */
93
94 /** Moves current_frame_index_ forward to match the current tick, one frame at a time
95 * (or in whole cycles, if far enough behind). Marks the animation finished() if it
96 * runs out of frames without cycles(). */
98
99 // ========================================================================
100 // Playback State Queries
101 // ========================================================================
102
103 /** True once the current tick has reached the current frame's end, meaning
104 * advance_to_current_frame() has a new frame to move to. */
105 bool need_update() const;
106
107 /** True if playback has run past the last frame, is not yet started, or has no frames.
108 * Always true for a cycling animation, since it never runs out. */
109 bool animation_finished() const;
110
111 /** True if the animation loops back to the first frame instead of stopping at the last. */
112 bool cycles() const { return cycles_; }
113
114 // ========================================================================
115 // Time Queries and Manipulation
116 // ========================================================================
117
118 /** Time since playback began, per get_playback_tick(). 0 if not started.
119 * Capped at the limit set by set_duration_limit(), if any. */
120 std::chrono::milliseconds get_elapsed_time() const;
121
122 /** Shifts the timeline so the current tick corresponds to the given animation time,
123 * then restarts frame tracking from the beginning to resync.
124 * Used to desynchronize otherwise-identical animations for visual variety.
125 * @param time e.g. 300ms starts the animation as if it had been playing for 300ms already */
126 void apply_time_offset(const std::chrono::milliseconds& time);
127
128 /** @param time Elapsed time past which animation_finished() reports true regardless of
129 * actual progress (0 = no limit). Does not affect frame playback, only that query. */
130 void set_duration_limit(const std::chrono::milliseconds& time);
131
132 /** get_end_time() minus get_begin_time(): how long one pass through all frames takes. */
133 std::chrono::milliseconds get_animation_duration() const;
134
135 /** Animation-time offset of the first frame's start. Default 0ms; see set_begin_time(). */
136 std::chrono::milliseconds get_begin_time() const;
137
138 /** Animation-time offset where the last frame ends.
139 * For 3 seconds of frames starting at 500ms (see get_begin_time()), this returns 3500ms. */
140 std::chrono::milliseconds get_end_time() const;
141
142 /** Rebases the timeline onto a new begin time, offsetting every frame's start_time_
143 * by the difference so their durations and order are unchanged. */
144 void set_begin_time(const std::chrono::milliseconds& new_begin_time);
145
146 // ========================================================================
147 // Frame Queries
148 // ========================================================================
149
150 /** Animation-time offset where the current frame starts. */
151 std::chrono::milliseconds get_current_frame_begin_time() const;
152
153 /** Animation-time offset where the current frame ends. */
154 std::chrono::milliseconds get_current_frame_end_time() const;
155
156 /** How far into its own duration the current frame is, clamped to [0, duration]. */
157 std::chrono::milliseconds get_time_in_current_frame() const;
158
159 const T& get_current_frame() const;
160 const T& get_first_frame() const;
161 const T& get_frame(std::size_t n) const;
162 const T& get_last_frame() const;
163
164 std::size_t get_frames_count() const;
165
166 // ========================================================================
167 // Optimization and Configuration
168 // ========================================================================
169
170 /** Clears the flag returned by is_static(). Needed because is_static_ is set purely from
171 * frame count: a single frame can still change visually over time through means this
172 * class doesn't track (e.g. unit_frame's interpolated offset/alpha/image parameters),
173 * in which case the owner must call this to keep need_update() reporting true. */
174 void force_change() { is_static_ = false; }
175
176 /** True when the single-unchanging-frame optimization is enabled: need_update() will
177 * always report false, since there is never a different frame to advance to.
178 * Set from add_frame()/the constructor when the animation has exactly one frame, unless
179 * force_change requests otherwise; cleared permanently by add_frame() past the first frame,
180 * or by force_change(). */
181 bool is_static() const { return is_static_; }
182
183 /** Selects which of the two global timelines get_current_animation_tick()
184 * reads for this animation: accelerated if true, normal if false. */
185 void set_uses_acceleration(bool uses_accel) { uses_acceleration_ = uses_accel; }
186
187 /** Which timeline this animation currently reads; see set_uses_acceleration(). */
188 bool uses_acceleration() const { return uses_acceleration_; }
189
190 static const T void_value_; // MSVC: the frame constructor below requires this to be public
191
192protected:
193 friend class unit_animation;
194
195 /** Drops whole frames from the front while their end time is still before starting_time,
196 * then advances the begin time by their durations. May overshoot starting_time if it
197 * falls inside a remaining frame, rather than split that frame.
198 * @param starting_time Animation time the new begin time should reach */
199 void remove_frames_until(const std::chrono::milliseconds& starting_time);
200
201 /** Reduces or extends the frame list so get_end_time() becomes ending_time: drops frames
202 * after the cut and shortens the one it falls in, extends the last frame if ending_time is
203 * later, or (if ending_time is before the current begin time) collapses to a zero-length
204 * first frame.
205 * @param ending_time Animation time the new end time should reach */
206 void set_end_time(const std::chrono::milliseconds& ending_time);
207
208private:
209 /** The tick get_elapsed_time() and get_time_in_current_frame() treat as "now": frozen at
210 * pause_tick_ while paused_, otherwise the live tick from get_current_animation_tick().
211 * Playback bookkeeping (start/pause/resume/advance) reads get_current_animation_tick()
212 * directly instead, since it needs the live clock even while paused_. */
213 std::chrono::steady_clock::time_point get_playback_tick() const;
214
215 // Internal frame representation
216 struct frame
217 {
218 std::chrono::milliseconds duration_; // How long this frame lasts
219 T value_; // The frame data
220 std::chrono::milliseconds start_time_; // When this frame starts (in animation time)
221 };
222
223 // Animation timeline configuration
224 std::chrono::milliseconds starting_frame_time_; // Logical start time of the animation
225 std::chrono::milliseconds max_animation_time_; // Optional duration cap (0 = unlimited)
226
227 // Frame storage and tracking
228 std::vector<frame> frames_; // All frames in sequence
229 std::size_t current_frame_index_; // Which frame is currently displayed
230
231 // Playback state
232 bool started_; // Whether values are initialized by start_animation()
233 bool paused_; // True while frozen at pause_tick_; see get_playback_tick()
234 bool finished_; // Set by advance_to_current_frame() when a non-cycling animation runs out of frames
235 bool cycles_; // Whether the animation loops/cycles
236 bool uses_acceleration_; // Which timeline to follow, normal or accelerated.
237
238 // Timeline synchronization
239 // The animation's position is tracked by relating animation time to real timeline ticks.
240 std::chrono::steady_clock::time_point animation_start_tick_; // Real tick corresponding to animation time 0 (can be adjusted).
241 std::chrono::steady_clock::time_point next_frame_tick_; // Real tick when next frame should display
242 std::chrono::steady_clock::time_point pause_tick_; // Real tick when pause_animation() was called
243
244 // Optimization flag
245 bool is_static_; // True if all frames identical (skip updates)
246};
247
248// NOTE: this needs to be down here or the templates won't build.
249#include "animated.tpp"
void update_animation_timers(double acceleration=1.0)
Updates both animation timelines.
Definition animated.cpp:38
std::chrono::steady_clock::time_point get_current_animation_tick(bool uses_acceleration)
Gets the current time point for animations.
Definition animated.cpp:49
bool is_static() const
True when the single-unchanging-frame optimization is enabled: need_update() will always report false...
Definition animated.hpp:181
std::chrono::milliseconds get_time_in_current_frame() const
How far into its own duration the current frame is, clamped to [0, duration].
animated(const anim_description &cfg, const std::chrono::milliseconds &start_time=std::chrono::milliseconds{0}, bool force_change=false)
Creates an animation with its frame list already built from cfg.
std::vector< frame > frames_
Definition animated.hpp:228
bool uses_acceleration() const
Which timeline this animation currently reads; see set_uses_acceleration().
Definition animated.hpp:188
void resume_animation()
Shifts the timeline forward by the time spent paused, then continues playback.
std::chrono::milliseconds get_begin_time() const
Animation-time offset of the first frame's start.
std::chrono::milliseconds get_current_frame_begin_time() const
Animation-time offset where the current frame starts.
virtual ~animated()=default
std::chrono::milliseconds max_animation_time_
Definition animated.hpp:225
bool uses_acceleration_
Definition animated.hpp:236
std::pair< std::chrono::milliseconds, T > frame_description
Definition animated.hpp:51
void set_uses_acceleration(bool uses_accel)
Selects which of the two global timelines get_current_animation_tick() reads for this animation: acce...
Definition animated.hpp:185
bool started_
Definition animated.hpp:232
std::chrono::steady_clock::time_point next_frame_tick_
Definition animated.hpp:241
void add_frame(const std::chrono::milliseconds &duration, const T &value, bool force_change=false)
Appends a frame, starting where the previous one ends (or at start_time, if first).
std::size_t current_frame_index_
Definition animated.hpp:229
void apply_time_offset(const std::chrono::milliseconds &time)
Shifts the timeline so the current tick corresponds to the given animation time, then restarts frame ...
void pause_animation()
Freezes the timeline in place.
const T & get_first_frame() const
bool is_static_
Definition animated.hpp:245
std::vector< frame_description > anim_description
Definition animated.hpp:52
void start_animation(const std::chrono::milliseconds &start_time_offset, bool cycles=false)
Anchors the timeline to the current tick, then begins playback.
std::chrono::steady_clock::time_point get_playback_tick() const
The tick get_elapsed_time() and get_time_in_current_frame() treat as "now": frozen at pause_tick_ whi...
const T & get_frame(std::size_t n) const
void set_end_time(const std::chrono::milliseconds &ending_time)
Reduces or extends the frame list so get_end_time() becomes ending_time: drops frames after the cut a...
void force_change()
Clears the flag returned by is_static().
Definition animated.hpp:174
std::chrono::steady_clock::time_point pause_tick_
Definition animated.hpp:242
bool finished_
Definition animated.hpp:234
bool need_update() const
True once the current tick has reached the current frame's end, meaning advance_to_current_frame() ha...
bool animation_finished() const
True if playback has run past the last frame, is not yet started, or has no frames.
std::chrono::milliseconds get_elapsed_time() const
Time since playback began, per get_playback_tick().
std::chrono::milliseconds starting_frame_time_
Definition animated.hpp:224
void remove_frames_until(const std::chrono::milliseconds &starting_time)
Drops whole frames from the front while their end time is still before starting_time,...
bool cycles_
Definition animated.hpp:235
const T & get_current_frame() const
bool paused_
Definition animated.hpp:233
std::size_t get_frames_count() const
void set_begin_time(const std::chrono::milliseconds &new_begin_time)
Rebases the timeline onto a new begin time, offsetting every frame's start_time_ by the difference so...
std::chrono::steady_clock::time_point animation_start_tick_
Definition animated.hpp:240
std::chrono::milliseconds get_animation_duration() const
get_end_time() minus get_begin_time(): how long one pass through all frames takes.
void set_duration_limit(const std::chrono::milliseconds &time)
static const T void_value_
Definition animated.hpp:190
std::chrono::milliseconds get_end_time() const
Animation-time offset where the last frame ends.
std::chrono::milliseconds get_current_frame_end_time() const
Animation-time offset where the current frame ends.
animated(const std::chrono::milliseconds &start_time=std::chrono::milliseconds{0})
Creates an animation with no frames; add_frame() populates it afterward.
const T & get_last_frame() const
bool cycles() const
True if the animation loops back to the first frame instead of stopping at the last.
Definition animated.hpp:112
void advance_to_current_frame()
Moves current_frame_index_ forward to match the current tick, one frame at a time (or in whole cycles...
const config * cfg
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
std::chrono::milliseconds duration_
Definition animated.hpp:218
std::chrono::milliseconds start_time_
Definition animated.hpp:220
static map_location::direction n