Skip to content

Commit 6bcfbb3

Browse files
committed
feat(animation): enhance animation system with clip management and speed control
1 parent 054cc47 commit 6bcfbb3

5 files changed

Lines changed: 146 additions & 74 deletions

File tree

config/playgrounds/animation.jsonc

Lines changed: 86 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
"window": {
44
"title": "Default Game Window",
55
"size": {
6-
"width": 1920,
7-
"height": 1080
6+
"width": 1280,
7+
"height": 720
88
},
99
"fullscreen": false
1010
},
1111
"scene": {
1212
"renderer": {
1313
"sky": "assets/textures/sky.jpg",
14-
"postprocess": "assets/shaders/postprocess/vignette.frag"
14+
"postprocess": "assets/shaders/postprocess/dash-distortion.frag"
1515
},
1616
"assets": {
1717
"shaders": {
@@ -182,10 +182,22 @@
182182
"drone": "assets/gltf/mech_drone/mech_drone.glb",
183183
"map": "assets/gltf/map/map.glb",
184184
"gun": "assets/gltf/weapons/rifle.glb",
185-
"alistar": "assets/gltf/enemies/mundo/corporate_mundo.glb"
185+
"mundo": "assets/gltf/enemies/mundo/corporate_mundo.glb"
186186
}
187187
},
188188
"game": {
189+
"enemyHealthBars": {
190+
"enabled": true,
191+
"maxDistance": 22.0,
192+
"colors": {
193+
"low": [0.84, 0.18, 0.24, 0.96],
194+
"mid": [0.96, 0.76, 0.25, 0.96],
195+
"high": [0.28, 0.84, 0.45, 0.96],
196+
"border": [0.02, 0.03, 0.05, 0.92],
197+
"background": [0.12, 0.14, 0.18, 0.88],
198+
"highlight": [1.0, 1.0, 1.0, 0.12]
199+
}
200+
},
189201
"enemySpawner": {
190202
"spawnPoints": [
191203
[30.0, 0.0, 30.0],
@@ -281,15 +293,32 @@
281293
},
282294
{
283295
"type": "Free Camera Controller",
284-
"positionSensitivity": [8.0, 0.0, 8.0],
285-
"rotationSensitivity": 0.008,
286-
"speedupFactor": 2.5,
296+
"rotationSensitivity": 0.01,
297+
"fovSensitivity": 0.3
298+
},
299+
{
300+
"type": "PlayerMovement",
301+
"walkSpeed": 8.0,
302+
"sprintMultiplier": 2.0,
303+
"crouchSpeed": 4.0,
304+
"crouchHeight": 1.0,
305+
"slideSpeedMultiplier": 1.5,
306+
"slideDuration": 0.8,
307+
"slideCooldown": 0.5,
308+
"dashDistance": 5.0,
309+
"dashCooldown": 1.0,
287310
"groundLevel": 0.0,
288-
"playerHeight": 1.8
311+
"playerHeight": 2,
312+
"jumpForce": 9.0
289313
},
290314
{
291315
"type": "Player"
292316
},
317+
{
318+
"type": "Post Process Effects Component"
319+
}
320+
],
321+
"children": [
293322
{
294323
"type": "Health",
295324
"maxHealth": 100.0
@@ -301,68 +330,77 @@
301330
"height": 1.8
302331
},
303332
{
304-
"type": "light",
305-
"lightType": "spot",
306-
"color": [3, 3, 3],
307-
"attenuation": [1.0, 0.22, 0.2],
308-
"spotAngles": [13, 20]
309-
}
310-
],
311-
"children": [
312-
{
313-
"position": [1, -1, -1],
314-
"rotation": [45, 45, 0],
315-
"scale": [0.1, 0.1, 1.0],
333+
"position": [1, -1, -2],
334+
"rotation": [0, 0, 0],
335+
"scale": [0.3, 0.3, 0.3],
316336
"components": [
317337
{
318-
"type": "Mesh Renderer",
319-
"mesh": "cube",
320-
"material": "metal"
338+
"type": "Model Renderer",
339+
"model": "gun"
340+
}
341+
],
342+
"children": [
343+
{
344+
"position": [0, 0, 0],
345+
"components": [
346+
{
347+
"type": "light",
348+
"lightType": "spot",
349+
"color": [1, 0.8, 0.6],
350+
"attenuation": [1.0, 0.14, 0.07],
351+
"spotAngles": [15, 20]
352+
}
353+
]
321354
}
322355
]
323356
}
324357
]
325358
},
326359
{
327-
"name": "Ground",
328-
"position": [0.0, 0.0, 0.0],
329-
"rotation": [-90.0, 0.0, 0.0],
330-
"scale": [120.0, 120.0, 1.0],
331-
"components": [
332-
{
333-
"type": "Mesh Renderer",
334-
"mesh": "plane",
335-
"material": "ground"
336-
}
337-
]
338-
},
339-
{
340-
"position": [2, 4, -2],
341-
"scale": [5, 5, 5],
360+
"name": "map",
361+
"position": [0, 0, 0],
342362
"components": [
343363
{
344-
"type": "Mesh Renderer",
345-
"mesh": "monkey",
346-
"material": "monkey"
364+
"type": "Model Renderer",
365+
"model": "map"
347366
}
348367
]
349368
},
350369
{
351370
"name": "test enemy",
352371
"position": [10, 0, 10],
353-
"scale": [0.01, 0.01, 0.01],
372+
"scale": [0.05, 0.05, 0.05],
354373
"components": [
355374
{
356375
"type": "Model Renderer",
357-
"model": "alistar"
376+
"model": "mundo"
358377
},
359378
{
360379
"type": "Animation",
361-
"model": "alistar",
362-
"clip": "Run01",
363-
"loop": true,
364-
"speed": 1.0,
365-
"autoplay": true
380+
"model": "mundo",
381+
"default": "idle",
382+
"clips": {
383+
"idle": {
384+
"clip": "IdleVariant1",
385+
"loop": true,
386+
"speed": 1.0
387+
},
388+
"walk": {
389+
"clip": "Run01",
390+
"loop": true,
391+
"speed": 1.0
392+
},
393+
"attack": {
394+
"clip": "Attack1",
395+
"loop": false,
396+
"speed": 1.0
397+
},
398+
"die": {
399+
"clip": "Death",
400+
"loop": false,
401+
"speed": 1.0
402+
}
403+
}
366404
}
367405
]
368406
}

src/common/animation/animator.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <queue>
34
#include <unordered_map>
45
#include <vector>
56

@@ -10,8 +11,9 @@ namespace our {
1011
#define MAX_BONES 110
1112

1213
class Animator {
13-
const Animation* currentAnimation;
14+
const Animation* currentAnimation = nullptr;
1415
float currentTime; // in ticks
16+
float speed; // animation speed
1517
// there are 2 types of animations that assimp supports
1618
// 1. skeletal animations that affect the bones and are used for skinning meshes
1719
// 2. node animations that affect any node in the hierarchy but are not used for skinning
@@ -55,15 +57,17 @@ namespace our {
5557
public:
5658
Animator() : finalBoneMatrices(MAX_BONES, glm::mat4(1.0f)) {}
5759

58-
void play(const Animation* anim, bool loop = true) {
60+
// speed should be affected by the scale and movement speed of the real entity
61+
void play(const Animation* anim, bool loop = true, float speed = 1.0f) {
5962
currentAnimation = anim;
6063
currentTime = 0.0f;
6164
this->loop = loop;
65+
this->speed = speed;
6266
}
6367

6468
void update(float deltaTime) {
6569
if (!currentAnimation) return;
66-
currentTime += currentAnimation->ticksPerSecond * deltaTime;
70+
currentTime += currentAnimation->ticksPerSecond * speed * deltaTime;
6771
if (loop) {
6872
currentTime = std::fmod(currentTime, currentAnimation->duration);
6973
} else {

src/common/components/animation.cpp

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,53 @@ namespace our {
1313
<< "\033[0m" << std::endl;
1414
return;
1515
}
16-
autoplay = data.value("autoplay", autoplay);
17-
loop = data.value("loop", loop);
18-
speed = data.value("speed", speed);
19-
clip = data.value("clip", "");
20-
std::cout << "lodead animationComponent: " << clip << std::endl;
21-
if (!clip.empty()) {
22-
auto it = model->animations.find(clip);
23-
if (it != model->animations.end()) {
24-
if (autoplay) animator.play(&it->second, loop);
25-
} else {
26-
std::cerr << "\033[31mAnimation clip not found: " << clip << "\033[0m" << std::endl;
16+
17+
// deserializing clip:
18+
if (data.contains("clips")) {
19+
for (auto& [name, clipData] : data["clips"].items()) {
20+
AnimationClip clip;
21+
22+
if (!clipData.contains("clip")) {
23+
std::cerr << "\033[31mClip missing 'clip' field: " << name << "\033[0m" << std::endl;
24+
continue;
25+
}
26+
27+
clip.clip = clipData["clip"].get<std::string>();
28+
if (clipData.contains("loop")) clip.loop = clipData["loop"].get<bool>();
29+
if (clipData.contains("speed")) clip.speed = clipData["speed"].get<float>();
30+
clips[name] = clip;
31+
std::cout << "Loaded animation clip: " << name << " -> " << clip.clip << " (loop: " << clip.loop
32+
<< ", speed: " << clip.speed << ")" << std::endl;
2733
}
2834
}
35+
// default clip is the clip that will be played when the animation component is first initialized
36+
defaultClip = data.value("default", "");
37+
if (!defaultClip.empty()) {
38+
play(defaultClip);
39+
}
2940
}
3041

31-
void AnimationComponent::play(const std::string& clipName, bool loop) {
42+
void AnimationComponent::play(const std::string& clipName, float speedScale) {
3243
if (!model) {
3344
std::cerr << "\033[31mCannot play animation: model is not set\033[0m" << std::endl;
3445
return;
3546
}
36-
auto it = model->animations.find(clipName);
37-
if (it != model->animations.end()) {
38-
animator.play(&it->second, loop);
39-
} else {
40-
std::cerr << "\033[31mAnimation clip not found: " << clipName << "\033[0m" << std::endl;
47+
48+
auto clipIt = clips.find(clipName);
49+
if (clipIt == clips.end()) {
50+
std::cerr << "\033[31mClip not found: " << clipName << "\033[0m" << std::endl;
51+
return;
52+
}
53+
AnimationClip& clip = clipIt->second;
54+
auto animIt = model->animations.find(clip.clip);
55+
if (animIt == model->animations.end()) {
56+
std::cerr << "\033[31mAnimation not found in model: " << clip.clip << "\033[0m" << std::endl;
57+
return;
4158
}
59+
// speed scale is used so we can change the speed of the animation
60+
// depending on the movement speed of the entity
61+
// and the scale of the model (eg. a giant monster should have slower animations than a small character)
62+
animator.play(&animIt->second, clip.loop, clip.speed * speedScale);
4263
}
4364

4465
} // namespace our

src/common/components/animation.hpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,26 @@
77
#include "model/model.hpp"
88

99
namespace our {
10+
struct AnimationClip {
11+
std::string clip;
12+
bool loop = false;
13+
float speed = 1.0f;
14+
};
15+
1016
class AnimationComponent : public Component {
1117
public:
1218
Model* model;
1319
Animator animator;
14-
std::string clip;
15-
bool autoplay = false;
16-
bool loop = false;
17-
float speed = 1.0f;
20+
std::string defaultClip;
21+
// maps clip names to their corresponding animation clip data (animation name, loop, speed)
22+
std::unordered_map<std::string, AnimationClip> clips;
1823
static std::string getID() {
1924
return "Animation";
2025
}
2126

2227
void deserialize(const nlohmann::json& data) override;
2328

24-
void play(const std::string& clipName, bool loop = false);
29+
void play(const std::string& clipName, float speedScale = 1.0f);
2530
};
2631

2732
} // namespace our

src/common/systems/animation-system.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ namespace our {
99
for (auto entity : world->getEntities()) {
1010
AnimationComponent* animComp = entity->getComponent<AnimationComponent>();
1111
if (!animComp) continue;
12-
animComp->animator.update(deltaTime * animComp->speed);
12+
if (animComp->animator.isFinished()) {
13+
std::string defaultAnimation = animComp->defaultClip.empty() ? "idle" : animComp->defaultClip;
14+
animComp->play(defaultAnimation);
15+
}
16+
animComp->animator.update(deltaTime);
1317
}
1418
}
1519
} // namespace our

0 commit comments

Comments
 (0)