Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
ECS_Systems_Animation.cpp
Go to the documentation of this file.
1/**
2 * @file ECS_Systems_Animation.cpp
3 * @brief Implementation of AnimationSystem
4 * @author Nicolas Chereau
5 * @date 2025
6 */
7
9#include "World.h"
10#include "GameEngine.h"
13#include "system/system_utils.h"
14
15// ========================================================================
16// Constructor
17// ========================================================================
18
20{
21 // Require both VisualAnimation_data and VisualSprite_data
23
24 //signature.set(World::Get().GetComponentID<VisualAnimation_data>());
25 //signature.set(World::Get().GetComponentID<VisualSprite_data>());
27}
28
29// ========================================================================
30// Main Update Loop
31// ========================================================================
32
34{
35 World& world = World::Get();
36
37 // Iterate through all entities with animation components
38 for (EntityID entity : m_entities)
39 {
40 if (!world.HasComponent<VisualAnimation_data>(entity) || !world.HasComponent<VisualSprite_data>(entity))
41 continue;
42
43 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
44 auto& spriteData = world.GetComponent<VisualSprite_data>(entity);
45
47 }
48}
49
50// ========================================================================
51// Update Single Entity
52// ========================================================================
53
55{
56 // Clear animation finished flag (it's only set for one frame)
57 animData.animationJustFinished = false;
58
59 // Check if animation is playing
60 if (!animData.isPlaying || animData.isPaused)
61 return;
62
63 // Resolve animation sequence pointer if needed
64 if (!animData.currentSequence)
65 {
67 {
68 // Failed to resolve - stop trying to avoid spam
69 animData.isPlaying = false;
70 return;
71 }
72 }
73
74 const Olympe::AnimationSequence* sequence = animData.currentSequence;
75 if (!sequence)
76 return;
77
78 // Check if using new spritesheet-based format or old frame-based format
79 bool useNewFormat = (!sequence->spritesheetId.empty() && sequence->frameCount > 0);
80 bool useOldFormat = (!sequence->frames.empty());
81
83 return; // No valid animation data
84
85 // Accumulate frame timer
86 float deltaTime = GameEngine::fDt * animData.playbackSpeed * sequence->speed;
87 animData.frameTimer += deltaTime;
88
89 if (useNewFormat)
90 {
91 // NEW FORMAT: Use spritesheet + frame range
92
93 // Get animation bank
95 if (!bank)
96 return;
97
98 // Get spritesheet
99 const OlympeAnimation::SpriteSheet* sheet = bank->GetSpriteSheet(sequence->spritesheetId);
100 if (!sheet)
101 {
102 SYSTEM_LOG << "AnimationSystem: Spritesheet not found: " << sequence->spritesheetId << "\n";
103 return;
104 }
105
106 // Check if frame duration has elapsed
107 if (animData.frameTimer >= sequence->frameDuration)
108 {
109 animData.frameTimer = 0.0f;
110 animData.currentFrame++;
111
112 int maxFrame = sequence->startFrame + sequence->frameCount - 1;
113
114 // Check if animation has finished
115 if (animData.currentFrame > maxFrame)
116 {
117 if (sequence->loop || animData.loop)
118 {
119 // Loop back to start
120 animData.currentFrame = sequence->startFrame;
121 animData.loopCount++;
122 }
123 else
124 {
125 // Animation finished
126 animData.currentFrame = maxFrame;
127 animData.animationJustFinished = true;
128
129 // Check for next animation
130 if (!sequence->nextAnimation.empty())
131 {
132 PlayAnimation(entity, sequence->nextAnimation, true);
133 return;
134 }
135 else
136 {
137 // Stay on last frame
138 animData.isPlaying = false;
139 }
140 }
141 }
142 }
143
144 // Calculate srcRect from spritesheet grid
145 int frameIndex = animData.currentFrame;
146 if (frameIndex < 0) frameIndex = 0;
147 if (frameIndex >= sheet->totalFrames) frameIndex = sheet->totalFrames - 1;
148
149 int row = frameIndex / sheet->columns;
150 int col = frameIndex % sheet->columns;
151
152 spriteData.srcRect.x = static_cast<float>(sheet->margin + col * (sheet->frameWidth + sheet->spacing));
153 spriteData.srcRect.y = static_cast<float>(sheet->margin + row * (sheet->frameHeight + sheet->spacing));
154 spriteData.srcRect.w = static_cast<float>(sheet->frameWidth);
155 spriteData.srcRect.h = static_cast<float>(sheet->frameHeight);
156
157 // Update hotspot
158 spriteData.hotSpot = { sheet->hotspot.x, sheet->hotspot.y };
159
160 // Load sprite texture if needed
161 if (!spriteData.sprite && !sheet->path.empty())
162 {
163 std::string textureId = animData.bankId + "_" + sequence->spritesheetId;
165 }
166 }
167 else
168 {
169 // OLD FORMAT: Use frame-by-frame data (backward compatibility)
170
171 // Get current frame data
172 if (animData.currentFrame < 0 || animData.currentFrame >= static_cast<int>(sequence->frames.size()))
173 {
174 animData.currentFrame = 0;
175 }
176
177 const Olympe::AnimationFrame& currentFrame = sequence->frames[animData.currentFrame];
178
179 // Check if frame duration has elapsed
180 if (animData.frameTimer >= currentFrame.duration)
181 {
182 animData.frameTimer = 0.0f;
183 animData.currentFrame++;
184
185 // Check if animation has finished
186 if (animData.currentFrame >= static_cast<int>(sequence->frames.size()))
187 {
188 if (sequence->loop || animData.loop)
189 {
190 // Loop back to start
191 animData.currentFrame = 0;
192 animData.loopCount++;
193 }
194 else
195 {
196 // Animation finished
197 animData.currentFrame = static_cast<int>(sequence->frames.size()) - 1;
198 animData.animationJustFinished = true;
199
200 // Check for next animation
201 if (!sequence->nextAnimation.empty())
202 {
203 PlayAnimation(entity, sequence->nextAnimation, true);
204 return;
205 }
206 else
207 {
208 // Stay on last frame
209 animData.isPlaying = false;
210 }
211 }
212 }
213 }
214
215 // Update sprite srcRect with current frame
216 if (animData.currentFrame >= 0 && animData.currentFrame < static_cast<int>(sequence->frames.size()))
217 {
218 const Olympe::AnimationFrame& frame = sequence->frames[animData.currentFrame];
219 spriteData.srcRect = frame.srcRect;
220
221 // Update hotspot if specified
222 if (frame.hotSpot.x != 0.0f || frame.hotSpot.y != 0.0f)
223 {
224 spriteData.hotSpot.x = frame.hotSpot.x;
225 spriteData.hotSpot.y = frame.hotSpot.y;
226 }
227 }
228
229 // Load sprite texture if needed
230 if (!spriteData.sprite && !sequence->spritesheetPath.empty())
231 {
232 std::string textureId = animData.bankId + "_" + animData.currentAnimName;
233 spriteData.sprite = DataManager::Get().GetSprite(textureId, sequence->spritesheetPath);
234 }
235 }
236}
237
238// ========================================================================
239// Animation Resolution
240// ========================================================================
241
243{
244 if (animData.bankId.empty() || animData.currentAnimName.empty())
245 {
246 SYSTEM_LOG << "[AnimationSystem] ERROR: Empty bankId or animName for entity\n";
247 return false;
248 }
249
250 // Get animation sequence from AnimationManager
253
254 if (!sequence)
255 {
256 SYSTEM_LOG << "[AnimationSystem] ERROR: Animation '" << animData.currentAnimName
257 << "' not found in bank '" << animData.bankId << "'\n";
258 return false;
259 }
260
261 animData.currentSequence = sequence;
262 return true;
263}
264
265// ========================================================================
266// Public API Methods
267// ========================================================================
268
269void AnimationSystem::PlayAnimation(EntityID entity, const std::string& animName, bool restart)
270{
271 World& world = World::Get();
272
273 if (!world.HasComponent<VisualAnimation_data>(entity))
274 return;
275
276 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
277
278 // Check if animation is already playing
279 if (animData.currentAnimName == animName && !restart)
280 return;
281
282 // Set new animation
284 animData.currentFrame = 0;
285 animData.frameTimer = 0.0f;
286 animData.isPlaying = true;
287 animData.isPaused = false;
288 animData.loopCount = 0;
289 animData.animationJustFinished = false;
290
291 // Clear sequence pointer to force re-resolution
292 animData.currentSequence = nullptr;
293
294 // Resolve immediately
296}
297
299{
300 World& world = World::Get();
301
302 if (!world.HasComponent<VisualAnimation_data>(entity))
303 return;
304
305 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
306 animData.isPaused = true;
307}
308
310{
311 World& world = World::Get();
312
313 if (!world.HasComponent<VisualAnimation_data>(entity))
314 return;
315
316 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
317 animData.isPaused = false;
318}
319
321{
322 World& world = World::Get();
323
324 if (!world.HasComponent<VisualAnimation_data>(entity))
325 return;
326
327 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
328 animData.isPlaying = false;
329 animData.isPaused = false;
330 animData.currentFrame = 0;
331 animData.frameTimer = 0.0f;
332}
333
335{
336 World& world = World::Get();
337
338 if (!world.HasComponent<VisualAnimation_data>(entity))
339 return;
340
341 auto& animData = world.GetComponent<VisualAnimation_data>(entity);
342 animData.playbackSpeed = speed;
343}
Core animation data structures for 2D sprite animation system.
std::bitset< MAX_COMPONENTS > ComponentSignature
Definition ECS_Entity.h:31
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
Animation system for 2D sprite animation.
Core game engine class.
World and ECS Manager for Olympe Engine.
void StopAnimation(EntityID entity)
Stop animation playback.
void PauseAnimation(EntityID entity)
Pause animation playback.
void ResumeAnimation(EntityID entity)
Resume paused animation.
void PlayAnimation(EntityID entity, const std::string &animName, bool restart=false)
Play a specific animation on an entity.
void SetPlaybackSpeed(EntityID entity, float speed)
Set playback speed multiplier.
virtual void Process() override
bool ResolveAnimationSequence(VisualAnimation_data &animData)
Resolve animation sequence pointer from AnimationManager.
void UpdateEntity(EntityID entity, VisualAnimation_data &animData, VisualSprite_data &spriteData)
Update a single entity's animation.
static DataManager & Get()
Definition DataManager.h:87
Sprite * GetSprite(const std::string &id, const std::string &path, ResourceCategory category=ResourceCategory::GameEntity)
std::set< EntityID > m_entities
Definition ECS_Systems.h:42
ComponentSignature requiredSignature
Definition ECS_Systems.h:39
static float fDt
Delta time between frames in seconds.
Definition GameEngine.h:120
static AnimationManager & Get()
const Olympe::AnimationSequence * GetAnimationSequence(const std::string &bankId, const std::string &animName) const
Get animation sequence from a bank by name.
AnimationBank * GetBank(const std::string &bankName)
Core ECS manager and world coordinator.
Definition World.h:210
static World & Get()
Get singleton instance (short form)
Definition World.h:232
bool HasComponent(EntityID entity) const
Definition World.h:451
T & GetComponent(EntityID entity)
Definition World.h:438
Represents a single frame in an animation (DEPRECATED - use SpritesheetInfo + frame ranges)
SDL_FRect srcRect
Source rectangle in spritesheet (x, y, w, h)
SDL_FPoint hotSpot
Render offset (pivot point)
float duration
Duration of this frame in seconds.
Defines a complete animation sequence.
float speed
Speed multiplier.
ECS component for animated sprites.
std::string currentAnimName
#define SYSTEM_LOG