aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format3
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules4
-rw-r--r--Doxyfile12
-rwxr-xr-xasset/texture/circle.pngbin0 -> 509 bytes
-rw-r--r--asset/texture/img.pngbin92742 -> 11476 bytes
-rwxr-xr-xasset/texture/square.pngbin0 -> 162 bytes
-rw-r--r--asset/texture/test_ap43.pngbin0 -> 2394 bytes
-rw-r--r--contributing.md155
-rw-r--r--game/.crepe-root0
-rw-r--r--game/.gitignore1
-rw-r--r--game/CMakeLists.txt124
-rw-r--r--game/Config.h60
-rw-r--r--game/EngineConfig.h19
-rw-r--r--game/Events.h5
-rw-r--r--game/GameScene.cpp130
-rw-r--r--game/GameScene.h11
-rw-r--r--game/MoveCameraManualyScript.cpp23
-rw-r--r--game/MoveCameraManualyScript.h11
-rw-r--r--game/PreviewScene.cpp182
-rw-r--r--game/PreviewScene.h11
-rw-r--r--game/QuitScript.cpp21
-rw-r--r--game/QuitScript.h11
-rw-r--r--game/Random.cpp29
-rw-r--r--game/Random.h10
-rw-r--r--game/StartGameScript.cpp75
-rw-r--r--game/StartGameScript.h12
-rw-r--r--game/background/AquariumScript.cpp26
-rw-r--r--game/background/AquariumScript.h12
-rw-r--r--game/background/AquariumSubScene.cpp225
-rw-r--r--game/background/AquariumSubScene.h18
-rw-r--r--game/background/BackgroundSubScene.cpp37
-rw-r--r--game/background/BackgroundSubScene.h10
-rw-r--r--game/background/CMakeLists.txt9
-rw-r--r--game/background/ForestParallaxScript.cpp55
-rw-r--r--game/background/ForestParallaxScript.h17
-rw-r--r--game/background/ForestSubScene.cpp169
-rw-r--r--game/background/ForestSubScene.h15
-rw-r--r--game/background/HallwayScript.cpp70
-rw-r--r--game/background/HallwayScript.h13
-rw-r--r--game/background/HallwaySubScene.cpp167
-rw-r--r--game/background/HallwaySubScene.h24
-rw-r--r--game/background/StartSubScene.cpp527
-rw-r--r--game/background/StartSubScene.h20
-rw-r--r--game/coins/CoinPoolSubScene.cpp13
-rw-r--r--game/coins/CoinPoolSubScene.h11
-rw-r--r--game/coins/CoinScript.cpp71
-rw-r--r--game/coins/CoinScript.h14
-rw-r--r--game/coins/CoinSubScene.cpp64
-rw-r--r--game/coins/CoinSubScene.h12
-rw-r--r--game/coins/CoinSystemScript.cpp258
-rw-r--r--game/coins/CoinSystemScript.h107
-rw-r--r--game/enemy/BattleScript.cpp61
-rw-r--r--game/enemy/BattleScript.h26
-rw-r--r--game/enemy/EnemyBulletPool.cpp11
-rw-r--r--game/enemy/EnemyBulletPool.h11
-rw-r--r--game/enemy/EnemyBulletScript.cpp40
-rw-r--r--game/enemy/EnemyBulletScript.h11
-rw-r--r--game/enemy/EnemyBulletSubScene.cpp52
-rw-r--r--game/enemy/EnemyBulletSubScene.h10
-rw-r--r--game/enemy/EnemyConfig.h8
-rw-r--r--game/enemy/EnemyPool.cpp10
-rw-r--r--game/enemy/EnemyPool.h8
-rw-r--r--game/enemy/EnemyScript.cpp206
-rw-r--r--game/enemy/EnemyScript.h37
-rw-r--r--game/enemy/EnemySubScene.cpp117
-rw-r--r--game/enemy/EnemySubScene.h10
-rw-r--r--game/hud/HudConfig.h33
-rw-r--r--game/hud/HudScript.cpp98
-rw-r--r--game/hud/HudScript.h25
-rw-r--r--game/hud/HudSubScene.cpp68
-rw-r--r--game/hud/HudSubScene.h8
-rw-r--r--game/hud/SpeedScript.cpp42
-rw-r--r--game/hud/SpeedScript.h15
-rw-r--r--game/main.cpp27
-rw-r--r--game/makefile5
-rw-r--r--game/menus/BannerSubScene.cpp49
-rw-r--r--game/menus/BannerSubScene.h20
-rw-r--r--game/menus/ButtonNextMainMenuSubScript.cpp44
-rw-r--r--game/menus/ButtonNextMainMenuSubScript.h14
-rw-r--r--game/menus/ButtonReplaySubScript.cpp43
-rw-r--r--game/menus/ButtonReplaySubScript.h21
-rw-r--r--game/menus/ButtonSetMainMenuSubScript.cpp23
-rw-r--r--game/menus/ButtonSetMainMenuSubScript.h14
-rw-r--r--game/menus/ButtonSetShopSubScript.cpp17
-rw-r--r--game/menus/ButtonSetShopSubScript.h14
-rw-r--r--game/menus/ButtonShowCreditsSubScript.cpp20
-rw-r--r--game/menus/ButtonShowCreditsSubScript.h14
-rw-r--r--game/menus/ButtonSubScene.cpp266
-rw-r--r--game/menus/ButtonSubScene.h84
-rw-r--r--game/menus/FloatingWindowSubScene.cpp220
-rw-r--r--game/menus/FloatingWindowSubScene.h17
-rw-r--r--game/menus/IButtonScript.cpp32
-rw-r--r--game/menus/IButtonScript.h10
-rw-r--r--game/menus/IFloatingWindowScript.cpp22
-rw-r--r--game/menus/IFloatingWindowScript.h15
-rw-r--r--game/menus/MenusConfig.h16
-rw-r--r--game/menus/endgame/EndGameSubScene.cpp127
-rw-r--r--game/menus/endgame/EndGameSubScene.h9
-rw-r--r--game/menus/endgame/EndGameSubScript.cpp101
-rw-r--r--game/menus/endgame/EndGameSubScript.h18
-rw-r--r--game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp23
-rw-r--r--game/menus/mainmenu/ButtonTransitionPreviewSubScript.h12
-rw-r--r--game/menus/mainmenu/CreditsSubScene.cpp132
-rw-r--r--game/menus/mainmenu/CreditsSubScene.h9
-rw-r--r--game/menus/mainmenu/CreditsSubScript.cpp58
-rw-r--r--game/menus/mainmenu/CreditsSubScript.h18
-rw-r--r--game/menus/mainmenu/ITransitionScript.cpp30
-rw-r--r--game/menus/mainmenu/ITransitionScript.h15
-rw-r--r--game/menus/mainmenu/MainMenuConfig.h19
-rw-r--r--game/menus/mainmenu/MainMenuScene.cpp137
-rw-r--r--game/menus/mainmenu/MainMenuScene.h10
-rw-r--r--game/menus/mainmenu/TransitionStartSubScript.cpp16
-rw-r--r--game/menus/mainmenu/TransitionStartSubScript.h9
-rw-r--r--game/menus/shop/ButtonBuySelectBubbleScript.cpp34
-rw-r--r--game/menus/shop/ButtonBuySelectBubbleScript.h14
-rw-r--r--game/menus/shop/ButtonBuySelectBulletScript.cpp34
-rw-r--r--game/menus/shop/ButtonBuySelectBulletScript.h14
-rw-r--r--game/menus/shop/ShopLoadScript.cpp126
-rw-r--r--game/menus/shop/ShopLoadScript.h10
-rw-r--r--game/menus/shop/ShopMenuScene.cpp326
-rw-r--r--game/menus/shop/ShopMenuScene.h12
-rw-r--r--game/menus/shop/Shopconfig.h14
-rw-r--r--game/missile/AlertScript.cpp38
-rw-r--r--game/missile/AlertScript.h11
-rw-r--r--game/missile/AlertSubScene.cpp33
-rw-r--r--game/missile/AlertSubScene.h8
-rw-r--r--game/missile/MissilePool.cpp18
-rw-r--r--game/missile/MissilePool.h11
-rw-r--r--game/missile/MissileScript.cpp106
-rw-r--r--game/missile/MissileScript.h20
-rw-r--r--game/missile/MissileSubScene.cpp101
-rw-r--r--game/missile/MissileSubScene.h12
-rw-r--r--game/missile/SpawnEvent.cpp49
-rw-r--r--game/missile/SpawnEvent.h19
-rw-r--r--game/player/PlayerAudioScript.cpp62
-rw-r--r--game/player/PlayerAudioScript.h13
-rw-r--r--game/player/PlayerBulletPool.cpp11
-rw-r--r--game/player/PlayerBulletPool.h11
-rw-r--r--game/player/PlayerBulletScript.cpp41
-rw-r--r--game/player/PlayerBulletScript.h11
-rw-r--r--game/player/PlayerBulletSubScene.cpp52
-rw-r--r--game/player/PlayerBulletSubScene.h10
-rw-r--r--game/player/PlayerEndScript.cpp104
-rw-r--r--game/player/PlayerEndScript.h14
-rw-r--r--game/player/PlayerScript.cpp214
-rw-r--r--game/player/PlayerScript.h32
-rw-r--r--game/player/PlayerSubScene.cpp222
-rw-r--r--game/player/PlayerSubScene.h10
-rw-r--r--game/prefab/CMakeLists.txt6
-rw-r--r--game/prefab/ZapperObject.cpp118
-rw-r--r--game/prefab/ZapperObject.h40
-rw-r--r--game/prefab/ZapperPoolScript.cpp70
-rw-r--r--game/prefab/ZapperPoolScript.h33
-rw-r--r--game/prefab/ZapperPoolSubScene.cpp17
-rw-r--r--game/prefab/ZapperPoolSubScene.h19
-rw-r--r--game/preview/NpcScript.cpp29
-rw-r--r--game/preview/NpcScript.h8
-rw-r--r--game/preview/NpcSubScene.cpp65
-rw-r--r--game/preview/NpcSubScene.h10
-rw-r--r--game/preview/PrevPlayerScript.cpp157
-rw-r--r--game/preview/PrevPlayerScript.h32
-rw-r--r--game/preview/PrevPlayerSubScene.cpp87
-rw-r--r--game/preview/PrevPlayerSubScene.h10
-rw-r--r--game/preview/PreviewReplaySubScript.cpp55
-rw-r--r--game/preview/PreviewReplaySubScript.h24
-rw-r--r--game/preview/PreviewStartRecSubScript.cpp20
-rw-r--r--game/preview/PreviewStartRecSubScript.h11
-rw-r--r--game/preview/PreviewStopRecSubScript.cpp20
-rw-r--r--game/preview/PreviewStopRecSubScript.h11
-rw-r--r--game/preview/SmokeSubScene.cpp37
-rw-r--r--game/preview/SmokeSubScene.h10
-rw-r--r--game/scheduler/ObjectsScheduler.cpp120
-rw-r--r--game/scheduler/ObjectsScheduler.h34
-rwxr-xr-xgame/util/scrollgen36
-rw-r--r--game/workers/CollisionScript.cpp69
-rw-r--r--game/workers/CollisionScript.h12
-rw-r--r--game/workers/PanicFromPlayerScript.cpp55
-rw-r--r--game/workers/PanicFromPlayerScript.h8
-rw-r--r--game/workers/WorkerScript.cpp145
-rw-r--r--game/workers/WorkerScript.h8
-rw-r--r--game/workers/WorkersSubScene.cpp423
-rw-r--r--game/workers/WorkersSubScene.h20
m---------lib/fontconfig0
m---------lib/sdl20
m---------lib/sdl_image0
m---------lib/sdl_ttf0
-rw-r--r--mwe/ecs-homemade/inc/ComponentManager.hpp4
-rw-r--r--mwe/ecs-homemade/src/ComponentManager.cpp5
-rw-r--r--mwe/events/include/event.h4
-rw-r--r--mwe/events/include/eventHandler.h7
-rw-r--r--mwe/events/include/eventManager.h25
-rw-r--r--mwe/events/src/event.cpp5
-rw-r--r--mwe/events/src/eventManager.cpp5
-rw-r--r--mwe/events/src/loopManager.cpp6
-rw-r--r--mwe/events/src/uiObject.cpp6
-rw-r--r--mwe/events/src/window.cpp6
-rw-r--r--mwe/gameloop/include/gameObject.h5
-rw-r--r--mwe/gameloop/src/gameObject.cpp5
-rw-r--r--mwe/gameloop/src/loopManager.cpp10
-rw-r--r--mwe/gameloop/src/window.cpp6
-rw-r--r--mwe/resource-manager/main.cpp5
-rw-r--r--mwe/resource-manager/map_layer.cpp18
-rw-r--r--mwe/resource-manager/map_layer.h4
-rw-r--r--mwe/resource-manager/stb_image.h788
-rw-r--r--readme.md1
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/crepe/CMakeLists.txt6
-rw-r--r--src/crepe/Collider.cpp2
-rw-r--r--src/crepe/Collider.h16
-rw-r--r--src/crepe/Component.cpp12
-rw-r--r--src/crepe/Component.h39
-rw-r--r--src/crepe/ComponentManager.cpp34
-rw-r--r--src/crepe/ComponentManager.h148
-rw-r--r--src/crepe/Particle.cpp14
-rw-r--r--src/crepe/Particle.h23
-rw-r--r--src/crepe/Resource.cpp6
-rw-r--r--src/crepe/Resource.h30
-rw-r--r--src/crepe/api/AI.cpp98
-rw-r--r--src/crepe/api/AI.h132
-rw-r--r--src/crepe/api/Animator.cpp57
-rw-r--r--src/crepe/api/Animator.h106
-rw-r--r--src/crepe/api/Asset.cpp2
-rw-r--r--src/crepe/api/Asset.h4
-rw-r--r--src/crepe/api/AssetManager.cpp17
-rw-r--r--src/crepe/api/AssetManager.h62
-rw-r--r--src/crepe/api/AssetManager.hpp22
-rw-r--r--src/crepe/api/AudioSource.cpp15
-rw-r--r--src/crepe/api/AudioSource.h74
-rw-r--r--src/crepe/api/BehaviorScript.cpp8
-rw-r--r--src/crepe/api/BehaviorScript.h16
-rw-r--r--src/crepe/api/BehaviorScript.hpp17
-rw-r--r--src/crepe/api/BoxCollider.cpp11
-rw-r--r--src/crepe/api/BoxCollider.h24
-rw-r--r--src/crepe/api/Button.cpp11
-rw-r--r--src/crepe/api/Button.h60
-rw-r--r--src/crepe/api/CMakeLists.txt46
-rw-r--r--src/crepe/api/Camera.cpp12
-rw-r--r--src/crepe/api/Camera.h53
-rw-r--r--src/crepe/api/CircleCollider.cpp9
-rw-r--r--src/crepe/api/CircleCollider.h18
-rw-r--r--src/crepe/api/Color.cpp18
-rw-r--r--src/crepe/api/Color.h2
-rw-r--r--src/crepe/api/Components.h16
-rw-r--r--src/crepe/api/Config.h64
-rw-r--r--src/crepe/api/Engine.cpp68
-rw-r--r--src/crepe/api/Engine.h81
-rw-r--r--src/crepe/api/Engine.hpp12
-rw-r--r--src/crepe/api/Event.h125
-rw-r--r--src/crepe/api/EventHandler.h76
-rw-r--r--src/crepe/api/EventHandler.hpp2
-rw-r--r--src/crepe/api/GameObject.cpp27
-rw-r--r--src/crepe/api/GameObject.h49
-rw-r--r--src/crepe/api/GameObject.hpp4
-rw-r--r--src/crepe/api/IKeyListener.cpp19
-rw-r--r--src/crepe/api/IKeyListener.h49
-rw-r--r--src/crepe/api/IMouseListener.cpp29
-rw-r--r--src/crepe/api/IMouseListener.h72
-rw-r--r--src/crepe/api/KeyCodes.h25
-rw-r--r--src/crepe/api/LoopManager.cpp69
-rw-r--r--src/crepe/api/LoopManager.h119
-rw-r--r--src/crepe/api/LoopManager.hpp45
-rw-r--r--src/crepe/api/LoopTimer.cpp79
-rw-r--r--src/crepe/api/LoopTimer.h144
-rw-r--r--src/crepe/api/Metadata.h2
-rw-r--r--src/crepe/api/ParticleEmitter.cpp22
-rw-r--r--src/crepe/api/ParticleEmitter.h65
-rw-r--r--src/crepe/api/Rigidbody.cpp6
-rw-r--r--src/crepe/api/Rigidbody.h174
-rw-r--r--src/crepe/api/Scene.cpp14
-rw-r--r--src/crepe/api/Scene.h70
-rw-r--r--src/crepe/api/Scene.hpp19
-rw-r--r--src/crepe/api/Script.cpp78
-rw-r--r--src/crepe/api/Script.h272
-rw-r--r--src/crepe/api/Script.hpp89
-rw-r--r--src/crepe/api/Sprite.cpp33
-rw-r--r--src/crepe/api/Sprite.h139
-rw-r--r--src/crepe/api/Text.cpp27
-rw-r--r--src/crepe/api/Text.h60
-rw-r--r--src/crepe/api/Texture.cpp39
-rw-r--r--src/crepe/api/Texture.h75
-rw-r--r--src/crepe/api/Transform.cpp13
-rw-r--r--src/crepe/api/Transform.h21
-rw-r--r--src/crepe/api/UIObject.cpp8
-rw-r--r--src/crepe/api/UIObject.h25
-rw-r--r--src/crepe/api/Vector2.cpp33
-rw-r--r--src/crepe/api/Vector2.h94
-rw-r--r--src/crepe/api/Vector2.hpp186
-rw-r--r--src/crepe/facade/CMakeLists.txt6
-rw-r--r--src/crepe/facade/DB.cpp14
-rw-r--r--src/crepe/facade/DB.h2
-rw-r--r--src/crepe/facade/EventData.h54
-rw-r--r--src/crepe/facade/Font.cpp21
-rw-r--r--src/crepe/facade/Font.h42
-rw-r--r--src/crepe/facade/FontFacade.cpp44
-rw-r--r--src/crepe/facade/FontFacade.h34
-rw-r--r--src/crepe/facade/SDLContext.cpp482
-rw-r--r--src/crepe/facade/SDLContext.h367
-rw-r--r--src/crepe/facade/Sound.cpp56
-rw-r--r--src/crepe/facade/Sound.h77
-rw-r--r--src/crepe/facade/SoundContext.cpp32
-rw-r--r--src/crepe/facade/SoundContext.h62
-rw-r--r--src/crepe/facade/SoundHandle.h12
-rw-r--r--src/crepe/facade/Texture.cpp30
-rw-r--r--src/crepe/facade/Texture.h69
-rw-r--r--src/crepe/manager/CMakeLists.txt30
-rw-r--r--src/crepe/manager/ComponentManager.cpp103
-rw-r--r--src/crepe/manager/ComponentManager.h253
-rw-r--r--src/crepe/manager/ComponentManager.hpp (renamed from src/crepe/ComponentManager.hpp)106
-rw-r--r--src/crepe/manager/EventManager.cpp (renamed from src/crepe/api/EventManager.cpp)6
-rw-r--r--src/crepe/manager/EventManager.h (renamed from src/crepe/api/EventManager.h)59
-rw-r--r--src/crepe/manager/EventManager.hpp (renamed from src/crepe/api/EventManager.hpp)21
-rw-r--r--src/crepe/manager/LoopTimerManager.cpp91
-rw-r--r--src/crepe/manager/LoopTimerManager.h177
-rw-r--r--src/crepe/manager/Manager.cpp5
-rw-r--r--src/crepe/manager/Manager.h24
-rw-r--r--src/crepe/manager/Mediator.h41
-rw-r--r--src/crepe/manager/ReplayManager.cpp70
-rw-r--r--src/crepe/manager/ReplayManager.h96
-rw-r--r--src/crepe/manager/ResourceManager.cpp30
-rw-r--r--src/crepe/manager/ResourceManager.h78
-rw-r--r--src/crepe/manager/ResourceManager.hpp30
-rw-r--r--src/crepe/manager/SaveManager.cpp (renamed from src/crepe/api/SaveManager.cpp)82
-rw-r--r--src/crepe/manager/SaveManager.h (renamed from src/crepe/api/SaveManager.h)38
-rw-r--r--src/crepe/manager/SceneManager.cpp (renamed from src/crepe/api/SceneManager.cpp)22
-rw-r--r--src/crepe/manager/SceneManager.h (renamed from src/crepe/api/SceneManager.h)15
-rw-r--r--src/crepe/manager/SceneManager.hpp (renamed from src/crepe/api/SceneManager.hpp)12
-rw-r--r--src/crepe/manager/SystemManager.cpp66
-rw-r--r--src/crepe/manager/SystemManager.h93
-rw-r--r--src/crepe/manager/SystemManager.hpp44
-rw-r--r--src/crepe/system/AISystem.cpp187
-rw-r--r--src/crepe/system/AISystem.h81
-rw-r--r--src/crepe/system/AnimatorSystem.cpp44
-rw-r--r--src/crepe/system/AnimatorSystem.h8
-rw-r--r--src/crepe/system/AudioSystem.cpp64
-rw-r--r--src/crepe/system/AudioSystem.h51
-rw-r--r--src/crepe/system/CMakeLists.txt10
-rw-r--r--src/crepe/system/CollisionSystem.cpp601
-rw-r--r--src/crepe/system/CollisionSystem.h292
-rw-r--r--src/crepe/system/EventSystem.cpp9
-rw-r--r--src/crepe/system/EventSystem.h21
-rw-r--r--src/crepe/system/InputSystem.cpp225
-rw-r--r--src/crepe/system/InputSystem.h139
-rw-r--r--src/crepe/system/ParticleSystem.cpp121
-rw-r--r--src/crepe/system/ParticleSystem.h32
-rw-r--r--src/crepe/system/PhysicsSystem.cpp131
-rw-r--r--src/crepe/system/PhysicsSystem.h6
-rw-r--r--src/crepe/system/RenderSystem.cpp112
-rw-r--r--src/crepe/system/RenderSystem.h50
-rw-r--r--src/crepe/system/ReplaySystem.cpp54
-rw-r--r--src/crepe/system/ReplaySystem.h44
-rw-r--r--src/crepe/system/ScriptSystem.cpp46
-rw-r--r--src/crepe/system/ScriptSystem.h32
-rw-r--r--src/crepe/system/System.cpp4
-rw-r--r--src/crepe/system/System.h21
-rw-r--r--src/crepe/types.h16
-rw-r--r--src/crepe/util/AbsolutePosition.cpp20
-rw-r--r--src/crepe/util/AbsolutePosition.h29
-rw-r--r--src/crepe/util/CMakeLists.txt2
-rw-r--r--src/crepe/util/Log.cpp1
-rw-r--r--src/crepe/util/Log.h31
-rw-r--r--src/crepe/util/OptionalRef.h12
-rw-r--r--src/crepe/util/OptionalRef.hpp9
-rw-r--r--src/crepe/util/dbg.h21
-rw-r--r--src/doc/feature/animator_creation.dox0
-rw-r--r--src/doc/feature/bgm.dox22
-rw-r--r--src/doc/feature/config.dox61
-rw-r--r--src/doc/feature/gameobject.dox4
-rw-r--r--src/doc/feature/proxy.dox43
-rw-r--r--src/doc/feature/savemgr.dox80
-rw-r--r--src/doc/feature/scene.dox58
-rw-r--r--src/doc/feature/script.dox48
-rw-r--r--src/doc/feature/script_ecs.dox57
-rw-r--r--src/doc/feature/sfx.dox24
-rw-r--r--src/doc/features.dox59
-rw-r--r--src/doc/index.dox16
-rw-r--r--src/doc/internal/component.dox41
-rw-r--r--src/doc/internal/resource.dox12
-rw-r--r--src/doc/internal/style.dox9
-rw-r--r--src/doc/internal/system.dox26
-rw-r--r--src/doc/internals.dox10
-rw-r--r--src/doc/layout.xml35
-rw-r--r--src/doc/style.css33
-rw-r--r--src/example/AITest.cpp96
-rw-r--r--src/example/CMakeLists.txt8
-rw-r--r--src/example/asset_manager.cpp36
-rw-r--r--src/example/button.cpp43
-rw-r--r--src/example/gameloop.cpp7
-rw-r--r--src/example/loadfont.cpp51
-rw-r--r--src/example/rendering_particle.cpp103
-rw-r--r--src/example/replay.cpp89
-rw-r--r--src/example/savemgr.cpp44
-rw-r--r--src/test/AssetTest.cpp15
-rw-r--r--src/test/AudioTest.cpp170
-rw-r--r--src/test/CMakeLists.txt15
-rw-r--r--src/test/CollisionTest.cpp364
-rw-r--r--src/test/DBTest.cpp11
-rw-r--r--src/test/ECSTest.cpp296
-rw-r--r--src/test/EventTest.cpp169
-rw-r--r--src/test/InputTest.cpp337
-rw-r--r--src/test/LoopManagerTest.cpp78
-rw-r--r--src/test/LoopTimerTest.cpp97
-rw-r--r--src/test/OptionalRefTest.cpp9
-rw-r--r--src/test/ParticleTest.cpp150
-rw-r--r--src/test/PhysicsTest.cpp84
-rw-r--r--src/test/Profiling.cpp262
-rw-r--r--src/test/RenderSystemTest.cpp182
-rw-r--r--src/test/ReplayManagerTest.cpp38
-rw-r--r--src/test/ResourceManagerTest.cpp84
-rw-r--r--src/test/SaveManagerTest.cpp51
-rw-r--r--src/test/SceneManagerTest.cpp72
-rw-r--r--src/test/ScriptECSTest.cpp41
-rw-r--r--src/test/ScriptEventTest.cpp50
-rw-r--r--src/test/ScriptSaveManagerTest.cpp35
-rw-r--r--src/test/ScriptSceneTest.cpp30
-rw-r--r--src/test/ScriptTest.cpp125
-rw-r--r--src/test/ScriptTest.h40
-rw-r--r--src/test/ValueBrokerTest.cpp4
-rw-r--r--src/test/Vector2Test.cpp542
-rw-r--r--src/test/main.cpp22
420 files changed, 20727 insertions, 3332 deletions
diff --git a/.clang-format b/.clang-format
index 1ee37ec..9ebf218 100644
--- a/.clang-format
+++ b/.clang-format
@@ -24,6 +24,9 @@ AlignEscapedNewlines: DontAlign
BreakBeforeBinaryOperators: All
AlwaysBreakTemplateDeclarations: Yes
PackConstructorInitializers: CurrentLine
+# only option that doesn't result in copious indentation
+AlignAfterOpenBracket: BlockIndent
+SpaceBeforeCpp11BracedList: true
...
# vim: ft=yaml
diff --git a/.gitignore b/.gitignore
index 8bfb819..90dcffe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ CTestTestfile.cmake
_deps
CMakeUserPresets.json
compile.sh
+asset/jetpack_joyride
diff --git a/.gitmodules b/.gitmodules
index bd6e7f7..8155600 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -26,3 +26,7 @@
path = lib/whereami/lib
url = https://github.com/gpakosz/whereami
shallow = true
+[submodule "lib/fontconfig"]
+ path = lib/fontconfig
+ url = https://gitlab.freedesktop.org/fontconfig/fontconfig.git
+ shallow = true
diff --git a/Doxyfile b/Doxyfile
index e0a31df..78f486b 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -17,17 +17,21 @@ GENERATE_LATEX = NO
LAYOUT_FILE = src/doc/layout.xml
TAB_SIZE = 2
-HTML_INDEX_NUM_ENTRIES = 2
+HTML_INDEX_NUM_ENTRIES = 999
HTML_EXTRA_STYLESHEET = src/doc/style.css
+SHOW_HEADERFILE = NO
+DISABLE_INDEX = NO
+GENERATE_TREEVIEW = NO
-USE_MDFILE_AS_MAINPAGE = ./readme.md
REPEAT_BRIEF = NO
-INTERNAL_DOCS = YES
-EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
HIDE_UNDOC_NAMESPACES = YES
HIDE_UNDOC_CLASSES = YES
QUIET = YES
+WARNINGS = NO
+# set these to NO for user-only docs
+INTERNAL_DOCS = YES
+EXTRACT_PRIVATE = YES
diff --git a/asset/texture/circle.png b/asset/texture/circle.png
new file mode 100755
index 0000000..0a92ac7
--- /dev/null
+++ b/asset/texture/circle.png
Binary files differ
diff --git a/asset/texture/img.png b/asset/texture/img.png
index 43b1eca..649a3f1 100644
--- a/asset/texture/img.png
+++ b/asset/texture/img.png
Binary files differ
diff --git a/asset/texture/square.png b/asset/texture/square.png
new file mode 100755
index 0000000..d07ec98
--- /dev/null
+++ b/asset/texture/square.png
Binary files differ
diff --git a/asset/texture/test_ap43.png b/asset/texture/test_ap43.png
new file mode 100644
index 0000000..e758ed7
--- /dev/null
+++ b/asset/texture/test_ap43.png
Binary files differ
diff --git a/contributing.md b/contributing.md
index 77a2908..d217410 100644
--- a/contributing.md
+++ b/contributing.md
@@ -17,6 +17,23 @@ that you can click on to open them.
working/compiling version of the project
- Pull requests for new code include either automated tests for the new code or
an explanation as to why the code can not (reliably) be tested
+- Non-bugfix pull requests must be approved by at least 2 reviewers before being
+ merged
+- Pull requests should have the following labels (where appropriate)
+ |label|meaning|
+ |:-:|-|
+ |`fix me`|has feedback that should be resolved/discussed by its author|
+ |`review me`|needs additional reviewers (minimum of 2 per PR)|
+ |`do not review`|is actively being worked on or not ready for feedback|
+ |`high priority`|should be worked on before all the others|
+ - PRs start with the `review me` label
+ - Reviewers—
+ - Add the `fix me` label after adding comments
+ - Authors—
+ - Remove the `review me` label if the pull request has enough reviewers
+ - Add the `do not review` label while processing feedback / pushing
+ additional commits
+
<!--
- TODO: tagging / versions
-->
@@ -160,40 +177,28 @@ that you can click on to open them.
```
</td></tr></table></details>
- <details><summary>
- <code>using namespace</code> may not be used in header files (.h, .hpp), only
- in source files (.cpp).
+ <a href="https://en.cppreference.com/w/cpp/language/using_declaration">Using-declarations</a>
+ may not be used in header files (<code>.h</code>, <code>.hpp</code>), only in
+ source files (<code>.cpp</code>).
</summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>
example.h:
```cpp
namespace crepe {
- void foo();
+ std::string foo();
}
```
- example.cpp:
- ```cpp
- #include "example.h"
- using namespace crepe;
- void foo() {}
- ```
</td><td>
example.h:
```cpp
+ using namespace std;
+
namespace crepe {
- template <typename T>
- T foo();
+ string foo();
}
```
-
- example.hpp:
- ```cpp
- #include "example.h"
- using namespace crepe;
- template <typename T>
- T foo();
- ```
</td></tr></table></details>
- <details><summary>
@@ -495,6 +500,12 @@ that you can click on to open them.
</td></tr></table></details>
- <details><summary>
Ensure const-correctness
+
+ > [!IMPORTANT]
+ > C-style APIs that work on (possibly internal) references to structs can be
+ > called from const member functions in C++. If the compiler allows you to
+ > mark a function as `const` even though it has side effects, it should
+ > **not** be marked as `const`.
</summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>
```cpp
@@ -626,15 +637,21 @@ that you can click on to open them.
</summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>
```cpp
+ void Foo::bar() { }
+
void Foo::set_value(int value) {
this->value = value;
+ this->bar();
}
```
</td><td>
```cpp
+ void Foo::bar() { }
+
void Foo::set_value(int new_value) {
value = new_value;
+ bar();
}
```
</td></tr></table></details>
@@ -795,6 +812,49 @@ that you can click on to open them.
```
</td></tr></table></details>
- Do not implement new classes as singletons
+- <details><summary>
+ Retrieving the first or last indices for iterators with a known or expected
+ size should be done using <code>.front()</code> or <code>.back()</code>
+ instead of by index
+ </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>
+
+ ```cpp
+ vector<int> foo = { 1, 2, 3 };
+ int bar = foo.first();
+ ```
+ </td><td>
+
+ ```cpp
+ vector<int> foo = { 1, 2, 3 };
+ int bar = foo[0];
+ ```
+ </td></tr></table></details>
+- <details><summary>
+ Always explicitly check against <code>NULL</code> (for C APIs) or
+ <code>nullptr</code> (for C++ APIs) when checking if a pointer is valid
+ </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td>
+
+ ```cpp
+ string foo = "Hello world";
+ if (foo.c_str() == nullptr)
+ // ...
+
+ void * bar = malloc();
+ if (bar == NULL)
+ // ...
+ ```
+ </td><td>
+
+ ```cpp
+ string foo = "Hello world";
+ if (!foo.c_str())
+ // ...
+
+ void * bar = malloc();
+ if (!bar)
+ // ...
+ ```
+ </td></tr></table></details>
## CMakeLists-specific
@@ -823,6 +883,11 @@ that you can click on to open them.
parameter of `TEST()` / `TEST_F()` macro)
- Test source files match their suite name (or test fixture name in the case of
tests that use a fixture)
+- Tests that measure time or use delays must be [disabled][gtest-disable] (by
+ prepending `DISABLED_` to the suite or case name).
+
+ These tests will still be compiled, but will only run when the `test_main`
+ binary is run with the `--gtest_also_run_disabled_tests` flag.
# Structure
@@ -842,6 +907,8 @@ that you can click on to open them.
# Documentation
+[Doxygen commands](https://www.doxygen.nl/manual/commands.html)
+
- All documentation is written in U.S. English
- <details><summary>
Doxygen commands are used with a backslash instead of an at-sign.
@@ -927,6 +994,52 @@ that you can click on to open them.
Foo & operator=(Foo &&) = delete;
```
</td></tr></table></details>
+- Do not use markdown headings in Doxygen
+
+## Documenting features
+
+Engine features are small 'building blocks' that the user (game developer) may
+reference when building a game with the engine. Features do not necessarily map
+1-1 to engine components or systems. If a component or system has a single,
+distinct feature it should be named after that feature, not the component or
+system itself.
+
+The sources for these pages are located under `src/doc/feature/`, and have the
+following format:
+
+- A feature description which explains—
+ - the purpose and function of the feature (focus on what it enables or
+ achieves for the user)
+ - additional information about when to implement the feature, such as specific
+ use cases or scenarios
+- A list of 'see also' references to relevant classes and/or types
+- A **minimal** example to demonstrate how the feature is used. The example
+ should be written such that the following is clear to the reader:
+ - Which headers need to be included to utilize the feature
+ - *Why* the example works, not what is happening in the example
+ - Where is this code supposed to be called (e.g. inside scene/script
+ functions)
+ - Which restrictions should be kept in mind (e.g. copy/move semantics, max
+ component instances, speed considerations)
+
+Features should be documented as clear and concise as possible, so the following
+points should be kept in mind:
+
+- <details><summary>
+ If a page expands on an example from another page, directly reference the
+ other page using a cross-reference (`\ref`) in a `\note` block at the top of
+ the page.
+ </summary>
+
+ ```
+ \note This page builds on top of the example shown in \ref feature_script
+ ```
+ </details>
+- When explaining the usage of specific functions, qualify them such that
+ Doxygen is able to add a cross-reference or manually add a reference using the
+ `\ref` command.
+- Users will likely copy-paste examples as-is, so do this yourself to check if
+ the example code actually works!
# Libraries
@@ -934,4 +1047,8 @@ that you can click on to open them.
subdirectory
- When adding new submodules, please set the `shallow` option to `true` in the
[.gitmodules](./.gitmodules) file
+- When adding new libraries, please update the library version table in
+ [readme\.md](./readme.md)
+
+[gtest-disable]: https://google.github.io/googletest/advanced.html#temporarily-disabling-tests
diff --git a/game/.crepe-root b/game/.crepe-root
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/game/.crepe-root
diff --git a/game/.gitignore b/game/.gitignore
new file mode 100644
index 0000000..2bd69c0
--- /dev/null
+++ b/game/.gitignore
@@ -0,0 +1 @@
+asset
diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt
new file mode 100644
index 0000000..a94beca
--- /dev/null
+++ b/game/CMakeLists.txt
@@ -0,0 +1,124 @@
+cmake_minimum_required(VERSION 3.28)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
+set(CMAKE_BUILD_TYPE Release)
+project(game C CXX)
+
+add_subdirectory(../src crepe)
+
+add_executable(main)
+
+target_sources(main PUBLIC
+ # enemy
+ enemy/BattleScript.cpp
+ enemy/EnemyPool.cpp
+ enemy/EnemyBulletScript.cpp
+ enemy/EnemyBulletSubScene.cpp
+ enemy/EnemyBulletPool.cpp
+ enemy/EnemySubScene.cpp
+ enemy/EnemyScript.cpp
+
+ #background
+ background/AquariumSubScene.cpp
+ background/AquariumScript.cpp
+ background/BackgroundSubScene.cpp
+ background/ForestParallaxScript.cpp
+ background/ForestSubScene.cpp
+ background/HallwaySubScene.cpp
+ background/StartSubScene.cpp
+ background/HallwayScript.cpp
+
+ # mainscenes
+ GameScene.cpp
+ menus/shop/ShopMenuScene.cpp
+ menus/shop/ShopLoadScript.cpp
+ menus/shop/ButtonBuySelectBubbleScript.cpp
+ menus/shop/ButtonBuySelectBulletScript.cpp
+ menus/mainmenu/MainMenuScene.cpp
+ PreviewScene.cpp
+ main.cpp
+
+ # missile
+ missile/MissilePool.cpp
+ missile/MissileScript.cpp
+ missile/MissileSubScene.cpp
+ missile/AlertSubScene.cpp
+ missile/AlertScript.cpp
+ missile/SpawnEvent.cpp
+
+ #scheduling
+ scheduler/ObjectsScheduler.cpp
+
+ # Preview
+ preview/SmokeSubScene.cpp
+ preview/NpcSubScene.cpp
+ preview/NpcScript.cpp
+ preview/PrevPlayerSubScene.cpp
+ preview/PrevPlayerScript.cpp
+ preview/PreviewStopRecSubScript.cpp
+ preview/PreviewStartRecSubScript.cpp
+ preview/PreviewReplaySubScript.cpp
+
+ # scripts
+ GameScene.cpp
+ MoveCameraManualyScript.cpp
+ StartGameScript.cpp
+ QuitScript.cpp
+
+ # player
+ player/PlayerScript.cpp
+ player/PlayerSubScene.cpp
+ player/PlayerBulletPool.cpp
+ player/PlayerBulletScript.cpp
+ player/PlayerBulletSubScene.cpp
+ player/PlayerEndScript.cpp
+ player/PlayerAudioScript.cpp
+
+ # workers
+ workers/WorkersSubScene.cpp
+ workers/WorkerScript.cpp
+ workers/PanicFromPlayerScript.cpp
+ workers/CollisionScript.cpp
+
+ # menus
+ menus/BannerSubScene.cpp
+ menus/ButtonSubScene.cpp
+ menus/IButtonScript.cpp
+ menus/ButtonSetShopSubScript.cpp
+ menus/ButtonSetMainMenuSubScript.cpp
+ menus/ButtonReplaySubScript.cpp
+ menus/ButtonNextMainMenuSubScript.cpp
+ menus/FloatingWindowSubScene.cpp
+ menus/IFloatingWindowScript.cpp
+ menus/ButtonShowCreditsSubScript.cpp
+ menus/mainmenu/ButtonTransitionPreviewSubScript.cpp
+ menus/mainmenu/ITransitionScript.cpp
+ menus/mainmenu/TransitionStartSubScript.cpp
+ menus/mainmenu/CreditsSubScene.cpp
+ menus/mainmenu/CreditsSubScript.cpp
+ menus/endgame/EndGameSubScene.cpp
+ menus/endgame/EndGameSubScript.cpp
+
+ # coins
+ coins/CoinSubScene.cpp
+ coins/CoinPoolSubScene.cpp
+ coins/CoinSystemScript.cpp
+ coins/CoinScript.cpp
+
+ # hud
+ hud/HudSubScene.cpp
+ hud/HudScript.cpp
+ hud/SpeedScript.cpp
+
+ #random
+ Random.cpp
+)
+
+add_subdirectory(background)
+add_subdirectory(prefab)
+
+target_link_libraries(main PUBLIC crepe)
+target_include_directories(main PRIVATE .)
+
diff --git a/game/Config.h b/game/Config.h
new file mode 100644
index 0000000..d2b5fc4
--- /dev/null
+++ b/game/Config.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "types.h"
+
+static constexpr int SORT_IN_LAY_BACK_BACKGROUND = 3; // For all scenes
+static constexpr int SORT_IN_LAY_BACKGROUND = 4; // For all scenes
+static constexpr int SORT_IN_LAY_FORE_BACKGROUND = 5; // For all scenes
+static constexpr int SORT_IN_LAY_PARTICLES_BACKGROUND = 6; // For all scenes
+static constexpr int SORT_IN_LAY_COINS = 7; // Only for GameScene
+static constexpr int SORT_IN_LAY_OBSTACLES = 8; // Only for GameScene
+static constexpr int SORT_IN_LAY_WORKERS_BACK = 9; // Only for GameScene
+static constexpr int SORT_IN_LAY_PLAYER = 10; // Only for GameScene
+static constexpr int SORT_IN_LAY_WORKERS_FRONT = 12; // Only for GameScene
+static constexpr int SORT_IN_LAY_PARTICLES_FOREGROUND = 15; // Only for GameScene
+
+static constexpr int COLL_LAY_BOT_TOP = 1; // Only for GameScene
+static constexpr int COLL_LAY_BOT_LOW = 2; // Only for GameScene
+static constexpr int COLL_LAY_BOT_HIGH = 3; // Only for GameScene
+static constexpr int COLL_LAY_PLAYER = 4; // Only for GameScene
+static constexpr int COLL_LAY_WALL_FRAGS = 5; // Only for GameScene
+static constexpr int COLL_LAY_ZAPPER = 6; // Only for GameScene
+static constexpr int COLL_LAY_LASER = 7; // Only for GameScene
+static constexpr int COLL_LAY_MISSILE = 8; // Only for GameScene
+static constexpr int COLL_LAY_BULLET = 9; // Only for GameScene
+static constexpr int COLL_LAY_ENEMY = 10; // Only for GameScene
+static constexpr int COLL_LAY_PLAYER_BULLET = 11; // Only for GameScene
+
+static constexpr float GAME_HEIGHT = 800; // In game units
+static constexpr float HALLWAY_HEIGHT = 450; // In game units
+
+static constexpr float VIEWPORT_X = 1100; // In game units
+// 'GAME_HEIGHT' (below) should be replaced by '500' when game development is finished
+static constexpr float VIEWPORT_Y = 500; // In game units
+
+// Font settings
+static constexpr const char * FONT = "Jetpackia";
+static constexpr crepe::vec2 FONTOFFSET = {0, 0};
+
+// Amount of coins in game
+static constexpr const char * TOTAL_COINS_GAME = "total_coins_game";
+
+// Amount of coins in current run
+static constexpr const char * TOTAL_COINS_RUN = "total_coins_run";
+
+// Distance
+static constexpr const char * DISTANCE_GAME = "distance_game";
+static constexpr const char * DISTANCE_RUN = "distance_run";
+
+// Player config
+static constexpr const char * PLAYER_NAME = "player";
+static constexpr int PLAYER_SPEED = 7500; // In game units
+static constexpr float PLAYER_GRAVITY_SCALE = 2.2; // factor
+static constexpr float PLAYER_HELP_KICK_SCALE = 0.2; // factor
+static constexpr float PLAYER_HELP_KICK_MAX = 0.3; // factor
+
+static constexpr const char * CAMERA_NAME = "camera";
+// Jetpack particles
+static constexpr const char * JETPACK_PARTICLES = "jetpack_particles";
+
+static constexpr bool DISABLE_REPLAY = false;
diff --git a/game/EngineConfig.h b/game/EngineConfig.h
new file mode 100644
index 0000000..6a03a14
--- /dev/null
+++ b/game/EngineConfig.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "Config.h"
+
+#include <crepe/api/Config.h>
+
+static const crepe::Config ENGINE_CONFIG {
+ .log {
+ .level = crepe::Log::Level::DEBUG,
+ },
+ .physics {
+ // this division factor is now the amount of seconds it approximately takes to naturally
+ // fall from the ceiling to floor
+ .gravity = HALLWAY_HEIGHT / 0.5,
+ },
+ .window_settings {
+ .window_title = "Jetpack joyride clone",
+ },
+};
diff --git a/game/Events.h b/game/Events.h
new file mode 100644
index 0000000..cf0be68
--- /dev/null
+++ b/game/Events.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "api/Event.h"
+
+struct EndGameEvent : public crepe::Event {};
diff --git a/game/GameScene.cpp b/game/GameScene.cpp
new file mode 100644
index 0000000..7803c9d
--- /dev/null
+++ b/game/GameScene.cpp
@@ -0,0 +1,130 @@
+#include "GameScene.h"
+#include "Config.h"
+#include "StartGameScript.h"
+#include "coins/CoinPoolSubScene.h"
+#include "coins/CoinSystemScript.h"
+
+#include "background/BackgroundSubScene.h"
+#include "enemy/BattleScript.h"
+#include "enemy/EnemyBulletPool.h"
+#include "enemy/EnemyBulletSubScene.h"
+#include "enemy/EnemyPool.h"
+#include "enemy/EnemySubScene.h"
+#include "hud/HudScript.h"
+#include "hud/HudSubScene.h"
+#include "hud/SpeedScript.h"
+#include "menus/endgame/EndGameSubScene.h"
+#include "missile/MissilePool.h"
+#include "missile/SpawnEvent.h"
+#include "player/PlayerBulletPool.h"
+#include "player/PlayerBulletSubScene.h"
+#include "player/PlayerSubScene.h"
+#include "prefab/ZapperPoolSubScene.h"
+#include "scheduler/ObjectsScheduler.h"
+#include "workers/WorkersSubScene.h"
+
+#include <cmath>
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/Asset.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void GameScene::load_scene() {
+ BackgroundSubScene background(*this);
+
+ GameObject camera = new_object(CAMERA_NAME, "camera", vec2(650, 0));
+ camera.add_component<Camera>(
+ ivec2(990, 720), vec2(VIEWPORT_X, VIEWPORT_Y),
+ Camera::Data {
+ .bg_color = Color::BLACK,
+ }
+ );
+ //camera.add_component<BehaviorScript>().set_script<MoveCameraManualyScript>();
+ camera.add_component<BehaviorScript>().set_script<CoinSystemScript>();
+ camera.add_component<BehaviorScript>().set_script<HudScript>();
+ camera.add_component<BehaviorScript>().set_script<SpeedScript>();
+ camera.add_component<BehaviorScript>().set_script<BattleScript>();
+ camera.add_component<BehaviorScript>().set_script<MissileSpawnEventHandler>();
+ camera.add_component<BehaviorScript>().set_script<ObjectsScheduler>();
+
+ camera.add_component<Rigidbody>(Rigidbody::Data {});
+ AI & enemy_path_1 = camera.add_component<AI>(400);
+ enemy_path_1.make_oval_path(100, 100, camera.transform.position, 1.5708, true);
+ AI & enemy_path_2 = camera.add_component<AI>(400);
+ enemy_path_2.make_oval_path(100, 100, {0, 0}, 1.5708, true);
+ AI & enemy_path_3 = camera.add_component<AI>(400);
+ enemy_path_3.make_oval_path(100, 100, {0, 0}, 1.5708, true);
+ // camer.add_component<AI>
+ PlayerSubScene player(*this);
+ MissilePool missile_pool(*this);
+ WorkersSubScene workers(*this);
+
+ GameObject floor = new_object("floor", "game_world", vec2(0, 325));
+ floor.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_TOP,
+ });
+ floor.add_component<BoxCollider>(vec2(INFINITY, 200));
+ GameObject floor_low = new_object("floor_low", "game_world", vec2(0, 350));
+ floor_low.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_LOW,
+ });
+ floor_low.add_component<BoxCollider>(vec2(INFINITY, 200));
+ GameObject floor_high = new_object("floor_high", "game_world", vec2(0, 300));
+ floor_high.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_HIGH,
+ });
+ GameObject ceiling = new_object("ceiling", "game_world", vec2(0, -325));
+ ceiling.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_TOP,
+ });
+ ceiling.add_component<BoxCollider>(vec2(INFINITY, 200));
+
+ ZapperPoolSubScene {*this};
+
+ GameObject start_game_script = new_object("start_game_script", "script", vec2(0, 0));
+ start_game_script.add_component<BehaviorScript>().set_script<StartGameScript>();
+
+ //create coin pool
+ CoinPoolSubScene coin_system;
+ coin_system.create_coins(*this);
+ EnemyBulletPool enemy_bullet_pool;
+ enemy_bullet_pool.create_bullets(*this);
+ PlayerBulletPool player_bullet_pool;
+ player_bullet_pool.create_bullets(*this);
+ EnemyPool enemy_pool;
+ enemy_pool.create_enemies(*this);
+ HudSubScene hud;
+ hud.create(*this);
+
+ GameObject background_music = new_object("background_music", "audio", vec2(0, 0));
+ Asset background_music_asset {"asset/music/level.ogg"};
+ background_music.add_component<AudioSource>(background_music_asset);
+
+ GameObject boom_audio = new_object("boom_audio", "audio", vec2(0, 0));
+ Asset boom_audio_asset {"asset/sfx/window_smash.ogg"};
+ boom_audio.add_component<AudioSource>(boom_audio_asset);
+
+ EndGameSubScene endgamewindow;
+ endgamewindow.create(*this);
+}
+
+string GameScene::get_name() const { return "scene1"; }
diff --git a/game/GameScene.h b/game/GameScene.h
new file mode 100644
index 0000000..16e2919
--- /dev/null
+++ b/game/GameScene.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+#include <string>
+
+class GameScene : public crepe::Scene {
+public:
+ void load_scene();
+
+ std::string get_name() const;
+};
diff --git a/game/MoveCameraManualyScript.cpp b/game/MoveCameraManualyScript.cpp
new file mode 100644
index 0000000..9d75a75
--- /dev/null
+++ b/game/MoveCameraManualyScript.cpp
@@ -0,0 +1,23 @@
+#include "MoveCameraManualyScript.h"
+
+using namespace crepe;
+using namespace std;
+
+void MoveCameraManualyScript::init() {
+ subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ return this->keypressed(ev);
+ });
+}
+
+bool MoveCameraManualyScript::keypressed(const KeyPressEvent & event) {
+ if (event.key == Keycode::RIGHT) {
+ Transform & cam = this->get_components_by_name<Transform>("camera").front();
+ cam.position.x += 100;
+ return true;
+ } else if (event.key == Keycode::LEFT) {
+ Transform & cam = this->get_components_by_name<Transform>("camera").front();
+ cam.position.x -= 100;
+ return true;
+ }
+ return false;
+}
diff --git a/game/MoveCameraManualyScript.h b/game/MoveCameraManualyScript.h
new file mode 100644
index 0000000..5a09055
--- /dev/null
+++ b/game/MoveCameraManualyScript.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class MoveCameraManualyScript : public crepe::Script {
+public:
+ void init();
+
+private:
+ bool keypressed(const crepe::KeyPressEvent & event);
+};
diff --git a/game/PreviewScene.cpp b/game/PreviewScene.cpp
new file mode 100644
index 0000000..bc28192
--- /dev/null
+++ b/game/PreviewScene.cpp
@@ -0,0 +1,182 @@
+#include "PreviewScene.h"
+
+#include "Config.h"
+#include "background/AquariumSubScene.h"
+#include "background/ForestSubScene.h"
+#include "background/HallwaySubScene.h"
+#include "background/StartSubScene.h"
+#include "hud/HudScript.h"
+#include "hud/HudSubScene.h"
+#include "hud/SpeedScript.h"
+#include "menus/ButtonSubScene.h"
+#include "missile/MissilePool.h"
+#include "missile/SpawnEvent.h"
+#include "preview/NpcSubScene.h"
+#include "preview/PrevPlayerSubScene.h"
+#include "preview/SmokeSubScene.h"
+
+#include "missile/MissileSubScene.h"
+
+#include <cmath>
+#include <crepe/api/Animator.h>
+#include <crepe/api/Asset.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+
+#include <crepe/ValueBroker.h>
+#include <crepe/manager/SaveManager.h>
+#include <crepe/types.h>
+#include <iostream>
+
+using namespace crepe;
+using namespace std;
+
+void PreviewScene::load_scene() {
+
+ StartSubScene start;
+ HallwaySubScene hallway;
+ ForestSubScene forest;
+ AquariumSubScene aquarium;
+
+ float begin_x = 400;
+
+ begin_x = start.create(*this, begin_x);
+
+ begin_x = hallway.create(*this, begin_x, 1, Color::YELLOW);
+
+ begin_x = aquarium.create(*this, begin_x);
+
+ begin_x = hallway.create(*this, begin_x, 2, Color::GREEN);
+
+ GameObject camera = new_object("camera", "camera", vec2(650, 0));
+ camera.add_component<Camera>(
+ ivec2(990, 720), vec2(VIEWPORT_X, VIEWPORT_Y),
+ Camera::Data {
+ .bg_color = Color::BLACK,
+ }
+ );
+
+ camera.add_component<BehaviorScript>().set_script<MissileSpawnEventHandler>();
+ camera.add_component<BehaviorScript>().set_script<HudScript>();
+ camera.add_component<BehaviorScript>().set_script<SpeedScript>();
+
+ GameObject floor = new_object("floor", "game_world", vec2(0, 325));
+ floor.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_TOP,
+ });
+ floor.add_component<BoxCollider>(vec2(INFINITY, 200));
+ GameObject floor_low = new_object("floor_low", "game_world", vec2(0, 350));
+ floor_low.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_LOW,
+ });
+ floor_low.add_component<BoxCollider>(vec2(INFINITY, 200));
+ GameObject floor_high = new_object("floor_high", "game_world", vec2(0, 300));
+ floor_high.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_HIGH,
+ });
+ GameObject ceiling = new_object("ceiling", "game_world", vec2(0, -325));
+ ceiling.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = COLL_LAY_BOT_TOP,
+ });
+ ceiling.add_component<BoxCollider>(vec2(INFINITY, 200));
+
+ GameObject world = this->new_object("world", "TAG", vec2 {0, 0}, 0, 1);
+ world.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::STATIC,
+ .collision_layer = 100,
+ });
+
+ world.add_component<BoxCollider>(vec2(100, INFINITY), vec2(VIEWPORT_X, VIEWPORT_Y));
+ world.add_component<BoxCollider>(vec2(100, INFINITY), vec2(100, VIEWPORT_Y));
+
+ PrevPlayerSubScene player(*this);
+ NpcSubScene npc(*this);
+ SmokeSubScene smoke(*this);
+ MissilePool mpool(*this);
+
+ HudSubScene hud;
+ hud.create(*this);
+
+ const float Y_POS_BUTTONS = -220;
+ const float X_POS_BUTTONS = -150;
+ const float X_POS_BUTTONS_SPACING = 145;
+ ButtonSubScene button;
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "BACK",
+ .text_width = 60,
+ .position = {X_POS_BUTTONS, Y_POS_BUTTONS},
+ .script_type = ButtonSubScene::ScriptSelect::NEXT,
+ .button_type = ButtonSubScene::ButtonSelect::BACK,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = "Next button",
+ .sorting_layer_offset = 20,
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "START REC",
+ .text_width = 130,
+ .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING, Y_POS_BUTTONS},
+ .script_type = ButtonSubScene::ScriptSelect::PREVIEW_START,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = "Next button",
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::YELLOW,
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "STOP REC",
+ .text_width = 120,
+ .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING * 2, Y_POS_BUTTONS},
+ .script_type = ButtonSubScene::ScriptSelect::PREVIEW_STOP,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = "Next button",
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::BLUE,
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "REPLAY",
+ .text_width = 90,
+ .position = {X_POS_BUTTONS + X_POS_BUTTONS_SPACING * 3, Y_POS_BUTTONS},
+ .script_type = ButtonSubScene::ScriptSelect::PREVIEW_REPLAY,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = "Next button",
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::ORANGE,
+ }
+ );
+}
+
+string PreviewScene::get_name() const { return "preview scene"; }
diff --git a/game/PreviewScene.h b/game/PreviewScene.h
new file mode 100644
index 0000000..afe911e
--- /dev/null
+++ b/game/PreviewScene.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+#include <string>
+
+class PreviewScene : public crepe::Scene {
+public:
+ void load_scene();
+
+ std::string get_name() const;
+};
diff --git a/game/QuitScript.cpp b/game/QuitScript.cpp
new file mode 100644
index 0000000..0c9f55a
--- /dev/null
+++ b/game/QuitScript.cpp
@@ -0,0 +1,21 @@
+
+
+#include "QuitScript.h"
+
+#include <crepe/api/Event.h>
+#include <crepe/api/KeyCodes.h>
+
+using namespace crepe;
+
+bool QuitScript::on_event(const KeyPressEvent & ev) {
+ if (Keycode::ESCAPE == ev.key) {
+ trigger_event<ShutDownEvent>(ShutDownEvent {});
+ }
+ return false;
+}
+
+void QuitScript::init() {
+ subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ return this->on_event(ev);
+ });
+}
diff --git a/game/QuitScript.h b/game/QuitScript.h
new file mode 100644
index 0000000..b79a744
--- /dev/null
+++ b/game/QuitScript.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class QuitScript : public crepe::Script {
+private:
+ bool on_event(const crepe::KeyPressEvent & ev);
+
+public:
+ void init();
+};
diff --git a/game/Random.cpp b/game/Random.cpp
new file mode 100644
index 0000000..64cf1f3
--- /dev/null
+++ b/game/Random.cpp
@@ -0,0 +1,29 @@
+#include <cstdlib>
+
+#include "Random.h"
+
+float Random::f(float upper, float lower) {
+ float range = upper - lower;
+ float x = ((float) rand() / (float) (RAND_MAX)) * range;
+ return x + lower;
+}
+
+double Random::d(double upper, double lower) {
+ double range = upper - lower;
+ double x = ((double) rand() / (double) (RAND_MAX)) * range;
+ return x + lower;
+}
+
+int Random::i(int upper, int lower) {
+ int range = upper - lower;
+ int x = rand() % range;
+ return x + lower;
+}
+
+unsigned Random::u(unsigned upper, unsigned lower) {
+ unsigned range = upper - lower;
+ unsigned x = rand() % range;
+ return x + lower;
+}
+
+bool Random::b() { return rand() % 2; }
diff --git a/game/Random.h b/game/Random.h
new file mode 100644
index 0000000..94f98d2
--- /dev/null
+++ b/game/Random.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class Random {
+public:
+ static float f(float upper = 1.0, float lower = 0.0);
+ static double d(double upper = 1.0, double lower = 0.0);
+ static int i(int upper, int lower = 0);
+ static unsigned u(unsigned upper, unsigned lower = 0);
+ static bool b();
+};
diff --git a/game/StartGameScript.cpp b/game/StartGameScript.cpp
new file mode 100644
index 0000000..6d47e65
--- /dev/null
+++ b/game/StartGameScript.cpp
@@ -0,0 +1,75 @@
+#include "StartGameScript.h"
+#include "Config.h"
+#include "api/BehaviorScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+using namespace std;
+
+void StartGameScript::fixed_update(crepe::duration_t dt) {
+ Transform & player_transform = this->get_components_by_name<Transform>("player").front();
+ // Create hole in wall and activate panic lamp
+ if (player_transform.position.x > 75 && !this->created_hole) {
+ Sprite & lamp_sprite = this->get_components_by_name<Sprite>("start_end").back();
+ lamp_sprite.active = true;
+ Sprite & hole_sprite = this->get_components_by_name<Sprite>("start_hole").front();
+ hole_sprite.active = true;
+
+ RefVector<Rigidbody> frags_rg
+ = this->get_components_by_tag<Rigidbody>("wall_fragment");
+ RefVector<Sprite> frags_sprite = this->get_components_by_tag<Sprite>("wall_fragment");
+ for (Rigidbody & frag_rg : frags_rg) {
+ frag_rg.active = true;
+ }
+ for (Sprite & frag_sprite : frags_sprite) {
+ frag_sprite.active = true;
+ }
+
+ RefVector<ParticleEmitter> smoke_emitters
+ = this->get_components_by_name<ParticleEmitter>("smoke_particles");
+ for (ParticleEmitter & emitter : smoke_emitters) {
+ emitter.active = true;
+ }
+
+ AudioSource & boom_audio
+ = this->get_components_by_name<AudioSource>("boom_audio").front();
+ boom_audio.play();
+
+ BehaviorScript & player_audio_script
+ = this->get_components_by_name<BehaviorScript>("player_audio").front();
+ player_audio_script.active = true;
+
+ this->created_hole = true;
+ }
+
+ // Take jetpack from jetpack stand
+ if (player_transform.position.x > 275 && !this->took_jetpack) {
+ Animator & jetpack_stand_anim
+ = this->get_components_by_name<Animator>("start_begin").back();
+ jetpack_stand_anim.next_anim();
+ Sprite & jetpack_sprite = this->get_components_by_name<Sprite>("player").back();
+ jetpack_sprite.active = true;
+
+ AudioSource & background_music
+ = this->get_components_by_name<AudioSource>("background_music").front();
+ background_music.play(true);
+
+ this->took_jetpack = true;
+ }
+
+ // Start camera movement, enable player jumping and disable this script
+ if (player_transform.position.x > 500) {
+ Rigidbody & rb = this->get_components_by_name<Rigidbody>("camera").front();
+ rb.data.linear_velocity = vec2(PLAYER_SPEED * 0.02, 0);
+ BehaviorScript & player_script
+ = this->get_components_by_name<BehaviorScript>("player").front();
+ player_script.active = true;
+ BehaviorScript & this_script
+ = this->get_components_by_name<BehaviorScript>("start_game_script").front();
+ this_script.active = false;
+ }
+}
diff --git a/game/StartGameScript.h b/game/StartGameScript.h
new file mode 100644
index 0000000..ad62e1a
--- /dev/null
+++ b/game/StartGameScript.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class StartGameScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ bool created_hole = false;
+ bool took_jetpack = false;
+};
diff --git a/game/background/AquariumScript.cpp b/game/background/AquariumScript.cpp
new file mode 100644
index 0000000..e698e3a
--- /dev/null
+++ b/game/background/AquariumScript.cpp
@@ -0,0 +1,26 @@
+#include "AquariumScript.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void AquariumScript::fixed_update(crepe::duration_t dt) {
+ Transform & trans_cam = this->get_components_by_name<Transform>("camera").front();
+
+ float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2;
+
+ if (cam_left_x > this->start_x + this->lenght) {
+ //Move whole background 12000 to the right
+ RefVector<Transform> trans = this->get_components_by_tag<Transform>("background_aqua");
+ for (Transform & tran : trans) {
+ tran.position.x += 12000;
+ }
+ this->start_x += 12000;
+ }
+}
diff --git a/game/background/AquariumScript.h b/game/background/AquariumScript.h
new file mode 100644
index 0000000..b068628
--- /dev/null
+++ b/game/background/AquariumScript.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class AquariumScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ float start_x = 10200;
+ const float lenght = 3000;
+};
diff --git a/game/background/AquariumSubScene.cpp b/game/background/AquariumSubScene.cpp
new file mode 100644
index 0000000..2a07daf
--- /dev/null
+++ b/game/background/AquariumSubScene.cpp
@@ -0,0 +1,225 @@
+#include "AquariumSubScene.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+float AquariumSubScene::create(Scene & scn, float begin_x) {
+ this->add_background(scn, begin_x);
+
+ GameObject aquarium_begin
+ = scn.new_object("aquarium_begin", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_begin_asset {"asset/background/aquarium/glassTubeFG_1_TVOS.png"};
+ aquarium_begin.add_component<Sprite>(
+ aquarium_begin_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ GameObject aquarium_middle_1
+ = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_middle_1_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"};
+ aquarium_middle_1.add_component<Sprite>(
+ aquarium_middle_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 2,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 400;
+
+ this->add_background(scn, begin_x - 200);
+ this->add_bubbles(aquarium_middle_1, vec2(-400, 300), 2, 0.7f);
+ this->add_bubbles(aquarium_middle_1, vec2(-100, 300), 4, 1.0f);
+ this->add_bubbles(aquarium_middle_1, vec2(500, 300), 4, 0.9f);
+
+ GameObject aquarium_middle_2
+ = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_middle_2_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"};
+ aquarium_middle_2.add_component<Sprite>(
+ aquarium_middle_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 3,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 400;
+
+ this->add_bubbles(aquarium_middle_2, vec2(300, 300), 2, 0.6f);
+
+ GameObject aquarium_middle_3
+ = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_middle_3_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"};
+ aquarium_middle_3.add_component<Sprite>(
+ aquarium_middle_3_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 4,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 400;
+
+ this->add_background(scn, begin_x - 200);
+
+ GameObject aquarium_middle_4
+ = scn.new_object("aquarium_middle", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_middle_4_asset {"asset/background/aquarium/glassTubeFG_3_TVOS.png"};
+ aquarium_middle_4.add_component<Sprite>(
+ aquarium_middle_4_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ this->add_background(scn, begin_x);
+ this->add_bubbles(aquarium_middle_4, vec2(175, 300), 4, 1.0f);
+ this->add_bubbles(aquarium_middle_4, vec2(200, 300), 4, 0.7f);
+
+ GameObject aquarium_end
+ = scn.new_object("aquarium_end", "background_aqua", vec2(begin_x, 0));
+ Asset aquarium_end_asset {"asset/background/aquarium/glassTubeFG_2_TVOS.png"};
+ aquarium_end.add_component<Sprite>(
+ aquarium_end_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ return begin_x;
+}
+
+void AquariumSubScene::add_background(Scene & scn, float begin_x) {
+ GameObject bg_1 = scn.new_object("aquarium_bg_1", "background_aqua", vec2(begin_x, 0));
+ Asset bg_1_1_asset {"asset/background/aquarium/AquariumBG1_1_TVOS.png"};
+ bg_1.add_component<Sprite>(
+ bg_1_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 400),
+ .position_offset = vec2(-200, 100),
+ }
+ );
+ Asset bg_1_2_asset {"asset/background/aquarium/AquariumBG1_2_TVOS.png"};
+ bg_1.add_component<Sprite>(
+ bg_1_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 400),
+ .position_offset = vec2(200, 100),
+ }
+ );
+ GameObject bg_2 = scn.new_object("aquarium_bg_2", "background_aqua", vec2(begin_x, 0));
+ Asset bg_2_1_asset {"asset/background/aquarium/AquariumBG2_1_TVOS.png"};
+ bg_2.add_component<Sprite>(
+ bg_2_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 3,
+ .size = vec2(0, 400),
+ .position_offset = vec2(200, -50),
+ }
+ );
+ Asset bg_2_2_asset {"asset/background/aquarium/AquariumBG2_2_TVOS.png"};
+ bg_2.add_component<Sprite>(
+ bg_2_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 3,
+ .size = vec2(0, 400),
+ .position_offset = vec2(-200, -50),
+ }
+ );
+ GameObject bg_3 = scn.new_object("aquarium_bg_3", "background_aqua", vec2(begin_x, 0));
+ Asset bg_3_1_asset {"asset/background/aquarium/AquariumBG3_1_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 400),
+ .position_offset = vec2(200, -200),
+ }
+ );
+ Asset bg_3_2_asset {"asset/background/aquarium/AquariumBG3_2_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 400),
+ .position_offset = vec2(-200, -200),
+ }
+ );
+}
+
+void AquariumSubScene::add_bubbles(
+ GameObject & obj, vec2 offset, int order_in_layer, float scale
+) {
+ Sprite & sprite = obj.add_component<Sprite>(
+ Asset {"asset/background/aquarium/bubble.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = order_in_layer,
+ .size = vec2(0, 12.5),
+ .scale_offset = scale,
+ }
+ );
+ obj.add_component<ParticleEmitter>(
+ sprite,
+ ParticleEmitter::Data {
+ .offset = offset,
+ .max_particles = 20,
+ .emission_rate = 1.2,
+ .min_speed = 50,
+ .max_speed = 100,
+ .min_angle = 265,
+ .max_angle = 275,
+ .force_over_time = vec2(0, -50),
+ }
+ );
+ Sprite & sprite_small = obj.add_component<Sprite>(
+ Asset {"asset/background/aquarium/bubble.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = order_in_layer,
+ .size = vec2(0, 7.5),
+ .scale_offset = scale,
+ }
+ );
+ obj.add_component<ParticleEmitter>(
+ sprite_small,
+ ParticleEmitter::Data {
+ .offset = offset,
+ .max_particles = 20,
+ .emission_rate = 0.8,
+ .min_speed = 50,
+ .max_speed = 100,
+ .min_angle = 265,
+ .max_angle = 275,
+ .force_over_time = vec2(0, -50),
+ }
+ );
+}
diff --git a/game/background/AquariumSubScene.h b/game/background/AquariumSubScene.h
new file mode 100644
index 0000000..9dbb04e
--- /dev/null
+++ b/game/background/AquariumSubScene.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <crepe/types.h>
+
+namespace crepe {
+class Scene;
+class GameObject;
+} // namespace crepe
+
+class AquariumSubScene {
+public:
+ float create(crepe::Scene & scn, float begin_x);
+
+private:
+ void add_background(crepe::Scene & scn, float begin_x);
+ void
+ add_bubbles(crepe::GameObject & obj, crepe::vec2 offset, int order_in_layer, float scale);
+};
diff --git a/game/background/BackgroundSubScene.cpp b/game/background/BackgroundSubScene.cpp
new file mode 100644
index 0000000..4bbd977
--- /dev/null
+++ b/game/background/BackgroundSubScene.cpp
@@ -0,0 +1,37 @@
+#include "BackgroundSubScene.h"
+#include "AquariumScript.h"
+#include "AquariumSubScene.h"
+#include "ForestSubScene.h"
+#include "HallwayScript.h"
+#include "HallwaySubScene.h"
+#include "StartSubScene.h"
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/Scene.h>
+
+using namespace crepe;
+using namespace std;
+
+BackgroundSubScene::BackgroundSubScene(Scene & scn) {
+ StartSubScene start;
+ HallwaySubScene hallway;
+ ForestSubScene forest;
+ AquariumSubScene aquarium;
+
+ float begin_x = 400;
+
+ begin_x = start.create(scn, begin_x);
+
+ begin_x = hallway.create(scn, begin_x, 1, Color::YELLOW);
+
+ begin_x = forest.create(scn, begin_x, "1");
+
+ begin_x += 3000;
+
+ begin_x = aquarium.create(scn, begin_x);
+
+ GameObject scripts = scn.new_object("scrips_background", "background");
+ scripts.add_component<BehaviorScript>().set_script<HallwayScript>();
+ scripts.add_component<BehaviorScript>().set_script<AquariumScript>();
+}
diff --git a/game/background/BackgroundSubScene.h b/game/background/BackgroundSubScene.h
new file mode 100644
index 0000000..06bdac4
--- /dev/null
+++ b/game/background/BackgroundSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class BackgroundSubScene {
+public:
+ BackgroundSubScene(crepe::Scene & scn);
+};
diff --git a/game/background/CMakeLists.txt b/game/background/CMakeLists.txt
new file mode 100644
index 0000000..1d705f5
--- /dev/null
+++ b/game/background/CMakeLists.txt
@@ -0,0 +1,9 @@
+target_sources(main PUBLIC
+ AquariumSubScene.cpp
+ BackgroundSubScene.cpp
+ ForestParallaxScript.cpp
+ ForestSubScene.cpp
+ HallwaySubScene.cpp
+ StartSubScene.cpp
+)
+
diff --git a/game/background/ForestParallaxScript.cpp b/game/background/ForestParallaxScript.cpp
new file mode 100644
index 0000000..7470da2
--- /dev/null
+++ b/game/background/ForestParallaxScript.cpp
@@ -0,0 +1,55 @@
+#include "ForestParallaxScript.h"
+
+#include "../Config.h"
+
+using namespace crepe;
+using namespace std;
+
+ForestParallaxScript::ForestParallaxScript(
+ float begin_x, float end_x, std::string unique_bg_name
+)
+ : begin_x(begin_x),
+ end_x(end_x),
+ name(unique_bg_name) {}
+
+void ForestParallaxScript::fixed_update(crepe::duration_t dt) {
+ RefVector<Transform> vec_2
+ = this->get_components_by_name<Transform>("forest_bg_2_" + name);
+ RefVector<Transform> vec_3
+ = this->get_components_by_name<Transform>("forest_bg_3_" + name);
+
+ for (Transform & t : vec_2) {
+ if (t.position.x > end_x - 400) {
+ t.position.x = begin_x - 400;
+ }
+ }
+ for (Transform & t : vec_3) {
+ if (t.position.x > end_x - 400) {
+ t.position.x = begin_x - 400;
+ }
+ }
+
+ //Move whole background 12000 to the right
+ Transform & trans_cam = this->get_components_by_name<Transform>("camera").front();
+
+ float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2;
+
+ if (cam_left_x > this->start_x + this->lenght) {
+ //Move whole background 12000 to the right
+ RefVector<Transform> trans
+ = this->get_components_by_tag<Transform>("background_forest");
+ for (Transform & tran : trans) {
+ tran.position.x += 12000;
+ }
+ this->start_x += 12000;
+
+ RefVector<Transform> trans_back
+ = this->get_components_by_tag<Transform>("forest_background");
+ for (Transform & tran : trans_back) {
+ tran.position.x += 12000;
+ }
+
+ begin_x += 12000;
+ end_x += 12000;
+ }
+}
diff --git a/game/background/ForestParallaxScript.h b/game/background/ForestParallaxScript.h
new file mode 100644
index 0000000..d45fdd9
--- /dev/null
+++ b/game/background/ForestParallaxScript.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class ForestParallaxScript : public crepe::Script {
+public:
+ ForestParallaxScript(float begin_x, float end_x, std::string unique_bg_name);
+
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ float begin_x;
+ float end_x;
+ const std::string name;
+ float start_x = 4200;
+ const float lenght = 3000;
+};
diff --git a/game/background/ForestSubScene.cpp b/game/background/ForestSubScene.cpp
new file mode 100644
index 0000000..83e48dd
--- /dev/null
+++ b/game/background/ForestSubScene.cpp
@@ -0,0 +1,169 @@
+#include "ForestSubScene.h"
+#include "ForestParallaxScript.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+float ForestSubScene::create(Scene & scn, float begin_x, std::string unique_bg_name) {
+ GameObject script = scn.new_object("forest_script", "background_forest");
+ script.add_component<BehaviorScript>().set_script<ForestParallaxScript>(
+ begin_x - 400, begin_x + 3000 + 400, unique_bg_name
+ );
+
+ this->add_background(scn, begin_x, unique_bg_name);
+
+ GameObject begin = scn.new_object("forest_begin", "background_forest", vec2(begin_x, 0));
+ Asset begin_asset {"asset/background/forest/forestFG_1_TVOS.png"};
+ begin.add_component<Sprite>(
+ begin_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 800;
+
+ this->add_background(scn, begin_x, unique_bg_name);
+
+ GameObject middle_1
+ = scn.new_object("forest_middle", "background_forest", vec2(begin_x, 0));
+ Asset middle_1_asset {"asset/background/forest/forestFG_3_TVOS.png"};
+ middle_1.add_component<Sprite>(
+ middle_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 2,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 800;
+
+ this->add_background(scn, begin_x, unique_bg_name);
+
+ GameObject middle_2
+ = scn.new_object("forest_middle", "background_forest", vec2(begin_x, 0));
+ Asset middle_2_asset {"asset/background/forest/forestFG_3_TVOS.png"};
+ middle_2.add_component<Sprite>(
+ middle_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 3,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 800;
+
+ this->add_background(scn, begin_x, unique_bg_name);
+
+ GameObject end = scn.new_object("forest_end", "background_forest", vec2(begin_x, 0));
+ Asset end_asset {"asset/background/forest/forestFG_2_TVOS.png"};
+ end.add_component<Sprite>(
+ end_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ this->add_background(scn, begin_x + 200, unique_bg_name);
+
+ return begin_x;
+}
+
+void ForestSubScene::add_background(Scene & scn, float begin_x, std::string name) {
+ GameObject bg_1
+ = scn.new_object("forest_bg_1_" + name, "forest_background", vec2(begin_x, 0));
+ Asset bg_1_asset {"asset/background/forest/forestBG1_1_TVOS.png"};
+ bg_1.add_component<Sprite>(
+ bg_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 2,
+ .size = vec2(0, 800),
+ }
+ );
+ GameObject bg_2
+ = scn.new_object("forest_bg_2_" + name, "forest_background", vec2(begin_x, 0));
+ Asset bg_2_1_asset {"asset/background/forest/forestBG2_1_TVOS.png"};
+ bg_2.add_component<Sprite>(
+ bg_2_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 400),
+ .position_offset = vec2(200, 0),
+ }
+ );
+ Asset bg_2_2_asset {"asset/background/forest/forestBG2_2_TVOS.png"};
+ bg_2.add_component<Sprite>(
+ bg_2_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 400),
+ .position_offset = vec2(-200, 0),
+ }
+ );
+ GameObject bg_3
+ = scn.new_object("forest_bg_3_" + name, "forest_background", vec2(begin_x, 0));
+ Asset bg_3_1_asset {"asset/background/forest/forestBG3_1_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 200),
+ .position_offset = vec2(300, 0),
+ }
+ );
+ Asset bg_3_2_asset {"asset/background/forest/forestBG3_2_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 200),
+ .position_offset = vec2(100, 0),
+ }
+ );
+ Asset bg_3_3_asset {"asset/background/forest/forestBG3_3_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_3_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 200),
+ .position_offset = vec2(-100, 0),
+ }
+ );
+ Asset bg_3_4_asset {"asset/background/forest/forestBG3_4_TVOS.png"};
+ bg_3.add_component<Sprite>(
+ bg_3_4_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACK_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 200),
+ .position_offset = vec2(-300, 0),
+ }
+ );
+
+ bg_2.add_component<Rigidbody>(Rigidbody::Data {
+ .linear_velocity = vec2(30, 0),
+ });
+ bg_3.add_component<Rigidbody>(Rigidbody::Data {
+ .linear_velocity = vec2(40, 0),
+ });
+}
diff --git a/game/background/ForestSubScene.h b/game/background/ForestSubScene.h
new file mode 100644
index 0000000..0a04001
--- /dev/null
+++ b/game/background/ForestSubScene.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <string>
+
+namespace crepe {
+class Scene;
+}
+
+class ForestSubScene {
+public:
+ float create(crepe::Scene & scn, float begin_x, std::string unique_bg_name);
+
+private:
+ void add_background(crepe::Scene & scn, float begin_x, std::string name);
+};
diff --git a/game/background/HallwayScript.cpp b/game/background/HallwayScript.cpp
new file mode 100644
index 0000000..a5bb94c
--- /dev/null
+++ b/game/background/HallwayScript.cpp
@@ -0,0 +1,70 @@
+#include "HallwayScript.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void HallwayScript::fixed_update(crepe::duration_t dt) {
+ Transform & trans_cam = this->get_components_by_name<Transform>("camera").front();
+
+ float cam_left_x = trans_cam.position.x - VIEWPORT_X / 2;
+
+ if (cam_left_x > this->start_x + this->lenght) {
+ //Move whole background 6000 to the right
+ RefVector<Transform> trans = this->get_components_by_tag<Transform>("background_hall");
+ for (Transform & tran : trans) {
+ tran.position.x += 6000;
+ }
+ this->start_x += 6000;
+
+ //Change sector number
+ Animator & anim = this->get_components_by_name<Animator>("hallway_begin").front();
+ int column = (current_sector - 1) / 4;
+ int row = (current_sector - 1) % 4;
+ anim.set_anim(column);
+ for (int i = 0; i < row; i++) {
+ anim.next_anim();
+ }
+ RefVector<Sprite> sprites = this->get_components_by_name<Sprite>("hallway_begin");
+ switch (current_sector % 7) {
+ case 0:
+ sprites[1].get().data.color = Color::YELLOW;
+ sprites[2].get().data.color = Color::YELLOW;
+ break;
+ case 1:
+ sprites[1].get().data.color = Color::MAGENTA;
+ sprites[2].get().data.color = Color::MAGENTA;
+ break;
+ case 2:
+ sprites[1].get().data.color = Color::CYAN;
+ sprites[2].get().data.color = Color::CYAN;
+ break;
+ case 3:
+ sprites[1].get().data.color = Color::GREEN;
+ sprites[2].get().data.color = Color::GREEN;
+ break;
+ case 4:
+ sprites[1].get().data.color = Color::RED;
+ sprites[2].get().data.color = Color::RED;
+ break;
+ case 5:
+ sprites[1].get().data.color = Color::BLUE;
+ sprites[2].get().data.color = Color::BLUE;
+ break;
+ case 6:
+ sprites[1].get().data.color = Color::WHITE;
+ sprites[2].get().data.color = Color::WHITE;
+ break;
+ }
+ current_sector++;
+ if (current_sector > 16) {
+ current_sector = 1;
+ }
+ }
+}
diff --git a/game/background/HallwayScript.h b/game/background/HallwayScript.h
new file mode 100644
index 0000000..04b2933
--- /dev/null
+++ b/game/background/HallwayScript.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class HallwayScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ float start_x = 1200;
+ const float lenght = 3000;
+ int current_sector = 2;
+};
diff --git a/game/background/HallwaySubScene.cpp b/game/background/HallwaySubScene.cpp
new file mode 100644
index 0000000..31af2d5
--- /dev/null
+++ b/game/background/HallwaySubScene.cpp
@@ -0,0 +1,167 @@
+#include "HallwaySubScene.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+using namespace std;
+
+float HallwaySubScene::create(
+ Scene & scn, float begin_x, unsigned int sector_num, Color sector_color
+) {
+ GameObject begin = scn.new_object("hallway_begin", "background_hall", vec2(begin_x, 0));
+ Asset begin_asset {"asset/background/hallway/hallway1FG_1_TVOS.png"};
+ begin.add_component<Sprite>(
+ begin_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ this->add_sector_number(begin, vec2(-200, 0), sector_num, sector_color);
+ this->add_lamp(begin, vec2(330, -120), 11);
+ this->add_lamp(begin, vec2(430, -120), 9);
+
+ GameObject middle_1
+ = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0));
+ Asset middle_asset {"asset/background/hallway/hallway1FG_2_TVOS.png"};
+ middle_1.add_component<Sprite>(
+ middle_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 2,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ GameObject middle_2
+ = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0));
+ Asset middle_asset_2 {"asset/background/hallway/hallway1FG_2_TVOS.png"};
+ middle_2.add_component<Sprite>(
+ middle_asset_2,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 3,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 200;
+
+ GameObject middle_3
+ = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0));
+ Asset middle_asset_3 {"asset/background/hallway/hallway1FG_2_TVOS.png"};
+ middle_3.add_component<Sprite>(
+ middle_asset_3,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 4,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 400;
+
+ this->add_lamp(middle_3, vec2(0, -120));
+
+ GameObject middle_4
+ = scn.new_object("hallway_middle", "background_hall", vec2(begin_x, 0));
+ Asset middle_asset_4 {"asset/background/hallway/hallway1FG_2_TVOS.png"};
+ middle_4.add_component<Sprite>(
+ middle_asset_4,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ GameObject end = scn.new_object("hallway_end", "background_hall", vec2(begin_x, 0));
+ Asset end_asset {"asset/background/hallway/hallway1FG_1_TVOS.png"};
+ end.add_component<Sprite>(
+ end_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 600;
+
+ return begin_x;
+}
+
+void HallwaySubScene::add_lamp(GameObject & obj, vec2 offset, unsigned int fps) {
+ Asset lamp_asset {"asset/background/hallway/alarmLight_TVOS.png"};
+ obj.add_component<Sprite>(
+ lamp_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset,
+ }
+ );
+ Asset lamp_glow_asset {"asset/background/hallway/alarmGlow_TVOS.png"};
+ Sprite & lamp_glow_sprite = obj.add_component<Sprite>(
+ lamp_glow_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 300),
+ .position_offset = offset - vec2(65, -30),
+ }
+ );
+ obj.add_component<Animator>(
+ lamp_glow_sprite, ivec2(422, 384), uvec2(6, 1),
+ Animator::Data {
+ .fps = fps,
+ .looping = true,
+ }
+ );
+}
+
+void HallwaySubScene::add_sector_number(
+ GameObject & obj, vec2 offset, unsigned int sector_num, Color sector_color
+) {
+ Asset sector_text_asset {"asset/background/hallway/sectorText_TVOS.png"};
+ obj.add_component<Sprite>(
+ sector_text_asset,
+ Sprite::Data {
+ .color = sector_color,
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset,
+ }
+ );
+ Asset sector_num_asset {"asset/background/hallway/sectorNumbers_TVOS.png"};
+ Sprite & sector_num_sprite = obj.add_component<Sprite>(
+ sector_num_asset,
+ Sprite::Data {
+ .color = sector_color,
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset + vec2(200, 0),
+ }
+ );
+ Animator & sector_num_anim = obj.add_component<Animator>(
+ sector_num_sprite, ivec2(256, 128), uvec2(4, 4), Animator::Data {}
+ );
+ int column = (sector_num - 1) / 4;
+ int row = (sector_num - 1) % 4;
+ sector_num_anim.set_anim(column);
+ for (int i = 0; i < row; i++) {
+ sector_num_anim.next_anim();
+ }
+ sector_num_anim.pause();
+}
diff --git a/game/background/HallwaySubScene.h b/game/background/HallwaySubScene.h
new file mode 100644
index 0000000..c38b4a9
--- /dev/null
+++ b/game/background/HallwaySubScene.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <crepe/types.h>
+
+namespace crepe {
+class Scene;
+class GameObject;
+class Color;
+} // namespace crepe
+
+class HallwaySubScene {
+public:
+ float create(
+ crepe::Scene & scn, float begin_x, unsigned int sector_num, crepe::Color sector_color
+ );
+
+private:
+ void add_lamp(crepe::GameObject & obj, crepe::vec2 offset, unsigned int fps = 10);
+
+ void add_sector_number(
+ crepe::GameObject & obj, crepe::vec2 offset, unsigned int sector_num,
+ crepe::Color sector_color
+ );
+};
diff --git a/game/background/StartSubScene.cpp b/game/background/StartSubScene.cpp
new file mode 100644
index 0000000..ba80517
--- /dev/null
+++ b/game/background/StartSubScene.cpp
@@ -0,0 +1,527 @@
+#include "StartSubScene.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+using namespace std;
+
+float StartSubScene::create(Scene & scn, float begin_x) {
+ this->create_wall_fragments(scn, begin_x - 300);
+
+ GameObject begin = scn.new_object("start_begin", "background", vec2(begin_x, 0));
+ Asset begin_asset {"asset/background/start/titleFG_1_TVOS.png"};
+ begin.add_component<Sprite>(
+ begin_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ GameObject hole = scn.new_object("start_hole", "background", vec2(begin_x - 250, 140));
+ Asset hole_asset {"asset/background/start/titleWallHole.png"};
+ Sprite & hole_sprite = hole.add_component<Sprite>(
+ hole_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 200),
+ }
+ );
+ hole_sprite.active = false;
+ begin_x += 700;
+
+ this->add_table(begin, vec2(-150, 150));
+ this->add_light(begin, vec2(-125, -150));
+ this->add_jetpack_stand(begin, vec2(-125, 200));
+
+ GameObject end = scn.new_object("start_end", "background", vec2(begin_x, 0));
+ Asset end_asset {"asset/background/start/titleFG_2_TVOS.png"};
+ end.add_component<Sprite>(
+ end_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, GAME_HEIGHT),
+ }
+ );
+ begin_x += 100;
+
+ this->add_lamp(end, vec2(-350, -95));
+
+ return begin_x;
+}
+
+void StartSubScene::add_lamp(GameObject & obj, vec2 offset, unsigned int fps) {
+ Asset lamp_asset {"asset/background/start/alarmLight_TVOS.png"};
+ obj.add_component<Sprite>(
+ lamp_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset,
+ }
+ );
+ Asset lamp_glow_asset {"asset/background/start/alarmGlow_TVOS.png"};
+ Sprite & lamp_glow_sprite = obj.add_component<Sprite>(
+ lamp_glow_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 300),
+ .position_offset = offset - vec2(65, -55),
+ }
+ );
+ lamp_glow_sprite.active = false;
+ obj.add_component<Animator>(
+ lamp_glow_sprite, ivec2(422, 384), uvec2(6, 1),
+ Animator::Data {
+ .fps = fps,
+ .looping = true,
+ }
+ );
+}
+
+void StartSubScene::add_table(GameObject & obj, vec2 offset) {
+ Asset table_asset {"asset/background/start/table.png"};
+ obj.add_component<Sprite>(
+ table_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset,
+ }
+ );
+ Asset gramophone_asset {"asset/background/start/gramophone_TVOS.png"};
+ Sprite & gramophone_sprite = obj.add_component<Sprite>(
+ gramophone_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 100),
+ .position_offset = offset + vec2(0, -50),
+ }
+ );
+ obj.add_component<Animator>(
+ gramophone_sprite, ivec2(64, 128), uvec2(2, 1),
+ Animator::Data {
+ .fps = 10,
+ .looping = true,
+ }
+ );
+}
+
+void StartSubScene::add_light(crepe::GameObject & obj, crepe::vec2 offset) {
+ Asset light_asset {"asset/background/start/title_light_TVOS.png"};
+ obj.add_component<Sprite>(
+ light_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 200),
+ .position_offset = offset,
+ }
+ );
+ Asset light_glow_asset {"asset/background/start/lightEffect2.png"};
+ obj.add_component<Sprite>(
+ light_glow_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 100),
+ .position_offset = offset + vec2(0, 75),
+ }
+ );
+ Asset light_effect_asset {"asset/background/start/lightEffect.png"};
+ obj.add_component<Sprite>(
+ light_effect_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ .position_offset = offset + vec2(0, 350),
+ }
+ );
+}
+
+void StartSubScene::add_jetpack_stand(crepe::GameObject & obj, crepe::vec2 offset) {
+ Asset jetpack_stand_asset {"asset/background/start/JetpackStand.png"};
+ Sprite & jetpeck_stand_sprite = obj.add_component<Sprite>(
+ jetpack_stand_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 70),
+ .position_offset = offset,
+ }
+ );
+ obj.add_component<Animator>(
+ jetpeck_stand_sprite, ivec2(34, 46), uvec2(2, 1),
+ Animator::Data {
+ .fps = 10,
+ .looping = true,
+ }
+ )
+ .pause();
+ Asset do_not_steal = {"asset/background/start/doNotTouchSign_TVOS.png"};
+ obj.add_component<Sprite>(
+ do_not_steal,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 1,
+ .size = vec2(0, 100),
+ .position_offset = offset + vec2(-75, -25),
+ }
+ );
+}
+
+void StartSubScene::create_wall_fragments(crepe::Scene & scn, float begin_x) {
+ GameObject frag_1 = scn.new_object("frag_1", "wall_fragment", vec2(begin_x, 200));
+ Asset frag_1_asset {"asset/background/start/StartWall_frag1.png"};
+ Sprite & frag_1_sprite = frag_1.add_component<Sprite>(
+ frag_1_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_1_sprite.active = false;
+ Rigidbody & frag_1_rb = frag_1.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -400),
+ .linear_velocity_coefficient = vec2(0.5, 0.6),
+ .angular_velocity = 500,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_1_rb.active = false;
+ frag_1.add_component<CircleCollider>(25);
+
+ GameObject frag_2 = scn.new_object("frag_2", "wall_fragment", vec2(begin_x, 180));
+ Asset frag_2_asset {"asset/background/start/StartWall_frag2.png"};
+ Sprite & frag_2_sprite = frag_2.add_component<Sprite>(
+ frag_2_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_2_sprite.active = false;
+ Rigidbody & frag_2_rb = frag_2.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -400),
+ .linear_velocity_coefficient = vec2(0.35, 0.4),
+ .angular_velocity = 400,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_2_rb.active = false;
+ frag_2.add_component<CircleCollider>(55);
+
+ GameObject frag_3 = scn.new_object("frag_3", "wall_fragment", vec2(begin_x, 170));
+ Asset frag_3_asset {"asset/background/start/StartWall_frag3.png"};
+ Sprite & frag_3_sprite = frag_3.add_component<Sprite>(
+ frag_3_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_3_sprite.active = false;
+ Rigidbody & frag_3_rb = frag_3.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -400),
+ .linear_velocity_coefficient = vec2(0.3, 0.3),
+ .angular_velocity = 300,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_3_rb.active = false;
+ frag_3.add_component<CircleCollider>(35);
+
+ GameObject frag_4 = scn.new_object("frag_4", "wall_fragment", vec2(begin_x, 160));
+ Asset frag_4_asset {"asset/background/start/StartWall_frag4.png"};
+ Sprite & frag_4_sprite = frag_4.add_component<Sprite>(
+ frag_4_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_4_sprite.active = false;
+ Rigidbody & frag_4_rb = frag_4.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(700, -400),
+ .linear_velocity_coefficient = vec2(0.2, 0.2),
+ .angular_velocity = 200,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_4_rb.active = false;
+ frag_4.add_component<CircleCollider>(60);
+
+ GameObject frag_5 = scn.new_object("frag_5", "wall_fragment", vec2(begin_x, 150));
+ Asset frag_5_asset {"asset/background/start/StartWall_frag5.png"};
+ Sprite & frag_5_sprite = frag_5.add_component<Sprite>(
+ frag_5_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_5_sprite.active = false;
+ Rigidbody & frag_5_rb = frag_5.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(600, -500),
+ .linear_velocity_coefficient = vec2(0.25, 0.15),
+ .angular_velocity = 100,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_5_rb.active = false;
+ frag_5.add_component<CircleCollider>(5);
+
+ GameObject frag_6 = scn.new_object("frag_6", "wall_fragment", vec2(begin_x, 140));
+ Asset frag_6_asset {"asset/background/start/StartWall_frag6.png"};
+ Sprite & frag_6_sprite = frag_6.add_component<Sprite>(
+ frag_6_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_6_sprite.active = false;
+ Rigidbody & frag_6_rb = frag_6.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(300, -800),
+ .linear_velocity_coefficient = vec2(0.35, 0.25),
+ .angular_velocity = 100,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_6_rb.active = false;
+ frag_6.add_component<CircleCollider>(30);
+
+ GameObject frag_7 = scn.new_object("frag_7", "wall_fragment", vec2(begin_x, 130));
+ Asset frag_7_asset {"asset/background/start/StartWall_frag7.png"};
+ Sprite & frag_7_sprite = frag_7.add_component<Sprite>(
+ frag_7_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_7_sprite.active = false;
+ Rigidbody & frag_7_rb = frag_7.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -500),
+ .linear_velocity_coefficient = vec2(0.45, 0.6),
+ .angular_velocity = 800,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_7_rb.active = false;
+ frag_7.add_component<CircleCollider>(45);
+
+ GameObject frag_8 = scn.new_object("frag_8", "wall_fragment", vec2(begin_x, 120));
+ Asset frag_8_asset {"asset/background/start/StartWall_frag8.png"};
+ Sprite & frag_8_sprite = frag_8.add_component<Sprite>(
+ frag_8_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_8_sprite.active = false;
+ Rigidbody & frag_8_rb = frag_8.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -400),
+ .linear_velocity_coefficient = vec2(0.5, 0.6),
+ .angular_velocity = 500,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_8_rb.active = false;
+ frag_8.add_component<CircleCollider>(25);
+
+ GameObject frag_9 = scn.new_object("frag_9", "wall_fragment", vec2(begin_x, 110));
+ Asset frag_9_asset {"asset/background/start/StartWall_frag9.png"};
+ Sprite & frag_9_sprite = frag_9.add_component<Sprite>(
+ frag_9_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_9_sprite.active = false;
+ Rigidbody & frag_9_rb = frag_9.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(200, -400),
+ .linear_velocity_coefficient = vec2(0.5, 0.25),
+ .angular_velocity = 500,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_9_rb.active = false;
+ frag_9.add_component<CircleCollider>(15);
+
+ GameObject frag_10 = scn.new_object("frag_10", "wall_fragment", vec2(begin_x, 100));
+ Asset frag_10_asset {"asset/background/start/StartWall_frag10.png"};
+ Sprite & frag_10_sprite = frag_10.add_component<Sprite>(
+ frag_10_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_10_sprite.active = false;
+ Rigidbody & frag_10_rb = frag_10.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(400, -900),
+ .linear_velocity_coefficient = vec2(0.35, 0.4),
+ .angular_velocity = 300,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_10_rb.active = false;
+ frag_10.add_component<CircleCollider>(60);
+
+ GameObject frag_11 = scn.new_object("frag_11", "wall_fragment", vec2(begin_x, 70));
+ Asset frag_11_asset {"asset/background/start/StartWall_frag11.png"};
+ Sprite & frag_11_sprite = frag_11.add_component<Sprite>(
+ frag_11_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_11_sprite.active = false;
+ Rigidbody & frag_11_rb = frag_11.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(600, -400),
+ .linear_velocity_coefficient = vec2(0.3, 0.3),
+ .angular_velocity = 200,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_11_rb.active = false;
+ frag_11.add_component<CircleCollider>(5);
+
+ GameObject frag_12 = scn.new_object("frag_12", "wall_fragment", vec2(begin_x, 80));
+ Asset frag_12_asset {"asset/background/start/StartWall_frag12.png"};
+ Sprite & frag_12_sprite = frag_12.add_component<Sprite>(
+ frag_12_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_FORE_BACKGROUND,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ }
+ );
+ frag_12_sprite.active = false;
+ Rigidbody & frag_12_rb = frag_12.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .linear_velocity = vec2(500, -800),
+ .linear_velocity_coefficient = vec2(0.25, 0.15),
+ .angular_velocity = 100,
+ .angular_velocity_coefficient = 0.55,
+ .elasticity_coefficient = 0.5,
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_WALL_FRAGS,
+ });
+ frag_12_rb.active = false;
+ frag_12.add_component<CircleCollider>(50);
+
+ GameObject smoke_particles_1
+ = scn.new_object("smoke_particles", "particle_emitter", vec2(begin_x - 100, 200));
+ Asset smoke_asset_1 {"asset/particles/smoke.png"};
+ Sprite & smoke_sprite_1 = smoke_particles_1.add_component<Sprite>(
+ smoke_asset_1,
+ Sprite::Data {
+ .color = Color(255, 255, 255, 50),
+ .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 100),
+ }
+ );
+ ParticleEmitter & emitter_1 = smoke_particles_1.add_component<ParticleEmitter>(
+ smoke_sprite_1,
+ ParticleEmitter::Data {
+ .emission_rate = 20,
+ .min_speed = 40,
+ .max_speed = 100,
+ .min_angle = -30,
+ .max_angle = 10,
+ .end_lifespan = 4,
+ }
+ );
+ emitter_1.active = false;
+
+ GameObject smoke_particles_2
+ = scn.new_object("smoke_particles", "particle_emitter", vec2(begin_x - 100, 200));
+ Asset smoke_asset_2 {"asset/particles/smoke.png"};
+ Sprite & smoke_sprite_2 = smoke_particles_2.add_component<Sprite>(
+ smoke_asset_2,
+ Sprite::Data {
+ .color = Color(255, 255, 255, 50),
+ .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND,
+ .order_in_layer = 0,
+ .size = vec2(0, 70),
+ }
+ );
+ ParticleEmitter & emitter_2 = smoke_particles_2.add_component<ParticleEmitter>(
+ smoke_sprite_2,
+ ParticleEmitter::Data {
+ .emission_rate = 30,
+ .min_speed = 40,
+ .max_speed = 100,
+ .min_angle = -45,
+ .max_angle = 5,
+ .end_lifespan = 3,
+ }
+ );
+ emitter_2.active = false;
+}
diff --git a/game/background/StartSubScene.h b/game/background/StartSubScene.h
new file mode 100644
index 0000000..c83e3d5
--- /dev/null
+++ b/game/background/StartSubScene.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <crepe/types.h>
+
+namespace crepe {
+class Scene;
+class GameObject;
+} // namespace crepe
+
+class StartSubScene {
+public:
+ float create(crepe::Scene & scn, float begin_x);
+
+private:
+ void add_lamp(crepe::GameObject & obj, crepe::vec2 offset, unsigned int fps = 10);
+ void add_table(crepe::GameObject & obj, crepe::vec2 offset);
+ void add_light(crepe::GameObject & obj, crepe::vec2 offset);
+ void add_jetpack_stand(crepe::GameObject & obj, crepe::vec2 offset);
+ void create_wall_fragments(crepe::Scene & scn, float begin_x);
+};
diff --git a/game/coins/CoinPoolSubScene.cpp b/game/coins/CoinPoolSubScene.cpp
new file mode 100644
index 0000000..f8b5b70
--- /dev/null
+++ b/game/coins/CoinPoolSubScene.cpp
@@ -0,0 +1,13 @@
+#include "CoinPoolSubScene.h"
+#include "CoinSubScene.h"
+
+using namespace crepe;
+using namespace std;
+
+void CoinPoolSubScene::create_coins(crepe::Scene & scn) {
+ int amount = 0;
+ CoinSubScene coin;
+ while (amount < this->MAXIMUM_AMOUNT) {
+ amount = coin.create(scn, amount);
+ }
+}
diff --git a/game/coins/CoinPoolSubScene.h b/game/coins/CoinPoolSubScene.h
new file mode 100644
index 0000000..07626d6
--- /dev/null
+++ b/game/coins/CoinPoolSubScene.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class CoinPoolSubScene {
+public:
+ void create_coins(crepe::Scene & scn);
+
+private:
+ static constexpr int MAXIMUM_AMOUNT = 100;
+};
diff --git a/game/coins/CoinScript.cpp b/game/coins/CoinScript.cpp
new file mode 100644
index 0000000..514f4de
--- /dev/null
+++ b/game/coins/CoinScript.cpp
@@ -0,0 +1,71 @@
+#include "CoinScript.h"
+
+#include "manager/SaveManager.h"
+
+#include "../Config.h"
+#include "../hud/HudScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+using namespace std;
+
+bool CoinScript::on_collision(const CollisionEvent & collisionData) {
+ if (collisionData.info.other.metadata.tag != "coin") return false;
+ if (!this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name)
+ .front()
+ .get()
+ .active)
+ return false;
+ this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name)
+ .front()
+ .get()
+ .active
+ = false;
+ this->get_components_by_name<CircleCollider>(collisionData.info.other.metadata.name)
+ .front()
+ .get()
+ .active
+ = false;
+ this->amount++;
+
+ AudioSource & audio = this->get_components_by_id<AudioSource>(
+ collisionData.info.other.metadata.game_object_id
+ )
+ .front();
+ audio.play();
+
+ this->get_components_by_name<Sprite>(collisionData.info.other.metadata.name)
+ .back()
+ .get()
+ .active
+ = true;
+ this->get_components_by_name<Animator>(collisionData.info.other.metadata.name)
+ .back()
+ .get()
+ .active
+ = true;
+
+ return false;
+}
+
+void CoinScript::init() {
+ this->subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+}
+
+void CoinScript::fixed_update(crepe::duration_t dt) {
+ this->trigger_event(GetCoinEvent {
+ .amount_of_coins = this->amount,
+ });
+}
+
+bool CoinScript::save() {
+ SaveManager & savemgr = this->get_save_manager();
+ savemgr.set(TOTAL_COINS_RUN, this->amount);
+ return false;
+}
diff --git a/game/coins/CoinScript.h b/game/coins/CoinScript.h
new file mode 100644
index 0000000..5718025
--- /dev/null
+++ b/game/coins/CoinScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "api/Script.h"
+
+class CoinScript : public crepe::Script {
+public:
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+ bool on_collision(const crepe::CollisionEvent & collisionData);
+ bool save();
+
+private:
+ int amount = 0;
+};
diff --git a/game/coins/CoinSubScene.cpp b/game/coins/CoinSubScene.cpp
new file mode 100644
index 0000000..d154819
--- /dev/null
+++ b/game/coins/CoinSubScene.cpp
@@ -0,0 +1,64 @@
+#include "CoinSubScene.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+
+using namespace crepe;
+using namespace std;
+
+int CoinSubScene::create(Scene & scn, int coin_counter) {
+ vec2 size = {20, 20};
+
+ string unique_name = "coin_" + to_string(coin_counter++);
+
+ GameObject coin = scn.new_object(unique_name.c_str(), "coin", vec2 {650, 0}, 0, 1);
+ coin.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::KINEMATIC,
+ .kinematic_collision = false,
+ .collision_layers = {COLL_LAY_PLAYER},
+ });
+ coin.add_component<CircleCollider>((size.x / 2) - 3).active = false;
+ crepe::OptionalRef<crepe::Sprite> coin_sprite = coin.add_component<Sprite>(
+ Asset {"asset/coin/coin1_TVOS.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_COINS,
+ .order_in_layer = 0,
+ .size = size,
+ }
+ );
+ coin_sprite->active = false;
+ coin.add_component<Animator>(
+ coin_sprite, ivec2 {32, 32}, uvec2 {8, 1},
+ Animator::Data {
+ .fps = 15,
+ .looping = true,
+ }
+ );
+ coin.add_component<AudioSource>(Asset {"asset/sfx/coin_pickup_1.ogg"}).volume = 3;
+
+ Sprite & pick_up = coin.add_component<Sprite>(
+ Asset {"asset/coin/coinCollect1_TVOS.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_COINS,
+ .order_in_layer = 1,
+ .size = size * 2,
+ }
+ );
+ pick_up.active = false;
+ coin.add_component<Animator>(
+ pick_up, ivec2 {64, 64}, uvec2 {5, 1},
+ Animator::Data {
+ .fps = 5,
+ .looping = false,
+ }
+ )
+ .active
+ = false;
+
+ return coin_counter;
+}
diff --git a/game/coins/CoinSubScene.h b/game/coins/CoinSubScene.h
new file mode 100644
index 0000000..7a1c60a
--- /dev/null
+++ b/game/coins/CoinSubScene.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <crepe/api/GameObject.h>
+
+namespace crepe {
+class Scene;
+}
+
+class CoinSubScene {
+public:
+ int create(crepe::Scene & scn, int coin_counter);
+};
diff --git a/game/coins/CoinSystemScript.cpp b/game/coins/CoinSystemScript.cpp
new file mode 100644
index 0000000..f9816c9
--- /dev/null
+++ b/game/coins/CoinSystemScript.cpp
@@ -0,0 +1,258 @@
+#include "CoinSystemScript.h"
+
+#include <random>
+
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Metadata.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+
+using namespace crepe;
+using namespace std;
+
+void CoinSystemScript::init() { engine.seed(rd()); }
+
+void CoinSystemScript::add_location(const crepe::vec2 & location) {
+ coin_locations.push_back(CoinData(location));
+}
+
+float CoinSystemScript::preset_1(const vec2 & begin_position) {
+ vec2 top = {begin_position.x, begin_position.y - (this->ROW_OFFSET_1)};
+ vec2 bottom = {begin_position.x, begin_position.y + (this->ROW_OFFSET_1)};
+
+ // Add locations for the top row
+ for (int i = 0; i < COLUM_AMOUNT_1; ++i) {
+ add_location(top);
+ top.x += this->COLUM_OFFSET_1;
+ }
+
+ // Add locations for the bottom row
+ bottom.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1;
+ for (int i = 0; i < COLUM_AMOUNT_1; ++i) {
+ add_location(bottom);
+ bottom.x += this->COLUM_OFFSET_1;
+ }
+
+ // Add locations for the next set of the top row
+ top.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1;
+ for (int i = 0; i < COLUM_AMOUNT_1; ++i) {
+ add_location(top);
+ top.x += this->COLUM_OFFSET_1;
+ }
+
+ // Add locations for the next set of the bottom row
+ bottom.x += this->COLUM_OFFSET_1 * COLUM_AMOUNT_1;
+ for (int i = 0; i < COLUM_AMOUNT_1; ++i) {
+ add_location(bottom);
+ bottom.x += this->COLUM_OFFSET_1;
+ }
+
+ return bottom.x - begin_position.x;
+}
+
+float CoinSystemScript::preset_2(const vec2 & begin_position) {
+ vec2 top
+ = {begin_position.x + this->COLUM_OFFSET_2, begin_position.y - this->ROW_OFFSET_2};
+ vec2 middle = begin_position;
+ vec2 bottom
+ = {begin_position.x + this->COLUM_OFFSET_2, begin_position.y + this->ROW_OFFSET_2};
+
+ // Add locations for the next set of the bottom row
+ for (int i = 0; i < COLUM_AMOUNT_2 - 2; ++i) {
+ add_location(bottom);
+ bottom.x += this->COLUM_OFFSET_2;
+ }
+
+ // Add locations for the next set of the middle row
+ for (int i = 0; i < COLUM_AMOUNT_2; ++i) {
+ add_location(middle);
+ middle.x += this->COLUM_OFFSET_2;
+ }
+
+ // Add locations for the next set of the top row
+ for (int i = 0; i < COLUM_AMOUNT_2 - 2; ++i) {
+ add_location(top);
+ top.x += this->COLUM_OFFSET_2;
+ }
+
+ return middle.x - begin_position.x;
+}
+
+float CoinSystemScript::preset_3(const vec2 & begin_position) {
+ vec2 location = {begin_position.x, begin_position.y - (this->ROW_OFFSET_3)};
+
+ // Add locations for the top row
+ for (int i = 0; i < COLUM_AMOUNT_3; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_3;
+ }
+
+ // Add locations for the bottom row
+ location.y += this->ROW_OFFSET_3;
+ location.x += this->COLUM_OFFSET_3;
+ for (int i = 0; i < COLUM_AMOUNT_3; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_3;
+ }
+
+ // Add locations for the next set of the top row
+ location.y += this->ROW_OFFSET_3;
+ location.x += this->COLUM_OFFSET_3;
+ for (int i = 0; i < COLUM_AMOUNT_3; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_3;
+ }
+
+ return location.x - begin_position.x;
+}
+
+float CoinSystemScript::preset_4(const vec2 & begin_position) {
+ vec2 location = {begin_position.x, begin_position.y + (this->ROW_OFFSET_4)};
+
+ // Add locations for the top row
+ for (int i = 0; i < COLUM_AMOUNT_4; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_4;
+ }
+
+ // Add locations for the bottom row
+ location.y -= this->ROW_OFFSET_4;
+ location.x += this->COLUM_OFFSET_4;
+ for (int i = 0; i < COLUM_AMOUNT_4; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_4;
+ }
+
+ // Add locations for the next set of the top row
+ location.y -= this->ROW_OFFSET_4;
+ location.x += this->COLUM_OFFSET_4;
+ for (int i = 0; i < COLUM_AMOUNT_4; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_4;
+ }
+
+ return location.x - begin_position.x;
+}
+
+float CoinSystemScript::preset_5(const vec2 & begin_position) {
+ vec2 location = {begin_position.x, begin_position.y - ROW_OFFSET_5 / 2};
+ for (int i = 0; i < COLUM_AMOUNT_5; ++i) {
+ add_location(location);
+ location.x += this->COLUM_OFFSET_5;
+ }
+ return location.x - begin_position.x;
+}
+
+void CoinSystemScript::frame_update(crepe::duration_t dt) {
+ this->despawn_coins();
+ this->generate_locations();
+ this->spawn_coins();
+}
+
+void CoinSystemScript::despawn_coins() {
+ // Get the current x-position of the CoinSystem's Transform component
+ float position = this->get_component<Transform>().position.x;
+
+ // Retrieve all active coin sprites tagged as "coin"
+ RefVector<Sprite> coin_sprites = this->get_components_by_tag<Sprite>("coin");
+
+ for (Sprite & coin_sprite : coin_sprites) {
+ if (!coin_sprite.active) continue; // Skip inactive sprites
+
+ // Retrieve the corresponding Transform, Metadata, and CircleCollider components
+ Transform & coin_transform
+ = this->get_components_by_id<Transform>(coin_sprite.game_object_id).front().get();
+ Metadata & coin_metadata
+ = this->get_components_by_id<Metadata>(coin_sprite.game_object_id).front().get();
+ CircleCollider & coin_collider
+ = this->get_components_by_id<CircleCollider>(coin_sprite.game_object_id)
+ .front()
+ .get();
+
+ // Check if the coin is out of bounds based on DESPAWN_DISTANCE
+ if (coin_transform.position.x < position - this->DESPAWN_DISTANCE) {
+ // Find the coin in the coin_locations vector using its name
+ auto it = std::find_if(
+ coin_locations.begin(), coin_locations.end(),
+ [&coin_metadata](const CoinData & data) {
+ return data.name == coin_metadata.name;
+ }
+ );
+
+ // If a match is found, erase it from coin_locations
+ if (it != coin_locations.end()) {
+ coin_locations.erase(it);
+ coin_sprite.active = false;
+ coin_collider.active = false;
+ }
+ }
+ }
+}
+
+void CoinSystemScript::spawn_coins() {
+ // Get the current x-position of the CoinSystem's Transform component
+ float position = this->get_component<Transform>().position.x;
+
+ // Iterate through the list of coin locations
+ for (auto & coin : coin_locations) {
+ // Skip this coin if it is already active
+ if (coin.active) continue;
+ // Skip this coin if it is not within the defined spawn area
+ if (coin.start_location.x < this->SPAWN_DISTANCE + position
+ || coin.start_location.x > this->SPAWN_AREA + this->SPAWN_DISTANCE + position)
+ continue;
+
+ // Retrieve all sprites tagged as "coin"
+ RefVector<Sprite> coin_sprites = this->get_components_by_tag<Sprite>("coin");
+
+ // Check for an available (inactive) coin sprite
+ for (Sprite & coin_sprite : coin_sprites) {
+ // Skip this sprite if it is already active
+ if (coin_sprite.active) continue;
+ if (coin_sprite.data.order_in_layer == 1) {
+ coin_sprite.active = false;
+ continue;
+ }
+
+ // Found an available (inactive) coin sprite
+ // Retrieve its associated components
+ Transform & coin_transform
+ = this->get_components_by_id<Transform>(coin_sprite.game_object_id)
+ .front()
+ .get();
+ Metadata & coin_metadata
+ = this->get_components_by_id<Metadata>(coin_sprite.game_object_id)
+ .front()
+ .get();
+ CircleCollider & coin_collider
+ = this->get_components_by_id<CircleCollider>(coin_sprite.game_object_id)
+ .front()
+ .get();
+
+ // Assign data and set active
+ coin.name = coin_metadata.name;
+ coin.active = true;
+ coin_sprite.active = true;
+ coin_collider.active = true;
+ coin_transform.position = coin.start_location;
+
+ // Break out of the inner loop since we've assigned this coin to an available sprite
+ break;
+ }
+ }
+}
+
+void CoinSystemScript::generate_locations() {
+ float position = this->get_component<Transform>().position.x;
+ if (position + SPAWN_DISTANCE + SYSTEM_POSITION_OFFSET < this->system_position) return;
+
+ std::discrete_distribution<int> dist(weights.begin(), weights.end());
+ int selected_index = dist(engine);
+
+ std::uniform_real_distribution<float> space_dist(SPAWN_SPACING_MIN, SPAWN_SPACING_MAX);
+ float spacing = space_dist(engine);
+
+ // Call the corresponding function and return the new x position
+ this->system_position += functions[selected_index]({this->system_position, 0});
+ this->system_position += spacing;
+}
diff --git a/game/coins/CoinSystemScript.h b/game/coins/CoinSystemScript.h
new file mode 100644
index 0000000..5c94273
--- /dev/null
+++ b/game/coins/CoinSystemScript.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <random>
+#include <string>
+
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+class CoinSystemScript : public crepe::Script {
+private:
+ struct CoinData {
+ crepe::vec2 start_location = {0, 0};
+ std::string name = "";
+ bool active = false;
+ CoinData(crepe::vec2 start_location)
+ : start_location(start_location),
+ name(""),
+ active(false) {}
+ };
+
+public:
+ CoinSystemScript() {};
+ void init() override;
+ void frame_update(crepe::duration_t dt) override;
+
+private:
+ void add_location(const crepe::vec2 & location);
+ void despawn_coins();
+ void spawn_coins();
+ void generate_locations();
+ float preset_1(const crepe::vec2 & begin_position);
+ float preset_2(const crepe::vec2 & begin_position);
+ float preset_3(const crepe::vec2 & begin_position);
+ float preset_4(const crepe::vec2 & begin_position);
+ float preset_5(const crepe::vec2 & begin_position);
+
+private:
+ std::vector<std::function<float(const crepe::vec2 &)>> functions
+ = {[this](const crepe::vec2 & pos) { return preset_1(pos); },
+ [this](const crepe::vec2 & pos) { return preset_2(pos); },
+ [this](const crepe::vec2 & pos) { return preset_3(pos); },
+ [this](const crepe::vec2 & pos) { return preset_4(pos); },
+ [this](const crepe::vec2 & pos) { return preset_5(pos); }};
+ std::vector<int> weights = {20, 20, 20, 20, 20};
+ std::random_device rd;
+ std::default_random_engine engine;
+ float system_position = 1400;
+ static constexpr float SYSTEM_POSITION_OFFSET = 200;
+
+private:
+ static constexpr float SPAWN_SPACING_MIN = 400;
+ static constexpr float SPAWN_SPACING_MAX = 1000;
+ static constexpr float SPAWN_DISTANCE = 600;
+ static constexpr float DESPAWN_DISTANCE = 600;
+ static constexpr float SPAWN_AREA = 50;
+ std::vector<CoinData> coin_locations;
+
+private:
+ // preset one settings
+ // ***** *****
+ //
+ //
+ //
+ // ***** *****
+ static constexpr float ROW_OFFSET_1 = 100;
+ static constexpr float COLUM_OFFSET_1 = 25;
+ static constexpr int COLUM_AMOUNT_1 = 5;
+
+private:
+ // preset two settings
+ //
+ // ********
+ // **********
+ // ********
+ //
+ static constexpr float ROW_OFFSET_2 = 25;
+ static constexpr float COLUM_OFFSET_2 = 25;
+ static constexpr int COLUM_AMOUNT_2 = 10;
+ // preset three settings
+ // ***
+ //
+ // ***
+ //
+ // ***
+ static constexpr float ROW_OFFSET_3 = 100;
+ static constexpr float COLUM_OFFSET_3 = 25;
+ static constexpr int COLUM_AMOUNT_3 = 3;
+ // preset four settings
+ // ***
+ //
+ // ***
+ //
+ // ***
+ static constexpr float ROW_OFFSET_4 = 100;
+ static constexpr float COLUM_OFFSET_4 = 25;
+ static constexpr int COLUM_AMOUNT_4 = 3;
+ // preset five settings
+ //
+ // ***
+ //
+ static constexpr float ROW_OFFSET_5 = 25;
+ static constexpr float COLUM_OFFSET_5 = 25;
+ static constexpr int COLUM_AMOUNT_5 = 3;
+};
diff --git a/game/enemy/BattleScript.cpp b/game/enemy/BattleScript.cpp
new file mode 100644
index 0000000..798cbb9
--- /dev/null
+++ b/game/enemy/BattleScript.cpp
@@ -0,0 +1,61 @@
+#include "BattleScript.h"
+#include "EnemyConfig.h"
+#include "EnemyScript.h"
+#include "api/Transform.h"
+#include <crepe/api/AI.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Metadata.h>
+using namespace std;
+using namespace crepe;
+
+BattleScript::BattleScript() { engine.seed(rd()); }
+void BattleScript::init() {
+ std::uniform_int_distribution<int> dist(2, 10);
+ int random_enemy_amount = dist(this->engine);
+ // this->create_battle(random_enemy_amount);
+ this->subscribe<BattleStartEvent>([this](const BattleStartEvent & e) -> bool {
+ return this->create_battle(e);
+ });
+}
+void BattleScript::fixed_update(duration_t dt) {
+ if (!battle_active) return;
+ bool enemies_alive = false;
+ RefVector<AI> enemy_ai = this->get_components_by_tag<AI>("enemy");
+
+ for (AI & ai : enemy_ai) {
+ if (ai.active) {
+ enemies_alive = true;
+ }
+ }
+ if (!enemies_alive) {
+ this->battle_active = false;
+ this->trigger_event<BattleWonEvent>();
+ }
+}
+bool BattleScript::create_battle(const BattleStartEvent & e) {
+ this->battle_active = e.battle;
+ this->spawn_enemies(e.num_enemies);
+ return false;
+}
+void BattleScript::spawn_enemies(int amount) {
+ RefVector<AI> enemy_ai = this->get_components_by_tag<AI>("enemy");
+ std::uniform_real_distribution<float> dist(70, 150);
+ int spawned = 0;
+ for (int i = 0; i < ENEMY_POOL_MAX; i++) {
+ AI & ai = enemy_ai[i];
+ Transform & enemy_transform
+ = this->get_components_by_id<Transform>(ai.game_object_id).front();
+ if (ai.active == true || enemy_transform.position != ENEMY_POOL_LOCATION) continue;
+ this->queue_event<SpawnEnemyEvent>(
+ SpawnEnemyEvent {
+ .speed = dist(engine),
+ .column = i,
+ },
+ ai.game_object_id
+ );
+ spawned++;
+ if (spawned >= amount) {
+ return;
+ }
+ }
+}
diff --git a/game/enemy/BattleScript.h b/game/enemy/BattleScript.h
new file mode 100644
index 0000000..57aa16c
--- /dev/null
+++ b/game/enemy/BattleScript.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+#include <random>
+struct BattleWonEvent : public crepe::Event {};
+
+struct BattleStartEvent : public crepe::Event {
+public:
+ int num_enemies = 0;
+ bool battle = false;
+};
+class BattleScript : public crepe::Script {
+public:
+ BattleScript();
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+
+private:
+ bool battle_active = false;
+ std::random_device rd;
+ std::default_random_engine engine;
+ void spawn_enemies(int amount);
+ bool create_battle(const BattleStartEvent & e);
+};
diff --git a/game/enemy/EnemyBulletPool.cpp b/game/enemy/EnemyBulletPool.cpp
new file mode 100644
index 0000000..3ee4816
--- /dev/null
+++ b/game/enemy/EnemyBulletPool.cpp
@@ -0,0 +1,11 @@
+#include "EnemyBulletPool.h"
+#include "EnemyBulletSubScene.h"
+using namespace std;
+
+void EnemyBulletPool::create_bullets(crepe::Scene & scn) {
+ EnemyBulletSubScene bullet;
+ int amount = 0;
+ while (amount < this->MAXIMUM_AMOUNT) {
+ amount = bullet.create(scn, amount);
+ }
+}
diff --git a/game/enemy/EnemyBulletPool.h b/game/enemy/EnemyBulletPool.h
new file mode 100644
index 0000000..ee53fc4
--- /dev/null
+++ b/game/enemy/EnemyBulletPool.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class EnemyBulletPool {
+public:
+ void create_bullets(crepe::Scene & scn);
+
+private:
+ static constexpr int MAXIMUM_AMOUNT = 20;
+};
diff --git a/game/enemy/EnemyBulletScript.cpp b/game/enemy/EnemyBulletScript.cpp
new file mode 100644
index 0000000..d208a88
--- /dev/null
+++ b/game/enemy/EnemyBulletScript.cpp
@@ -0,0 +1,40 @@
+#include "EnemyBulletScript.h"
+#include <crepe/api/Camera.h>
+#include <crepe/api/Metadata.h>
+#include <crepe/api/Rigidbody.h>
+#include <iostream>
+
+#include "EnemyConfig.h"
+using namespace crepe;
+using namespace std;
+void EnemyBulletScript::init() {
+ this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool {
+ return this->on_collide(e);
+ });
+}
+void EnemyBulletScript::fixed_update(crepe::duration_t dt) {
+ Transform & transform = this->get_component<Transform>();
+ Camera & camera = this->get_components_by_name<Camera>("camera").front();
+ Transform & cam_transform = this->get_components_by_name<Transform>("camera").front();
+ Rigidbody & bullet_body = this->get_component<Rigidbody>();
+ //move
+ transform.position += bullet_body.data.linear_velocity * dt.count();
+ vec2 half_screen = camera.viewport_size / 2;
+ float despawn_location = cam_transform.position.x - half_screen.x - 50;
+ if (transform.position.x < despawn_location) {
+ this->despawn_bullet();
+ }
+}
+
+void EnemyBulletScript::despawn_bullet() {
+ Transform & transform = this->get_component<Transform>();
+ Rigidbody & bullet_body = this->get_component<Rigidbody>();
+ bullet_body.active = false;
+ transform.position = ENEMY_BULLET_POOL_LOCATION;
+}
+
+bool EnemyBulletScript::on_collide(const CollisionEvent & e) {
+ //cout << "collision happened with " << e.info.other.metadata.tag << endl;
+ this->despawn_bullet();
+ return false;
+}
diff --git a/game/enemy/EnemyBulletScript.h b/game/enemy/EnemyBulletScript.h
new file mode 100644
index 0000000..7dab751
--- /dev/null
+++ b/game/enemy/EnemyBulletScript.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Script.h>
+
+class EnemyBulletScript : public crepe::Script {
+public:
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+ bool on_collide(const crepe::CollisionEvent & e);
+ void despawn_bullet();
+};
diff --git a/game/enemy/EnemyBulletSubScene.cpp b/game/enemy/EnemyBulletSubScene.cpp
new file mode 100644
index 0000000..bc31ba8
--- /dev/null
+++ b/game/enemy/EnemyBulletSubScene.cpp
@@ -0,0 +1,52 @@
+#include <string>
+
+#include "../Config.h"
+#include "EnemyConfig.h"
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+#include "../Random.h"
+#include "EnemyBulletScript.h"
+#include "EnemyBulletSubScene.h"
+#include "EnemyScript.h"
+#include "api/Color.h"
+using namespace crepe;
+using namespace std;
+int EnemyBulletSubScene::create(Scene & scn, int counter) {
+ string unique_name = "enemy_bullet_" + to_string(counter++);
+ GameObject bullet = scn.new_object(
+ unique_name.c_str(), "enemy_bullet", ENEMY_BULLET_POOL_LOCATION, 0, 1
+ );
+
+ Rigidbody & bullet_body = bullet.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 0,
+ .body_type = Rigidbody::BodyType::KINEMATIC,
+ .linear_velocity = vec2 {-300, 0},
+ .kinematic_collision = false,
+ .collision_layers = {COLL_LAY_MISSILE, COLL_LAY_ZAPPER},
+ .collision_layer = COLL_LAY_BULLET
+ });
+ bullet_body.active = false;
+ BoxCollider & bullet_collider = bullet.add_component<BoxCollider>(vec2(40, 10));
+ //bullet_collider.active = false;
+ Asset bullet_asset {"asset/other_effects/effect_smgbullet_x2.png"};
+ Sprite & bullet_sprite = bullet.add_component<Sprite>(
+ bullet_asset,
+ Sprite::Data {
+ .color = Color::BLUE,
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = 1,
+ .size = vec2(60, 0),
+ }
+ );
+ bullet.add_component<BehaviorScript>().set_script<EnemyBulletScript>();
+ return counter;
+}
diff --git a/game/enemy/EnemyBulletSubScene.h b/game/enemy/EnemyBulletSubScene.h
new file mode 100644
index 0000000..ac78ad9
--- /dev/null
+++ b/game/enemy/EnemyBulletSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class EnemyBulletSubScene {
+public:
+ int create(crepe::Scene & scn, int counter);
+};
diff --git a/game/enemy/EnemyConfig.h b/game/enemy/EnemyConfig.h
new file mode 100644
index 0000000..f9fb469
--- /dev/null
+++ b/game/enemy/EnemyConfig.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <crepe/types.h>
+
+//button config
+// static constexpr crepe::vec2 PLAYER_BULLET_POOL_LOCATION = {0, -850};
+static constexpr crepe::vec2 ENEMY_BULLET_POOL_LOCATION = {0, -750};
+static constexpr crepe::vec2 ENEMY_POOL_LOCATION = {0, -650};
+static constexpr int ENEMY_POOL_MAX = 12;
diff --git a/game/enemy/EnemyPool.cpp b/game/enemy/EnemyPool.cpp
new file mode 100644
index 0000000..135fc35
--- /dev/null
+++ b/game/enemy/EnemyPool.cpp
@@ -0,0 +1,10 @@
+#include "EnemyPool.h"
+#include "EnemySubScene.h"
+using namespace std;
+void EnemyPool::create_enemies(crepe::Scene & scn) {
+ EnemySubScene enemy;
+ int amount = 0;
+ while (amount < ENEMY_POOL_MAX) {
+ amount = enemy.create(scn, amount);
+ }
+}
diff --git a/game/enemy/EnemyPool.h b/game/enemy/EnemyPool.h
new file mode 100644
index 0000000..cfd0b1c
--- /dev/null
+++ b/game/enemy/EnemyPool.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "EnemyConfig.h"
+#include <crepe/api/Scene.h>
+class EnemyPool {
+public:
+ void create_enemies(crepe::Scene & scn);
+};
diff --git a/game/enemy/EnemyScript.cpp b/game/enemy/EnemyScript.cpp
new file mode 100644
index 0000000..0822c29
--- /dev/null
+++ b/game/enemy/EnemyScript.cpp
@@ -0,0 +1,206 @@
+#include "EnemyScript.h"
+#include "../Config.h"
+#include "../Random.h"
+#include "../enemy/EnemyConfig.h"
+#include "api/Color.h"
+#include "api/Sprite.h"
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+#include <random>
+using namespace crepe;
+using namespace std;
+EnemyScript::EnemyScript() {
+ engine.seed(rd());
+ this->last_fired = std::chrono::steady_clock::now();
+ this->shot_delay = std::chrono::duration<float>(1.5 + Random::f(2, 0));
+}
+void EnemyScript::init() {
+ Metadata & meta = this->get_component<Metadata>();
+ this->subscribe<SpawnEnemyEvent>(
+ [this](const SpawnEnemyEvent & e) -> bool { return this->spawn_enemy(e); },
+ meta.game_object_id
+ );
+ this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool {
+ return this->on_collide(e);
+ });
+};
+void EnemyScript::fixed_update(duration_t dt) {
+ if (!spawned) return;
+ auto now = std::chrono::steady_clock::now();
+ std::chrono::duration<float> elapsed_hit = now - last_hit;
+ //hit blink timer
+ if (elapsed_hit > blink_time) {
+ set_hit_blink(false);
+ }
+ Transform & transform = this->get_component<Transform>();
+ if (!this->alive) {
+ Camera & camera = this->get_components_by_name<Camera>("camera").front();
+ Transform & cam_transform = this->get_components_by_name<Transform>("camera").front();
+ vec2 half_screen = camera.viewport_size / 2;
+ float x_value = cam_transform.position.x - half_screen.x - 100;
+ if (transform.position.x < x_value) {
+ this->despawn_enemy();
+ }
+ return;
+ }
+
+ Transform & player_transform = this->get_components_by_name<Transform>("player").front();
+ Rigidbody & enemy_body = this->get_component<Rigidbody>();
+ AI & ai_component = this->get_component<AI>();
+
+ float direction_to_player_y = player_transform.position.y - transform.position.y;
+ float distance_to_player_y = std::abs(direction_to_player_y);
+
+ float adjustment_speed = speed * (distance_to_player_y / MAX_DISTANCE);
+ adjustment_speed = std::clamp(adjustment_speed, MIN_SPEED, MAX_SPEED);
+ Rigidbody & player_body = this->get_components_by_tag<Rigidbody>("player").front();
+ // move path nodes
+ for (vec2 & path_node : ai_component.path) {
+ path_node.y += (direction_to_player_y > 0 ? 1 : -1) * adjustment_speed * dt.count();
+ path_node.x += player_body.data.linear_velocity.x * dt.count();
+ }
+ //bullet fire logic:
+
+ std::chrono::duration<float> elapsed = now - last_fired;
+ if (elapsed > shot_delay) {
+ this->shoot(transform.position);
+ last_fired = now;
+ this->shot_delay = std::chrono::duration<float>(Random::f(3, 1.5));
+ }
+}
+
+bool EnemyScript::spawn_enemy(const SpawnEnemyEvent & e) {
+
+ this->speed = e.speed;
+ this->alive = true;
+ this->spawned = true;
+ this->health = 2;
+ RefVector<Animator> animators = this->get_components<Animator>();
+ for (Animator & anim : animators) {
+ anim.active = false;
+ anim.set_anim(0);
+ }
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ for (Sprite & sprite : sprites) {
+ sprite.data.position_offset.x = 0;
+ }
+ Sprite & jetpack = sprites[2];
+ jetpack.data.position_offset.x = 20;
+ Sprite & gun = sprites[3];
+ gun.data.position_offset.x = -20;
+ AI & ai_component = this->get_component<AI>();
+ Transform & transform = this->get_component<Transform>();
+ Camera & camera = this->get_components_by_name<Camera>("camera").front();
+ Transform & cam_transform = this->get_components_by_name<Transform>("camera").front();
+ Rigidbody & rb = this->get_component<Rigidbody>();
+ rb.data.collision_layers = {COLL_LAY_BOT_TOP, COLL_LAY_PLAYER_BULLET};
+ rb.data.collision_layer = COLL_LAY_ENEMY;
+ vec2 half_screen = camera.viewport_size / 2;
+ float x_value = cam_transform.position.x + half_screen.x - 40 * (1 + e.column);
+ uniform_real_distribution<float> dist(
+ cam_transform.position.y - half_screen.y + 100,
+ cam_transform.position.y + half_screen.y - 100
+ );
+ float random_height = dist(engine);
+ vec2 spawn_location
+ = {cam_transform.position.x + camera.viewport_size.x / 2 + 100, random_height};
+ transform.position = spawn_location;
+ ai_component.path.clear();
+ ai_component.make_oval_path(10, 30, vec2 {x_value, random_height}, 1.5708, true);
+ ai_component.active = true;
+ this->last_fired = std::chrono::steady_clock::now();
+
+ return false;
+}
+
+void EnemyScript::set_hit_blink(bool status) {
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ for (Sprite & sprite : sprites) {
+ if (status) {
+ sprite.data.color = Color::RED;
+ continue;
+ }
+ sprite.data.color = Color::WHITE;
+ }
+}
+
+bool EnemyScript::on_collide(const CollisionEvent & e) {
+ if (!this->alive) return false;
+ if (e.info.other.metadata.tag == "player_bullet") {
+ this->health--;
+ last_hit = std::chrono::steady_clock::now();
+ //Sprite& sprite;
+ set_hit_blink(true);
+ if (health <= 0) {
+ this->death();
+ }
+ }
+
+ //body_animator.play();
+
+ return false;
+}
+void EnemyScript::death() {
+
+ Rigidbody & rb = this->get_component<Rigidbody>();
+ Transform & tr = this->get_component<Transform>();
+ RefVector<Animator> animators = this->get_components<Animator>();
+ for (Animator & anim : animators) {
+ anim.active = false;
+ anim.set_anim(3);
+ }
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ for (Sprite & sprite : sprites) {
+ sprite.data.position_offset.x = 15;
+ }
+ rb.data.linear_velocity_coefficient = {0.5, 1};
+ rb.data.collision_layers = {COLL_LAY_BOT_TOP};
+ rb.data.collision_layer = 0;
+
+ rb.data.gravity_scale = 1;
+ tr.rotation = 90;
+ AI & ai = this->get_component<AI>();
+ ai.active = false;
+ this->alive = false;
+ AI & ai_component = this->get_component<AI>();
+ ai_component.active = false;
+}
+void EnemyScript::despawn_enemy() {
+ Transform & transform = this->get_component<Transform>();
+ Rigidbody & rb = this->get_component<Rigidbody>();
+ rb.data.gravity_scale = 0;
+ rb.data.linear_velocity = {0, 0};
+ transform.rotation = 0;
+ transform.position = ENEMY_POOL_LOCATION;
+
+ this->spawned = false;
+}
+
+void EnemyScript::shoot(const vec2 & location) {
+ RefVector<Transform> bullet_transforms
+ = this->get_components_by_tag<Transform>("enemy_bullet");
+
+ for (Transform & bullet_pos : bullet_transforms) {
+ if (bullet_pos.position.x == 0 && bullet_pos.position.y == -750) {
+
+ bullet_pos.position = location;
+ bullet_pos.position.x -= 20;
+ Rigidbody & bullet_body
+ = this->get_components_by_id<Rigidbody>(bullet_pos.game_object_id).front();
+ BoxCollider bullet_collider
+ = this->get_components_by_id<BoxCollider>(bullet_pos.game_object_id).front();
+ bullet_collider.active = true;
+ bullet_body.active = true;
+ AudioSource & audio = this->get_component<AudioSource>();
+ audio.play();
+ return;
+ }
+ }
+}
diff --git a/game/enemy/EnemyScript.h b/game/enemy/EnemyScript.h
new file mode 100644
index 0000000..be71a78
--- /dev/null
+++ b/game/enemy/EnemyScript.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <chrono>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+#include <random>
+struct SpawnEnemyEvent : public crepe::Event {
+ float speed = 0;
+ int column = 0;
+};
+class EnemyScript : public crepe::Script {
+public:
+ EnemyScript();
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+ void shoot(const crepe::vec2 & position);
+ bool on_collide(const crepe::CollisionEvent & collisionData);
+ void despawn_enemy();
+ bool spawn_enemy(const SpawnEnemyEvent & e);
+ void death();
+ void set_hit_blink(bool status);
+
+private:
+ std::random_device rd;
+ std::default_random_engine engine;
+ bool alive = false;
+ bool spawned = false;
+ float speed = 50;
+ int health = 2;
+ const float MIN_SPEED = 20;
+ const float MAX_SPEED = 150;
+ const float MAX_DISTANCE = 200;
+ std::chrono::time_point<std::chrono::steady_clock> last_fired;
+ std::chrono::time_point<std::chrono::steady_clock> last_hit;
+ std::chrono::duration<float> shot_delay = std::chrono::duration<float>(0);
+ std::chrono::duration<float> blink_time = std::chrono::duration<float>(0.1);
+};
diff --git a/game/enemy/EnemySubScene.cpp b/game/enemy/EnemySubScene.cpp
new file mode 100644
index 0000000..c6aecec
--- /dev/null
+++ b/game/enemy/EnemySubScene.cpp
@@ -0,0 +1,117 @@
+#include <string>
+
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+#include "../Config.h"
+#include "EnemyConfig.h"
+#include "EnemyScript.h"
+#include "EnemySubScene.h"
+using namespace crepe;
+using namespace std;
+//#TODO add sound
+int EnemySubScene::create(Scene & scn, int enemy_counter) {
+
+ string unique_name = "enemy_" + to_string(enemy_counter++);
+ GameObject enemy = scn.new_object(unique_name.c_str(), "enemy", ENEMY_POOL_LOCATION, 0, 1);
+
+ enemy.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 0,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .max_linear_velocity = 400,
+ .collision_layers = {COLL_LAY_BOT_TOP, COLL_LAY_PLAYER_BULLET},
+ .collision_layer = COLL_LAY_ENEMY,
+
+ });
+ // normal body
+ Asset enemy_body_asset {"asset/workers/worker2Body.png"};
+ enemy.add_component<BoxCollider>(vec2(40, 60));
+ Sprite & enemy_body_sprite = enemy.add_component<Sprite>(
+ enemy_body_asset,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 0,
+ .size = vec2(0, 50),
+ }
+ );
+ Animator & body_animator = enemy.add_component<Animator>(
+ enemy_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .col = 1,
+ .row = 0,
+ .looping = false,
+ }
+ );
+ body_animator.pause();
+
+ Asset enemy_head_asset {"asset/workers/worker2Head.png"};
+ Sprite & enemy_head_sprite = enemy.add_component<Sprite>(
+ enemy_head_asset,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 1,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ enemy.add_component<Animator>(
+ enemy_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+
+ //jetpack
+ //enemy.add_component<CircleCollider>(25, vec2(0, -20));
+ Asset enemy_jetpack_asset {"asset/barry/jetpackDefault.png"};
+ Sprite & enemy_jetpack_sprite = enemy.add_component<Sprite>(
+ enemy_jetpack_asset,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 2,
+ .size = vec2(0, 60),
+ .position_offset = vec2(20, 0),
+ }
+ );
+ enemy_jetpack_sprite.active = true;
+ enemy.add_component<Animator>(
+ enemy_jetpack_sprite, ivec2(32, 44), uvec2(4, 4),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ //gun
+ Asset enemy_pistol_asset {"asset/workers/gun.png"};
+ Sprite & enemy_pistol_sprite = enemy.add_component<Sprite>(
+ enemy_pistol_asset,
+ Sprite::Data {
+ .flip = {false, false},
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 2,
+ .size = vec2(0, 20),
+ .position_offset = vec2(-20, 0),
+ }
+ );
+ enemy.add_component<AudioSource>(Asset("asset/sfx/bike_gun_2.ogg")).volume = 0.1;
+ AI & ai_component = enemy.add_component<AI>(3000);
+ ai_component.path_follow_on();
+ ai_component.active = false;
+ BehaviorScript & enemy_script
+ = enemy.add_component<BehaviorScript>().set_script<EnemyScript>();
+ //enemy_script.active = false;
+ return enemy_counter;
+}
diff --git a/game/enemy/EnemySubScene.h b/game/enemy/EnemySubScene.h
new file mode 100644
index 0000000..3899250
--- /dev/null
+++ b/game/enemy/EnemySubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class EnemySubScene {
+public:
+ int create(crepe::Scene & scn, int enemy_counter);
+};
diff --git a/game/hud/HudConfig.h b/game/hud/HudConfig.h
new file mode 100644
index 0000000..facc298
--- /dev/null
+++ b/game/hud/HudConfig.h
@@ -0,0 +1,33 @@
+#pragma once
+#include <crepe/types.h>
+
+static constexpr crepe::vec2 TOP_LEFT = {-530, -230};
+static constexpr const char * HUD_DISTANCE = "hud_distance";
+static constexpr const char * HUD_BEST = "hud_best";
+static constexpr const char * HUD_COINS = "hud_coins";
+static constexpr const char * HUD_FPS = "hud_fps";
+
+// Distance
+static constexpr const char * DISTANCE_PLACEHOLDER = "0000m";
+static constexpr const char * DISTANCE_UNIT = "m";
+static constexpr int DISTANCE_LENGTH = 5;
+static constexpr float DISTANCE_CHAR_WIDTH = 12;
+static constexpr float STEP_SIZE_DISTANCE = 100;
+
+// BEST
+static constexpr const char * BEST = "BEST:";
+static constexpr int BEST_LENGTH = 5;
+static constexpr float BEST_CHAR_WIDTH = 10;
+static constexpr crepe::vec2 BEST_OFFSET = {0, 25};
+
+// COINS
+static constexpr const char * COINS = "0000";
+static constexpr int COINS_LENGTH = 4;
+static constexpr float COINS_CHAR_WIDTH = 10;
+static constexpr crepe::vec2 COINS_OFFSET = {0, 50};
+
+// FPS
+static constexpr const char * FPS = "00";
+static constexpr int FPS_LENGTH = 2;
+static constexpr float FPS_CHAR_WIDTH = 10;
+static constexpr crepe::vec2 FPS_OFFSET = {1030, 0};
diff --git a/game/hud/HudScript.cpp b/game/hud/HudScript.cpp
new file mode 100644
index 0000000..e4aeae7
--- /dev/null
+++ b/game/hud/HudScript.cpp
@@ -0,0 +1,98 @@
+#include "HudScript.h"
+#include "HudConfig.h"
+
+#include "../Config.h"
+#include "../Events.h"
+#include "api/KeyCodes.h"
+#include "menus/endgame/EndGameSubScript.h"
+
+#include <climits>
+
+#include <crepe/api/Text.h>
+#include <crepe/api/Transform.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace crepe;
+using namespace std;
+
+void HudScript::init() {
+ savemgr = &this->get_save_manager();
+ savemgr->set(TOTAL_COINS_RUN, 0);
+ Text & txt = this->get_components_by_name<Text>(HUD_BEST).front();
+ string record
+ = BEST + to_string(savemgr->get<int>(DISTANCE_GAME, 0).get()) + DISTANCE_UNIT;
+ txt.text = record;
+ txt.dimensions = {BEST_CHAR_WIDTH * record.size(), (BEST_CHAR_WIDTH) * 2};
+ txt.offset
+ = TOP_LEFT + FONTOFFSET + BEST_OFFSET + vec2 {record.size() * BEST_CHAR_WIDTH / 2, 0};
+
+ this->subscribe<GetCoinEvent>([this](const GetCoinEvent e) -> bool {
+ return this->get_coin(e);
+ });
+ this->subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ return this->toggle_fps(ev);
+ });
+ this->subscribe<EndGameEvent>([this](const EndGameEvent e) -> bool {
+ return this->save();
+ });
+}
+
+bool HudScript::toggle_fps(crepe::KeyPressEvent ev) {
+ if (ev.key != Keycode::D1) return false;
+ Text & txt_fps = this->get_components_by_name<Text>(HUD_FPS).front();
+ this->show_fps = !this->show_fps;
+ if (this->show_fps) {
+ txt_fps.active = true;
+ } else {
+ txt_fps.active = false;
+ }
+ return true;
+}
+
+void HudScript::frame_update(crepe::duration_t dt) {
+
+ // Distance
+ Text & txt_dt = this->get_components_by_name<Text>(HUD_DISTANCE).front();
+ Transform & tf = this->get_components_by_name<Transform>(PLAYER_NAME).front();
+ string distance
+ = to_string(static_cast<int>(tf.position.x / STEP_SIZE_DISTANCE)) + DISTANCE_UNIT;
+ this->distance_st = distance;
+ txt_dt.text = distance;
+ txt_dt.dimensions = {DISTANCE_CHAR_WIDTH * distance.size(), (DISTANCE_CHAR_WIDTH) * 2};
+ txt_dt.offset
+ = TOP_LEFT + FONTOFFSET + vec2 {distance.size() * DISTANCE_CHAR_WIDTH / 2, 0};
+
+ // Coins
+ Text & txt_co = this->get_components_by_name<Text>(HUD_COINS).front();
+ string amount_of_coins = to_string(this->coin_amount);
+ this->coin_amount_st = amount_of_coins;
+ txt_co.text = amount_of_coins;
+ txt_co.dimensions = {COINS_CHAR_WIDTH * amount_of_coins.size(), (COINS_CHAR_WIDTH) * 2};
+ txt_co.offset = TOP_LEFT + FONTOFFSET + COINS_OFFSET
+ + vec2 {amount_of_coins.size() * COINS_CHAR_WIDTH / 2, 0};
+
+ // FPS
+ Text & txt_fps = this->get_components_by_name<Text>(HUD_FPS).front();
+ float fps = this->get_loop_timer().get_fps();
+ string fps_amount = to_string(this->get_loop_timer().get_fps());
+ txt_fps.text = fps_amount;
+ txt_fps.dimensions = {FPS_CHAR_WIDTH * fps_amount.size(), (FPS_CHAR_WIDTH) * 2};
+ txt_fps.offset = TOP_LEFT + FONTOFFSET + FPS_OFFSET
+ + vec2 {fps_amount.size() * FPS_CHAR_WIDTH / 2, 0};
+ if (fps >= 30) txt_fps.data.text_color = Color::YELLOW;
+ if (fps >= 50) txt_fps.data.text_color = Color::GREEN;
+ if (fps < 30) txt_fps.data.text_color = Color::RED;
+}
+
+bool HudScript::get_coin(const GetCoinEvent e) {
+ this->coin_amount = e.amount_of_coins;
+ return true;
+}
+
+bool HudScript::save() {
+ SaveManager & savemgr = this->get_save_manager();
+ savemgr.set(TOTAL_COINS_RUN, this->coin_amount);
+ savemgr.set(DISTANCE_RUN, this->distance_st);
+ this->trigger_event<ShowScoreEvent>();
+ return false;
+}
diff --git a/game/hud/HudScript.h b/game/hud/HudScript.h
new file mode 100644
index 0000000..2b789db
--- /dev/null
+++ b/game/hud/HudScript.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+#include <crepe/manager/SaveManager.h>
+
+struct GetCoinEvent : public crepe::Event {
+ int amount_of_coins;
+};
+
+class HudScript : public crepe::Script {
+public:
+ void init() override;
+ void frame_update(crepe::duration_t dt) override;
+ bool get_coin(const GetCoinEvent e);
+ bool toggle_fps(crepe::KeyPressEvent ev);
+ bool save();
+
+private:
+ crepe::SaveManager * savemgr;
+ bool show_fps = false;
+ int coin_amount = 0;
+ std::string coin_amount_st = "";
+ std::string distance_st = "";
+};
diff --git a/game/hud/HudSubScene.cpp b/game/hud/HudSubScene.cpp
new file mode 100644
index 0000000..dcc07b4
--- /dev/null
+++ b/game/hud/HudSubScene.cpp
@@ -0,0 +1,68 @@
+#include "HudSubScene.h"
+#include "HudConfig.h"
+
+#include "../Config.h"
+
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Text.h>
+
+using namespace crepe;
+using namespace std;
+
+void HudSubScene::create(Scene & scn) {
+
+ // Distance
+ GameObject hud_dis = scn.new_object(HUD_DISTANCE);
+
+ crepe::vec2 size_distance
+ = {DISTANCE_CHAR_WIDTH * DISTANCE_LENGTH, (DISTANCE_CHAR_WIDTH) * 2};
+ hud_dis.add_component<Text>(
+ size_distance, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ TOP_LEFT + FONTOFFSET + vec2 {DISTANCE_LENGTH * DISTANCE_CHAR_WIDTH / 2, 0},
+ DISTANCE_PLACEHOLDER
+ );
+
+ // Best
+ GameObject hud_best = scn.new_object(HUD_BEST);
+ crepe::vec2 size_best = {BEST_CHAR_WIDTH * BEST_LENGTH, (BEST_CHAR_WIDTH) * 2};
+ hud_best.add_component<Text>(
+ size_best, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::GREY,
+ },
+ TOP_LEFT + FONTOFFSET + BEST_OFFSET + vec2 {BEST_LENGTH * BEST_CHAR_WIDTH / 2, 0}, BEST
+ );
+
+ // Coins
+ GameObject hud_coin = scn.new_object(HUD_COINS);
+ crepe::vec2 size_coin = {COINS_CHAR_WIDTH * COINS_LENGTH, (COINS_CHAR_WIDTH) * 2};
+ hud_coin.add_component<Text>(
+ size_coin, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::GOLD,
+ },
+ TOP_LEFT + FONTOFFSET + COINS_OFFSET + vec2 {COINS_LENGTH * COINS_CHAR_WIDTH / 2, 0},
+ COINS
+ );
+
+ // Fps
+ GameObject hud_fps = scn.new_object(HUD_FPS);
+ crepe::vec2 size_fps = {FPS_CHAR_WIDTH * FPS_LENGTH, (FPS_CHAR_WIDTH) * 2};
+ hud_fps
+ .add_component<Text>(
+ size_fps, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::GREEN,
+ },
+ TOP_LEFT + FONTOFFSET + FPS_OFFSET + vec2 {FPS_LENGTH * FPS_CHAR_WIDTH / 2, 0}, FPS
+ )
+ .active
+ = false;
+}
diff --git a/game/hud/HudSubScene.h b/game/hud/HudSubScene.h
new file mode 100644
index 0000000..0cd368e
--- /dev/null
+++ b/game/hud/HudSubScene.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class HudSubScene {
+public:
+ void create(crepe::Scene & scn);
+};
diff --git a/game/hud/SpeedScript.cpp b/game/hud/SpeedScript.cpp
new file mode 100644
index 0000000..2ced47a
--- /dev/null
+++ b/game/hud/SpeedScript.cpp
@@ -0,0 +1,42 @@
+#include "SpeedScript.h"
+
+#include "../Events.h"
+#include "api/BehaviorScript.h"
+#include <crepe/api/Event.h>
+#include <crepe/api/KeyCodes.h>
+#include <crepe/manager/LoopTimerManager.h>
+
+using namespace crepe;
+using namespace std;
+
+void SpeedScript::init() {
+ this->subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ if (ev.key != Keycode::HOME) return false;
+ LoopTimerManager & lp = this->get_loop_timer();
+ this->toggle = !this->toggle;
+ if (this->toggle) {
+ this->timescale = lp.get_time_scale();
+ lp.set_time_scale(0);
+ } else {
+ lp.set_time_scale(this->timescale);
+ }
+
+ return true;
+ });
+ this->subscribe<EndGameEvent>([this](const EndGameEvent e) {
+ this->get_component<BehaviorScript>().active = false;
+ return false;
+ });
+}
+
+void SpeedScript::fixed_update(crepe::duration_t dt) {
+ LoopTimerManager & lp = this->get_loop_timer();
+ if (this->get_key_state(Keycode::PAGE_UP)) {
+ if (lp.get_time_scale() >= 2) return;
+ lp.set_time_scale(lp.get_time_scale() + 0.1);
+ }
+ if (this->get_key_state(Keycode::PAGE_DOWN)) {
+ if (lp.get_time_scale() <= 0.5) return;
+ lp.set_time_scale(lp.get_time_scale() - 0.1);
+ }
+}
diff --git a/game/hud/SpeedScript.h b/game/hud/SpeedScript.h
new file mode 100644
index 0000000..b40f7cc
--- /dev/null
+++ b/game/hud/SpeedScript.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+#include <crepe/manager/SaveManager.h>
+
+class SpeedScript : public crepe::Script {
+public:
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+
+private:
+ crepe::SaveManager * savemgr;
+ bool toggle = false;
+ float timescale = 1;
+};
diff --git a/game/main.cpp b/game/main.cpp
new file mode 100644
index 0000000..95cb35c
--- /dev/null
+++ b/game/main.cpp
@@ -0,0 +1,27 @@
+#include <cstdlib>
+
+#include <crepe/api/Engine.h>
+#include <crepe/api/Script.h>
+
+#include "EngineConfig.h"
+#include "GameScene.h"
+#include "PreviewScene.h"
+#include "menus/mainmenu/MainMenuScene.h"
+#include "menus/shop/ShopMenuScene.h"
+
+using namespace crepe;
+
+int main() {
+ srand(time(NULL));
+
+ Config::get_instance() = ENGINE_CONFIG;
+
+ Engine gameloop;
+
+ gameloop.add_scene<MainMenuScene>();
+ gameloop.add_scene<ShopMenuScene>();
+ gameloop.add_scene<GameScene>();
+ gameloop.add_scene<PreviewScene>();
+
+ return gameloop.main();
+}
diff --git a/game/makefile b/game/makefile
new file mode 100644
index 0000000..3fedf7f
--- /dev/null
+++ b/game/makefile
@@ -0,0 +1,5 @@
+.PHONY: FORCE
+
+format: FORCE
+ $(MAKE) -C .. $@
+
diff --git a/game/menus/BannerSubScene.cpp b/game/menus/BannerSubScene.cpp
new file mode 100644
index 0000000..006a829
--- /dev/null
+++ b/game/menus/BannerSubScene.cpp
@@ -0,0 +1,49 @@
+#include "BannerSubScene.h"
+#include "MenusConfig.h"
+
+#include "../Config.h"
+
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Text.h>
+
+using namespace crepe;
+using namespace std;
+
+void BannerSubScene::create(Scene & scn, const Data & data) {
+ GameObject menu_banner = scn.new_object("menu_banner", "", {0, -414});
+ menu_banner.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {1100, 88},
+ }
+ );
+ menu_banner.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_2_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {1100, 66},
+ .position_offset {0, 77},
+ }
+ );
+ menu_banner.add_component<Sprite>(
+ Asset("asset/ui/settings_container/banner_bottom.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {1100, 7},
+ .position_offset {0, 113},
+ }
+ );
+ crepe::vec2 size
+ = {data.banner_title_width, (data.banner_title_width / data.banner_title.size()) * 2};
+
+ menu_banner.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = true,
+ .text_color = Color::WHITE,
+ },
+ data.banner_title_offset + FONTOFFSET, data.banner_title
+ );
+}
diff --git a/game/menus/BannerSubScene.h b/game/menus/BannerSubScene.h
new file mode 100644
index 0000000..c194dfc
--- /dev/null
+++ b/game/menus/BannerSubScene.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <crepe/api/GameObject.h>
+#include <crepe/types.h>
+
+namespace crepe {
+class Scene;
+}
+
+class BannerSubScene {
+public:
+ struct Data {
+ const std::string & banner_title = "NODATA";
+ const float banner_title_width = 100;
+ const crepe::vec2 & banner_title_offset = {0, 0};
+ };
+
+public:
+ void create(crepe::Scene & scn, const Data & data);
+};
diff --git a/game/menus/ButtonNextMainMenuSubScript.cpp b/game/menus/ButtonNextMainMenuSubScript.cpp
new file mode 100644
index 0000000..63a2777
--- /dev/null
+++ b/game/menus/ButtonNextMainMenuSubScript.cpp
@@ -0,0 +1,44 @@
+#include "ButtonNextMainMenuSubScript.h"
+#include "ButtonReplaySubScript.h"
+#include "MenusConfig.h"
+#include "ValueBroker.h"
+
+#include "manager/SaveManager.h"
+
+#include "../Config.h"
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void ButtonNextMainMenuSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonNextMainMenuSubScript::on_button_press(const ButtonPressEvent & e) {
+ RefVector<AudioSource> audios
+ = this->get_components_by_name<AudioSource>("background_music");
+
+ for (AudioSource & audio : audios) {
+ audio.stop();
+ }
+
+ this->trigger_event<DeleteRecordingEvent>();
+ SaveManager & savemgr = this->get_save_manager();
+
+ ValueBroker<int> coins = savemgr.get<int>(TOTAL_COINS_RUN, 0);
+ ValueBroker<int> coins_game = savemgr.get<int>(TOTAL_COINS_GAME, 0);
+ savemgr.set(TOTAL_COINS_GAME, coins_game.get() + coins.get());
+
+ ValueBroker<int> distance = savemgr.get<int>(DISTANCE_RUN, 0);
+ ValueBroker<int> distance_game = savemgr.get<int>(DISTANCE_GAME, 0);
+ if (distance.get() > distance_game.get()) savemgr.set(DISTANCE_GAME, distance.get());
+
+ this->set_next_scene(MAINMENU_SCENE);
+ return false;
+}
diff --git a/game/menus/ButtonNextMainMenuSubScript.h b/game/menus/ButtonNextMainMenuSubScript.h
new file mode 100644
index 0000000..3bc3f52
--- /dev/null
+++ b/game/menus/ButtonNextMainMenuSubScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonNextMainMenuSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/ButtonReplaySubScript.cpp b/game/menus/ButtonReplaySubScript.cpp
new file mode 100644
index 0000000..01cccbf
--- /dev/null
+++ b/game/menus/ButtonReplaySubScript.cpp
@@ -0,0 +1,43 @@
+#include "ButtonReplaySubScript.h"
+#include "Config.h"
+#include "MenusConfig.h"
+
+#include "../Events.h"
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void ButtonReplaySubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+ this->subscribe<EndGameEvent>([this](const EndGameEvent & e) {
+ return this->set_recording();
+ });
+ this->subscribe<DeleteRecordingEvent>([this](const DeleteRecordingEvent & e) {
+ return this->delete_recording();
+ });
+ if (DISABLE_REPLAY) return;
+ replay.record_start();
+}
+
+bool ButtonReplaySubScript::on_button_press(const ButtonPressEvent & e) {
+ if (DISABLE_REPLAY) return false;
+ replay.play(this->recording);
+ return false;
+}
+
+bool ButtonReplaySubScript::set_recording() {
+ if (DISABLE_REPLAY) return false;
+ this->recording = replay.record_end();
+ return false;
+}
+
+bool ButtonReplaySubScript::delete_recording() {
+ if (DISABLE_REPLAY) return false;
+ replay.release(this->recording);
+ return false;
+}
diff --git a/game/menus/ButtonReplaySubScript.h b/game/menus/ButtonReplaySubScript.h
new file mode 100644
index 0000000..3eb8aa9
--- /dev/null
+++ b/game/menus/ButtonReplaySubScript.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+struct DeleteRecordingEvent : public crepe::Event {};
+
+class ButtonReplaySubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+private:
+ crepe::recording_t recording = 0;
+ bool set_recording();
+ bool delete_recording();
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/ButtonSetMainMenuSubScript.cpp b/game/menus/ButtonSetMainMenuSubScript.cpp
new file mode 100644
index 0000000..1c6bcb2
--- /dev/null
+++ b/game/menus/ButtonSetMainMenuSubScript.cpp
@@ -0,0 +1,23 @@
+#include "ButtonSetMainMenuSubScript.h"
+#include "MenusConfig.h"
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void ButtonSetMainMenuSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonSetMainMenuSubScript::on_button_press(const ButtonPressEvent & e) {
+ RefVector<AudioSource> audios
+ = this->get_components_by_name<AudioSource>("background_music");
+
+ this->set_next_scene(MAINMENU_SCENE);
+ return false;
+}
diff --git a/game/menus/ButtonSetMainMenuSubScript.h b/game/menus/ButtonSetMainMenuSubScript.h
new file mode 100644
index 0000000..2fb2634
--- /dev/null
+++ b/game/menus/ButtonSetMainMenuSubScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonSetMainMenuSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/ButtonSetShopSubScript.cpp b/game/menus/ButtonSetShopSubScript.cpp
new file mode 100644
index 0000000..4f395eb
--- /dev/null
+++ b/game/menus/ButtonSetShopSubScript.cpp
@@ -0,0 +1,17 @@
+#include "ButtonSetShopSubScript.h"
+#include "MenusConfig.h"
+
+using namespace crepe;
+using namespace std;
+
+void ButtonSetShopSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonSetShopSubScript::on_button_press(const ButtonPressEvent & e) {
+ this->set_next_scene(SHOP_SCENE);
+ return false;
+}
diff --git a/game/menus/ButtonSetShopSubScript.h b/game/menus/ButtonSetShopSubScript.h
new file mode 100644
index 0000000..4017a4c
--- /dev/null
+++ b/game/menus/ButtonSetShopSubScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonSetShopSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/ButtonShowCreditsSubScript.cpp b/game/menus/ButtonShowCreditsSubScript.cpp
new file mode 100644
index 0000000..ec0e980
--- /dev/null
+++ b/game/menus/ButtonShowCreditsSubScript.cpp
@@ -0,0 +1,20 @@
+#include "ButtonShowCreditsSubScript.h"
+#include "MenusConfig.h"
+#include "mainmenu/CreditsSubScript.h"
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void ButtonShowCreditsSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonShowCreditsSubScript::on_button_press(const ButtonPressEvent & e) {
+ this->trigger_event<ShowCreditsEvent>();
+ return false;
+}
diff --git a/game/menus/ButtonShowCreditsSubScript.h b/game/menus/ButtonShowCreditsSubScript.h
new file mode 100644
index 0000000..3c73c44
--- /dev/null
+++ b/game/menus/ButtonShowCreditsSubScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonShowCreditsSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/ButtonSubScene.cpp b/game/menus/ButtonSubScene.cpp
new file mode 100644
index 0000000..1fe6b03
--- /dev/null
+++ b/game/menus/ButtonSubScene.cpp
@@ -0,0 +1,266 @@
+#include "ButtonSubScene.h"
+#include "ButtonNextMainMenuSubScript.h"
+#include "ButtonReplaySubScript.h"
+#include "ButtonSetMainMenuSubScript.h"
+#include "ButtonSetShopSubScript.h"
+#include "ButtonShowCreditsSubScript.h"
+#include "IButtonScript.h"
+#include "MenusConfig.h"
+
+#include "../preview/PreviewReplaySubScript.h"
+#include "../preview/PreviewStartRecSubScript.h"
+#include "../preview/PreviewStopRecSubScript.h"
+#include "api/Asset.h"
+#include "mainmenu/ButtonTransitionPreviewSubScript.h"
+
+#include "../Config.h"
+#include "mainmenu/CreditsSubScript.h"
+#include "menus/shop/ButtonBuySelectBubbleScript.h"
+#include "menus/shop/ButtonBuySelectBulletScript.h"
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Button.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Text.h>
+
+using namespace crepe;
+using namespace std;
+
+void ButtonSubScene::create(Scene & scn, const Data & data) {
+ GameObject button_object
+ = scn.new_object("button", data.tag, data.position, 0, data.scale);
+ this->set_button_overlay(button_object, data);
+ this->btn_text(button_object, data);
+ this->set_script(button_object, data);
+ this->set_icon(button_object, data);
+}
+
+void ButtonSubScene::btn_text(crepe::GameObject & button_object, const Data & data) {
+
+ crepe::vec2 size = {data.text_width, (data.text_width / data.text.size()) * 2};
+ button_object.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = data.worldspace,
+ .text_color = Color::WHITE,
+ },
+ data.text_offset + FONTOFFSET, data.text
+ );
+}
+
+void ButtonSubScene::set_script(crepe::GameObject & button_object, const Data & data) {
+ switch (data.script_type) {
+ case ScriptSelect::PREVIEW:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonTransitionPreviewSubScript>();
+ break;
+ case ScriptSelect::SHOP:
+ button_object.add_component<BehaviorScript>().set_script<ButtonSetShopSubScript>();
+ break;
+ case ScriptSelect::MAINMENU:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonSetMainMenuSubScript>();
+ break;
+ case ScriptSelect::NEXT:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonNextMainMenuSubScript>();
+ break;
+ case ScriptSelect::REPLAY:
+ button_object.add_component<BehaviorScript>().set_script<ButtonReplaySubScript>();
+ break;
+ case ScriptSelect::CREDITS_BACK:
+ button_object.add_component<BehaviorScript>().set_script<CreditsSubScript>(data.tag
+ );
+ break;
+ case ScriptSelect::CREDITS_SHOW:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonShowCreditsSubScript>();
+ break;
+ case ScriptSelect::PREVIEW_REPLAY:
+ button_object.add_component<BehaviorScript>().set_script<PreviewReplaySubScript>();
+ break;
+ case ScriptSelect::PREVIEW_START:
+ button_object.add_component<BehaviorScript>().set_script<PreviewStartRecSubScript>(
+ );
+ break;
+ case ScriptSelect::PREVIEW_STOP:
+ button_object.add_component<BehaviorScript>().set_script<PreviewStopRecSubScript>(
+ );
+ break;
+ case ScriptSelect::SHOP_BULLET:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonBuySelectBulletScript>();
+ break;
+ case ScriptSelect::SHOP_BUBBLE:
+ button_object.add_component<BehaviorScript>()
+ .set_script<ButtonBuySelectBubbleScript>();
+ break;
+ case ScriptSelect::NONE:
+ button_object.add_component<BehaviorScript>().set_script<IButtonScript>();
+ break;
+ }
+}
+
+void ButtonSubScene::set_icon(crepe::GameObject & button_object, const Data & data) {
+ switch (data.icon_type) {
+ case IconSelect::SHOP:
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/buttonCoinsSmall.png"),
+ Sprite::Data {
+ .sorting_in_layer
+ = STARTING_SORTING_IN_LAYER + 3 + data.sorting_layer_offset,
+ .size = ICON_SIZE,
+ .position_offset = data.icon_offset,
+ .world_space = data.worldspace,
+ }
+ );
+ break;
+ case IconSelect::COINS:
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/buttonCoinsSmall.png"),
+ Sprite::Data {
+ .sorting_in_layer
+ = STARTING_SORTING_IN_LAYER + 3 + data.sorting_layer_offset,
+ .size = ICON_SIZE,
+ .position_offset = data.icon_offset,
+ .world_space = data.worldspace,
+ }
+ );
+ break;
+ case IconSelect::NONE:
+ break;
+ }
+}
+
+void ButtonSubScene::set_button_overlay(crepe::GameObject & button_object, const Data & data) {
+ switch (data.button_type) {
+ case ButtonSelect::LARGE:
+ this->large_btn_overlay(button_object, data);
+ break;
+ case ButtonSelect::BACK:
+ this->back_btn_overlay(button_object, data);
+ break;
+ case ButtonSelect::NEXT:
+ this->next_btn_overlay(button_object, data);
+ break;
+ }
+}
+
+void ButtonSubScene::large_btn_overlay(crepe::GameObject & button_object, const Data & data) {
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/buttonBacking.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset,
+ .size = LARGE_OVERLAY_SIZE,
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Button>(LARGE_OVERLAY_SIZE, Button::Data {});
+ if (!data.color_side) return;
+ this->btn_color_side(button_object, SIDE_PANEL_OFFSET, data);
+}
+
+void ButtonSubScene::back_btn_overlay(crepe::GameObject & button_object, const Data & data) {
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/backbuttonright.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset,
+ .size = SMALL_OVERLAY_SIZE_RIGHT,
+ .position_offset = {20, 0},
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/backbuttonleft.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset,
+ .size = SMALL_OVERLAY_SIZE_LEFT,
+ .position_offset = {-80, 0},
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Button>(
+ vec2 {
+ SMALL_OVERLAY_SIZE_LEFT.x + SMALL_OVERLAY_SIZE_RIGHT.x, SMALL_OVERLAY_SIZE_LEFT.y
+ },
+ Button::Data {}
+ );
+}
+
+void ButtonSubScene::next_btn_overlay(crepe::GameObject & button_object, const Data & data) {
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/backbuttonright.png"),
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset,
+ .size = SMALL_OVERLAY_SIZE_RIGHT,
+ .position_offset = {-20, 0},
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Sprite>(
+ Asset("asset/ui/backbuttonleft.png"),
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1 + data.sorting_layer_offset,
+ .size = SMALL_OVERLAY_SIZE_LEFT,
+ .position_offset = {80, 0},
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Button>(
+ vec2 {
+ SMALL_OVERLAY_SIZE_LEFT.x + SMALL_OVERLAY_SIZE_RIGHT.x, SMALL_OVERLAY_SIZE_LEFT.y
+ },
+ Button::Data {}
+ );
+}
+
+void ButtonSubScene::btn_color_side(
+ crepe::GameObject & button_object, const vec2 & offset, const Data & data
+) {
+ Asset * selected;
+ Asset blue = Asset("asset/ui/buttonSmallBlue.png");
+ Asset orange = Asset("asset/ui/buttonSmallOrange.png");
+ Asset purple = Asset("asset/ui/buttonSmallPurple.png");
+ Asset yellow = Asset("asset/ui/buttonSmallYellow.png");
+ switch (data.btn_side_color) {
+ case ButtonSideColor::BLUE:
+ selected = &blue;
+ break;
+ case ButtonSideColor::ORANGE:
+ selected = &orange;
+ break;
+ case ButtonSideColor::PURPLE:
+ selected = &purple;
+ break;
+ case ButtonSideColor::YELLOW:
+ selected = &yellow;
+ break;
+ case ButtonSideColor::NONE:
+ selected = &blue;
+ break;
+ }
+
+ button_object.add_component<Sprite>(
+ *selected,
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 2 + data.sorting_layer_offset,
+ .size = SIDE_PANEL_SIZE,
+ .position_offset = offset,
+ .world_space = data.worldspace,
+ }
+ );
+ button_object.add_component<Sprite>(
+ *selected,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 2 + data.sorting_layer_offset,
+ .size = SIDE_PANEL_SIZE,
+ .position_offset = {-offset.x, offset.y},
+ .world_space = data.worldspace,
+ }
+ );
+}
diff --git a/game/menus/ButtonSubScene.h b/game/menus/ButtonSubScene.h
new file mode 100644
index 0000000..d4c7223
--- /dev/null
+++ b/game/menus/ButtonSubScene.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <crepe/api/GameObject.h>
+
+#include <string>
+
+namespace crepe {
+class Scene;
+}
+
+class ButtonSubScene {
+public:
+ //script enum
+ enum class ScriptSelect {
+ PREVIEW,
+ SHOP,
+ MAINMENU,
+ NEXT,
+ REPLAY,
+ CREDITS_SHOW,
+ CREDITS_BACK,
+ PREVIEW_START,
+ PREVIEW_STOP,
+ PREVIEW_REPLAY,
+ SHOP_BULLET,
+ SHOP_BUBBLE,
+ NONE,
+ };
+ //icon enum
+ enum class IconSelect {
+ SHOP,
+ COINS,
+ NONE,
+ };
+ //icon enum
+ enum class ButtonSelect {
+ BACK,
+ NEXT,
+ LARGE,
+ };
+
+ enum class ButtonSideColor {
+ BLUE,
+ ORANGE,
+ PURPLE,
+ YELLOW,
+ NONE,
+ };
+ //data struct
+ struct Data {
+ const std::string & text = "NODATA";
+ const crepe::vec2 & text_offset = {0, 0};
+ const float text_width = 200;
+ const crepe::vec2 & icon_offset = {0, 0};
+ const IconSelect icon_type = IconSelect::NONE;
+ const crepe::vec2 & position = {0, 0};
+ const ScriptSelect script_type = ScriptSelect::NONE;
+ const ButtonSelect button_type = ButtonSelect::LARGE;
+ const float scale = 1;
+ const bool worldspace = true;
+ const bool color_side = true;
+ const std::string & tag = "";
+ const int sorting_layer_offset = 0;
+ const ButtonSideColor btn_side_color = ButtonSideColor::NONE;
+ };
+
+public:
+ void create(crepe::Scene & scn, const Data & data);
+
+private:
+ void large_btn_overlay(crepe::GameObject & button_object, const Data & data);
+ void back_btn_overlay(crepe::GameObject & button_object, const Data & data);
+ void next_btn_overlay(crepe::GameObject & button_object, const Data & data);
+ void btn_color_side(
+ crepe::GameObject & button_object, const crepe::vec2 & offset, const Data & data
+ );
+ void btn_text(crepe::GameObject & button_object, const Data & data);
+ void set_script(crepe::GameObject & button_object, const Data & data);
+ void set_icon(crepe::GameObject & button_object, const Data & data);
+ void set_button_overlay(crepe::GameObject & button_object, const Data & data);
+
+private:
+ static constexpr crepe::vec2 SIDE_PANEL_OFFSET = {113, 0};
+};
diff --git a/game/menus/FloatingWindowSubScene.cpp b/game/menus/FloatingWindowSubScene.cpp
new file mode 100644
index 0000000..4420bfa
--- /dev/null
+++ b/game/menus/FloatingWindowSubScene.cpp
@@ -0,0 +1,220 @@
+
+#include "FloatingWindowSubScene.h"
+#include "MenusConfig.h"
+
+#include <crepe/api/Camera.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void FloatingWindowSubScene::create(Scene & scn, const Data & data) {
+ const vec2 SIZE = {data.width, data.width * 0.75f};
+ const vec2 POSITION_CORRECTION = vec2 {0, -SIZE.y / 2} + data.offset;
+ const float THICKNESS_BANNER = 34;
+ const float MIDDLE_OFFSET_FACTOR_TICKNESS = 0.83;
+ const float MIDDLE_OFFSET_FACTOR_OFFSET = 1.2;
+ const float MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH = 0.86;
+ const float MIDDLE_OFFSET_OFFSET_ADDITION = -0.5;
+ const float BOTTOM_OFFSET_X = 3;
+ const float BOTTOM_OFFSET_Y = -3;
+
+ GameObject floatingwindow = scn.new_object("FloatingWindow", data.group_tag);
+
+ // Top_middle
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {SIZE.x, THICKNESS_BANNER},
+ .position_offset = POSITION_CORRECTION + vec2 {0, 0},
+ .world_space = false,
+ }
+ );
+
+ // Top_Left
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_left_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset
+ = POSITION_CORRECTION + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, 0},
+ .world_space = false,
+ }
+ );
+
+ // Top_Right
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_right_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset
+ = POSITION_CORRECTION + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, 0},
+ .world_space = false,
+ }
+ );
+
+ // Top_middle_2
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_2_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {SIZE.x, THICKNESS_BANNER},
+ .position_offset = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER},
+ .world_space = false,
+ }
+ );
+
+ // Top_Left_2
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_2_left_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset = POSITION_CORRECTION
+ + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, THICKNESS_BANNER},
+ .world_space = false,
+ }
+ );
+
+ // Top_Right_2
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_2_right_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset
+ = POSITION_CORRECTION + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, THICKNESS_BANNER},
+ .world_space = false,
+ }
+ );
+
+ // Top_middle_3
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_3_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {SIZE.x, THICKNESS_BANNER},
+ .position_offset = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER * 2},
+ .world_space = false,
+ }
+ );
+
+ // Top_Left_3
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_3_left_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2, THICKNESS_BANNER * 2},
+ .world_space = false,
+ }
+ );
+
+ // Top_Right_3
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/top_3_right_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER, THICKNESS_BANNER},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2, THICKNESS_BANNER * 2},
+ .world_space = false,
+ }
+ );
+
+ // Middle_Mid
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/middle_mid_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 7,
+ .size
+ = {SIZE.x * MIDDLE_OFFSET_FACTOR_OFFSET * MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH
+ + data.width_middle_offset,
+ SIZE.y},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {0, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2},
+ .world_space = false,
+ }
+ );
+
+ // Middle_Left
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/middle_left_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, SIZE.y},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2 * MIDDLE_OFFSET_FACTOR_OFFSET - MIDDLE_OFFSET_OFFSET_ADDITION, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2},
+ .world_space = false,
+ }
+ );
+
+ // Middle_Right
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/middle_right_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS, SIZE.y},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2 * MIDDLE_OFFSET_FACTOR_OFFSET + MIDDLE_OFFSET_OFFSET_ADDITION, THICKNESS_BANNER * 3 + SIZE.y / 2 - THICKNESS_BANNER / 2},
+ .world_space = false,
+ }
+ );
+
+ // Bot_Middle
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/bot_middle_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 7,
+ .size
+ = {SIZE.x * MIDDLE_OFFSET_FACTOR_OFFSET * MIDDLE_OFFSET_FACTOR_MIDDLE_WIDTH
+ + data.width_middle_offset,
+ THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS},
+ .position_offset
+ = POSITION_CORRECTION + vec2 {0, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y},
+ .world_space = false,
+ }
+ );
+
+ // Bot_Left
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/bot_left_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size
+ = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS,
+ THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {-SIZE.x / 2 - THICKNESS_BANNER / 2 - BOTTOM_OFFSET_X, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y},
+ .world_space = false,
+ }
+ );
+
+ // Bot_Right
+ floatingwindow.add_component<Sprite>(
+ Asset("asset/ui/settings_container/bot_right_setting.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 8,
+ .size
+ = {THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS,
+ THICKNESS_BANNER * MIDDLE_OFFSET_FACTOR_TICKNESS},
+ .position_offset
+ = POSITION_CORRECTION
+ + vec2 {SIZE.x / 2 + THICKNESS_BANNER / 2 + BOTTOM_OFFSET_X, THICKNESS_BANNER * 3 + SIZE.y + BOTTOM_OFFSET_Y},
+ .world_space = false,
+ }
+ );
+}
diff --git a/game/menus/FloatingWindowSubScene.h b/game/menus/FloatingWindowSubScene.h
new file mode 100644
index 0000000..7b9de96
--- /dev/null
+++ b/game/menus/FloatingWindowSubScene.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+#include <crepe/types.h>
+
+class FloatingWindowSubScene {
+public:
+ struct Data {
+ const std::string group_tag = "";
+ float width = 200;
+ crepe::vec2 offset = {0, 0};
+ float width_middle_offset = 0;
+ };
+
+public:
+ void create(crepe::Scene & scn, const Data & data);
+};
diff --git a/game/menus/IButtonScript.cpp b/game/menus/IButtonScript.cpp
new file mode 100644
index 0000000..34efbd0
--- /dev/null
+++ b/game/menus/IButtonScript.cpp
@@ -0,0 +1,32 @@
+#include "IButtonScript.h"
+
+#include "system/InputSystem.h"
+
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void IButtonScript::init() {
+ this->subscribe<ButtonExitEvent>([this](const ButtonExitEvent & e) {
+ return this->on_button_exit(e);
+ });
+ this->subscribe<ButtonEnterEvent>([this](const ButtonEnterEvent & e) {
+ return this->on_button_enter(e);
+ });
+}
+bool IButtonScript::on_button_exit(const ButtonExitEvent & e) {
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ for (Sprite & sprite : sprites) {
+ sprite.data.color = Color {255, 255, 255, 255};
+ }
+ return false;
+}
+bool IButtonScript::on_button_enter(const ButtonEnterEvent & e) {
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ for (Sprite & sprite : sprites) {
+ sprite.data.color = Color {200, 200, 200, 255};
+ }
+ return false;
+}
diff --git a/game/menus/IButtonScript.h b/game/menus/IButtonScript.h
new file mode 100644
index 0000000..e45375b
--- /dev/null
+++ b/game/menus/IButtonScript.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class IButtonScript : public virtual crepe::Script {
+public:
+ virtual void init();
+ virtual bool on_button_exit(const crepe::ButtonExitEvent & e);
+ virtual bool on_button_enter(const crepe::ButtonEnterEvent & e);
+};
diff --git a/game/menus/IFloatingWindowScript.cpp b/game/menus/IFloatingWindowScript.cpp
new file mode 100644
index 0000000..4b538ef
--- /dev/null
+++ b/game/menus/IFloatingWindowScript.cpp
@@ -0,0 +1,22 @@
+#include "IFloatingWindowScript.h"
+
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+
+void IFloatingWindowScript::init() { this->disable_all_sprites(); }
+
+void IFloatingWindowScript::disable_all_sprites() {
+ RefVector<Sprite> sprites = this->get_components_by_tag<Sprite>(this->tag);
+ for (Sprite & sprite : sprites) {
+ sprite.active = false;
+ }
+}
+
+void IFloatingWindowScript::enable_all_sprites() {
+ RefVector<Sprite> sprites = this->get_components_by_tag<Sprite>(this->tag);
+ for (Sprite & sprite : sprites) {
+ sprite.active = true;
+ }
+}
diff --git a/game/menus/IFloatingWindowScript.h b/game/menus/IFloatingWindowScript.h
new file mode 100644
index 0000000..e39378f
--- /dev/null
+++ b/game/menus/IFloatingWindowScript.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+#include <string>
+
+class IFloatingWindowScript : public virtual crepe::Script {
+public:
+ virtual void init();
+ void disable_all_sprites();
+ void enable_all_sprites();
+
+protected:
+ std::string tag = "";
+};
diff --git a/game/menus/MenusConfig.h b/game/menus/MenusConfig.h
new file mode 100644
index 0000000..3e357a5
--- /dev/null
+++ b/game/menus/MenusConfig.h
@@ -0,0 +1,16 @@
+#pragma once
+#include <crepe/types.h>
+
+//generic menu config
+static constexpr int STARTING_SORTING_IN_LAYER = 7;
+//Scene names
+static constexpr const char * START_SCENE = "scene1";
+static constexpr const char * PREVIEW_SCENE = "preview scene";
+static constexpr const char * SHOP_SCENE = "shopmenu";
+static constexpr const char * MAINMENU_SCENE = "mainmenu";
+//button config
+static constexpr crepe::vec2 LARGE_OVERLAY_SIZE = {250, 100};
+static constexpr crepe::vec2 SMALL_OVERLAY_SIZE_RIGHT = {150, 100};
+static constexpr crepe::vec2 SMALL_OVERLAY_SIZE_LEFT = {50, 100};
+static constexpr crepe::vec2 SIDE_PANEL_SIZE = {50, 150};
+static constexpr crepe::vec2 ICON_SIZE = {50, 50};
diff --git a/game/menus/endgame/EndGameSubScene.cpp b/game/menus/endgame/EndGameSubScene.cpp
new file mode 100644
index 0000000..b33072a
--- /dev/null
+++ b/game/menus/endgame/EndGameSubScene.cpp
@@ -0,0 +1,127 @@
+
+#include "EndGameSubScene.h"
+#include "EndGameSubScript.h"
+
+#include "../../Config.h"
+#include "../ButtonSubScene.h"
+#include "../FloatingWindowSubScene.h"
+
+#include <string>
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Text.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void EndGameSubScene::create(Scene & scn) {
+
+ const std::string TAG = "end_game_tag";
+ GameObject script = scn.new_object("script");
+ script.add_component<BehaviorScript>().set_script<EndGameSubScript>(TAG);
+
+ // Window
+ FloatingWindowSubScene window;
+ window.create(
+ scn,
+ FloatingWindowSubScene::Data {
+ .group_tag = TAG,
+ .width = 500,
+ .offset = {0, -50},
+ .width_middle_offset = -2,
+ }
+ );
+
+ // Titel
+ const string TITEL_STRING = "GAME OVER";
+ GameObject titel = scn.new_object("titel", TAG);
+ crepe::vec2 size = {200, (200.0f / TITEL_STRING.size()) * 2};
+ titel.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {0, -207} + FONTOFFSET, TITEL_STRING
+ );
+
+ const float Y_SPACING = 50;
+ const float Y_OFFSET = -100;
+
+ // Gold gathered
+ const string GOLD_STRING = "gold:0";
+ GameObject gold = scn.new_object("gold_endgame", TAG);
+ crepe::vec2 size_gold = {200, (200.0f / GOLD_STRING.size()) * 2};
+ gold.add_component<Text>(
+ size_gold, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::GOLD,
+ },
+ vec2 {0, Y_OFFSET} + FONTOFFSET, GOLD_STRING
+ );
+
+ // Distance
+ const string DISTANCE_STRING = "0M";
+ GameObject distance = scn.new_object("distance_endgame", TAG);
+ crepe::vec2 size_distance = {200, (200.0f / DISTANCE_STRING.size()) * 2};
+ distance.add_component<Text>(
+ size_distance, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {0, Y_SPACING + Y_OFFSET} + FONTOFFSET, DISTANCE_STRING
+ );
+
+ // Highscore
+ const string HIGHSCORE_STRING = "NEW HIGHSCORE";
+ GameObject highscore = scn.new_object("highscore_endgame", "highscore_tag_end");
+ crepe::vec2 size_highscore = {200, (200.0f / HIGHSCORE_STRING.size()) * 2};
+ highscore
+ .add_component<Text>(
+ size_highscore, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {0, Y_SPACING * 2 + Y_OFFSET} + FONTOFFSET, HIGHSCORE_STRING
+ )
+ .active
+ = false;
+
+ // Buttons
+ vec2 button_position = {190, 190};
+ ButtonSubScene button;
+ button.create(
+ scn,
+ ButtonSubScene::Data {
+ .text = "NEXT",
+ .text_width = 100,
+ .position = button_position,
+ .script_type = ButtonSubScene::ScriptSelect::NEXT,
+ .button_type = ButtonSubScene::ButtonSelect::NEXT,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = TAG,
+ .sorting_layer_offset = 20,
+ }
+ );
+
+ button.create(
+ scn,
+ ButtonSubScene::Data {
+ .text = "REPLAY",
+ .text_width = 150,
+ .position = {-button_position.x, button_position.y},
+ .script_type = ButtonSubScene::ScriptSelect::REPLAY,
+ .button_type = ButtonSubScene::ButtonSelect::BACK,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = TAG,
+ .sorting_layer_offset = 20,
+ }
+ );
+}
diff --git a/game/menus/endgame/EndGameSubScene.h b/game/menus/endgame/EndGameSubScene.h
new file mode 100644
index 0000000..204f3b7
--- /dev/null
+++ b/game/menus/endgame/EndGameSubScene.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class EndGameSubScene {
+
+public:
+ void create(crepe::Scene & scn);
+};
diff --git a/game/menus/endgame/EndGameSubScript.cpp b/game/menus/endgame/EndGameSubScript.cpp
new file mode 100644
index 0000000..6793f3e
--- /dev/null
+++ b/game/menus/endgame/EndGameSubScript.cpp
@@ -0,0 +1,101 @@
+#include "EndGameSubScript.h"
+
+#include "../../Config.h"
+#include "../../Events.h"
+#include "../ButtonReplaySubScript.h"
+#include "../IFloatingWindowScript.h"
+#include "ValueBroker.h"
+#include "manager/SaveManager.h"
+
+#include <string>
+
+#include <crepe/api/Button.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Text.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+
+EndGameSubScript::EndGameSubScript(const std::string & tag) { this->tag = tag; }
+
+void EndGameSubScript::init() {
+ this->disable_all();
+ this->subscribe<EndGameEvent>([this](const EndGameEvent e) { return this->enable_all(); });
+ this->subscribe<EndGameEvent>([this](const EndGameEvent e) {
+ return this->reset_timescale();
+ });
+ this->subscribe<ShowScoreEvent>([this](const ShowScoreEvent e) {
+ return this->showscore();
+ });
+}
+
+bool EndGameSubScript::disable_all() {
+ IFloatingWindowScript::disable_all_sprites();
+ RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag);
+ for (Button & button : buttons) {
+ button.active = false;
+ }
+ RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag);
+ for (Text & text : texts) {
+ text.active = false;
+ }
+ return false;
+}
+
+bool EndGameSubScript::enable_all() {
+ IFloatingWindowScript::enable_all_sprites();
+ RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag);
+ for (Button & button : buttons) {
+ button.active = true;
+ }
+ RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag);
+ for (Text & text : texts) {
+ text.active = true;
+ }
+ return false;
+}
+
+bool EndGameSubScript::reset_timescale() {
+ this->get_loop_timer().set_time_scale(1);
+ return false;
+}
+
+bool EndGameSubScript::showscore() {
+ // Gather text
+ Text & coins_text = this->get_components_by_name<Text>("gold_endgame").front().get();
+ Text & distance_text
+ = this->get_components_by_name<Text>("distance_endgame").front().get();
+ Text & highscore_text
+ = this->get_components_by_name<Text>("highscore_endgame").front().get();
+ highscore_text.active = false;
+
+ // Gather saved data
+ SaveManager & savemgr = this->get_save_manager();
+ ValueBroker<std::string> coins = savemgr.get<std::string>(TOTAL_COINS_RUN, "0");
+ ValueBroker<std::string> distance = savemgr.get<std::string>(DISTANCE_RUN, "0");
+ int distance_run = savemgr.get<int>(DISTANCE_RUN, 0).get();
+ int distance_game = savemgr.get<int>(DISTANCE_GAME, 0).get();
+
+ // Show highscore
+ if (distance_run > distance_game) highscore_text.active = true;
+
+ const float CHAR_SIZE_DIS = 20;
+ // Show distance
+ std::string distance_string = "DISTANCE:" + distance.get();
+ distance_text.text = distance_string;
+ crepe::vec2 size_distance
+ = {CHAR_SIZE_DIS * distance_string.size(),
+ (CHAR_SIZE_DIS * distance_string.size() / distance_string.size()) * 2};
+ distance_text.dimensions = size_distance;
+
+ const float CHAR_SIZE_COIN = 16;
+ // Show coins
+ std::string coins_string = "Coins:" + coins.get();
+ coins_text.text = coins_string;
+ crepe::vec2 size_coins
+ = {CHAR_SIZE_COIN * coins_string.size(),
+ (CHAR_SIZE_COIN * coins_string.size() / coins_string.size()) * 2};
+ coins_text.dimensions = size_coins;
+
+ return false;
+}
diff --git a/game/menus/endgame/EndGameSubScript.h b/game/menus/endgame/EndGameSubScript.h
new file mode 100644
index 0000000..c25f6ca
--- /dev/null
+++ b/game/menus/endgame/EndGameSubScript.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "../IFloatingWindowScript.h"
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+struct ShowScoreEvent : public crepe::Event {};
+
+class EndGameSubScript : public IFloatingWindowScript {
+public:
+ EndGameSubScript(const std::string & tag);
+ void init() override;
+ bool disable_all();
+ bool enable_all();
+ bool reset_timescale();
+ bool showscore();
+};
diff --git a/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp
new file mode 100644
index 0000000..4c4dfc1
--- /dev/null
+++ b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.cpp
@@ -0,0 +1,23 @@
+#include "ButtonTransitionPreviewSubScript.h"
+
+#include "../MenusConfig.h"
+
+using namespace crepe;
+using namespace std;
+
+void ButtonTransitionPreviewSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonTransitionPreviewSubScript::on_button_press(const ButtonPressEvent & e) {
+ if (!this->transition) this->transition = true;
+ return false;
+}
+
+const char * ButtonTransitionPreviewSubScript::get_scene_name() const {
+ // Provide the next scene defined in MainMenuConfig
+ return PREVIEW_SCENE;
+}
diff --git a/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h
new file mode 100644
index 0000000..d6d8149
--- /dev/null
+++ b/game/menus/mainmenu/ButtonTransitionPreviewSubScript.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "ITransitionScript.h"
+
+#include "../IButtonScript.h"
+
+class ButtonTransitionPreviewSubScript : public ITransitionScript, public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+ const char * get_scene_name() const override;
+};
diff --git a/game/menus/mainmenu/CreditsSubScene.cpp b/game/menus/mainmenu/CreditsSubScene.cpp
new file mode 100644
index 0000000..65576ee
--- /dev/null
+++ b/game/menus/mainmenu/CreditsSubScene.cpp
@@ -0,0 +1,132 @@
+
+#include "CreditsSubScene.h"
+#include "CreditsSubScript.h"
+
+#include "../../Config.h"
+#include "../ButtonSubScene.h"
+#include "../FloatingWindowSubScene.h"
+
+#include <string>
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Text.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void CreditsSubScene::create(Scene & scn) {
+
+ const std::string TAG = "credits_tag";
+ GameObject script = scn.new_object("script");
+ script.add_component<BehaviorScript>().set_script<CreditsSubScript>(TAG);
+
+ // Window
+ FloatingWindowSubScene window;
+ window.create(
+ scn,
+ FloatingWindowSubScene::Data {
+ .group_tag = TAG,
+ .width = 500,
+ .offset = {150, -50},
+ .width_middle_offset = -2,
+ }
+ );
+
+ // Titel
+ const string TITEL_STRING = "Credits";
+ GameObject titel = scn.new_object("titel", TAG);
+ crepe::vec2 size = {200, (200.0f / TITEL_STRING.size()) * 2};
+ titel.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207} + FONTOFFSET, TITEL_STRING
+ );
+
+ // Buttons
+ vec2 button_position = {190, 190};
+ ButtonSubScene button;
+ button.create(
+ scn,
+ ButtonSubScene::Data {
+ .text = "Back",
+ .text_width = 150,
+ .position = {-button_position.x + 150, button_position.y},
+ .script_type = ButtonSubScene::ScriptSelect::CREDITS_BACK,
+ .button_type = ButtonSubScene::ButtonSelect::BACK,
+ .scale = 0.6,
+ .worldspace = false,
+ .tag = TAG,
+ .sorting_layer_offset = 20,
+ }
+ );
+
+ const float SIZE_CHAR_NAMES = 10;
+ const float Y_OFFSET_NAMES_BEGIN = 100;
+ const float Y_OFFSET_NAMES = 30;
+ const string LOEK = "Loek Le Blansch";
+ crepe::vec2 size_loek
+ = {LOEK.size() * SIZE_CHAR_NAMES, (LOEK.size() * SIZE_CHAR_NAMES / LOEK.size()) * 2};
+ titel.add_component<Text>(
+ size_loek, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207 + Y_OFFSET_NAMES + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, LOEK
+ );
+
+ const string WOUTER = "Wouter Boerenkamps";
+ crepe::vec2 size_wouter
+ = {WOUTER.size() * SIZE_CHAR_NAMES,
+ (WOUTER.size() * SIZE_CHAR_NAMES / WOUTER.size()) * 2};
+ titel.add_component<Text>(
+ size_wouter, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207 + Y_OFFSET_NAMES * 2 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, WOUTER
+ );
+
+ const string JARO = "Jaro Rutjes";
+ crepe::vec2 size_jaro
+ = {JARO.size() * SIZE_CHAR_NAMES, (JARO.size() * SIZE_CHAR_NAMES / JARO.size()) * 2};
+ titel.add_component<Text>(
+ size_jaro, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207 + Y_OFFSET_NAMES * 3 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, JARO
+ );
+
+ const string MAX = "Max Smits";
+ crepe::vec2 size_max
+ = {MAX.size() * SIZE_CHAR_NAMES, (MAX.size() * SIZE_CHAR_NAMES / MAX.size()) * 2};
+ titel.add_component<Text>(
+ size_max, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207 + Y_OFFSET_NAMES * 4 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, MAX
+ );
+
+ const string NIELS = "Niels Stunnebrink";
+ crepe::vec2 size_niels
+ = {NIELS.size() * SIZE_CHAR_NAMES, (NIELS.size() * SIZE_CHAR_NAMES / NIELS.size()) * 2
+ };
+ titel.add_component<Text>(
+ size_niels, FONT,
+ Text::Data {
+ .world_space = false,
+ .text_color = Color::WHITE,
+ },
+ vec2 {150, -207 + Y_OFFSET_NAMES * 5 + Y_OFFSET_NAMES_BEGIN} + FONTOFFSET, NIELS
+ );
+}
diff --git a/game/menus/mainmenu/CreditsSubScene.h b/game/menus/mainmenu/CreditsSubScene.h
new file mode 100644
index 0000000..e7ff735
--- /dev/null
+++ b/game/menus/mainmenu/CreditsSubScene.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class CreditsSubScene {
+
+public:
+ void create(crepe::Scene & scn);
+};
diff --git a/game/menus/mainmenu/CreditsSubScript.cpp b/game/menus/mainmenu/CreditsSubScript.cpp
new file mode 100644
index 0000000..4224dc8
--- /dev/null
+++ b/game/menus/mainmenu/CreditsSubScript.cpp
@@ -0,0 +1,58 @@
+#include "CreditsSubScript.h"
+
+#include "../../Events.h"
+#include "../ButtonReplaySubScript.h"
+#include "../IFloatingWindowScript.h"
+
+#include <string>
+
+#include <crepe/api/Button.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Text.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+
+CreditsSubScript::CreditsSubScript(const std::string & tag) { this->tag = tag; }
+
+void CreditsSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+ this->subscribe<ShowCreditsEvent>([this](const ShowCreditsEvent & e) {
+ this->enable_all();
+ return false;
+ });
+ this->disable_all();
+}
+
+bool CreditsSubScript::disable_all() {
+ IFloatingWindowScript::disable_all_sprites();
+ RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag);
+ for (Button & button : buttons) {
+ button.active = false;
+ }
+ RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag);
+ for (Text & text : texts) {
+ text.active = false;
+ }
+ return false;
+}
+
+bool CreditsSubScript::enable_all() {
+ IFloatingWindowScript::enable_all_sprites();
+ RefVector<Button> buttons = this->get_components_by_tag<Button>(this->tag);
+ for (Button & button : buttons) {
+ button.active = true;
+ }
+ RefVector<Text> texts = this->get_components_by_tag<Text>(this->tag);
+ for (Text & text : texts) {
+ text.active = true;
+ }
+ return false;
+}
+
+bool CreditsSubScript::on_button_press(const ButtonPressEvent & e) {
+ return this->disable_all();
+}
diff --git a/game/menus/mainmenu/CreditsSubScript.h b/game/menus/mainmenu/CreditsSubScript.h
new file mode 100644
index 0000000..81f941a
--- /dev/null
+++ b/game/menus/mainmenu/CreditsSubScript.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "../IButtonScript.h"
+#include "../IFloatingWindowScript.h"
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+struct ShowCreditsEvent : public crepe::Event {};
+
+class CreditsSubScript : public IFloatingWindowScript, public IButtonScript {
+public:
+ CreditsSubScript(const std::string & tag);
+ void init() override;
+ bool disable_all();
+ bool enable_all();
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+};
diff --git a/game/menus/mainmenu/ITransitionScript.cpp b/game/menus/mainmenu/ITransitionScript.cpp
new file mode 100644
index 0000000..54b0875
--- /dev/null
+++ b/game/menus/mainmenu/ITransitionScript.cpp
@@ -0,0 +1,30 @@
+#include "ITransitionScript.h"
+#include "MainMenuConfig.h"
+
+#include "../../Config.h"
+#include "../MenusConfig.h"
+
+#include <crepe/api/Camera.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void ITransitionScript::frame_update(crepe::duration_t delta_time) {
+ if (this->transition) {
+ // cout << "transition:" << velocity << std::endl;
+ Transform & cam = this->get_components_by_name<Transform>(CAMERA_NAME).front();
+ RefVector<Transform> info_tf = this->get_components_by_tag<Transform>(MENU_INFO_TAG);
+ for (Transform & tf : info_tf) {
+ tf.position.y -= VELOCITY_INFO_UP * delta_time.count();
+ }
+ if (velocity < VELOCITY_MAX && cam.position.x < SLOW_DOWN)
+ velocity += VELOCITY_STEP * delta_time.count();
+ else if (velocity > 20) velocity -= VELOCITY_STEP * delta_time.count();
+ if (cam.position.x < END) cam.position.x += (velocity * delta_time.count());
+ if (cam.position.x >= END) {
+ this->set_next_scene(this->get_scene_name());
+ }
+ }
+}
diff --git a/game/menus/mainmenu/ITransitionScript.h b/game/menus/mainmenu/ITransitionScript.h
new file mode 100644
index 0000000..9a2ef90
--- /dev/null
+++ b/game/menus/mainmenu/ITransitionScript.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class ITransitionScript : public virtual crepe::Script {
+public:
+ void frame_update(crepe::duration_t delta_time) override;
+ virtual const char * get_scene_name() const = 0;
+
+private:
+ float velocity = 20;
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/mainmenu/MainMenuConfig.h b/game/menus/mainmenu/MainMenuConfig.h
new file mode 100644
index 0000000..f4ca5a6
--- /dev/null
+++ b/game/menus/mainmenu/MainMenuConfig.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <crepe/types.h>
+
+//main menu config
+static constexpr float STARTMAP_OFFSET = 50;
+static constexpr crepe::vec2 MENU_OFFSET = {0, 0};
+static constexpr float MENU_BUTTON_SPACING = 10;
+static constexpr const char * MENU_BUTTON_NAME = "menu_button_background";
+static constexpr crepe::vec2 MENU_OFFSET_BUTTON = {-400, -200};
+static constexpr crepe::vec2 MENU_OFFSET_BUTTON_BACKGROUND = {-400, 0};
+static constexpr const char * MENU_INFO_TAG = "menu_info";
+static constexpr crepe::vec2 MENU_OFFSET_INFO = {350, -365};
+static constexpr crepe::vec2 MENU_OFFSET_INFO_BACKGROUND = {350, -365}; //375
+//Moving to new scene (Start and Preview)
+static constexpr float SLOW_DOWN = 200;
+static constexpr float END = 300;
+static constexpr float VELOCITY_MAX = 200;
+static constexpr float VELOCITY_STEP = 200;
+static constexpr float VELOCITY_INFO_UP = 40;
diff --git a/game/menus/mainmenu/MainMenuScene.cpp b/game/menus/mainmenu/MainMenuScene.cpp
new file mode 100644
index 0000000..c5d2030
--- /dev/null
+++ b/game/menus/mainmenu/MainMenuScene.cpp
@@ -0,0 +1,137 @@
+
+#include "MainMenuScene.h"
+#include "CreditsSubScene.h"
+#include "MainMenuConfig.h"
+#include "QuitScript.h"
+#include "TransitionStartSubScript.h"
+
+#include "../ButtonSubScene.h"
+#include "../MenusConfig.h"
+
+#include "../../Config.h"
+#include "../../background/HallwaySubScene.h"
+#include "../../background/StartSubScene.h"
+
+#include "../endgame/EndGameSubScene.h"
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace crepe;
+using namespace std;
+
+void MainMenuScene::load_scene() {
+ ButtonSubScene button;
+
+ GameObject camera_object = this->new_object(CAMERA_NAME);
+ camera_object.add_component<Camera>(
+ ivec2(990, 720), vec2(1100, 800),
+ Camera::Data {
+ .bg_color = Color::BLACK,
+ }
+ );
+ camera_object.add_component<BehaviorScript>().set_script<TransitionStartSubScript>();
+ camera_object.add_component<BehaviorScript>().set_script<QuitScript>();
+
+ //Button menu
+ GameObject menu_button = this->new_object(MENU_BUTTON_NAME, MENU_BUTTON_NAME, MENU_OFFSET);
+ menu_button.add_component<Sprite>(
+ Asset("asset/ui/background.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0,
+ .size = {300, 860},
+ .position_offset = MENU_OFFSET_BUTTON_BACKGROUND,
+ }
+ );
+
+ vec2 pos_btn = MENU_OFFSET_BUTTON;
+
+ //Preview btn
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "PREVIEW",
+ .text_width = 200,
+ .position = pos_btn,
+ .script_type = ButtonSubScene::ScriptSelect::PREVIEW,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE,
+ }
+ );
+
+ //Shop btn
+ pos_btn.y += MENU_BUTTON_SPACING + LARGE_OVERLAY_SIZE.y;
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "SHOP",
+ .text_offset = {-20, 0},
+ .text_width = 110,
+ .icon_offset = {60, 0},
+ .icon_type = ButtonSubScene::IconSelect::SHOP,
+ .position = pos_btn,
+ .script_type = ButtonSubScene::ScriptSelect::SHOP,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::ORANGE,
+ }
+ );
+
+ //Credits btn
+ pos_btn.y += MENU_BUTTON_SPACING + LARGE_OVERLAY_SIZE.y;
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "CREDITS",
+ .text_offset = {0, 0},
+ .text_width = 200,
+ .position = pos_btn,
+ .script_type = ButtonSubScene::ScriptSelect::CREDITS_SHOW,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::BLUE,
+ }
+ );
+
+ //Start of map
+ StartSubScene start;
+ HallwaySubScene hallway;
+ float begin_x = start.create(*this, STARTMAP_OFFSET);
+ begin_x = hallway.create(*this, begin_x, 1, Color::YELLOW);
+
+ //INFO menu
+ GameObject menu_info
+ = this->new_object("MENU_INFO_BACKGROUND", MENU_INFO_TAG, MENU_OFFSET);
+ menu_info.add_component<Sprite>(
+ Asset("asset/ui/itemsButtonBlankDark.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0,
+ .size = {250, 80},
+ .position_offset = MENU_OFFSET_INFO,
+ .world_space = false,
+ }
+ );
+ SaveManager & savemgr = this->get_save_manager();
+ string number = std::to_string(savemgr.get<int>(TOTAL_COINS_GAME, 0).get());
+ float amount_number = static_cast<float>(number.size());
+ // savemgr.set(COIN_GAME_AMOUNT, amount);
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = number,
+ .text_offset = {-10 - (amount_number - 1) * 10, 0},
+ .text_width = amount_number * 20,
+ .icon_offset = {60, 0},
+ .icon_type = ButtonSubScene::IconSelect::COINS,
+ .position = MENU_OFFSET_INFO,
+ .script_type = ButtonSubScene::ScriptSelect::SHOP,
+ .scale = 0.6,
+ .worldspace = false,
+ .color_side = false,
+ .tag = MENU_INFO_TAG,
+ }
+ );
+
+ CreditsSubScene creditscene;
+ creditscene.create(*this);
+}
+
+string MainMenuScene::get_name() const { return MAINMENU_SCENE; }
diff --git a/game/menus/mainmenu/MainMenuScene.h b/game/menus/mainmenu/MainMenuScene.h
new file mode 100644
index 0000000..1eea90e
--- /dev/null
+++ b/game/menus/mainmenu/MainMenuScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+#include <string>
+
+class MainMenuScene : public crepe::Scene {
+public:
+ void load_scene();
+ std::string get_name() const;
+};
diff --git a/game/menus/mainmenu/TransitionStartSubScript.cpp b/game/menus/mainmenu/TransitionStartSubScript.cpp
new file mode 100644
index 0000000..f737f7f
--- /dev/null
+++ b/game/menus/mainmenu/TransitionStartSubScript.cpp
@@ -0,0 +1,16 @@
+#include "TransitionStartSubScript.h"
+
+#include "../MenusConfig.h"
+
+using namespace crepe;
+using namespace std;
+
+void TransitionStartSubScript::fixed_update(crepe::duration_t dt) {
+ if (this->get_key_state(Keycode::SPACE) && this->transition == false)
+ this->transition = true;
+}
+
+const char * TransitionStartSubScript::get_scene_name() const {
+ // Provide the next scene defined in MainMenuConfig
+ return START_SCENE;
+}
diff --git a/game/menus/mainmenu/TransitionStartSubScript.h b/game/menus/mainmenu/TransitionStartSubScript.h
new file mode 100644
index 0000000..f9862ea
--- /dev/null
+++ b/game/menus/mainmenu/TransitionStartSubScript.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "ITransitionScript.h"
+
+class TransitionStartSubScript : public ITransitionScript {
+public:
+ void fixed_update(crepe::duration_t dt) override;
+ const char * get_scene_name() const override;
+};
diff --git a/game/menus/shop/ButtonBuySelectBubbleScript.cpp b/game/menus/shop/ButtonBuySelectBubbleScript.cpp
new file mode 100644
index 0000000..741afde
--- /dev/null
+++ b/game/menus/shop/ButtonBuySelectBubbleScript.cpp
@@ -0,0 +1,34 @@
+#include "ButtonBuySelectBubbleScript.h"
+#include "../MenusConfig.h"
+#include "Config.h"
+#include "ValueBroker.h"
+#include "manager/SaveManager.h"
+#include "menus/shop/Shopconfig.h"
+
+using namespace crepe;
+using namespace std;
+
+void ButtonBuySelectBubbleScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonBuySelectBubbleScript::on_button_press(const ButtonPressEvent & e) {
+ SaveManager & save = this->get_save_manager();
+ ValueBroker<int> buy_bullet = save.get<int>(BUY_BUBBLE_SAVE, 0);
+ if (!buy_bullet.get()) {
+ ValueBroker<int> coins = save.get<int>(TOTAL_COINS_GAME, 0);
+ if (coins.get() >= 1000) {
+ int coin = coins.get();
+ coin -= 1000;
+ save.set(TOTAL_COINS_GAME, coin);
+ save.set(BUY_BUBBLE_SAVE, 1);
+ }
+ } else {
+ save.set(JETPACK_PARTICLES, 1);
+ }
+ this->trigger_event<ShopUpdate>();
+ return false;
+}
diff --git a/game/menus/shop/ButtonBuySelectBubbleScript.h b/game/menus/shop/ButtonBuySelectBubbleScript.h
new file mode 100644
index 0000000..ce276ef
--- /dev/null
+++ b/game/menus/shop/ButtonBuySelectBubbleScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "../IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonBuySelectBubbleScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/shop/ButtonBuySelectBulletScript.cpp b/game/menus/shop/ButtonBuySelectBulletScript.cpp
new file mode 100644
index 0000000..d30849c
--- /dev/null
+++ b/game/menus/shop/ButtonBuySelectBulletScript.cpp
@@ -0,0 +1,34 @@
+#include "ButtonBuySelectBulletScript.h"
+#include "../MenusConfig.h"
+#include "Config.h"
+#include "ValueBroker.h"
+#include "manager/SaveManager.h"
+#include "menus/shop/Shopconfig.h"
+
+using namespace crepe;
+using namespace std;
+
+void ButtonBuySelectBulletScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool ButtonBuySelectBulletScript::on_button_press(const ButtonPressEvent & e) {
+ SaveManager & save = this->get_save_manager();
+ ValueBroker<int> buy_bullet = save.get<int>(BUY_BULLET_SAVE, 0);
+ if (!buy_bullet.get()) {
+ ValueBroker<int> coins = save.get<int>(TOTAL_COINS_GAME, 0);
+ if (coins.get() >= 0) {
+ int coin = coins.get();
+ coin -= 0;
+ save.set(TOTAL_COINS_GAME, coin);
+ save.set(BUY_BULLET_SAVE, 1);
+ }
+ } else {
+ save.set(JETPACK_PARTICLES, 0);
+ }
+ this->trigger_event<ShopUpdate>();
+ return false;
+}
diff --git a/game/menus/shop/ButtonBuySelectBulletScript.h b/game/menus/shop/ButtonBuySelectBulletScript.h
new file mode 100644
index 0000000..86de0ac
--- /dev/null
+++ b/game/menus/shop/ButtonBuySelectBulletScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "../IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class ButtonBuySelectBulletScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+protected:
+ bool transition = false;
+};
diff --git a/game/menus/shop/ShopLoadScript.cpp b/game/menus/shop/ShopLoadScript.cpp
new file mode 100644
index 0000000..a545fe2
--- /dev/null
+++ b/game/menus/shop/ShopLoadScript.cpp
@@ -0,0 +1,126 @@
+#include "ShopLoadScript.h"
+#include "Shopconfig.h"
+#include "api/Button.h"
+#include "api/Sprite.h"
+#include "api/Text.h"
+#include "manager/SaveManager.h"
+#include <crepe/ValueBroker.h>
+
+using namespace crepe;
+using namespace std;
+
+void ShopLoadScript::init() {
+ this->update();
+ this->subscribe<ShopUpdate>([this](const ShopUpdate e) { return this->update(); });
+}
+
+bool ShopLoadScript::update() {
+ SaveManager & save = this->get_save_manager();
+ ValueBroker<int> buy_bullet = save.get<int>(BUY_BULLET_SAVE, 0);
+ ValueBroker<int> buy_bubble = save.get<int>(BUY_BUBBLE_SAVE, 0);
+
+ if (buy_bullet.get()) {
+ auto sprites = this->get_components_by_tag<Sprite>(BUY_BULLET);
+ for (auto sprite : sprites) {
+ sprite.get().active = false;
+ }
+ auto buttons = this->get_components_by_tag<Button>(BUY_BULLET);
+ for (auto btn : buttons) {
+ btn.get().active = false;
+ }
+ auto texts = this->get_components_by_tag<Text>(BUY_BULLET);
+ for (auto txt : texts) {
+ txt.get().active = false;
+ }
+ auto sprites1 = this->get_components_by_tag<Sprite>(SELECT_BULLET);
+ for (auto sprite : sprites1) {
+ sprite.get().active = true;
+ }
+ auto buttons1 = this->get_components_by_tag<Button>(SELECT_BULLET);
+ for (auto btn : buttons1) {
+ btn.get().active = true;
+ }
+ auto texts1 = this->get_components_by_tag<Text>(SELECT_BULLET);
+ for (auto txt : texts1) {
+ txt.get().active = true;
+ }
+ } else {
+ auto sprites = this->get_components_by_tag<Sprite>(SELECT_BULLET);
+ for (auto sprite : sprites) {
+ sprite.get().active = false;
+ }
+ auto buttons = this->get_components_by_tag<Button>(SELECT_BULLET);
+ for (auto btn : buttons) {
+ btn.get().active = false;
+ }
+ auto texts = this->get_components_by_tag<Text>(SELECT_BULLET);
+ for (auto txt : texts) {
+ txt.get().active = false;
+ }
+ auto sprites1 = this->get_components_by_tag<Sprite>(BUY_BULLET);
+ for (auto sprite : sprites1) {
+ sprite.get().active = true;
+ }
+ auto buttons1 = this->get_components_by_tag<Button>(BUY_BULLET);
+ for (auto btn : buttons1) {
+ btn.get().active = true;
+ }
+ auto texts1 = this->get_components_by_tag<Text>(BUY_BULLET);
+ for (auto txt : texts1) {
+ txt.get().active = true;
+ }
+ }
+
+ if (buy_bubble.get()) {
+ auto sprites = this->get_components_by_tag<Sprite>(BUY_BUBBLE);
+ for (auto sprite : sprites) {
+ sprite.get().active = false;
+ }
+ auto buttons = this->get_components_by_tag<Button>(BUY_BUBBLE);
+ for (auto btn : buttons) {
+ btn.get().active = false;
+ }
+ auto texts = this->get_components_by_tag<Text>(BUY_BUBBLE);
+ for (auto txt : texts) {
+ txt.get().active = false;
+ }
+ auto sprites1 = this->get_components_by_tag<Sprite>(SELECT_BUBBLE);
+ for (auto sprite : sprites1) {
+ sprite.get().active = true;
+ }
+ auto buttons1 = this->get_components_by_tag<Button>(SELECT_BUBBLE);
+ for (auto btn : buttons1) {
+ btn.get().active = true;
+ }
+ auto texts1 = this->get_components_by_tag<Text>(SELECT_BUBBLE);
+ for (auto txt : texts1) {
+ txt.get().active = true;
+ }
+ } else {
+ auto sprites = this->get_components_by_tag<Sprite>(SELECT_BUBBLE);
+ for (auto sprite : sprites) {
+ sprite.get().active = false;
+ }
+ auto buttons = this->get_components_by_tag<Button>(SELECT_BUBBLE);
+ for (auto btn : buttons) {
+ btn.get().active = false;
+ }
+ auto texts = this->get_components_by_tag<Text>(SELECT_BUBBLE);
+ for (auto txt : texts) {
+ txt.get().active = false;
+ }
+ auto sprites1 = this->get_components_by_tag<Sprite>(BUY_BUBBLE);
+ for (auto sprite : sprites1) {
+ sprite.get().active = true;
+ }
+ auto buttons1 = this->get_components_by_tag<Button>(BUY_BUBBLE);
+ for (auto btn : buttons1) {
+ btn.get().active = true;
+ }
+ auto texts1 = this->get_components_by_tag<Text>(BUY_BUBBLE);
+ for (auto txt : texts1) {
+ txt.get().active = true;
+ }
+ }
+ return false;
+}
diff --git a/game/menus/shop/ShopLoadScript.h b/game/menus/shop/ShopLoadScript.h
new file mode 100644
index 0000000..b17bf1c
--- /dev/null
+++ b/game/menus/shop/ShopLoadScript.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+#include <crepe/manager/SaveManager.h>
+
+class ShopLoadScript : public crepe::Script {
+public:
+ void init() override;
+ bool update();
+};
diff --git a/game/menus/shop/ShopMenuScene.cpp b/game/menus/shop/ShopMenuScene.cpp
new file mode 100644
index 0000000..4975a95
--- /dev/null
+++ b/game/menus/shop/ShopMenuScene.cpp
@@ -0,0 +1,326 @@
+
+#include "ShopMenuScene.h"
+
+#include "../../Config.h"
+#include "../ButtonSubScene.h"
+#include "../MenusConfig.h"
+#include "api/BehaviorScript.h"
+#include "menus/BannerSubScene.h"
+#include "menus/shop/ShopLoadScript.h"
+#include "types.h"
+
+#include "Shopconfig.h"
+#include <crepe/api/Camera.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Text.h>
+
+using namespace crepe;
+using namespace std;
+
+void ShopMenuScene::load_scene() {
+ GameObject camera_object = this->new_object(CAMERA_NAME);
+ camera_object.add_component<Camera>(
+ ivec2(990, 720), vec2(1100, 800),
+ Camera::Data {
+ .bg_color = Color::RED,
+ }
+ );
+ BannerSubScene banner;
+ banner.create(
+ *this,
+ {
+ .banner_title = "SHOP",
+ .banner_title_width = 200,
+ .banner_title_offset = {0, 65},
+ }
+ );
+ GameObject menu_background = this->new_object("menu_background");
+ menu_background.add_component<Sprite>(
+ Asset("asset/ui/background.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 0,
+ .size = {1100, 860},
+ .position_offset {0},
+ }
+ );
+ menu_background.add_component<BehaviorScript>().set_script<ShopLoadScript>();
+
+ ButtonSubScene button;
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "BACK",
+ .text_width = 115,
+ .position = {-400, -350},
+ .script_type = ButtonSubScene::ScriptSelect::MAINMENU,
+ .button_type = ButtonSubScene::ButtonSelect::BACK,
+ .scale = 0.8,
+ .sorting_layer_offset = 1,
+ }
+ );
+
+ const float CHAR_SIZE = 16;
+ const float CHAR_SIZE_COIN = 16;
+ crepe::vec2 size;
+
+ GameObject shop_item_bullet = this->new_object("bullet", "shop_item", vec2(-100, 0));
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .angle_offset = 30,
+ .position_offset = {0, 0},
+ }
+ );
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .angle_offset = 10,
+ .position_offset = {-10, -30},
+ }
+ );
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .angle_offset = -10,
+ .position_offset = {-40, 30},
+ }
+ );
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .angle_offset = 0,
+ .position_offset = {10, 15},
+ }
+ );
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/other_effects/effect_rocketmgshell_TVOS.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .angle_offset = -5,
+ .position_offset = {45, -5},
+ }
+ );
+
+ const string BULLETS_STRING = "BULLETS";
+ size
+ = {CHAR_SIZE * BULLETS_STRING.size(),
+ (CHAR_SIZE * BULLETS_STRING.size() / BULLETS_STRING.size()) * 2};
+
+ shop_item_bullet.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = true,
+ .text_color = Color::WHITE,
+ },
+ vec2 {0, -75}, BULLETS_STRING
+ );
+ shop_item_bullet.add_component<Sprite>(
+ Asset("asset/ui/buttonCoinsSmall.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 45},
+ .position_offset = {25, 75},
+ }
+ );
+
+ const string BULLETS_GOLD_STRING = "0";
+ size
+ = {CHAR_SIZE_COIN * BULLETS_GOLD_STRING.size(),
+ (CHAR_SIZE_COIN * BULLETS_GOLD_STRING.size() / BULLETS_GOLD_STRING.size()) * 2};
+ shop_item_bullet.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = true,
+ .text_color = Color::GOLD,
+ },
+ vec2 {-5, 75}, BULLETS_GOLD_STRING
+ );
+
+ GameObject shop_item_bubble = this->new_object("bubble", "shop_item", vec2(100, 0));
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 10},
+ .position_offset = {0, 0},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 10},
+ .position_offset = {-50, -20},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .position_offset = {45, -40},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .position_offset = {-20, 40},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 15},
+ .position_offset = {15, -25},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 10},
+ .position_offset = {10, 5},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 10},
+ .position_offset = {-5, -20},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .position_offset = {15, -40},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 20},
+ .position_offset = {-20, 10},
+ }
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/background/aquarium/bubble.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 15},
+ .position_offset = {30, -25},
+ }
+ );
+
+ const string BUBBLE_STRING = "BUBBLE";
+ size
+ = {CHAR_SIZE * BUBBLE_STRING.size(),
+ (CHAR_SIZE * BUBBLE_STRING.size() / BUBBLE_STRING.size()) * 2};
+ shop_item_bubble.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = true,
+ .text_color = Color::WHITE,
+ },
+ vec2 {0, -75}, BUBBLE_STRING
+ );
+ shop_item_bubble.add_component<Sprite>(
+ Asset("asset/ui/buttonCoinsSmall.png"),
+ Sprite::Data {
+ .sorting_in_layer = STARTING_SORTING_IN_LAYER + 1,
+ .size = {0, 45},
+ .position_offset = {45, 75},
+ }
+ );
+
+ const string BUBBLE_GOLD_STRING = "1000";
+ size
+ = {CHAR_SIZE_COIN * BUBBLE_GOLD_STRING.size(),
+ (CHAR_SIZE_COIN * BUBBLE_GOLD_STRING.size() / BUBBLE_GOLD_STRING.size()) * 2};
+ shop_item_bubble.add_component<Text>(
+ size, FONT,
+ Text::Data {
+ .world_space = true,
+ .text_color = Color::GOLD,
+ },
+ vec2 {-10, 75}, BUBBLE_GOLD_STRING
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "BUY",
+ .text_width = 100,
+ .position = vec2(-100, 120),
+ .script_type = ButtonSubScene::ScriptSelect::SHOP_BULLET,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.4,
+ .tag = BUY_BULLET,
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE
+
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "BUY",
+ .text_width = 100,
+ .position = vec2(100, 120),
+ .script_type = ButtonSubScene::ScriptSelect::SHOP_BUBBLE,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.4,
+ .tag = BUY_BUBBLE,
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "SELECT",
+ .text_width = 100,
+ .position = vec2(-100, 120),
+ .script_type = ButtonSubScene::ScriptSelect::SHOP_BULLET,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.4,
+ .tag = SELECT_BULLET,
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE
+ }
+ );
+
+ button.create(
+ *this,
+ ButtonSubScene::Data {
+ .text = "SELECT",
+ .text_width = 100,
+ .position = vec2(100, 120),
+ .script_type = ButtonSubScene::ScriptSelect::SHOP_BUBBLE,
+ .button_type = ButtonSubScene::ButtonSelect::LARGE,
+ .scale = 0.4,
+ .tag = SELECT_BUBBLE,
+ .sorting_layer_offset = 20,
+ .btn_side_color = ButtonSubScene::ButtonSideColor::PURPLE
+ }
+ );
+}
+
+string ShopMenuScene::get_name() const { return SHOP_SCENE; }
diff --git a/game/menus/shop/ShopMenuScene.h b/game/menus/shop/ShopMenuScene.h
new file mode 100644
index 0000000..34c05ff
--- /dev/null
+++ b/game/menus/shop/ShopMenuScene.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <string>
+
+#include <crepe/api/Scene.h>
+
+class ShopMenuScene : public crepe::Scene {
+public:
+ void load_scene();
+
+ std::string get_name() const;
+};
diff --git a/game/menus/shop/Shopconfig.h b/game/menus/shop/Shopconfig.h
new file mode 100644
index 0000000..a686242
--- /dev/null
+++ b/game/menus/shop/Shopconfig.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "api/Event.h"
+
+//tags
+static constexpr const char * BUY_BULLET = "BUY_BULLET";
+static constexpr const char * SELECT_BULLET = "SELECT_BULLET";
+static constexpr const char * BUY_BUBBLE = "BUY_BUBBLE";
+static constexpr const char * SELECT_BUBBLE = "SELECT_BUBBLE";
+
+//save_data
+static constexpr const char * BUY_BULLET_SAVE = "BUY_BULLET_SAVE";
+static constexpr const char * BUY_BUBBLE_SAVE = "BUY_BUBBLE_SAVE";
+
+struct ShopUpdate : public crepe::Event {};
diff --git a/game/missile/AlertScript.cpp b/game/missile/AlertScript.cpp
new file mode 100644
index 0000000..0e6f5c5
--- /dev/null
+++ b/game/missile/AlertScript.cpp
@@ -0,0 +1,38 @@
+#include "AlertScript.h"
+#include "../Config.h"
+
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+
+using namespace crepe;
+
+void AlertScript::fixed_update(crepe::duration_t dt) {
+ const auto & cam = this->get_components_by_name<Transform>("camera").front().get();
+ //missile transform
+ const auto & this_transform = this->get_component<Transform>();
+ auto missile_transforms = this->get_components_by_name<Transform>("missile");
+ const auto & this_collider = this->get_component<CircleCollider>();
+
+ auto alert_sprites = this->get_components_by_name<Sprite>("missile_alert");
+ auto alert_transforms = this->get_components_by_name<Transform>("missile_alert");
+
+ int idx = 0;
+ for (int i = 0; i < missile_transforms.size(); ++i) {
+ const auto & missile_transform = missile_transforms[i].get();
+ if (this_transform.game_object_id == missile_transform.game_object_id) {
+ idx = i;
+ break;
+ }
+ }
+
+ auto & alert_transform = alert_transforms[idx].get();
+ alert_transform.position.x = cam.position.x + (VIEWPORT_X / 2 - 100);
+ alert_transform.position.y = this_transform.position.y;
+
+ // check if transform is in camera view
+ if (this_transform.position.x > cam.position.x - (VIEWPORT_X / 2)
+ && this_transform.position.x < cam.position.x + (VIEWPORT_X / 2)) {
+ alert_sprites[idx].get().active = false;
+ }
+}
diff --git a/game/missile/AlertScript.h b/game/missile/AlertScript.h
new file mode 100644
index 0000000..5b0b3a1
--- /dev/null
+++ b/game/missile/AlertScript.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class AlertScript : public crepe::Script {
+private:
+ bool has_alert = false;
+
+public:
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/missile/AlertSubScene.cpp b/game/missile/AlertSubScene.cpp
new file mode 100644
index 0000000..5bce5ac
--- /dev/null
+++ b/game/missile/AlertSubScene.cpp
@@ -0,0 +1,33 @@
+#include "AlertSubScene.h"
+#include "../Config.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+
+MissileAlert::MissileAlert(Scene & scn) {
+ GameObject alert = scn.new_object("missile_alert", "missile_alert", {0, 0}, 0, 1);
+
+ Asset missile_alert_ss {"asset/obstacles/missile/missileAlert.png"};
+
+ auto & missile_alert_sprite = alert.add_component<Sprite>(
+ missile_alert_ss,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .size = {0, 100},
+ }
+ );
+
+ auto & missile_alert_anim = alert.add_component<Animator>(
+ missile_alert_sprite, ivec2 {64, 64}, uvec2 {4, 2},
+ Animator::Data {
+ .fps = 15,
+ .looping = true,
+ }
+ );
+
+ missile_alert_anim.set_anim(1);
+ missile_alert_sprite.active = false;
+}
diff --git a/game/missile/AlertSubScene.h b/game/missile/AlertSubScene.h
new file mode 100644
index 0000000..8ea288f
--- /dev/null
+++ b/game/missile/AlertSubScene.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class MissileAlert {
+public:
+ MissileAlert(crepe::Scene & scn);
+};
diff --git a/game/missile/MissilePool.cpp b/game/missile/MissilePool.cpp
new file mode 100644
index 0000000..23f03c9
--- /dev/null
+++ b/game/missile/MissilePool.cpp
@@ -0,0 +1,18 @@
+#include "MissilePool.h"
+#include "MissileSubScene.h"
+#include "missile/AlertSubScene.h"
+
+#include <crepe/api/Scene.h>
+
+using namespace std;
+using namespace crepe;
+
+MissilePool::MissilePool(Scene & scn) {
+ int amount = 0;
+ MissileSubScene missile;
+ while (amount < this->MAX_MISSILE_COUNT) {
+ MissileAlert alert(scn);
+ missile.create(scn);
+ amount++;
+ }
+}
diff --git a/game/missile/MissilePool.h b/game/missile/MissilePool.h
new file mode 100644
index 0000000..296701e
--- /dev/null
+++ b/game/missile/MissilePool.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class MissilePool {
+public:
+ MissilePool(crepe::Scene & scn);
+
+private:
+ static constexpr unsigned int MAX_MISSILE_COUNT = 5;
+};
diff --git a/game/missile/MissileScript.cpp b/game/missile/MissileScript.cpp
new file mode 100644
index 0000000..3aa4eb6
--- /dev/null
+++ b/game/missile/MissileScript.cpp
@@ -0,0 +1,106 @@
+#include "MissileScript.h"
+#include "../Config.h"
+#include <cmath>
+
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/KeyCodes.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/system/CollisionSystem.h>
+#include <crepe/types.h>
+
+using namespace std;
+using namespace crepe;
+
+void MissileScript::init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+ this->seeking_disabled = false;
+}
+void MissileScript::kill_missile() {
+ auto animations = this->get_components<Animator>();
+ auto sprites = this->get_components<Sprite>();
+ auto collider = this->get_component<CircleCollider>();
+ auto & this_script = this->get_components<BehaviorScript>().front().get();
+
+ animations[0].get().active = false;
+ animations[1].get().active = false;
+ animations[2].get().active = true;
+ sprites[0].get().active = false;
+ sprites[1].get().active = false;
+ sprites[2].get().active = true;
+
+ collider.active = false;
+ this_script.active = false;
+ this->seeking_disabled = false;
+}
+void MissileScript::activate() {
+ auto anim = this->get_components<Animator>();
+ auto sprites = this->get_components<Sprite>();
+
+ sprites[0].get().active = true;
+ sprites[1].get().active = true;
+ sprites[2].get().active = false;
+
+ anim[0].get().active = true;
+ anim[1].get().active = true;
+ anim[2].get().stop();
+}
+bool MissileScript::on_collision(const CollisionEvent & ev) {
+ auto & explosion_sound = this->get_components<AudioSource>().back().get();
+
+ this->kill_missile();
+ explosion_sound.play();
+
+ return false;
+}
+
+bool MissileScript::is_in_x_range(const Transform & missile, const Transform & player) {
+ return fabs(missile.position.x - player.position.x) <= this->X_RANGE;
+}
+
+void MissileScript::fixed_update(crepe::duration_t dt) {
+ auto & explosion_anim = this->get_components<Animator>().back().get();
+ auto & missile = this->get_component<Transform>();
+ auto & m_ai = this->get_component<AI>();
+
+ const auto & player = this->get_components_by_name<Transform>("player").front().get();
+ const auto & cam = this->get_components_by_name<Transform>("camera").front().get();
+ const auto & velocity = this->get_component<Rigidbody>().data.linear_velocity;
+
+ // check if animation is at the end
+ if (explosion_anim.data.row == 7) {
+ this->activate();
+ this->seeking_disabled = false;
+ return;
+ }
+
+ if (missile.position.x < (cam.position.x - VIEWPORT_X / 1.8)) {
+ this->kill_missile();
+ return;
+ }
+
+ if (this->seeking_disabled) {
+ m_ai.seek_off();
+ } else {
+ m_ai.seek_target = player.position;
+ m_ai.seek_on();
+
+ if (is_in_x_range(missile, player)) {
+ this->seeking_disabled = true;
+ m_ai.seek_off();
+ }
+ }
+
+ vec2 angle_pos = velocity;
+ float angle = atan2(angle_pos.y, angle_pos.x) * (180 / M_PI);
+
+ missile.rotation = angle;
+ missile.position += velocity * dt.count();
+}
diff --git a/game/missile/MissileScript.h b/game/missile/MissileScript.h
new file mode 100644
index 0000000..a492e18
--- /dev/null
+++ b/game/missile/MissileScript.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class MissileScript : public crepe::Script {
+private:
+ bool on_collision(const crepe::CollisionEvent & ev);
+
+ bool seeking_disabled;
+
+ // will be used to calculate when ai will be stopped
+ static constexpr int X_RANGE = 90;
+ bool is_in_x_range(const crepe::Transform & missile, const crepe::Transform & player);
+ void kill_missile();
+ void activate();
+
+public:
+ void init();
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/missile/MissileSubScene.cpp b/game/missile/MissileSubScene.cpp
new file mode 100644
index 0000000..d325050
--- /dev/null
+++ b/game/missile/MissileSubScene.cpp
@@ -0,0 +1,101 @@
+#include "MissileSubScene.h"
+#include "../Config.h"
+#include "../missile/MissileScript.h"
+#include "Random.h"
+#include "missile/AlertScript.h"
+
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+
+void MissileSubScene::create(crepe::Scene & scn) {
+ GameObject missle = scn.new_object("missile", "missile", {0, 0}, 0, 1);
+
+ Asset missle_ss {"asset/obstacles/missile/missile.png"};
+ Asset missle_thruster_ss {"asset/obstacles/missile/missileEffects.png"};
+ Asset missile_explosion_ss {"asset/obstacles/missile/missileExplosion.png"};
+ Asset explosion_sound {"asset/sfx/rocket_explode_1.ogg"};
+ Asset missile_fire {"asset/sfx/missile_launch.ogg"};
+
+ missle.add_component<BehaviorScript>().set_script<MissileScript>().active = false;
+ missle.add_component<BehaviorScript>().set_script<AlertScript>();
+
+ auto & sound = missle.add_component<AudioSource>(missile_fire);
+ sound.volume = 0.5;
+ auto & sound2 = missle.add_component<AudioSource>(explosion_sound);
+ sound2.volume = 3;
+
+ // sprites
+ auto & missle_sprite = missle.add_component<Sprite>(
+ missle_ss,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .size = {0, 35},
+ }
+ );
+
+ auto & missle_thruster_sprite = missle.add_component<Sprite>(
+ missle_thruster_ss,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .size = {0, 35},
+ .position_offset = {-20, 0},
+ }
+ );
+
+ auto & missile_explosion_sprite = missle.add_component<Sprite>(
+ missile_explosion_ss,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .size = {0, 50},
+ }
+ );
+
+ // Animations
+ missle.add_component<Animator>(
+ missle_sprite, ivec2 {32, 32}, uvec2 {4, 1},
+ Animator::Data {
+ .fps = 15,
+ .looping = true,
+ }
+ );
+
+ missle.add_component<Animator>(
+ missle_thruster_sprite, ivec2 {64, 64}, uvec2 {4, 2},
+ Animator::Data {
+ .fps = 15,
+ .looping = true,
+ }
+ );
+
+ auto & explosion_anim = missle.add_component<Animator>(
+ missile_explosion_sprite, ivec2 {64, 64}, uvec2 {8, 1},
+ Animator::Data {
+ .fps = 10,
+ }
+ );
+
+ missile_explosion_sprite.active = false;
+ explosion_anim.active = false;
+
+ missle.add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::KINEMATIC,
+ .max_linear_velocity = Random::f(250, 200),
+ .kinematic_collision = false,
+ .collision_layers = {COLL_LAY_PLAYER, COLL_LAY_BOT_TOP},
+ .collision_layer = COLL_LAY_MISSILE,
+ });
+
+ missle.add_component<CircleCollider>(3).active = false;
+
+ auto & missle_ai = missle.add_component<AI>(1000);
+}
diff --git a/game/missile/MissileSubScene.h b/game/missile/MissileSubScene.h
new file mode 100644
index 0000000..9ea422a
--- /dev/null
+++ b/game/missile/MissileSubScene.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class MissileSubScene {
+public:
+ MissileSubScene() = default;
+
+ void create(crepe::Scene & scn);
+};
diff --git a/game/missile/SpawnEvent.cpp b/game/missile/SpawnEvent.cpp
new file mode 100644
index 0000000..6109686
--- /dev/null
+++ b/game/missile/SpawnEvent.cpp
@@ -0,0 +1,49 @@
+#include "SpawnEvent.h"
+#include "Random.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+
+#include <cstdlib>
+
+using namespace crepe;
+
+void MissileSpawnEventHandler::init() {
+ subscribe<MissileSpawnEvent>([this](const MissileSpawnEvent & ev) -> bool {
+ return this->on_event(ev);
+ });
+}
+
+bool MissileSpawnEventHandler::on_event(const MissileSpawnEvent & event) {
+ auto alert_sprites = this->get_components_by_name<Sprite>("missile_alert");
+
+ auto missile_transforms = this->get_components_by_name<Transform>("missile");
+ auto colliders = this->get_components_by_name<CircleCollider>("missile");
+ auto missile_behaviorscripts = this->get_components_by_name<BehaviorScript>("missile");
+ auto missile_audiosources = this->get_components_by_name<AudioSource>("missile");
+
+ auto & camera_transform = this->get_components_by_name<Transform>("camera").front().get();
+
+ for (size_t i = 0; i < missile_transforms.size(); ++i) {
+ BehaviorScript & script = missile_behaviorscripts[i * 2].get();
+ if (script.active) continue;
+ script.active = true;
+ colliders[i].get().active = true;
+ missile_audiosources[i * 2].get().play();
+
+ auto & transform = missile_transforms[i].get();
+ transform.position.x = camera_transform.position.x + this->MISSILE_OFFSET;
+ transform.position.y = Random::i(this->MAX_RANGE, this->MIN_RANGE);
+
+ auto & alert_sprite = alert_sprites[i].get();
+ alert_sprite.active = true;
+ break;
+ }
+
+ return false;
+}
diff --git a/game/missile/SpawnEvent.h b/game/missile/SpawnEvent.h
new file mode 100644
index 0000000..84b1987
--- /dev/null
+++ b/game/missile/SpawnEvent.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+#include "../Config.h"
+
+struct MissileSpawnEvent : public crepe::Event {};
+
+class MissileSpawnEventHandler : public crepe::Script {
+private:
+ static constexpr int MISSILE_OFFSET = VIEWPORT_X;
+ static constexpr int MIN_RANGE = -150;
+ static constexpr int MAX_RANGE = 150;
+
+public:
+ void init();
+ bool on_event(const MissileSpawnEvent & ev);
+};
diff --git a/game/player/PlayerAudioScript.cpp b/game/player/PlayerAudioScript.cpp
new file mode 100644
index 0000000..6b5f630
--- /dev/null
+++ b/game/player/PlayerAudioScript.cpp
@@ -0,0 +1,62 @@
+#include "PlayerAudioScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+
+using namespace crepe;
+using namespace std;
+
+void PlayerAudioScript::fixed_update(crepe::duration_t dt) {
+ Animator & animator = this->get_components_by_name<Animator>("player").front();
+
+ if (animator.data.col == 0) {
+ if (animator.data.row != this->last_row) {
+ if (animator.data.row == 0) {
+ // right footstep
+ if (current_footstep == 0) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(0);
+ audio.play();
+ } else if (current_footstep == 1) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(2);
+ audio.play();
+ } else if (current_footstep == 2) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(4);
+ audio.play();
+ } else if (current_footstep == 3) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(6);
+ audio.play();
+ }
+ } else if (animator.data.row == 2) {
+ // left footstep
+ if (current_footstep == 0) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(1);
+ audio.play();
+ current_footstep = 1;
+ } else if (current_footstep == 1) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(3);
+ audio.play();
+ current_footstep = 2;
+ } else if (current_footstep == 2) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(5);
+ audio.play();
+ current_footstep = 3;
+ } else if (current_footstep == 3) {
+ AudioSource & audio
+ = this->get_components_by_name<AudioSource>("player_audio").at(7);
+ audio.play();
+ current_footstep = 0;
+ }
+ }
+ this->last_row = animator.data.row;
+ }
+ } else {
+ this->last_row = -1;
+ }
+}
diff --git a/game/player/PlayerAudioScript.h b/game/player/PlayerAudioScript.h
new file mode 100644
index 0000000..764cb20
--- /dev/null
+++ b/game/player/PlayerAudioScript.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+class PlayerAudioScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ int last_row = -1;
+ int current_footstep = 0;
+};
diff --git a/game/player/PlayerBulletPool.cpp b/game/player/PlayerBulletPool.cpp
new file mode 100644
index 0000000..5285ec8
--- /dev/null
+++ b/game/player/PlayerBulletPool.cpp
@@ -0,0 +1,11 @@
+#include "PlayerBulletPool.h"
+#include "PlayerBulletSubScene.h"
+using namespace std;
+
+void PlayerBulletPool::create_bullets(crepe::Scene & scn) {
+ PlayerBulletSubScene bullet;
+ int amount = 0;
+ while (amount < this->MAXIMUM_AMOUNT) {
+ amount = bullet.create(scn, amount);
+ }
+}
diff --git a/game/player/PlayerBulletPool.h b/game/player/PlayerBulletPool.h
new file mode 100644
index 0000000..9618d54
--- /dev/null
+++ b/game/player/PlayerBulletPool.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <crepe/api/Scene.h>
+
+class PlayerBulletPool {
+public:
+ void create_bullets(crepe::Scene & scn);
+
+private:
+ static constexpr int MAXIMUM_AMOUNT = 20;
+};
diff --git a/game/player/PlayerBulletScript.cpp b/game/player/PlayerBulletScript.cpp
new file mode 100644
index 0000000..a823375
--- /dev/null
+++ b/game/player/PlayerBulletScript.cpp
@@ -0,0 +1,41 @@
+
+#include <crepe/api/Camera.h>
+#include <crepe/api/Metadata.h>
+#include <crepe/api/Rigidbody.h>
+
+#include "PlayerBulletScript.h"
+
+using namespace crepe;
+using namespace std;
+void PlayerBulletScript::init() {
+ this->subscribe<CollisionEvent>([this](const CollisionEvent & e) -> bool {
+ return this->on_collide(e);
+ });
+}
+void PlayerBulletScript::fixed_update(crepe::duration_t dt) {
+ Transform & transform = this->get_component<Transform>();
+ Camera & camera = this->get_components_by_name<Camera>("camera").front();
+ Transform & cam_transform = this->get_components_by_name<Transform>("camera").front();
+ Rigidbody & bullet_body = this->get_component<Rigidbody>();
+ transform.rotation += bullet_body.data.angular_velocity * dt.count();
+ transform.position += bullet_body.data.linear_velocity * dt.count();
+ vec2 half_screen = camera.viewport_size / 2;
+ float despawn_location = cam_transform.position.x + half_screen.x + 50;
+ if (transform.position.x > despawn_location) {
+ this->despawn_bullet();
+ }
+}
+
+void PlayerBulletScript::despawn_bullet() {
+ Transform & transform = this->get_component<Transform>();
+ Rigidbody & bullet_body = this->get_component<Rigidbody>();
+ bullet_body.active = false;
+ BehaviorScript & bullet_script = this->get_component<BehaviorScript>();
+ bullet_script.active = false;
+ transform.position = {0, -850};
+}
+
+bool PlayerBulletScript::on_collide(const CollisionEvent & e) {
+ this->despawn_bullet();
+ return false;
+}
diff --git a/game/player/PlayerBulletScript.h b/game/player/PlayerBulletScript.h
new file mode 100644
index 0000000..0637790
--- /dev/null
+++ b/game/player/PlayerBulletScript.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Script.h>
+
+class PlayerBulletScript : public crepe::Script {
+public:
+ void init() override;
+ void fixed_update(crepe::duration_t dt) override;
+ bool on_collide(const crepe::CollisionEvent & e);
+ void despawn_bullet();
+};
diff --git a/game/player/PlayerBulletSubScene.cpp b/game/player/PlayerBulletSubScene.cpp
new file mode 100644
index 0000000..82ce4a9
--- /dev/null
+++ b/game/player/PlayerBulletSubScene.cpp
@@ -0,0 +1,52 @@
+#include <string>
+
+#include "../Config.h"
+#include <crepe/api/AI.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+#include "PlayerBulletScript.h"
+#include "PlayerBulletSubScene.h"
+#include "PlayerScript.h"
+using namespace crepe;
+using namespace std;
+int PlayerBulletSubScene::create(Scene & scn, int counter) {
+ string unique_name = "player_bullet_" + to_string(counter++);
+ GameObject player_bullet
+ = scn.new_object(unique_name.c_str(), "player_bullet", vec2 {0, -850}, 0, 1);
+
+ Rigidbody & player_bullet_body = player_bullet.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 0,
+ .body_type = Rigidbody::BodyType::KINEMATIC,
+ .linear_velocity = vec2 {450, 0},
+ .angular_velocity = 300,
+ .kinematic_collision = false,
+ .collision_layers = {COLL_LAY_ENEMY, COLL_LAY_ZAPPER},
+
+ .collision_layer = COLL_LAY_PLAYER_BULLET,
+
+ });
+ player_bullet_body.active = false;
+ BoxCollider & player_bullet_collider
+ = player_bullet.add_component<BoxCollider>(vec2(30, 30));
+
+ Asset player_bullet_asset {"asset/other_effects/crepe.png"};
+ Sprite & player_bullet_sprite = player_bullet.add_component<Sprite>(
+ player_bullet_asset,
+ Sprite::Data {
+ .flip = {true, false},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = 1,
+ .size = vec2(30, 0),
+ }
+ );
+ player_bullet.add_component<BehaviorScript>().set_script<PlayerBulletScript>().active
+ = false;
+ return counter;
+}
diff --git a/game/player/PlayerBulletSubScene.h b/game/player/PlayerBulletSubScene.h
new file mode 100644
index 0000000..72eda62
--- /dev/null
+++ b/game/player/PlayerBulletSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class PlayerBulletSubScene {
+public:
+ int create(crepe::Scene & scn, int counter);
+};
diff --git a/game/player/PlayerEndScript.cpp b/game/player/PlayerEndScript.cpp
new file mode 100644
index 0000000..62fd350
--- /dev/null
+++ b/game/player/PlayerEndScript.cpp
@@ -0,0 +1,104 @@
+#include "PlayerEndScript.h"
+
+#include "../Config.h"
+#include "../Events.h"
+#include "manager/LoopTimerManager.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PlayerEndScript::init() {
+ Rigidbody & rb_player = this->get_components_by_name<Rigidbody>("player").front();
+ rb_player.data.elasticity_coefficient = 0.7;
+
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+}
+
+bool PlayerEndScript::on_collision(const crepe::CollisionEvent & ev) {
+ if (ev.info.other.metadata.name == "floor") {
+ Transform & transform_player
+ = this->get_components_by_name<Transform>("player").front();
+ RefVector<Animator> anim_player = this->get_components_by_name<Animator>("player");
+ Rigidbody & rb_player = this->get_components_by_name<Rigidbody>("player").front();
+ Rigidbody & rb_camera = this->get_components_by_name<Rigidbody>("camera").front();
+
+ float dt = this->get_loop_timer().get_fixed_delta_time().count();
+
+ if (jump == 0) {
+ int random_number = rand() % 4;
+ for (Animator & anim : anim_player) {
+ anim.active = false;
+ anim.set_anim(6);
+ for (int i = 0; i < random_number; i++) {
+ anim.next_anim();
+ }
+ }
+ } else if (jump == 1) {
+ for (Animator & anim : anim_player) {
+ anim.next_anim();
+ }
+ }
+
+ if (jump == 0) {
+ rb_player.data.angular_velocity = 16000 * dt;
+ rb_player.data.angular_velocity_coefficient = 0.7;
+ jump++;
+ } else if (jump == 1) {
+ jump++;
+ } else if (jump == 2) {
+ RefVector<Rigidbody> rb_back_forest
+ = this->get_components_by_tag<Rigidbody>("forest_background");
+ for (Rigidbody & rb : rb_back_forest) {
+ rb.data.linear_velocity_coefficient = vec2(0.5, 0.5);
+ }
+
+ rb_player.data.angular_velocity = 0;
+ rb_player.data.elasticity_coefficient = 0;
+ if (rb_player.data.linear_velocity.x != 0) {
+ rb_player.data.linear_velocity = vec2(PLAYER_SPEED * dt, 0);
+ }
+ rb_player.data.linear_velocity_coefficient = vec2(0.5, 0.5);
+ rb_camera.data.linear_velocity_coefficient = vec2(0.5, 0.5);
+ for (Animator & anim : anim_player) {
+ anim.active = false;
+ anim.set_anim(7);
+ }
+ if (transform_player.rotation > 0 && transform_player.rotation < 90) {
+ // Do not call next_anim()
+ } else if (transform_player.rotation > 90 && transform_player.rotation < 180) {
+ for (Animator & anim : anim_player) {
+ anim.next_anim();
+ }
+ } else if (transform_player.rotation > 180 && transform_player.rotation < 270) {
+ for (Animator & anim : anim_player) {
+ anim.next_anim();
+ anim.next_anim();
+ }
+ } else {
+ for (Animator & anim : anim_player) {
+ anim.next_anim();
+ anim.next_anim();
+ anim.next_anim();
+ }
+ }
+ jump++;
+ }
+
+ if (rb_player.data.linear_velocity.x < 5 && jump == 3) {
+ this->trigger_event<EndGameEvent>();
+ jump++;
+ }
+
+ return false;
+ }
+
+ return false;
+}
diff --git a/game/player/PlayerEndScript.h b/game/player/PlayerEndScript.h
new file mode 100644
index 0000000..03ea8a9
--- /dev/null
+++ b/game/player/PlayerEndScript.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class PlayerEndScript : public crepe::Script {
+public:
+ void init();
+
+private:
+ bool on_collision(const crepe::CollisionEvent & ev);
+
+private:
+ int jump = 0;
+};
diff --git a/game/player/PlayerScript.cpp b/game/player/PlayerScript.cpp
new file mode 100644
index 0000000..8cbe8dc
--- /dev/null
+++ b/game/player/PlayerScript.cpp
@@ -0,0 +1,214 @@
+#include "PlayerScript.h"
+
+#include "../Config.h"
+#include "../enemy/BattleScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PlayerScript::init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+ subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ if (ev.repeat) return false;
+ return this->on_key_down(ev);
+ });
+ subscribe<KeyReleaseEvent>([this](const KeyReleaseEvent & ev) -> bool {
+ return this->on_key_up(ev);
+ });
+ this->last_fired = std::chrono::steady_clock::now();
+ this->body = get_component<Rigidbody>();
+}
+
+bool PlayerScript::on_key_down(const KeyPressEvent & ev) {
+ if (ev.key == Keycode::SPACE) {
+ const vec2 UP = {0, -1};
+ this->help_kick(UP);
+ }
+ return false;
+}
+
+bool PlayerScript::on_key_up(const KeyReleaseEvent & ev) {
+ if (ev.key == Keycode::SPACE) {
+ const vec2 DOWN = {0, 1};
+ this->help_kick(DOWN);
+ }
+ return false;
+}
+
+void PlayerScript::help_kick(const vec2 & direction) {
+ // softly "kick" the player (at start/end of flight)
+ vec2 & velocity = this->body->data.linear_velocity;
+ float kick_amount = std::min(
+ velocity.length() * PLAYER_HELP_KICK_SCALE, engine_gravity * PLAYER_HELP_KICK_MAX
+ );
+ velocity += direction * kick_amount;
+}
+
+bool PlayerScript::on_collision(const CollisionEvent & ev) {
+ BehaviorScript & play_scr = this->get_components_by_name<BehaviorScript>("player").front();
+ BehaviorScript & end_scr = this->get_components_by_name<BehaviorScript>("player").back();
+ RefVector<Animator> animators = this->get_components_by_name<Animator>("player");
+ RefVector<ParticleEmitter> emitters
+ = this->get_components_by_name<ParticleEmitter>("player");
+
+ if (ev.info.other.metadata.tag == "zapper") {
+ for (Animator & anim : animators) {
+ anim.active = true;
+ anim.set_anim(4);
+ anim.data.looping = true;
+ prev_anim = 0;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 0;
+ }
+ play_scr.active = false;
+ end_scr.active = true;
+
+ AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(0);
+ audio.play();
+
+ return false;
+ } else if (ev.info.other.metadata.tag == "laser") {
+ for (Animator & anim : animators) {
+ anim.active = true;
+ anim.set_anim(4);
+ anim.data.looping = true;
+ prev_anim = 0;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 0;
+ }
+ play_scr.active = false;
+ end_scr.active = true;
+
+ AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(1);
+ audio.play();
+
+ return false;
+ } else if (ev.info.other.metadata.tag == "missile"
+ || ev.info.other.metadata.tag == "enemy_bullet") {
+ for (Animator & anim : animators) {
+ anim.active = true;
+ anim.set_anim(5);
+ anim.data.looping = true;
+ prev_anim = 0;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 0;
+ }
+ play_scr.active = false;
+ end_scr.active = true;
+
+ AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(2);
+ audio.play();
+
+ return false;
+ }
+
+ return false;
+}
+
+void PlayerScript::fixed_update(crepe::duration_t dt) {
+ RefVector<Animator> animators = this->get_components_by_name<Animator>("player");
+ RefVector<ParticleEmitter> emitters
+ = this->get_components_by_name<ParticleEmitter>("player");
+ Transform & transform = this->get_components_by_name<Transform>("player").front();
+
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.boundary.offset = vec2(0, -transform.position.y);
+ }
+
+ Rigidbody & rb = this->body;
+ if (this->get_key_state(Keycode::ENTER)) {
+
+ auto now = std::chrono::steady_clock::now();
+ std::chrono::duration<float> elapsed = now - last_fired;
+ if (elapsed > shot_delay) {
+ this->shoot(transform.position, 0);
+ last_fired = now;
+ }
+ }
+ if (this->get_key_state(Keycode::SPACE)) {
+ rb.add_force_linear(
+ vec2(0, -1) * (engine_gravity * PLAYER_GRAVITY_SCALE * dt.count())
+ );
+
+ if (prev_anim != 1) {
+ for (Animator & anim : animators) {
+ anim.active = true;
+ anim.set_anim(1);
+ anim.data.looping = true;
+ prev_anim = 1;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 30;
+ }
+ }
+
+ AudioSource & audio = this->get_components_by_name<AudioSource>("player").at(
+ 3 + current_jetpack_sound
+ );
+ audio.play();
+ current_jetpack_sound++;
+ if (current_jetpack_sound > 7) {
+ current_jetpack_sound = 0;
+ }
+ } else if (transform.position.y == 200) {
+ Rigidbody & rb = this->body;
+ if (prev_anim != 0 && rb.data.linear_velocity.x != 0) {
+ for (Animator & anim : animators) {
+ anim.active = true;
+ anim.set_anim(0);
+ anim.data.looping = true;
+ prev_anim = 0;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 0;
+ }
+ }
+ } else {
+ if (prev_anim != 2) {
+ for (Animator & anim : animators) {
+ anim.set_anim(2);
+ anim.data.looping = false;
+ prev_anim = 2;
+ }
+ for (ParticleEmitter & emitter : emitters) {
+ emitter.data.emission_rate = 0;
+ }
+ }
+ }
+}
+
+void PlayerScript::shoot(const vec2 & location, float angle) {
+ RefVector<Transform> bullet_transforms
+ = this->get_components_by_tag<Transform>("player_bullet");
+
+ for (Transform & bullet_pos : bullet_transforms) {
+ if (bullet_pos.position.x == 0 && bullet_pos.position.y == -850) {
+
+ bullet_pos.position = location;
+ bullet_pos.position.x += 20;
+ Rigidbody & bullet_body
+ = this->get_components_by_id<Rigidbody>(bullet_pos.game_object_id).front();
+ BoxCollider bullet_collider
+ = this->get_components_by_id<BoxCollider>(bullet_pos.game_object_id).front();
+ bullet_body.active = true;
+ BehaviorScript & bullet_script
+ = this->get_components_by_id<BehaviorScript>(bullet_pos.game_object_id)
+ .front();
+ bullet_script.active = true;
+ return;
+ }
+ }
+}
diff --git a/game/player/PlayerScript.h b/game/player/PlayerScript.h
new file mode 100644
index 0000000..6a7dedb
--- /dev/null
+++ b/game/player/PlayerScript.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "util/OptionalRef.h"
+#include <chrono>
+#include <crepe/api/Config.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+class PlayerScript : public crepe::Script {
+public:
+ void init();
+ void fixed_update(crepe::duration_t dt);
+
+private:
+ bool on_collision(const crepe::CollisionEvent & ev);
+ bool on_key_down(const crepe::KeyPressEvent & ev);
+ bool on_key_up(const crepe::KeyReleaseEvent & ev);
+ // bool on_key_up(const crepe::KeyReleaseEvent& ev);
+ void shoot(const crepe::vec2 & location, float angle);
+ void help_kick(const crepe::vec2 & direction);
+
+private:
+ int prev_anim = 0;
+ std::chrono::time_point<std::chrono::steady_clock> last_fired;
+ std::chrono::time_point<std::chrono::steady_clock> last_switched;
+ std::chrono::duration<float> shot_delay = std::chrono::duration<float>(0.5);
+ std::chrono::duration<float> switch_delay = std::chrono::duration<float>(0.01);
+ int current_jetpack_sound = 0;
+
+ float & engine_gravity = crepe::Config::get_instance().physics.gravity;
+ crepe::OptionalRef<crepe::Rigidbody> body;
+};
diff --git a/game/player/PlayerSubScene.cpp b/game/player/PlayerSubScene.cpp
new file mode 100644
index 0000000..d0142e0
--- /dev/null
+++ b/game/player/PlayerSubScene.cpp
@@ -0,0 +1,222 @@
+#include "PlayerSubScene.h"
+#include "PlayerAudioScript.h"
+#include "PlayerEndScript.h"
+#include "PlayerScript.h"
+
+#include "../Config.h"
+#include "../coins/CoinScript.h"
+
+#include <crepe/ValueBroker.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/CircleCollider.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/manager/SaveManager.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+PlayerSubScene::PlayerSubScene(Scene & scn) {
+ GameObject player = scn.new_object("player", "player", vec2(-100, 200));
+
+ SaveManager & save = scn.get_save_manager();
+ ValueBroker<int> particle_type = save.get<int>(JETPACK_PARTICLES, 0);
+
+ string player_bullet_string;
+ string player_bullet_x2_string;
+ string player_shell_string;
+ if (particle_type.get() == 0) {
+ player_bullet_string = "asset/other_effects/effect_smgbullet.png";
+ player_bullet_x2_string = "asset/other_effects/effect_smgbullet_x2.png";
+ player_shell_string = "asset/other_effects/effect_rocketmgshell_TVOS.png";
+ } else {
+ player_bullet_string = "asset/background/aquarium/bubble.png";
+ player_bullet_x2_string = "asset/background/aquarium/bubble.png";
+ player_shell_string = "asset/background/aquarium/bubble.png";
+ }
+
+ Asset player_bullet {player_bullet_string};
+ Sprite & player_bullet_sprite = player.add_component<Sprite>(
+ player_bullet,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 3,
+ .size = vec2(0, 6),
+ }
+ );
+ player.add_component<ParticleEmitter>(player_bullet_sprite, ParticleEmitter::Data{
+ .offset = vec2(-15, 15),
+ .emission_rate = 0,
+ .min_speed = 300,
+ .max_speed = 500,
+ .min_angle = 85,
+ .max_angle = 100,
+ .boundary = ParticleEmitter::Boundary {
+ .height = 400,
+ .reset_on_exit = true,
+ },
+ });
+ Asset player_bullet_x2 {player_bullet_x2_string};
+ Sprite & player_bullet_x2_sprite = player.add_component<Sprite>(
+ player_bullet_x2,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 3,
+ .size = vec2(0, 12),
+ }
+ );
+ player.add_component<ParticleEmitter>(player_bullet_x2_sprite, ParticleEmitter::Data{
+ .offset = vec2(-15, 15),
+ .emission_rate = 0,
+ .min_speed = 300,
+ .max_speed = 500,
+ .min_angle = 85,
+ .max_angle = 100,
+ .boundary = ParticleEmitter::Boundary {
+ .height = 400,
+ .reset_on_exit = true,
+ },
+ });
+ Asset player_shell {player_shell_string};
+ Sprite & player_shell_sprite = player.add_component<Sprite>(
+ player_shell,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 3,
+ .size = vec2(0, 12),
+ .angle_offset = 90,
+ }
+ );
+ player.add_component<ParticleEmitter>(player_shell_sprite, ParticleEmitter::Data{
+ .offset = vec2(-15, 15),
+ .emission_rate = 0,
+ .min_speed = 200,
+ .max_speed = 500,
+ .min_angle = 110,
+ .max_angle = 120,
+ .force_over_time = vec2(0, 1000),
+ .boundary = ParticleEmitter::Boundary {
+ .height = 400,
+ .reset_on_exit = true,
+ },
+ });
+
+ Asset player_body_asset {"asset/barry/defaultBody.png"};
+ Sprite & player_body_sprite = player.add_component<Sprite>(
+ player_body_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 0,
+ .size = vec2(0, 50),
+ }
+ );
+ player.add_component<Animator>(
+ player_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ player.add_component<BoxCollider>(vec2(50, 35));
+ Asset player_head_asset {"asset/barry/defaultHead.png"};
+ Sprite & player_head_sprite = player.add_component<Sprite>(
+ player_head_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 1,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ player.add_component<Animator>(
+ player_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ player.add_component<CircleCollider>(25, vec2(0, -20));
+ Asset player_jetpack_asset {"asset/barry/jetpackDefault.png"};
+ Sprite & player_jetpack_sprite = player.add_component<Sprite>(
+ player_jetpack_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 2,
+ .size = vec2(0, 60),
+ .position_offset = vec2(-20, 0),
+ }
+ );
+ player_jetpack_sprite.active = false;
+ player.add_component<Animator>(
+ player_jetpack_sprite, ivec2(32, 44), uvec2(4, 4),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ player.add_component<BoxCollider>(vec2(40, 50), vec2(-20, 0));
+ player.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1.0,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(PLAYER_SPEED * 0.02, 0),
+ .collision_layers
+ = {COLL_LAY_BOT_TOP, COLL_LAY_ZAPPER, COLL_LAY_LASER, COLL_LAY_MISSILE, COLL_LAY_BULLET
+ },
+ .collision_layer = COLL_LAY_PLAYER,
+ });
+
+ player.add_component<BehaviorScript>().set_script<PlayerScript>().active = false;
+ player.add_component<BehaviorScript>().set_script<CoinScript>();
+ player.add_component<BehaviorScript>().set_script<PlayerEndScript>().active = false;
+
+ player.add_component<AudioSource>(Asset("asset/sfx/dud_zapper_lp.ogg"));
+ player.add_component<AudioSource>(Asset("asset/sfx/dud_zapper_pop.ogg"));
+ player.add_component<AudioSource>(Asset("asset/sfx/dud_fire.ogg"));
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_01.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_02.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_03.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_04.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_05.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_06.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_07.ogg")).volume
+ = 0.1;
+ player.add_component<AudioSource>(Asset("asset/sfx/jetpack_firecracker_lp_08.ogg")).volume
+ = 0.1;
+
+ GameObject player_audio = scn.new_object("player_audio", "player_audio", vec2(0, 0));
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_1.ogg")).volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_1.ogg"))
+ .volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_2.ogg")).volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_2.ogg"))
+ .volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_3.ogg")).volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_3.ogg"))
+ .volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_left_4.ogg")).volume
+ = 3.0;
+ player_audio.add_component<AudioSource>(Asset("asset/sfx/barefoot_step_right_4.ogg"))
+ .volume
+ = 3.0;
+
+ player_audio.add_component<BehaviorScript>().set_script<PlayerAudioScript>().active
+ = false;
+}
diff --git a/game/player/PlayerSubScene.h b/game/player/PlayerSubScene.h
new file mode 100644
index 0000000..bf94c32
--- /dev/null
+++ b/game/player/PlayerSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class PlayerSubScene {
+public:
+ PlayerSubScene(crepe::Scene & scn);
+};
diff --git a/game/prefab/CMakeLists.txt b/game/prefab/CMakeLists.txt
new file mode 100644
index 0000000..6c36ef2
--- /dev/null
+++ b/game/prefab/CMakeLists.txt
@@ -0,0 +1,6 @@
+target_sources(main PUBLIC
+ ZapperObject.cpp
+ ZapperPoolSubScene.cpp
+ ZapperPoolScript.cpp
+)
+
diff --git a/game/prefab/ZapperObject.cpp b/game/prefab/ZapperObject.cpp
new file mode 100644
index 0000000..24bbbd2
--- /dev/null
+++ b/game/prefab/ZapperObject.cpp
@@ -0,0 +1,118 @@
+#include <crepe/api/Transform.h>
+
+#include "Config.h"
+#include "ZapperObject.h"
+
+using namespace crepe;
+
+ZapperObject::ZapperObject(crepe::GameObject && base)
+ : GameObject(std::move(base)),
+ sprite {
+ .orb_start = add_component<Sprite>(
+ Asset {"asset/obstacles/zapper/orbAnim.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = 1,
+ .size = vec2(0, 50) * SCALE,
+ }
+ ),
+ .orb_end = add_component<Sprite>(
+ sprite.orb_start.source,
+ Sprite::Data {
+ .flip = {true, true},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = 1,
+ .size = vec2(0, 50) * SCALE,
+ }
+ ),
+ .glow_start = add_component<Sprite>(
+ Asset {"asset/obstacles/zapper/regular_zappers/glow.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = -1,
+ .size = vec2(128, 128) * SCALE,
+ }
+ ),
+ .glow_end = add_component<Sprite>(
+ sprite.glow_start.source,
+ Sprite::Data {
+ .flip = {true, true},
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = -1,
+ .size = vec2(128, 128) * SCALE,
+ }
+ ),
+ .beam = add_component<Sprite>(
+ Asset {"asset/obstacles/zapper/regular_zappers/zapEffect.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_OBSTACLES,
+ .order_in_layer = 0,
+ .size = vec2(0, 40 * SCALE),
+ .angle_offset = 90,
+ }
+ ),
+ },
+ animator {
+ .orb_start = add_component<Animator>(
+ sprite.orb_start, ivec2(62, 42), uvec2(4, 1),
+ Animator::Data {
+ .fps = 10,
+ .looping = true,
+ }
+ ),
+ .orb_end = add_component<Animator>(
+ sprite.orb_end, ivec2(62, 42), uvec2(4, 1), animator.orb_start.data
+ ),
+ .glow_start = add_component<Animator>(
+ sprite.glow_start, ivec2(128, 128), uvec2(16, 1),
+ Animator::Data {
+ .fps = 30,
+ .looping = true,
+ }
+ ),
+ .glow_end = add_component<Animator>(
+ sprite.glow_end, ivec2(128, 128), uvec2(16, 1), animator.glow_start.data
+ ),
+ },
+ body {add_component<Rigidbody>(Rigidbody::Data {
+ .body_type = Rigidbody::BodyType::KINEMATIC,
+ .kinematic_collision = false,
+ .collision_layer = COLL_LAY_ZAPPER,
+ })},
+ collider {add_component<BoxCollider>(vec2(0, 0))} {
+ this->set_active(false);
+}
+
+void ZapperObject::place(const crepe::vec2 & position, float rotation, float length) {
+ this->transform.position = position;
+ this->transform.rotation = rotation;
+
+ vec2 offset = vec2(0, 1) * length / 2;
+
+ this->sprite.orb_start.data.position_offset = offset;
+ this->sprite.glow_start.data.position_offset = offset;
+ this->sprite.orb_end.data.position_offset = -offset;
+ this->sprite.glow_end.data.position_offset = -offset;
+
+ this->sprite.beam.data.size.x = length;
+
+ this->collider.dimensions = offset.rotate(rotation) * 2 + vec2(30, 30) * SCALE;
+}
+
+void ZapperObject::set_active(bool active) {
+ this->sprite.orb_start.active = active;
+ this->sprite.orb_end.active = active;
+ this->sprite.glow_start.active = active;
+ this->sprite.glow_end.active = active;
+ this->sprite.beam.active = active;
+
+ this->animator.orb_start.active = active;
+ this->animator.orb_end.active = active;
+ this->animator.glow_start.active = active;
+ this->animator.glow_end.active = active;
+
+ this->body.active = active;
+ this->collider.active = active;
+
+ this->active = active;
+}
diff --git a/game/prefab/ZapperObject.h b/game/prefab/ZapperObject.h
new file mode 100644
index 0000000..42edc51
--- /dev/null
+++ b/game/prefab/ZapperObject.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+
+class ZapperObject : public crepe::GameObject {
+public:
+ ZapperObject(crepe::GameObject &&);
+
+public:
+ bool active = true;
+
+ struct {
+ crepe::Sprite & orb_start;
+ crepe::Sprite & orb_end;
+ crepe::Sprite & glow_start;
+ crepe::Sprite & glow_end;
+ crepe::Sprite & beam;
+ } sprite;
+
+ struct {
+ crepe::Animator & orb_start;
+ crepe::Animator & orb_end;
+ crepe::Animator & glow_start;
+ crepe::Animator & glow_end;
+ } animator;
+
+ crepe::Rigidbody & body;
+ crepe::BoxCollider & collider;
+
+private:
+ static constexpr float SCALE = 0.8;
+
+public:
+ void place(const crepe::vec2 & position, float rotation, float length);
+ void set_active(bool active);
+};
diff --git a/game/prefab/ZapperPoolScript.cpp b/game/prefab/ZapperPoolScript.cpp
new file mode 100644
index 0000000..b832ddd
--- /dev/null
+++ b/game/prefab/ZapperPoolScript.cpp
@@ -0,0 +1,70 @@
+#include <crepe/api/Camera.h>
+
+#include "../Config.h"
+#include "../Random.h"
+
+#include "ZapperPoolScript.h"
+#include "ZapperPoolSubScene.h"
+#include "util/OptionalRef.h"
+
+using namespace crepe;
+using namespace std;
+
+ZapperPoolScript::ZapperPoolScript(std::vector<ZapperObject> && pool) : pool(pool) {}
+
+void ZapperPoolScript::init() {
+ subscribe<CreateZapperEvent>([this](const CreateZapperEvent &) {
+ this->spawn_random();
+ return true;
+ });
+
+ camera_transform = get_components_by_name<Transform>(CAMERA_NAME).back();
+ camera_camera = get_components_by_name<Camera>(CAMERA_NAME).back();
+}
+
+void ZapperPoolScript::fixed_update(crepe::duration_t) {
+ float threshold
+ = camera_transform->position.x - camera_camera->viewport_size.x / 2 - OFFSCREEN_MARGIN;
+ for (ZapperObject & zapper : this->pool) {
+ if (!zapper.active) continue;
+
+ if (zapper.transform.position.x < threshold) zapper.set_active(false);
+ }
+}
+
+void ZapperPoolScript::spawn_random() {
+ OptionalRef<ZapperObject> zapper = this->get_next_zapper();
+ if (!zapper) return; // pool exhausted
+
+ bool horizontal = Random::b();
+ vec2 pos;
+ float rotation, length;
+ pos.x
+ = camera_transform->position.x + camera_camera->viewport_size.x / 2 + OFFSCREEN_MARGIN;
+
+ if (horizontal) {
+ rotation = 90;
+ length = Random::f(400, 200);
+ pos.y = Random::f(0.5, -0.5) * HALLWAY_HEIGHT;
+ // align zapper to right edge of camera
+ pos.x -= MAX_LENGTH - (length / 2);
+ } else {
+ rotation = 0;
+ length = Random::f(200, 75);
+ // ensure zapper doesn't crash into ceiling or floor
+ pos.y = Random::f(0.5, -0.5) * (HALLWAY_HEIGHT - length);
+ // align zapper to right edge of camera
+ pos.x -= MAX_LENGTH;
+ }
+
+ zapper->place(pos, rotation, length);
+ zapper->set_active(true);
+}
+
+OptionalRef<ZapperObject> ZapperPoolScript::get_next_zapper() {
+ for (ZapperObject & zapper : this->pool) {
+ if (zapper.active) continue;
+ return zapper;
+ }
+ return {};
+}
diff --git a/game/prefab/ZapperPoolScript.h b/game/prefab/ZapperPoolScript.h
new file mode 100644
index 0000000..2208c80
--- /dev/null
+++ b/game/prefab/ZapperPoolScript.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+#include "ZapperObject.h"
+#include "util/OptionalRef.h"
+
+class ZapperPoolSubScene;
+
+class ZapperPoolScript : public crepe::Script {
+public:
+ ZapperPoolScript(std::vector<ZapperObject> && pool);
+
+ void init();
+ void fixed_update(crepe::duration_t);
+
+ unsigned i = 0;
+
+private:
+ std::vector<ZapperObject> pool;
+
+private:
+ crepe::OptionalRef<crepe::Transform> camera_transform;
+ crepe::OptionalRef<crepe::Camera> camera_camera;
+ crepe::OptionalRef<ZapperObject> get_next_zapper();
+
+private:
+ void spawn_random();
+
+private:
+ static constexpr float MAX_LENGTH = 400;
+ static constexpr float OFFSCREEN_MARGIN = 50 + MAX_LENGTH;
+};
diff --git a/game/prefab/ZapperPoolSubScene.cpp b/game/prefab/ZapperPoolSubScene.cpp
new file mode 100644
index 0000000..a52aa75
--- /dev/null
+++ b/game/prefab/ZapperPoolSubScene.cpp
@@ -0,0 +1,17 @@
+#include <crepe/api/BehaviorScript.h>
+
+#include "ZapperPoolScript.h"
+#include "ZapperPoolSubScene.h"
+
+using namespace crepe;
+using namespace std;
+
+ZapperPoolSubScene::ZapperPoolSubScene(Scene & scene)
+ : controller {scene.new_object("controller")} {
+
+ vector<ZapperObject> pool;
+ for (size_t i = 0; i < this->POOL_SIZE; i++)
+ pool.emplace_back(scene.new_object("zapper", "zapper"));
+ BehaviorScript & behavior = this->controller.add_component<BehaviorScript>();
+ behavior.set_script<ZapperPoolScript>(std::move(pool));
+}
diff --git a/game/prefab/ZapperPoolSubScene.h b/game/prefab/ZapperPoolSubScene.h
new file mode 100644
index 0000000..6f6e297
--- /dev/null
+++ b/game/prefab/ZapperPoolSubScene.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Scene.h>
+#include <crepe/util/OptionalRef.h>
+
+class CreateZapperEvent : public crepe::Event {};
+
+class ZapperPoolSubScene {
+public:
+ ZapperPoolSubScene(crepe::Scene & scene);
+
+private:
+ crepe::GameObject controller;
+
+private:
+ static constexpr size_t POOL_SIZE = 4;
+};
diff --git a/game/preview/NpcScript.cpp b/game/preview/NpcScript.cpp
new file mode 100644
index 0000000..86117d4
--- /dev/null
+++ b/game/preview/NpcScript.cpp
@@ -0,0 +1,29 @@
+#include "NpcScript.h"
+
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace std;
+using namespace crepe;
+
+void NpcScript::fixed_update(duration_t dt) {
+ auto & rb = this->get_component<Rigidbody>();
+ auto npc = this->get_components<Sprite>();
+ auto & transform = this->get_component<Transform>();
+
+ if (transform.position.x < 200) {
+ rb.data.linear_velocity.x *= -1;
+ }
+ if (transform.position.x > 700) {
+ rb.data.linear_velocity.x *= -1;
+ }
+
+ if (rb.data.linear_velocity.x < 0) {
+ npc.front().get().data.flip = {true, false};
+ npc.back().get().data.flip = {true, false};
+ } else {
+ npc.front().get().data.flip = {false, false};
+ npc.back().get().data.flip = {false, false};
+ }
+}
diff --git a/game/preview/NpcScript.h b/game/preview/NpcScript.h
new file mode 100644
index 0000000..d278f83
--- /dev/null
+++ b/game/preview/NpcScript.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class NpcScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/preview/NpcSubScene.cpp b/game/preview/NpcSubScene.cpp
new file mode 100644
index 0000000..5ededb6
--- /dev/null
+++ b/game/preview/NpcSubScene.cpp
@@ -0,0 +1,65 @@
+
+
+#include "NpcSubScene.h"
+
+#include "../Config.h"
+#include "NpcScript.h"
+
+#include <crepe/ValueBroker.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace crepe;
+
+NpcSubScene::NpcSubScene(Scene & scn) {
+ GameObject npc = scn.new_object("npc", "npc_tag", vec2 {500, 0}, 0, 1);
+ Asset npc_body {"asset/workers/worker1Body.png"};
+ Asset npc_head {"asset/workers/worker1Head.png"};
+
+ auto & npc_body_sprite = npc.add_component<Sprite>(
+ npc_body,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .size = {0, 50},
+ }
+ );
+ auto & npc_head_sprite = npc.add_component<Sprite>(
+ npc_head,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .size = {0, 50},
+ .position_offset = {0, -20},
+ }
+ );
+
+ npc.add_component<Animator>(
+ npc_body_sprite, ivec2 {32, 32}, uvec2 {4, 8},
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ npc.add_component<Animator>(
+ npc_head_sprite, ivec2 {32, 32}, uvec2 {4, 8},
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ npc.add_component<BoxCollider>(vec2 {40, 50});
+
+ npc.add_component<Rigidbody>(Rigidbody::Data {
+ .mass = 10,
+ .gravity_scale = 1,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {-50, 0},
+ //.max_linear_velocity = 40,
+ .collision_layers = {COLL_LAY_BOT_TOP, 100},
+ .collision_layer = COLL_LAY_PLAYER,
+ });
+
+ npc.add_component<BehaviorScript>().set_script<NpcScript>();
+}
diff --git a/game/preview/NpcSubScene.h b/game/preview/NpcSubScene.h
new file mode 100644
index 0000000..a226195
--- /dev/null
+++ b/game/preview/NpcSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class NpcSubScene {
+public:
+ NpcSubScene(crepe::Scene & scn);
+};
diff --git a/game/preview/PrevPlayerScript.cpp b/game/preview/PrevPlayerScript.cpp
new file mode 100644
index 0000000..ae25dad
--- /dev/null
+++ b/game/preview/PrevPlayerScript.cpp
@@ -0,0 +1,157 @@
+#include "PrevPlayerScript.h"
+
+#include "../missile/SpawnEvent.h"
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Transform.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace crepe;
+
+bool PrevPlayerScript::key_pressed(const KeyPressEvent & ev) {
+ switch (ev.key) {
+ case Keycode::A:
+ this->get_component<Rigidbody>().data.linear_velocity.x = -move_speed;
+ this->body->data.flip = {true, false};
+ this->head->data.flip = {true, false};
+ break;
+ case Keycode::D:
+ this->get_component<Rigidbody>().data.linear_velocity.x = move_speed;
+ this->body->data.flip = {false, false};
+ this->head->data.flip = {false, false};
+ break;
+ case Keycode::D0:
+ this->body_anim->set_anim(0);
+ this->head_anim->set_anim(0);
+ break;
+ case Keycode::D1:
+ this->body_anim->set_anim(1);
+ this->head_anim->set_anim(1);
+ break;
+ case Keycode::D2:
+ this->body_anim->set_anim(2);
+ this->head_anim->set_anim(2);
+ break;
+ case Keycode::D3:
+ this->body_anim->set_anim(3);
+ this->head_anim->set_anim(3);
+ break;
+ case Keycode::D4:
+ this->body_anim->set_anim(4);
+ this->head_anim->set_anim(4);
+ break;
+ case Keycode::D5:
+ this->body_anim->set_anim(5);
+ this->head_anim->set_anim(5);
+ break;
+ case Keycode::D6:
+ this->body_anim->set_anim(6);
+ this->head_anim->set_anim(6);
+ break;
+ case Keycode::D7:
+ this->body_anim->set_anim(7);
+ this->head_anim->set_anim(7);
+ break;
+ case Keycode::LEFT:
+ this->get_component<Transform>().rotation += 10;
+ break;
+ case Keycode::RIGHT:
+ this->get_component<Transform>().rotation -= 10;
+ break;
+ case Keycode::UP:
+ this->head->data.position_offset += 10;
+ break;
+ case Keycode::DOWN:
+ this->head->data.position_offset -= 10;
+ break;
+ case Keycode::P:
+ this->get_components_by_name<AudioSource>("background_music").front().get().active
+ = true;
+ break;
+ case Keycode::J:
+ this->get_components_by_name<Transform>("camera").front().get().position.x
+ -= move_speed;
+ break;
+ case Keycode::K:
+ this->get_components_by_name<Transform>("camera").front().get().position.y
+ -= move_speed;
+ break;
+ case Keycode::L:
+ this->get_components_by_name<Transform>("camera").front().get().position.x
+ += move_speed;
+ break;
+ case Keycode::I:
+ this->get_components_by_name<Transform>("camera").front().get().position.y
+ += move_speed;
+ break;
+ case Keycode::M:
+ trigger_event<MissileSpawnEvent>(MissileSpawnEvent {});
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+void PrevPlayerScript::init() {
+ this->rb = get_component<Rigidbody>();
+
+ auto animations = this->get_components<Animator>();
+ body_anim = animations[0];
+ head_anim = animations[1];
+
+ auto sprites = this->get_components<Sprite>();
+ body = sprites[0];
+ head = sprites[1];
+
+ subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ return this->key_pressed(ev);
+ });
+ subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool {
+ if (ev.repeat) return false;
+ return this->on_key_down(ev);
+ });
+ subscribe<KeyReleaseEvent>([this](const KeyReleaseEvent & ev) -> bool {
+ return this->on_key_up(ev);
+ });
+};
+
+void PrevPlayerScript::fixed_update(crepe::duration_t dt) {
+ if (this->get_key_state(Keycode::SPACE)) {
+ this->rb->add_force_linear(
+ vec2(0, -1) * (engine_gravity * PLAYER_GRAVITY_SCALE * dt.count())
+ );
+ }
+
+ auto & savemgr = this->get_save_manager();
+ const auto & pos = this->get_component<Transform>().position;
+
+ savemgr.set("player_x", pos.x);
+ savemgr.set("player_y", pos.y);
+};
+
+bool PrevPlayerScript::on_key_down(const KeyPressEvent & ev) {
+ if (ev.key == Keycode::SPACE) {
+ const vec2 UP = {0, -1};
+ this->help_kick(UP);
+ }
+ return false;
+}
+
+bool PrevPlayerScript::on_key_up(const KeyReleaseEvent & ev) {
+ if (ev.key == Keycode::SPACE) {
+ const vec2 DOWN = {0, 1};
+ this->help_kick(DOWN);
+ }
+ return false;
+}
+
+void PrevPlayerScript::help_kick(const vec2 & direction) {
+ // softly "kick" the player (at start/end of flight)
+ vec2 & velocity = this->rb->data.linear_velocity;
+ float kick_amount = std::min(
+ velocity.length() * PLAYER_HELP_KICK_SCALE, engine_gravity * PLAYER_HELP_KICK_MAX
+ );
+ velocity += direction * kick_amount;
+}
diff --git a/game/preview/PrevPlayerScript.h b/game/preview/PrevPlayerScript.h
new file mode 100644
index 0000000..ae66449
--- /dev/null
+++ b/game/preview/PrevPlayerScript.h
@@ -0,0 +1,32 @@
+#include <crepe/api/Animator.h>
+#include <crepe/api/Config.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/util/OptionalRef.h>
+
+class PrevPlayerScript : public crepe::Script {
+private:
+ crepe::OptionalRef<crepe::Animator> head_anim;
+ crepe::OptionalRef<crepe::Animator> body_anim;
+ crepe::OptionalRef<crepe::Sprite> head;
+ crepe::OptionalRef<crepe::Sprite> body;
+
+private:
+ float move_speed = 100;
+
+private:
+ void init();
+ void fixed_update(crepe::duration_t dt);
+ bool key_pressed(const crepe::KeyPressEvent & ev);
+
+private:
+ bool on_key_down(const crepe::KeyPressEvent & ev);
+ bool on_key_up(const crepe::KeyReleaseEvent & ev);
+ void help_kick(const crepe::vec2 & direction);
+
+private:
+ float & engine_gravity = crepe::Config::get_instance().physics.gravity;
+ crepe::OptionalRef<crepe::Rigidbody> rb;
+};
diff --git a/game/preview/PrevPlayerSubScene.cpp b/game/preview/PrevPlayerSubScene.cpp
new file mode 100644
index 0000000..074cfb4
--- /dev/null
+++ b/game/preview/PrevPlayerSubScene.cpp
@@ -0,0 +1,87 @@
+
+#include "PrevPlayerSubScene.h"
+
+#include "../Config.h"
+#include "PrevPlayerScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+#include <crepe/ValueBroker.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace crepe;
+
+PrevPlayerSubScene::PrevPlayerSubScene(Scene & scn) {
+
+ GameObject player = scn.new_object("player", "player", vec2 {800, -100}, 0, 1);
+ Asset player_body_asset {"asset/barry/defaultBody.png"};
+ Sprite & player_body_sprite = player.add_component<Sprite>(
+ player_body_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 0,
+ .size = vec2(0, 50),
+ }
+ );
+ player.add_component<Animator>(
+ player_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ Asset player_head_asset {"asset/barry/defaultHead.png"};
+ Sprite & player_head_sprite = player.add_component<Sprite>(
+ player_head_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 1,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ player.add_component<Animator>(
+ player_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ Asset player_jetpack_asset {"asset/barry/jetpackDefault.png"};
+ Sprite & player_jetpack_sprite = player.add_component<Sprite>(
+ player_jetpack_asset,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PLAYER,
+ .order_in_layer = 2,
+ .size = vec2(0, 60),
+ .position_offset = vec2(-20, 0),
+ }
+ );
+ player_jetpack_sprite.active = false;
+ player.add_component<Animator>(
+ player_jetpack_sprite, ivec2(32, 44), uvec2(4, 4),
+ Animator::Data {
+ .fps = 5,
+ .looping = true,
+ }
+ );
+ player.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 1,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(100, 0),
+ .collision_layers = {COLL_LAY_BOT_TOP, 100},
+ .collision_layer = COLL_LAY_PLAYER,
+ });
+ player.add_component<BoxCollider>(vec2(40, 50));
+ player.add_component<BehaviorScript>().set_script<PrevPlayerScript>();
+
+ GameObject music = scn.new_object("background_music", "background_music");
+ AudioSource & audio = music.add_component<AudioSource>(Asset {"asset/music/level.ogg"});
+ audio.loop = true;
+ audio.play_on_awake = true;
+ audio.active = false;
+}
diff --git a/game/preview/PrevPlayerSubScene.h b/game/preview/PrevPlayerSubScene.h
new file mode 100644
index 0000000..a61f341
--- /dev/null
+++ b/game/preview/PrevPlayerSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class PrevPlayerSubScene {
+public:
+ PrevPlayerSubScene(crepe::Scene & scn);
+};
diff --git a/game/preview/PreviewReplaySubScript.cpp b/game/preview/PreviewReplaySubScript.cpp
new file mode 100644
index 0000000..580a608
--- /dev/null
+++ b/game/preview/PreviewReplaySubScript.cpp
@@ -0,0 +1,55 @@
+#include "PreviewReplaySubScript.h"
+#include "Config.h"
+#include "menus/ButtonReplaySubScript.h"
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PreviewReplaySubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+ this->subscribe<StopPreviewRecording>([this](const StopPreviewRecording & e) {
+ return this->stop_recording();
+ });
+ this->subscribe<DeleteRecordingEvent>([this](const DeleteRecordingEvent & e) {
+ return this->delete_recording();
+ });
+ this->subscribe<StartPreviewRecording>([this](const StartPreviewRecording & e) {
+ return this->start_recording();
+ });
+}
+
+bool PreviewReplaySubScript::on_button_press(const ButtonPressEvent & e) {
+ if (DISABLE_REPLAY) return false;
+ replay.play(this->recording);
+ return false;
+}
+bool PreviewReplaySubScript::start_recording() {
+ if (DISABLE_REPLAY) return false;
+ if (record_saved) {
+ this->stop_recording();
+ this->delete_recording();
+ }
+ replay.record_start();
+ this->record_started = true;
+ return false;
+}
+
+bool PreviewReplaySubScript::stop_recording() {
+ if (DISABLE_REPLAY) return false;
+ if (this->record_started) this->recording = replay.record_end();
+ this->record_saved = true;
+ return false;
+}
+
+bool PreviewReplaySubScript::delete_recording() {
+ if (DISABLE_REPLAY) return false;
+ if (this->record_started) this->stop_recording();
+ if (this->record_saved) replay.release(this->recording);
+ this->record_saved = false;
+ return false;
+}
diff --git a/game/preview/PreviewReplaySubScript.h b/game/preview/PreviewReplaySubScript.h
new file mode 100644
index 0000000..59b78c3
--- /dev/null
+++ b/game/preview/PreviewReplaySubScript.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "menus/IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+struct StartPreviewRecording : public crepe::Event {};
+struct StopPreviewRecording : public crepe::Event {};
+
+class PreviewReplaySubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+
+private:
+ crepe::recording_t recording = 0;
+ bool start_recording();
+ bool stop_recording();
+ bool delete_recording();
+
+private:
+ bool record_saved = false;
+ bool record_started = false;
+};
diff --git a/game/preview/PreviewStartRecSubScript.cpp b/game/preview/PreviewStartRecSubScript.cpp
new file mode 100644
index 0000000..8a2f54c
--- /dev/null
+++ b/game/preview/PreviewStartRecSubScript.cpp
@@ -0,0 +1,20 @@
+#include "PreviewStartRecSubScript.h"
+#include "PreviewReplaySubScript.h"
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PreviewStartRecSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool PreviewStartRecSubScript::on_button_press(const ButtonPressEvent & e) {
+ this->trigger_event<StartPreviewRecording>();
+ return false;
+}
diff --git a/game/preview/PreviewStartRecSubScript.h b/game/preview/PreviewStartRecSubScript.h
new file mode 100644
index 0000000..a54a085
--- /dev/null
+++ b/game/preview/PreviewStartRecSubScript.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "menus/IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class PreviewStartRecSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+};
diff --git a/game/preview/PreviewStopRecSubScript.cpp b/game/preview/PreviewStopRecSubScript.cpp
new file mode 100644
index 0000000..a229da8
--- /dev/null
+++ b/game/preview/PreviewStopRecSubScript.cpp
@@ -0,0 +1,20 @@
+#include "PreviewStopRecSubScript.h"
+#include "PreviewReplaySubScript.h"
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PreviewStopRecSubScript::init() {
+ IButtonScript::init();
+ this->subscribe<ButtonPressEvent>([this](const ButtonPressEvent & e) {
+ return this->on_button_press(e);
+ });
+}
+
+bool PreviewStopRecSubScript::on_button_press(const ButtonPressEvent & e) {
+ this->trigger_event<StopPreviewRecording>();
+ return false;
+}
diff --git a/game/preview/PreviewStopRecSubScript.h b/game/preview/PreviewStopRecSubScript.h
new file mode 100644
index 0000000..b2dd73b
--- /dev/null
+++ b/game/preview/PreviewStopRecSubScript.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "menus/IButtonScript.h"
+
+#include <crepe/api/Script.h>
+
+class PreviewStopRecSubScript : public IButtonScript {
+public:
+ void init() override;
+ bool on_button_press(const crepe::ButtonPressEvent & e);
+};
diff --git a/game/preview/SmokeSubScene.cpp b/game/preview/SmokeSubScene.cpp
new file mode 100644
index 0000000..e363f95
--- /dev/null
+++ b/game/preview/SmokeSubScene.cpp
@@ -0,0 +1,37 @@
+
+#include "SmokeSubScene.h"
+
+#include "../Config.h"
+
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+
+SmokeSubScene::SmokeSubScene(Scene & scn) {
+ GameObject smoke = scn.new_object("smoke_particle", "TAG", vec2 {500, -210}, 0, 1);
+
+ Asset smoke_ss {"asset/particles/smoke.png"};
+
+ auto & smoke_sprite = smoke.add_component<Sprite>(
+ smoke_ss,
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_PARTICLES_FOREGROUND,
+ .size = {0, 30},
+ }
+ );
+
+ smoke.add_component<ParticleEmitter>(
+ smoke_sprite,
+ ParticleEmitter::Data {
+ .offset = {0, -60},
+ .max_particles = 10,
+ .emission_rate = 25,
+ .min_angle = 60,
+ .max_angle = 120,
+ .begin_lifespan = 1,
+ .end_lifespan = 2,
+ }
+ );
+}
diff --git a/game/preview/SmokeSubScene.h b/game/preview/SmokeSubScene.h
new file mode 100644
index 0000000..93d8a2d
--- /dev/null
+++ b/game/preview/SmokeSubScene.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class SmokeSubScene {
+public:
+ SmokeSubScene(crepe::Scene & scn);
+};
diff --git a/game/scheduler/ObjectsScheduler.cpp b/game/scheduler/ObjectsScheduler.cpp
new file mode 100644
index 0000000..7f58c79
--- /dev/null
+++ b/game/scheduler/ObjectsScheduler.cpp
@@ -0,0 +1,120 @@
+
+
+#include "ObjectsScheduler.h"
+
+#include "../Config.h"
+#include "../Random.h"
+#include "../enemy/EnemyScript.h"
+#include "../missile/SpawnEvent.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+#include "enemy/BattleScript.h"
+#include "prefab/ZapperPoolSubScene.h"
+
+using namespace crepe;
+
+void ObjectsScheduler::preset_0() {
+ for (int i = 0; i < this->amount_of_boss_fights; i++) {
+ this->trigger_event<MissileSpawnEvent>(MissileSpawnEvent {});
+ }
+ if (this->amount_of_boss_fights >= 1) {
+ this->trigger_event<BattleStartEvent>(BattleStartEvent {
+ .num_enemies = Random::i(this->amount_of_boss_fights, 0),
+ .battle = false,
+ });
+ }
+}
+
+void ObjectsScheduler::preset_1() {
+ trigger_event<MissileSpawnEvent>(MissileSpawnEvent {});
+ if (this->amount_of_boss_fights >= 3) {
+ this->trigger_event<BattleStartEvent>(BattleStartEvent {
+ .num_enemies = Random::i(1, 0),
+ .battle = false,
+ });
+ }
+}
+
+void ObjectsScheduler::preset_2() {
+ trigger_event<CreateZapperEvent>(CreateZapperEvent {});
+ if (this->amount_of_boss_fights >= 2) {
+ this->trigger_event<BattleStartEvent>(BattleStartEvent {
+ .num_enemies = Random::i(2, 1),
+ .battle = false,
+ });
+ }
+}
+
+void ObjectsScheduler::preset_3() { trigger_event<CreateZapperEvent>(CreateZapperEvent {}); }
+
+void ObjectsScheduler::preset_4() {}
+
+void ObjectsScheduler::boss_fight_1() {
+ this->get_components_by_name<Rigidbody>("camera").front().get().data.linear_velocity.x = 0;
+ this->get_components_by_name<Rigidbody>("player").front().get().data.linear_velocity.x = 0;
+
+ this->amount_of_boss_fights++;
+ this->trigger_event<BattleStartEvent>(BattleStartEvent {
+ .num_enemies = amount_of_boss_fights,
+ .battle = true,
+ });
+
+ RefVector<Rigidbody> rb_back_forest
+ = this->get_components_by_tag<Rigidbody>("forest_background");
+ for (Rigidbody & rb : rb_back_forest) {
+ rb.data.linear_velocity.x = 0;
+ }
+}
+
+bool ObjectsScheduler::boss_fight_1_event() {
+ this->get_components_by_name<Rigidbody>("camera").front().get().data.linear_velocity.x
+ = PLAYER_SPEED * 0.02;
+ this->get_components_by_name<Rigidbody>("player").front().get().data.linear_velocity.x
+ = PLAYER_SPEED * 0.02;
+
+ bool first = true;
+ RefVector<Rigidbody> rb_back_forest
+ = this->get_components_by_tag<Rigidbody>("forest_background");
+ for (Rigidbody & rb : rb_back_forest) {
+ if (first == true) {
+ rb.data.linear_velocity.x = 30;
+ first = false;
+ } else {
+ rb.data.linear_velocity.x = 40;
+ first = true;
+ }
+ }
+
+ return false;
+}
+
+void ObjectsScheduler::init() {
+ this->obstacles.push_back([this]() { preset_0(); });
+ this->obstacles.push_back([this]() { preset_1(); });
+ this->obstacles.push_back([this]() { preset_2(); });
+ this->obstacles.push_back([this]() { preset_3(); });
+ this->obstacles.push_back([this]() { preset_4(); });
+
+ this->obstacles.push_back([this]() { boss_fight_1(); });
+
+ // subscribe to battlewonevent
+ this->subscribe<BattleWonEvent>([this](const BattleWonEvent & ev) -> bool {
+ return this->boss_fight_1_event();
+ });
+}
+
+void ObjectsScheduler::fixed_update(duration_t dt) {
+ int pos_x
+ = (int) this->get_components_by_name<Transform>("camera").front().get().position.x;
+
+ int boss_check = (pos_x - this->start_offset) / this->boss_fight_interval;
+ if (boss_check > this->last_boss_check) {
+ this->obstacles.back()();
+ this->last_boss_check = boss_check;
+ }
+ int obstacle_check = (pos_x - this->start_offset) / this->obstacle_interval;
+ if (obstacle_check > this->last_obstacle_check) {
+ this->obstacles[Random::i(this->obstacles.size() - 1, 0)]();
+ this->last_obstacle_check = obstacle_check;
+ }
+}
diff --git a/game/scheduler/ObjectsScheduler.h b/game/scheduler/ObjectsScheduler.h
new file mode 100644
index 0000000..7ada8e1
--- /dev/null
+++ b/game/scheduler/ObjectsScheduler.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "api/Script.h"
+#include <functional>
+#include <vector>
+
+class ObjectsScheduler : public crepe::Script {
+
+private:
+ std::vector<std::function<void()>> obstacles;
+
+ int last_boss_check = 0;
+ int last_obstacle_check = 0;
+
+ int boss_fight_interval = 5000;
+ int obstacle_interval = 350;
+ int start_offset = 1300;
+
+ int amount_of_boss_fights = 0;
+
+private:
+ void preset_0();
+ void preset_1();
+ void preset_2();
+ void preset_3();
+ void preset_4();
+ void boss_fight_1();
+
+ bool boss_fight_1_event();
+
+public:
+ void init();
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/util/scrollgen b/game/util/scrollgen
new file mode 100755
index 0000000..0389107
--- /dev/null
+++ b/game/util/scrollgen
@@ -0,0 +1,36 @@
+#!/bin/sh
+INPUT="$1"
+FRAMES="$2"
+
+die() {
+ echo "$@"
+ exit 1
+}
+check_command() {
+ cmd="$1"
+ command -v "$cmd" > /dev/null || die "command '$cmd' not found"
+}
+
+check_command magick
+check_command identify
+[ "$#" -eq 2 ] || die "usage: $0 <input image> <frame count>"
+[ -e "$INPUT" ] || die "file not found: $INPUT"
+[ "$FRAMES" -gt 0 ] || die "invalid frame count: $FRAMES"
+
+tile_width=$(identify -format "%w" "$INPUT")
+tile_height=$(identify -format "%h" "$INPUT")
+
+OUTPUT="$INPUT.scroll.png"
+magick -size "${tile_width}x$(( $tile_height * $FRAMES ))" 'xc:#ff00ff00' "$OUTPUT"
+
+for i in $(seq 0 $(( $FRAMES - 1 ))); do
+ offset_x=$(( $tile_width * $i / $FRAMES ))
+ offset_y=$(( i * $tile_height ))
+
+ magick "$OUTPUT" "$INPUT" -geometry "+${offset_x}+${offset_y}" -composite "$OUTPUT"
+ magick "$OUTPUT" "$INPUT" -geometry "+$(( $offset_x - $tile_width ))+${offset_y}" -composite "$OUTPUT"
+ echo "+${offset_x}+${offset_y}"
+done
+
+# magick -size ${total_width}x${sprite_height} xc:none canvas.png
+
diff --git a/game/workers/CollisionScript.cpp b/game/workers/CollisionScript.cpp
new file mode 100644
index 0000000..372bfec
--- /dev/null
+++ b/game/workers/CollisionScript.cpp
@@ -0,0 +1,69 @@
+#include "CollisionScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void CollisionScript::init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+}
+
+bool CollisionScript::on_collision(const CollisionEvent & ev) {
+ RefVector<Animator> animators = this->get_components<Animator>();
+ RefVector<Sprite> sprites = this->get_components<Sprite>();
+ Rigidbody & rb = this->get_component<Rigidbody>();
+ Transform & tr = this->get_component<Transform>();
+ BehaviorScript & bs_panic = this->get_components<BehaviorScript>().front();
+
+ if (ev.info.other.metadata.tag == "zapper") {
+ for (Animator & anim : animators) {
+ anim.active = false;
+ anim.set_anim(3);
+ }
+ for (Sprite & sprite : sprites) {
+ sprite.data.position_offset.x = 15;
+ }
+ rb.data.linear_velocity_coefficient = {0.5, 0.5};
+ tr.rotation = 90;
+ bs_panic.active = false;
+
+ return false;
+ } else if (ev.info.other.metadata.tag == "laser") {
+ for (Animator & anim : animators) {
+ anim.active = false;
+ anim.set_anim(3);
+ }
+ for (Sprite & sprite : sprites) {
+ sprite.data.position_offset.x = 15;
+ }
+ rb.data.linear_velocity_coefficient = {0.5, 0.5};
+ tr.rotation = 90;
+ bs_panic.active = false;
+
+ return false;
+ } else if (ev.info.other.metadata.tag == "missile"
+ || ev.info.other.metadata.tag == "enemy_bullet") {
+ for (Animator & anim : animators) {
+ anim.active = false;
+ anim.set_anim(3);
+ }
+ for (Sprite & sprite : sprites) {
+ sprite.data.position_offset.x = 15;
+ }
+ rb.data.linear_velocity_coefficient = {0.5, 0.5};
+ tr.rotation = 90;
+ bs_panic.active = false;
+
+ return false;
+ }
+
+ return false;
+}
diff --git a/game/workers/CollisionScript.h b/game/workers/CollisionScript.h
new file mode 100644
index 0000000..70c5fe1
--- /dev/null
+++ b/game/workers/CollisionScript.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <crepe/api/Event.h>
+#include <crepe/api/Script.h>
+
+class CollisionScript : public crepe::Script {
+public:
+ void init();
+
+private:
+ bool on_collision(const crepe::CollisionEvent & ev);
+};
diff --git a/game/workers/PanicFromPlayerScript.cpp b/game/workers/PanicFromPlayerScript.cpp
new file mode 100644
index 0000000..baa48df
--- /dev/null
+++ b/game/workers/PanicFromPlayerScript.cpp
@@ -0,0 +1,55 @@
+#include "PanicFromPlayerScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+void PanicFromPlayerScript::fixed_update(duration_t dt) {
+ Animator & anim_player = this->get_components_by_name<Animator>("player").front();
+
+ if (anim_player.data.col == 1) {
+ Transform & trans_player = this->get_components_by_name<Transform>("player").back();
+ Transform & trans_worker = this->get_components<Transform>().back();
+
+ float result_x = trans_player.position.x - trans_worker.position.x;
+
+ if (result_x < 100 && result_x > -20) {
+ RefVector<Animator> anim_worker = this->get_components<Animator>();
+ RefVector<Sprite> sprite_worker = this->get_components<Sprite>();
+ Rigidbody & rb_worker = this->get_components<Rigidbody>().back();
+
+ if (anim_worker.front().get().data.col != 1) {
+ anim_worker.front().get().set_anim(1);
+ anim_worker.back().get().set_anim(1);
+
+ anim_worker.front().get().data.fps = 10;
+ anim_worker.back().get().data.fps = 10;
+ }
+
+ if (result_x < 0) {
+ float min_value = 8000;
+ float max_value = 10000;
+ float value = min_value
+ + static_cast<float>(rand())
+ / (static_cast<float>(RAND_MAX / (max_value - min_value)));
+ rb_worker.data.linear_velocity.x = value * dt.count();
+ sprite_worker.front().get().data.flip.flip_x = false;
+ sprite_worker.back().get().data.flip.flip_x = false;
+ } else {
+ float min_value = -4000;
+ float max_value = -5000;
+ float value = min_value
+ + static_cast<float>(rand())
+ / (static_cast<float>(RAND_MAX / (max_value - min_value)));
+ rb_worker.data.linear_velocity.x = value * dt.count();
+ sprite_worker.front().get().data.flip.flip_x = true;
+ sprite_worker.back().get().data.flip.flip_x = true;
+ }
+ }
+ }
+};
diff --git a/game/workers/PanicFromPlayerScript.h b/game/workers/PanicFromPlayerScript.h
new file mode 100644
index 0000000..d173e89
--- /dev/null
+++ b/game/workers/PanicFromPlayerScript.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class PanicFromPlayerScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/workers/WorkerScript.cpp b/game/workers/WorkerScript.cpp
new file mode 100644
index 0000000..b0bfc4e
--- /dev/null
+++ b/game/workers/WorkerScript.cpp
@@ -0,0 +1,145 @@
+#include "WorkerScript.h"
+
+#include "../Config.h"
+#include "api/BehaviorScript.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Transform.h>
+#include <crepe/types.h>
+#include <cstdlib>
+
+using namespace crepe;
+using namespace std;
+
+void WorkerScript::fixed_update(duration_t dt) {
+ RefVector<Rigidbody> rb_workers = this->get_components_by_tag<Rigidbody>("worker");
+ RefVector<Transform> trans_workers = this->get_components_by_tag<Transform>("worker");
+
+ Rigidbody & rb_cam = this->get_components_by_name<Rigidbody>("camera").back();
+ Transform & trans_cam = this->get_components_by_name<Transform>("camera").back();
+
+ int counter = 0;
+ for (Rigidbody & rb_worker : rb_workers) {
+ Transform & trans_worker = trans_workers.at(counter);
+
+ float result_x = rb_cam.data.linear_velocity.x - rb_worker.data.linear_velocity.x;
+
+ if (result_x > 0) {
+ float left_cam_pos_x = trans_cam.position.x - VIEWPORT_X / 2;
+ if (trans_worker.position.x < left_cam_pos_x - 1000) {
+ trans_worker.position.x = left_cam_pos_x + VIEWPORT_X + 1000;
+
+ do {
+ float min_value = -2500 * dt.count();
+ float max_value = 2500 * dt.count();
+ rb_worker.data.linear_velocity.x
+ = min_value
+ + static_cast<float>(rand())
+ / (static_cast<float>(RAND_MAX / (max_value - min_value)));
+ } while (rb_worker.data.linear_velocity.x < 500 * dt.count()
+ && rb_worker.data.linear_velocity.x > -500 * dt.count());
+
+ RefVector<Sprite> sprite_worker
+ = this->get_components_by_id<Sprite>(trans_worker.game_object_id);
+ RefVector<Animator> animator_worker
+ = this->get_components_by_id<Animator>(trans_worker.game_object_id);
+ BehaviorScript & bs_panic
+ = this->get_components_by_id<BehaviorScript>(trans_worker.game_object_id)
+ .front();
+
+ if (rb_worker.data.linear_velocity.x < 0) {
+ sprite_worker.front().get().data.flip.flip_x = true;
+ sprite_worker.back().get().data.flip.flip_x = true;
+
+ animator_worker.front().get().data.fps
+ = -rb_worker.data.linear_velocity.x / 5;
+ animator_worker.back().get().data.fps
+ = -rb_worker.data.linear_velocity.x / 5;
+ animator_worker.front().get().set_anim(0);
+ animator_worker.back().get().set_anim(0);
+ animator_worker.front().get().active = true;
+ animator_worker.back().get().active = true;
+ } else {
+ sprite_worker.front().get().data.flip.flip_x = false;
+ sprite_worker.back().get().data.flip.flip_x = false;
+
+ animator_worker.front().get().data.fps
+ = rb_worker.data.linear_velocity.x / 5;
+ animator_worker.back().get().data.fps
+ = rb_worker.data.linear_velocity.x / 5;
+ animator_worker.front().get().set_anim(0);
+ animator_worker.back().get().set_anim(0);
+ animator_worker.front().get().active = true;
+ animator_worker.back().get().active = true;
+ }
+
+ trans_worker.rotation = 0;
+ bs_panic.active = true;
+ rb_worker.data.linear_velocity_coefficient = {1, 1};
+ for (Sprite & sprite : sprite_worker) {
+ sprite.data.position_offset.x = 0;
+ }
+ }
+ } else {
+ float right_cam_pos_x = trans_cam.position.x + VIEWPORT_X / 2;
+ if (trans_worker.position.x > right_cam_pos_x + 1000) {
+ do {
+ float min_value = -2500 * dt.count();
+ float max_value = 2500 * dt.count();
+ rb_worker.data.linear_velocity.x
+ = min_value
+ + static_cast<float>(rand())
+ / (static_cast<float>(RAND_MAX / (max_value - min_value)));
+ } while (rb_worker.data.linear_velocity.x < 500 * dt.count()
+ && rb_worker.data.linear_velocity.x > -500 * dt.count());
+
+ RefVector<Sprite> sprite_worker
+ = this->get_components_by_id<Sprite>(trans_worker.game_object_id);
+ RefVector<Animator> animator_worker
+ = this->get_components_by_id<Animator>(trans_worker.game_object_id);
+ BehaviorScript & bs_panic
+ = this->get_components_by_id<BehaviorScript>(trans_worker.game_object_id)
+ .front();
+
+ if (rb_worker.data.linear_velocity.x < 0) {
+ sprite_worker.front().get().data.flip.flip_x = true;
+ sprite_worker.back().get().data.flip.flip_x = true;
+
+ animator_worker.front().get().data.fps
+ = -rb_worker.data.linear_velocity.x / 5;
+ animator_worker.back().get().data.fps
+ = -rb_worker.data.linear_velocity.x / 5;
+
+ animator_worker.front().get().set_anim(0);
+ animator_worker.back().get().set_anim(0);
+ animator_worker.front().get().active = true;
+ animator_worker.back().get().active = true;
+ } else {
+ sprite_worker.front().get().data.flip.flip_x = false;
+ sprite_worker.back().get().data.flip.flip_x = false;
+
+ animator_worker.front().get().data.fps
+ = rb_worker.data.linear_velocity.x / 5;
+ animator_worker.back().get().data.fps
+ = rb_worker.data.linear_velocity.x / 5;
+
+ animator_worker.front().get().set_anim(0);
+ animator_worker.back().get().set_anim(0);
+ animator_worker.front().get().active = true;
+ animator_worker.back().get().active = true;
+ }
+
+ trans_worker.rotation = 0;
+ bs_panic.active = true;
+ rb_worker.data.linear_velocity_coefficient = {1, 1};
+ for (Sprite & sprite : sprite_worker) {
+ sprite.data.position_offset.x = 0;
+ }
+ }
+ }
+
+ counter++;
+ }
+}
diff --git a/game/workers/WorkerScript.h b/game/workers/WorkerScript.h
new file mode 100644
index 0000000..ae4a6c7
--- /dev/null
+++ b/game/workers/WorkerScript.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <crepe/api/Script.h>
+
+class WorkerScript : public crepe::Script {
+public:
+ void fixed_update(crepe::duration_t dt);
+};
diff --git a/game/workers/WorkersSubScene.cpp b/game/workers/WorkersSubScene.cpp
new file mode 100644
index 0000000..54996d1
--- /dev/null
+++ b/game/workers/WorkersSubScene.cpp
@@ -0,0 +1,423 @@
+#include "WorkersSubScene.h"
+#include "CollisionScript.h"
+#include "PanicFromPlayerScript.h"
+#include "WorkerScript.h"
+
+#include "../Config.h"
+#include "api/GameObject.h"
+
+#include <crepe/api/Animator.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Sprite.h>
+
+using namespace crepe;
+using namespace std;
+
+WorkersSubScene::WorkersSubScene(Scene & scn) {
+ this->worker1(scn, 1200, -50);
+ this->worker2(scn, 1300, 20);
+ this->worker3(scn, 1400, -40);
+ this->worker4(scn, 7250, 50);
+ this->worker5(scn, 3400, -20);
+ this->worker6(scn, 2000, 30);
+ this->worker7(scn, 3725, 35);
+ this->worker8(scn, 2200, -15);
+
+ GameObject script = scn.new_object("workers_script");
+ script.add_component<BehaviorScript>().set_script<WorkerScript>();
+}
+
+void WorkersSubScene::worker1(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_1 = scn.new_object("worker_1", "worker", vec2(start_x, 200));
+ Sprite & worker_1_body_sprite = worker_1.add_component<Sprite>(
+ Asset {"asset/workers/worker1Body.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 0,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_1.add_component<Animator>(
+ worker_1_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_1_head_sprite = worker_1.add_component<Sprite>(
+ Asset {"asset/workers/worker1Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 1,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_1.add_component<Animator>(
+ worker_1_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_1.add_component<BoxCollider>(vec2(50, 50));
+ worker_1.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ });
+ worker_1.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_1.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_1_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_1_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker2(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_2 = scn.new_object("worker_2", "worker", vec2(start_x, 200));
+ Sprite & worker_2_body_sprite = worker_2.add_component<Sprite>(
+ Asset {"asset/workers/worker2Body.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 2,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_2.add_component<Animator>(
+ worker_2_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_2_head_sprite = worker_2.add_component<Sprite>(
+ Asset {"asset/workers/worker1Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 3,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_2.add_component<Animator>(
+ worker_2_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_2.add_component<BoxCollider>(vec2(50, 50));
+ worker_2.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ });
+ worker_2.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_2.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_2_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_2_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker3(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_3 = scn.new_object("worker_3", "worker", vec2(start_x, 200));
+ Sprite & worker_3_body_sprite = worker_3.add_component<Sprite>(
+ Asset {"asset/workers/worker1Body.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 4,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_3.add_component<Animator>(
+ worker_3_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_3_head_sprite = worker_3.add_component<Sprite>(
+ Asset {"asset/workers/worker2Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 5,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_3.add_component<Animator>(
+ worker_3_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_3.add_component<BoxCollider>(vec2(50, 50));
+ worker_3.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_TOP},
+ });
+ worker_3.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_3.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_3_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_3_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker4(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_4 = scn.new_object("worker_4", "worker", vec2(start_x, 200));
+ Sprite & worker_4_body_sprite = worker_4.add_component<Sprite>(
+ Asset {"asset/workers/worker2Body.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 6,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_4.add_component<Animator>(
+ worker_4_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_4_head_sprite = worker_4.add_component<Sprite>(
+ Asset {"asset/workers/worker2Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 7,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_4.add_component<Animator>(
+ worker_4_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_4.add_component<BoxCollider>(vec2(50, 50));
+ worker_4.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_HIGH},
+ });
+ worker_4.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_4.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_4_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_4_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker5(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_5 = scn.new_object("worker_5", "worker", vec2(start_x, 200));
+ Sprite & worker_5_body_sprite = worker_5.add_component<Sprite>(
+ Asset {"asset/workers/workerFatBody.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 8,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_5.add_component<Animator>(
+ worker_5_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_5_head_sprite = worker_5.add_component<Sprite>(
+ Asset {"asset/workers/worker1Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 9,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_5.add_component<Animator>(
+ worker_5_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_5.add_component<BoxCollider>(vec2(50, 50));
+ worker_5.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_HIGH},
+ });
+ worker_5.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_5.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_5_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_5_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker6(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_6 = scn.new_object("worker_6", "worker", vec2(start_x, 200));
+ Sprite & worker_6_body_sprite = worker_6.add_component<Sprite>(
+ Asset {"asset/workers/workerFatBody.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 10,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_6.add_component<Animator>(
+ worker_6_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_6_head_sprite = worker_6.add_component<Sprite>(
+ Asset {"asset/workers/worker2Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 11,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_6.add_component<Animator>(
+ worker_6_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_6.add_component<BoxCollider>(vec2(50, 50));
+ worker_6.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_LOW},
+ });
+ worker_6.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_6.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_6_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_6_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker7(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_7 = scn.new_object("worker_7", "worker", vec2(start_x, 200));
+ Sprite & worker_7_body_sprite = worker_7.add_component<Sprite>(
+ Asset {"asset/workers/workerTallBody.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 12,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_7.add_component<Animator>(
+ worker_7_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_7_head_sprite = worker_7.add_component<Sprite>(
+ Asset {"asset/workers/worker1Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_BACK,
+ .order_in_layer = 13,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_7.add_component<Animator>(
+ worker_7_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_7.add_component<BoxCollider>(vec2(50, 50));
+ worker_7.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_LOW},
+ });
+ worker_7.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_7.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_7_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_7_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
+
+void WorkersSubScene::worker8(crepe::Scene & scn, float start_x, float init_speed) {
+ GameObject worker_8 = scn.new_object("worker_8", "worker", vec2(start_x, 200));
+ Sprite & worker_8_body_sprite = worker_8.add_component<Sprite>(
+ Asset {"asset/workers/workerTallBody.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 14,
+ .size = vec2(0, 50),
+ }
+ );
+ worker_8.add_component<Animator>(
+ worker_8_body_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ Sprite & worker_8_head_sprite = worker_8.add_component<Sprite>(
+ Asset {"asset/workers/worker2Head.png"},
+ Sprite::Data {
+ .sorting_in_layer = SORT_IN_LAY_WORKERS_FRONT,
+ .order_in_layer = 15,
+ .size = vec2(0, 50),
+ .position_offset = vec2(0, -20),
+ }
+ );
+ worker_8.add_component<Animator>(
+ worker_8_head_sprite, ivec2(32, 32), uvec2(4, 8),
+ Animator::Data {
+ .fps = static_cast<unsigned int>(abs(init_speed) / 5),
+ .looping = true,
+ }
+ );
+ worker_8.add_component<BoxCollider>(vec2(50, 50));
+ worker_8.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 20,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = vec2(init_speed, 0),
+ .collision_layers = {COLL_LAY_BOT_LOW},
+ });
+ worker_8.add_component<BehaviorScript>().set_script<PanicFromPlayerScript>();
+ worker_8.add_component<BehaviorScript>().set_script<CollisionScript>();
+
+ if (init_speed < 0) {
+ worker_8_body_sprite.data.flip = Sprite::FlipSettings {true, false};
+ worker_8_head_sprite.data.flip = Sprite::FlipSettings {true, false};
+ }
+}
diff --git a/game/workers/WorkersSubScene.h b/game/workers/WorkersSubScene.h
new file mode 100644
index 0000000..6692d87
--- /dev/null
+++ b/game/workers/WorkersSubScene.h
@@ -0,0 +1,20 @@
+#pragma once
+
+namespace crepe {
+class Scene;
+}
+
+class WorkersSubScene {
+public:
+ WorkersSubScene(crepe::Scene & scn);
+
+private:
+ void worker1(crepe::Scene & scn, float start_x, float init_speed);
+ void worker2(crepe::Scene & scn, float start_x, float init_speed);
+ void worker3(crepe::Scene & scn, float start_x, float init_speed);
+ void worker4(crepe::Scene & scn, float start_x, float init_speed);
+ void worker5(crepe::Scene & scn, float start_x, float init_speed);
+ void worker6(crepe::Scene & scn, float start_x, float init_speed);
+ void worker7(crepe::Scene & scn, float start_x, float init_speed);
+ void worker8(crepe::Scene & scn, float start_x, float init_speed);
+};
diff --git a/lib/fontconfig b/lib/fontconfig
new file mode 160000
+Subproject 72b9a48f57de6204d99ce1c217b5609ee92ece9
diff --git a/lib/sdl2 b/lib/sdl2
-Subproject c98c4fbff6d8f3016a3ce6685bf8f43433c3efc
+Subproject 9519b9916cd29a14587af0507292f2bd31dd575
diff --git a/lib/sdl_image b/lib/sdl_image
-Subproject 56560190a6c5b5740e5afdce747e2a2bfbf16db
+Subproject abcf63aa71b4e3ac32120fa9870a6500ddcdcc8
diff --git a/lib/sdl_ttf b/lib/sdl_ttf
-Subproject a3d0895c1b60c41ff9e85d9203ddd7485c014da
+Subproject 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f344
diff --git a/mwe/ecs-homemade/inc/ComponentManager.hpp b/mwe/ecs-homemade/inc/ComponentManager.hpp
index af9c3a1..7d26df4 100644
--- a/mwe/ecs-homemade/inc/ComponentManager.hpp
+++ b/mwe/ecs-homemade/inc/ComponentManager.hpp
@@ -54,8 +54,8 @@ void ComponentManager::DeleteComponents() {
}
template <typename T>
-std::vector<std::reference_wrapper<T>>
-ComponentManager::GetComponentsByID(std::uint32_t id) const {
+std::vector<std::reference_wrapper<T>> ComponentManager::GetComponentsByID(std::uint32_t id
+) const {
//Determine the type of T (this is used as the key of the unordered_map<>)
std::type_index type = typeid(T);
diff --git a/mwe/ecs-homemade/src/ComponentManager.cpp b/mwe/ecs-homemade/src/ComponentManager.cpp
index 33ba12e..835bdda 100644
--- a/mwe/ecs-homemade/src/ComponentManager.cpp
+++ b/mwe/ecs-homemade/src/ComponentManager.cpp
@@ -9,9 +9,8 @@ ComponentManager::ComponentManager() {}
void ComponentManager::DeleteAllComponentsOfId(std::uint32_t id) {
for (auto & [type, componentArray] :
mComponents) { //Loop through all the types (in the unordered_map<>)
- if (id
- < componentArray
- .size()) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<>
+ if (id < componentArray.size(
+ )) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<>
componentArray[id].clear(); //Clear the components at this specific id
}
}
diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h
index ee1bf52..6df98ee 100644
--- a/mwe/events/include/event.h
+++ b/mwe/events/include/event.h
@@ -27,8 +27,8 @@ public:
virtual ~Event() = default;
virtual std::uint32_t getEventType() const = 0;
virtual std::string toString() const;
- void addArgument(const std::string & key,
- const std::variant<int, std::string, float> & value);
+ void
+ addArgument(const std::string & key, const std::variant<int, std::string, float> & value);
std::variant<int, std::string, float> getArgument(const std::string & key) const;
diff --git a/mwe/events/include/eventHandler.h b/mwe/events/include/eventHandler.h
index 3a83b15..8598cee 100644
--- a/mwe/events/include/eventHandler.h
+++ b/mwe/events/include/eventHandler.h
@@ -22,8 +22,9 @@ private:
template <typename EventType>
class EventHandlerWrapper : public IEventHandlerWrapper {
public:
- explicit EventHandlerWrapper(const EventHandler<EventType> & handler,
- const bool destroyOnSuccess = false)
+ explicit EventHandlerWrapper(
+ const EventHandler<EventType> & handler, const bool destroyOnSuccess = false
+ )
: m_handler(handler),
m_handlerType(m_handler.target_type().name()),
m_destroyOnSuccess(destroyOnSuccess) {
@@ -42,5 +43,5 @@ private:
EventHandler<EventType> m_handler;
const std::string m_handlerType;
- bool m_destroyOnSuccess{false};
+ bool m_destroyOnSuccess {false};
};
diff --git a/mwe/events/include/eventManager.h b/mwe/events/include/eventManager.h
index 30e927f..43906e8 100644
--- a/mwe/events/include/eventManager.h
+++ b/mwe/events/include/eventManager.h
@@ -18,8 +18,8 @@ public:
}
void shutdown();
- void subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler,
- int eventId);
+ void
+ subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, int eventId);
void unsubscribe(int eventType, const std::string & handlerName, int eventId);
void triggerEvent(const Event & event_, int eventId);
void queueEvent(std::unique_ptr<Event> && event_, int eventId);
@@ -35,19 +35,23 @@ private:
};
template <typename EventType>
-inline void subscribe(const EventHandler<EventType> & callback, int eventId = 0,
- const bool unsubscribeOnSuccess = false) {
+inline void subscribe(
+ const EventHandler<EventType> & callback, int eventId = 0,
+ const bool unsubscribeOnSuccess = false
+) {
std::unique_ptr<IEventHandlerWrapper> handler
= std::make_unique<EventHandlerWrapper<EventType>>(callback, unsubscribeOnSuccess);
- EventManager::getInstance().subscribe(EventType::getStaticEventType(), std::move(handler),
- eventId);
+ EventManager::getInstance().subscribe(
+ EventType::getStaticEventType(), std::move(handler), eventId
+ );
}
template <typename EventType>
inline void unsubscribe(const EventHandler<EventType> & callback, int eventId = 0) {
const std::string handlerName = callback.target_type().name();
- EventManager::getInstance().unsubscribe(EventType::getStaticEventType(), handlerName,
- eventId);
+ EventManager::getInstance().unsubscribe(
+ EventType::getStaticEventType(), handlerName, eventId
+ );
}
inline void triggerEvent(const Event & triggeredEvent, int eventId = 0) {
@@ -55,6 +59,7 @@ inline void triggerEvent(const Event & triggeredEvent, int eventId = 0) {
}
inline void queueEvent(std::unique_ptr<Event> && queuedEvent, int eventId = 0) {
- EventManager::getInstance().queueEvent(std::forward<std::unique_ptr<Event>>(queuedEvent),
- eventId);
+ EventManager::getInstance().queueEvent(
+ std::forward<std::unique_ptr<Event>>(queuedEvent), eventId
+ );
}
diff --git a/mwe/events/src/event.cpp b/mwe/events/src/event.cpp
index 0040c73..5177199 100644
--- a/mwe/events/src/event.cpp
+++ b/mwe/events/src/event.cpp
@@ -3,8 +3,9 @@
// Event class methods
Event::Event(std::string eventType) { eventData["eventType"] = eventType; }
-void Event::addArgument(const std::string & key,
- const std::variant<int, std::string, float> & value) {
+void Event::addArgument(
+ const std::string & key, const std::variant<int, std::string, float> & value
+) {
eventData[key] = value;
}
diff --git a/mwe/events/src/eventManager.cpp b/mwe/events/src/eventManager.cpp
index 9e7d880..b77a0a3 100644
--- a/mwe/events/src/eventManager.cpp
+++ b/mwe/events/src/eventManager.cpp
@@ -2,8 +2,9 @@
void EventManager::shutdown() { subscribers.clear(); }
-void EventManager::subscribe(int eventType, std::unique_ptr<IEventHandlerWrapper> && handler,
- int eventId) {
+void EventManager::subscribe(
+ int eventType, std::unique_ptr<IEventHandlerWrapper> && handler, int eventId
+) {
if (eventId) {
std::unordered_map<
int, std::unordered_map<int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>::
diff --git a/mwe/events/src/loopManager.cpp b/mwe/events/src/loopManager.cpp
index c58a5e7..7be10df 100644
--- a/mwe/events/src/loopManager.cpp
+++ b/mwe/events/src/loopManager.cpp
@@ -51,8 +51,10 @@ void onKey(const KeyPressedEvent & e) {
std::cout << "keycode pressed: " << keyCode << std::endl;
}
void onMouse(const MousePressedEvent & e) {
- fprintf(stderr, "mouse Position X: %d Y: %d\n", e.getMousePosition().first,
- e.getMousePosition().second);
+ fprintf(
+ stderr, "mouse Position X: %d Y: %d\n", e.getMousePosition().first,
+ e.getMousePosition().second
+ );
}
void LoopManager::setup() {
gameRunning = window.initWindow();
diff --git a/mwe/events/src/uiObject.cpp b/mwe/events/src/uiObject.cpp
index 947d1a2..6b5b326 100644
--- a/mwe/events/src/uiObject.cpp
+++ b/mwe/events/src/uiObject.cpp
@@ -10,7 +10,7 @@ Text::Text(int width, int height)
: UIObject(width, height),
size(12),
font(nullptr),
- color{255, 255, 255} { // Default size and color
+ color {255, 255, 255} { // Default size and color
alignment.horizontal = Alignment::Horizontal::CENTER;
alignment.vertical = Alignment::Vertical::MIDDLE;
alignment.mode = Alignment::PositioningMode::RELATIVE;
@@ -21,8 +21,8 @@ TextInput::TextInput(int width, int height)
textBuffer(""),
placeholder(""),
isActive(false),
- textColor{255, 255, 255},
- backgroundColor{0, 0, 0},
+ textColor {255, 255, 255},
+ backgroundColor {0, 0, 0},
maxLength(100),
font(nullptr) {
alignment.horizontal = Alignment::Horizontal::LEFT;
diff --git a/mwe/events/src/window.cpp b/mwe/events/src/window.cpp
index af2b627..5cdd425 100644
--- a/mwe/events/src/window.cpp
+++ b/mwe/events/src/window.cpp
@@ -11,8 +11,10 @@ bool WindowManager::initWindow() {
return false;
}
- window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
- SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
+ window = SDL_CreateWindow(
+ "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
+ SCREEN_HEIGHT, SDL_WINDOW_SHOWN
+ );
if (!window) {
std::cerr << "Error creating SDL Window.\n";
return false;
diff --git a/mwe/gameloop/include/gameObject.h b/mwe/gameloop/include/gameObject.h
index 2764215..1955b59 100644
--- a/mwe/gameloop/include/gameObject.h
+++ b/mwe/gameloop/include/gameObject.h
@@ -3,8 +3,9 @@
class GameObject {
public:
GameObject();
- GameObject(std::string name, float x, float y, float width, float height, float velX,
- float velY);
+ GameObject(
+ std::string name, float x, float y, float width, float height, float velX, float velY
+ );
std::string getName() const;
float getX() const;
float getY() const;
diff --git a/mwe/gameloop/src/gameObject.cpp b/mwe/gameloop/src/gameObject.cpp
index 809e25f..31503d1 100644
--- a/mwe/gameloop/src/gameObject.cpp
+++ b/mwe/gameloop/src/gameObject.cpp
@@ -24,8 +24,9 @@ void GameObject::setVelX(float value) { velX = value; }
void GameObject::setVelY(float value) { velY = value; }
-GameObject::GameObject(std::string name, float x, float y, float width, float height,
- float velX, float velY)
+GameObject::GameObject(
+ std::string name, float x, float y, float width, float height, float velX, float velY
+)
: name(name),
x(x),
y(y),
diff --git a/mwe/gameloop/src/loopManager.cpp b/mwe/gameloop/src/loopManager.cpp
index fb06995..70cea4c 100644
--- a/mwe/gameloop/src/loopManager.cpp
+++ b/mwe/gameloop/src/loopManager.cpp
@@ -18,11 +18,13 @@ void LoopManager::processInput() {
if (event.key.keysym.sym == SDLK_ESCAPE) {
gameRunning = false;
} else if (event.key.keysym.sym == SDLK_i) {
- LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale()
- + 0.1);
+ LoopTimer::getInstance().setGameScale(
+ LoopTimer::getInstance().getGameScale() + 0.1
+ );
} else if (event.key.keysym.sym == SDLK_k) {
- LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale()
- - 0.1);
+ LoopTimer::getInstance().setGameScale(
+ LoopTimer::getInstance().getGameScale() - 0.1
+ );
}
break;
diff --git a/mwe/gameloop/src/window.cpp b/mwe/gameloop/src/window.cpp
index 8f802e1..df17773 100644
--- a/mwe/gameloop/src/window.cpp
+++ b/mwe/gameloop/src/window.cpp
@@ -36,8 +36,10 @@ bool WindowManager::initWindow() {
fprintf(stderr, "Error inititalising SDL.\n");
return false;
}
- window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
- SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
+ window = SDL_CreateWindow(
+ "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
+ SCREEN_HEIGHT, SDL_WINDOW_SHOWN
+ );
if (!window) {
fprintf(stderr, "Error creating SDL Window. \n");
return false;
diff --git a/mwe/resource-manager/main.cpp b/mwe/resource-manager/main.cpp
index 1910af8..e83d35f 100644
--- a/mwe/resource-manager/main.cpp
+++ b/mwe/resource-manager/main.cpp
@@ -23,8 +23,9 @@ int main() {
SDL_Event event;
- SDL_Window * window = SDL_CreateWindow("Tessting resources", SDL_WINDOWPOS_UNDEFINED,
- SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
+ SDL_Window * window = SDL_CreateWindow(
+ "Tessting resources", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0
+ );
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);
diff --git a/mwe/resource-manager/map_layer.cpp b/mwe/resource-manager/map_layer.cpp
index 17792a6..82f8f24 100644
--- a/mwe/resource-manager/map_layer.cpp
+++ b/mwe/resource-manager/map_layer.cpp
@@ -9,8 +9,9 @@ MapLayer::MapLayer() {}
MapLayer::~MapLayer() { m_subsets.clear(); }
-bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex,
- const std::vector<TextureMap *> & textures) {
+bool MapLayer::create(
+ const tmx::Map & map, std::uint32_t layerIndex, const std::vector<TextureMap *> & textures
+) {
const auto & layers = map.getLayers();
assert(layers[layerIndex]->getType() == tmx::Layer::Type::Tile);
@@ -68,9 +69,10 @@ bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex,
verts.emplace_back(vert);
vert = {{tilePosX + mapTileSize.x, tilePosY}, vertColour, {u + uNorm, v}};
verts.emplace_back(vert);
- vert = {{tilePosX + mapTileSize.x, tilePosY + mapTileSize.y},
- vertColour,
- {u + uNorm, v + vNorm}};
+ vert
+ = {{tilePosX + mapTileSize.x, tilePosY + mapTileSize.y},
+ vertColour,
+ {u + uNorm, v + vNorm}};
verts.emplace_back(vert);
}
}
@@ -89,7 +91,9 @@ bool MapLayer::create(const tmx::Map & map, std::uint32_t layerIndex,
void MapLayer::draw(SDL_Renderer * renderer) const {
assert(renderer);
for (const auto & s : m_subsets) {
- SDL_RenderGeometry(renderer, s.texture, s.vertexData.data(),
- static_cast<std::int32_t>(s.vertexData.size()), nullptr, 0);
+ SDL_RenderGeometry(
+ renderer, s.texture, s.vertexData.data(),
+ static_cast<std::int32_t>(s.vertexData.size()), nullptr, 0
+ );
}
}
diff --git a/mwe/resource-manager/map_layer.h b/mwe/resource-manager/map_layer.h
index fb656ed..2adbc0f 100644
--- a/mwe/resource-manager/map_layer.h
+++ b/mwe/resource-manager/map_layer.h
@@ -10,8 +10,8 @@ public:
explicit MapLayer();
~MapLayer();
- bool create(const tmx::Map &, std::uint32_t index,
- const std::vector<TextureMap *> & textures);
+ bool
+ create(const tmx::Map &, std::uint32_t index, const std::vector<TextureMap *> & textures);
void draw(SDL_Renderer *) const;
private:
diff --git a/mwe/resource-manager/stb_image.h b/mwe/resource-manager/stb_image.h
index 3462f3a..43fa6a6 100644
--- a/mwe/resource-manager/stb_image.h
+++ b/mwe/resource-manager/stb_image.h
@@ -409,9 +409,12 @@ extern "C" {
typedef struct {
int (*read)(
void * user, char * data,
- int size); // fill 'data' with 'size' bytes. return number of bytes actually read
- void (*skip)(void * user,
- int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+ int size
+ ); // fill 'data' with 'size' bytes. return number of bytes actually read
+ void (*skip)(
+ void * user,
+ int n
+ ); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
int (*eof)(void * user); // returns nonzero if we are at end of file/data
} stbi_io_callbacks;
@@ -420,24 +423,29 @@ typedef struct {
// 8-bits-per-channel interface
//
-STBIDEF stbi_uc * stbi_load_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * channels_in_file, int desired_channels);
-STBIDEF stbi_uc * stbi_load_from_callbacks(stbi_io_callbacks const * clbk, void * user,
- int * x, int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF stbi_uc * stbi_load_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
+STBIDEF stbi_uc * stbi_load_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
#ifndef STBI_NO_STDIO
-STBIDEF stbi_uc * stbi_load(char const * filename, int * x, int * y, int * channels_in_file,
- int desired_channels);
-STBIDEF stbi_uc * stbi_load_from_file(FILE * f, int * x, int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF stbi_uc * stbi_load(
+ char const * filename, int * x, int * y, int * channels_in_file, int desired_channels
+);
+STBIDEF stbi_uc *
+stbi_load_from_file(FILE * f, int * x, int * y, int * channels_in_file, int desired_channels);
// for stbi_load_from_file, file pointer is left pointing immediately after image
#endif
#ifndef STBI_NO_GIF
-STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int ** delays,
- int * x, int * y, int * z, int * comp,
- int req_comp);
+STBIDEF stbi_uc * stbi_load_gif_from_memory(
+ stbi_uc const * buffer, int len, int ** delays, int * x, int * y, int * z, int * comp,
+ int req_comp
+);
#endif
#ifdef STBI_WINDOWS_UTF8
@@ -449,17 +457,22 @@ STBIDEF int stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen, const wc
// 16-bits-per-channel interface
//
-STBIDEF stbi_us * stbi_load_16_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * channels_in_file, int desired_channels);
-STBIDEF stbi_us * stbi_load_16_from_callbacks(stbi_io_callbacks const * clbk, void * user,
- int * x, int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF stbi_us * stbi_load_16_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
+STBIDEF stbi_us * stbi_load_16_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
#ifndef STBI_NO_STDIO
-STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * channels_in_file,
- int desired_channels);
-STBIDEF stbi_us * stbi_load_from_file_16(FILE * f, int * x, int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF stbi_us * stbi_load_16(
+ char const * filename, int * x, int * y, int * channels_in_file, int desired_channels
+);
+STBIDEF stbi_us * stbi_load_from_file_16(
+ FILE * f, int * x, int * y, int * channels_in_file, int desired_channels
+);
#endif
////////////////////////////////////
@@ -467,17 +480,21 @@ STBIDEF stbi_us * stbi_load_from_file_16(FILE * f, int * x, int * y, int * chann
// float-per-channel interface
//
#ifndef STBI_NO_LINEAR
-STBIDEF float * stbi_loadf_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * channels_in_file, int desired_channels);
-STBIDEF float * stbi_loadf_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x,
- int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF float * stbi_loadf_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
+STBIDEF float * stbi_loadf_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file,
+ int desired_channels
+);
#ifndef STBI_NO_STDIO
-STBIDEF float * stbi_loadf(char const * filename, int * x, int * y, int * channels_in_file,
- int desired_channels);
-STBIDEF float * stbi_loadf_from_file(FILE * f, int * x, int * y, int * channels_in_file,
- int desired_channels);
+STBIDEF float * stbi_loadf(
+ char const * filename, int * x, int * y, int * channels_in_file, int desired_channels
+);
+STBIDEF float *
+stbi_loadf_from_file(FILE * f, int * x, int * y, int * channels_in_file, int desired_channels);
#endif
#endif
@@ -507,10 +524,11 @@ STBIDEF const char * stbi_failure_reason(void);
STBIDEF void stbi_image_free(void * retval_from_stbi_load);
// get image dimensions & components without fully decoding
-STBIDEF int stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * comp);
-STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x,
- int * y, int * comp);
+STBIDEF int
+stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, int * comp);
+STBIDEF int stbi_info_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp
+);
STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const * buffer, int len);
STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const * clbk, void * user);
@@ -542,17 +560,18 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli
// ZLIB client - used by PNG, available for other purposes
-STBIDEF char * stbi_zlib_decode_malloc_guesssize(const char * buffer, int len,
- int initial_size, int * outlen);
-STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(const char * buffer, int len,
- int initial_size, int * outlen,
- int parse_header);
+STBIDEF char * stbi_zlib_decode_malloc_guesssize(
+ const char * buffer, int len, int initial_size, int * outlen
+);
+STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(
+ const char * buffer, int len, int initial_size, int * outlen, int parse_header
+);
STBIDEF char * stbi_zlib_decode_malloc(const char * buffer, int len, int * outlen);
STBIDEF int stbi_zlib_decode_buffer(char * obuffer, int olen, const char * ibuffer, int ilen);
STBIDEF char * stbi_zlib_decode_noheader_malloc(const char * buffer, int len, int * outlen);
-STBIDEF int stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer,
- int ilen);
+STBIDEF int
+stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, int ilen);
#ifdef __cplusplus
}
@@ -910,68 +929,79 @@ typedef struct {
#ifndef STBI_NO_JPEG
static int stbi__jpeg_test(stbi__context * s);
-static void * stbi__jpeg_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__jpeg_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__jpeg_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_PNG
static int stbi__png_test(stbi__context * s);
-static void * stbi__png_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__png_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__png_info(stbi__context * s, int * x, int * y, int * comp);
static int stbi__png_is16(stbi__context * s);
#endif
#ifndef STBI_NO_BMP
static int stbi__bmp_test(stbi__context * s);
-static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__bmp_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__bmp_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_TGA
static int stbi__tga_test(stbi__context * s);
-static void * stbi__tga_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__tga_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__tga_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_PSD
static int stbi__psd_test(stbi__context * s);
-static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri, int bpc);
+static void * stbi__psd_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri,
+ int bpc
+);
static int stbi__psd_info(stbi__context * s, int * x, int * y, int * comp);
static int stbi__psd_is16(stbi__context * s);
#endif
#ifndef STBI_NO_HDR
static int stbi__hdr_test(stbi__context * s);
-static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static float * stbi__hdr_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__hdr_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_PIC
static int stbi__pic_test(stbi__context * s);
-static void * stbi__pic_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__pic_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__pic_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_GIF
static int stbi__gif_test(stbi__context * s);
-static void * stbi__gif_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
-static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int * y, int * z,
- int * comp, int req_comp);
+static void * stbi__gif_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
+static void * stbi__load_gif_main(
+ stbi__context * s, int ** delays, int * x, int * y, int * z, int * comp, int req_comp
+);
static int stbi__gif_info(stbi__context * s, int * x, int * y, int * comp);
#endif
#ifndef STBI_NO_PNM
static int stbi__pnm_test(stbi__context * s);
-static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri);
+static void * stbi__pnm_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+);
static int stbi__pnm_info(stbi__context * s, int * x, int * y, int * comp);
static int stbi__pnm_is16(stbi__context * s);
#endif
@@ -1135,8 +1165,10 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli
: stbi__vertically_flip_on_load_global)
#endif // STBI_THREAD_LOCAL
-static void * stbi__load_main(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri, int bpc) {
+static void * stbi__load_main(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri,
+ int bpc
+) {
memset(ri, 0,
sizeof(*ri)); // make sure it's initialized if we add new fields
ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
@@ -1198,9 +1230,8 @@ static stbi_uc * stbi__convert_16_to_8(stbi__uint16 * orig, int w, int h, int ch
if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
for (i = 0; i < img_len; ++i)
- reduced[i]
- = (stbi_uc) ((orig[i] >> 8)
- & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
+ reduced[i] = (stbi_uc) ((orig[i] >> 8) & 0xFF
+ ); // top half of each byte is sufficient approx of 16->8 bit scaling
STBI_FREE(orig);
return reduced;
@@ -1215,10 +1246,8 @@ static stbi__uint16 * stbi__convert_8_to_16(stbi_uc * orig, int w, int h, int ch
if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
for (i = 0; i < img_len; ++i)
- enlarged[i]
- = (stbi__uint16) ((orig[i] << 8)
- + orig
- [i]); // replicate to high and low byte, maps 0->0, 255->0xffff
+ enlarged[i] = (stbi__uint16) ((orig[i] << 8) + orig[i]
+ ); // replicate to high and low byte, maps 0->0, 255->0xffff
STBI_FREE(orig);
return enlarged;
@@ -1248,8 +1277,8 @@ static void stbi__vertical_flip(void * image, int w, int h, int bytes_per_pixel)
}
#ifndef STBI_NO_GIF
-static void stbi__vertical_flip_slices(void * image, int w, int h, int z,
- int bytes_per_pixel) {
+static void
+stbi__vertical_flip_slices(void * image, int w, int h, int z, int bytes_per_pixel) {
int slice;
int slice_size = w * h * bytes_per_pixel;
@@ -1261,8 +1290,9 @@ static void stbi__vertical_flip_slices(void * image, int w, int h, int z,
}
#endif
-static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int * x, int * y,
- int * comp, int req_comp) {
+static unsigned char * stbi__load_and_postprocess_8bit(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp
+) {
stbi__result_info ri;
void * result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
@@ -1272,8 +1302,9 @@ static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int *
STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
if (ri.bits_per_channel != 8) {
- result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y,
- req_comp == 0 ? *comp : req_comp);
+ result = stbi__convert_16_to_8(
+ (stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp
+ );
ri.bits_per_channel = 8;
}
@@ -1287,8 +1318,9 @@ static unsigned char * stbi__load_and_postprocess_8bit(stbi__context * s, int *
return (unsigned char *) result;
}
-static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int * x, int * y,
- int * comp, int req_comp) {
+static stbi__uint16 * stbi__load_and_postprocess_16bit(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp
+) {
stbi__result_info ri;
void * result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
@@ -1298,8 +1330,9 @@ static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int *
STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
if (ri.bits_per_channel != 16) {
- result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y,
- req_comp == 0 ? *comp : req_comp);
+ result = stbi__convert_8_to_16(
+ (stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp
+ );
ri.bits_per_channel = 16;
}
@@ -1315,8 +1348,8 @@ static stbi__uint16 * stbi__load_and_postprocess_16bit(stbi__context * s, int *
}
#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
-static void stbi__float_postprocess(float * result, int * x, int * y, int * comp,
- int req_comp) {
+static void
+stbi__float_postprocess(float * result, int * x, int * y, int * comp, int req_comp) {
if (stbi__vertically_flip_on_load && result != NULL) {
int channels = req_comp ? req_comp : *comp;
stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
@@ -1328,19 +1361,22 @@ static void stbi__float_postprocess(float * result, int * x, int * y, int * comp
#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
STBI_EXTERN
-__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags,
- const char * str, int cbmb,
- wchar_t * widestr, int cchwide);
+__declspec(dllimport) int __stdcall MultiByteToWideChar(
+ unsigned int cp, unsigned long flags, const char * str, int cbmb, wchar_t * widestr,
+ int cchwide
+);
STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(
unsigned int cp, unsigned long flags, const wchar_t * widestr, int cchwide, char * str,
- int cbmb, const char * defchar, int * used_default);
+ int cbmb, const char * defchar, int * used_default
+);
#endif
#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
-STBIDEF int stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen,
- const wchar_t * input) {
- return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL,
- NULL);
+STBIDEF int
+stbi_convert_wchar_to_utf8(char * buffer, size_t bufferlen, const wchar_t * input) {
+ return WideCharToMultiByte(
+ 65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL
+ );
}
#endif
@@ -1350,13 +1386,16 @@ static FILE * stbi__fopen(char const * filename, char const * mode) {
wchar_t wMode[64];
wchar_t wFilename[1024];
if (0
- == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename,
- sizeof(wFilename) / sizeof(*wFilename)))
+ == MultiByteToWideChar(
+ 65001 /* UTF8 */, 0, filename, -1, wFilename,
+ sizeof(wFilename) / sizeof(*wFilename)
+ ))
return 0;
if (0
- == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode,
- sizeof(wMode) / sizeof(*wMode)))
+ == MultiByteToWideChar(
+ 65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode) / sizeof(*wMode)
+ ))
return 0;
#if defined(_MSC_VER) && _MSC_VER >= 1400
@@ -1373,8 +1412,8 @@ static FILE * stbi__fopen(char const * filename, char const * mode) {
return f;
}
-STBIDEF stbi_uc * stbi_load(char const * filename, int * x, int * y, int * comp,
- int req_comp) {
+STBIDEF stbi_uc *
+stbi_load(char const * filename, int * x, int * y, int * comp, int req_comp) {
FILE * f = stbi__fopen(filename, "rb");
unsigned char * result;
if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
@@ -1395,8 +1434,8 @@ STBIDEF stbi_uc * stbi_load_from_file(FILE * f, int * x, int * y, int * comp, in
return result;
}
-STBIDEF stbi__uint16 * stbi_load_from_file_16(FILE * f, int * x, int * y, int * comp,
- int req_comp) {
+STBIDEF stbi__uint16 *
+stbi_load_from_file_16(FILE * f, int * x, int * y, int * comp, int req_comp) {
stbi__uint16 * result;
stbi__context s;
stbi__start_file(&s, f);
@@ -1408,8 +1447,8 @@ STBIDEF stbi__uint16 * stbi_load_from_file_16(FILE * f, int * x, int * y, int *
return result;
}
-STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * comp,
- int req_comp) {
+STBIDEF stbi_us *
+stbi_load_16(char const * filename, int * x, int * y, int * comp, int req_comp) {
FILE * f = stbi__fopen(filename, "rb");
stbi__uint16 * result;
if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
@@ -1420,39 +1459,45 @@ STBIDEF stbi_us * stbi_load_16(char const * filename, int * x, int * y, int * co
#endif //!STBI_NO_STDIO
-STBIDEF stbi_us * stbi_load_16_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * channels_in_file, int desired_channels) {
+STBIDEF stbi_us * stbi_load_16_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * channels_in_file,
+ int desired_channels
+) {
stbi__context s;
stbi__start_mem(&s, buffer, len);
return stbi__load_and_postprocess_16bit(&s, x, y, channels_in_file, desired_channels);
}
-STBIDEF stbi_us * stbi_load_16_from_callbacks(stbi_io_callbacks const * clbk, void * user,
- int * x, int * y, int * channels_in_file,
- int desired_channels) {
+STBIDEF stbi_us * stbi_load_16_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * channels_in_file,
+ int desired_channels
+) {
stbi__context s;
stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
return stbi__load_and_postprocess_16bit(&s, x, y, channels_in_file, desired_channels);
}
-STBIDEF stbi_uc * stbi_load_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * comp, int req_comp) {
+STBIDEF stbi_uc * stbi_load_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * comp, int req_comp
+) {
stbi__context s;
stbi__start_mem(&s, buffer, len);
return stbi__load_and_postprocess_8bit(&s, x, y, comp, req_comp);
}
-STBIDEF stbi_uc * stbi_load_from_callbacks(stbi_io_callbacks const * clbk, void * user,
- int * x, int * y, int * comp, int req_comp) {
+STBIDEF stbi_uc * stbi_load_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp, int req_comp
+) {
stbi__context s;
stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
return stbi__load_and_postprocess_8bit(&s, x, y, comp, req_comp);
}
#ifndef STBI_NO_GIF
-STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int ** delays,
- int * x, int * y, int * z, int * comp,
- int req_comp) {
+STBIDEF stbi_uc * stbi_load_gif_from_memory(
+ stbi_uc const * buffer, int len, int ** delays, int * x, int * y, int * z, int * comp,
+ int req_comp
+) {
unsigned char * result;
stbi__context s;
stbi__start_mem(&s, buffer, len);
@@ -1467,8 +1512,8 @@ STBIDEF stbi_uc * stbi_load_gif_from_memory(stbi_uc const * buffer, int len, int
#endif
#ifndef STBI_NO_LINEAR
-static float * stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp,
- int req_comp) {
+static float *
+stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp, int req_comp) {
unsigned char * data;
#ifndef STBI_NO_HDR
if (stbi__hdr_test(s)) {
@@ -1483,15 +1528,17 @@ static float * stbi__loadf_main(stbi__context * s, int * x, int * y, int * comp,
return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
}
-STBIDEF float * stbi_loadf_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * comp, int req_comp) {
+STBIDEF float * stbi_loadf_from_memory(
+ stbi_uc const * buffer, int len, int * x, int * y, int * comp, int req_comp
+) {
stbi__context s;
stbi__start_mem(&s, buffer, len);
return stbi__loadf_main(&s, x, y, comp, req_comp);
}
-STBIDEF float * stbi_loadf_from_callbacks(stbi_io_callbacks const * clbk, void * user, int * x,
- int * y, int * comp, int req_comp) {
+STBIDEF float * stbi_loadf_from_callbacks(
+ stbi_io_callbacks const * clbk, void * user, int * x, int * y, int * comp, int req_comp
+) {
stbi__context s;
stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
return stbi__loadf_main(&s, x, y, comp, req_comp);
@@ -1745,8 +1792,9 @@ static stbi_uc stbi__compute_y(int r, int g, int b) {
&& defined(STBI_NO_PNM)
// nothing
#else
-static unsigned char * stbi__convert_format(unsigned char * data, int img_n, int req_comp,
- unsigned int x, unsigned int y) {
+static unsigned char * stbi__convert_format(
+ unsigned char * data, int img_n, int req_comp, unsigned int x, unsigned int y
+) {
int i, j;
unsigned char * good;
@@ -1843,8 +1891,9 @@ static stbi__uint16 stbi__compute_y_16(int r, int g, int b) {
#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
// nothing
#else
-static stbi__uint16 * stbi__convert_format16(stbi__uint16 * data, int img_n, int req_comp,
- unsigned int x, unsigned int y) {
+static stbi__uint16 * stbi__convert_format16(
+ stbi__uint16 * data, int img_n, int req_comp, unsigned int x, unsigned int y
+) {
int i, j;
stbi__uint16 * good;
@@ -1920,8 +1969,9 @@ static stbi__uint16 * stbi__convert_format16(stbi__uint16 * data, int img_n, int
STBI_ASSERT(0);
STBI_FREE(data);
STBI_FREE(good);
- return (stbi__uint16 *) stbi__errpuc("unsupported",
- "Unsupported format conversion");
+ return (stbi__uint16 *) stbi__errpuc(
+ "unsupported", "Unsupported format conversion"
+ );
}
#undef STBI__CASE
}
@@ -2079,10 +2129,13 @@ typedef struct {
// kernels
void (*idct_block_kernel)(stbi_uc * out, int out_stride, short data[64]);
- void (*YCbCr_to_RGB_kernel)(stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb,
- const stbi_uc * pcr, int count, int step);
- stbi_uc * (*resample_row_hv_2_kernel)(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far,
- int w, int hs);
+ void (*YCbCr_to_RGB_kernel)(
+ stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, const stbi_uc * pcr, int count,
+ int step
+ );
+ stbi_uc * (*resample_row_hv_2_kernel)(
+ stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs
+ );
} stbi__jpeg;
static int stbi__build_huffman(stbi__huffman * h, int * count) {
@@ -2215,8 +2268,9 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg * j, stbi__huffman * h)
c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
if (c < 0 || c >= 256) // symbol id out of bounds!
return -1;
- STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]])
- == h->code[c]);
+ STBI_ASSERT(
+ (((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]
+ );
// convert the id to a symbol
j->code_bits -= k;
@@ -2280,9 +2334,10 @@ static const stbi_uc stbi__jpeg_dezigzag[64 + 15]
63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63};
// decode one 64-entry block--
-static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman * hdc,
- stbi__huffman * hac, stbi__int16 * fac, int b,
- stbi__uint16 * dequant) {
+static int stbi__jpeg_decode_block(
+ stbi__jpeg * j, short data[64], stbi__huffman * hdc, stbi__huffman * hac,
+ stbi__int16 * fac, int b, stbi__uint16 * dequant
+) {
int diff, dc, k;
int t;
@@ -2314,8 +2369,9 @@ static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits)
- return stbi__err("bad huffman code",
- "Combined length longer than code bits available");
+ return stbi__err(
+ "bad huffman code", "Combined length longer than code bits available"
+ );
j->code_buffer <<= s;
j->code_bits -= s;
// decode into unzigzag'd location
@@ -2340,8 +2396,8 @@ static int stbi__jpeg_decode_block(stbi__jpeg * j, short data[64], stbi__huffman
return 1;
}
-static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi__huffman * hdc,
- int b) {
+static int
+stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi__huffman * hdc, int b) {
int diff, dc;
int t;
if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
@@ -2371,8 +2427,9 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg * j, short data[64], stbi_
// @OPTIMIZE: store non-zigzagged during the decode passes,
// and only de-zigzag when dequantizing
-static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi__huffman * hac,
- stbi__int16 * fac) {
+static int stbi__jpeg_decode_block_prog_ac(
+ stbi__jpeg * j, short data[64], stbi__huffman * hac, stbi__int16 * fac
+) {
int k;
if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
@@ -2395,8 +2452,9 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi_
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits)
- return stbi__err("bad huffman code",
- "Combined length longer than code bits available");
+ return stbi__err(
+ "bad huffman code", "Combined length longer than code bits available"
+ );
j->code_buffer <<= s;
j->code_bits -= s;
zig = stbi__jpeg_dezigzag[k++];
@@ -2443,7 +2501,8 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg * j, short data[64], stbi_
int r, s;
int rs = stbi__jpeg_huff_decode(
j,
- hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
+ hac
+ ); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
if (rs < 0) return stbi__err("bad huffman code", "Corrupt JPEG");
s = rs & 15;
r = rs >> 4;
@@ -2691,18 +2750,24 @@ static void stbi__idct_simd(stbi_uc * out, int out_stride, short data[64]) {
= dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
__m128i rot0_1
= dct_const(stbi__f2f(0.5411961f) + stbi__f2f(0.765366865f), stbi__f2f(0.5411961f));
- __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f),
- stbi__f2f(1.175875602f));
- __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f),
- stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
- __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f(0.298631336f),
- stbi__f2f(-1.961570560f));
- __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f),
- stbi__f2f(-1.961570560f) + stbi__f2f(3.072711026f));
- __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f(2.053119869f),
- stbi__f2f(-0.390180644f));
- __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f),
- stbi__f2f(-0.390180644f) + stbi__f2f(1.501321110f));
+ __m128i rot1_0 = dct_const(
+ stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)
+ );
+ __m128i rot1_1 = dct_const(
+ stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)
+ );
+ __m128i rot2_0 = dct_const(
+ stbi__f2f(-1.961570560f) + stbi__f2f(0.298631336f), stbi__f2f(-1.961570560f)
+ );
+ __m128i rot2_1 = dct_const(
+ stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f(3.072711026f)
+ );
+ __m128i rot3_0 = dct_const(
+ stbi__f2f(-0.390180644f) + stbi__f2f(2.053119869f), stbi__f2f(-0.390180644f)
+ );
+ __m128i rot3_1 = dct_const(
+ stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f(1.501321110f)
+ );
// rounding biases in column/row passes, see stbi__idct_block for explanation.
__m128i bias_0 = _mm_set1_epi32(512);
@@ -3091,13 +3156,15 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) {
for (j = 0; j < h; ++j) {
for (i = 0; i < w; ++i) {
int ha = z->img_comp[n].ha;
- if (!stbi__jpeg_decode_block(z, data, z->huff_dc + z->img_comp[n].hd,
- z->huff_ac + ha, z->fast_ac[ha], n,
- z->dequant[z->img_comp[n].tq]))
+ if (!stbi__jpeg_decode_block(
+ z, data, z->huff_dc + z->img_comp[n].hd, z->huff_ac + ha,
+ z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq]
+ ))
return 0;
- z->idct_block_kernel(z->img_comp[n].data + z->img_comp[n].w2 * j * 8
- + i * 8,
- z->img_comp[n].w2, data);
+ z->idct_block_kernel(
+ z->img_comp[n].data + z->img_comp[n].w2 * j * 8 + i * 8,
+ z->img_comp[n].w2, data
+ );
// every data block is an MCU, so countdown the restart interval
if (--z->todo <= 0) {
if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
@@ -3124,14 +3191,16 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) {
int x2 = (i * z->img_comp[n].h + x) * 8;
int y2 = (j * z->img_comp[n].v + y) * 8;
int ha = z->img_comp[n].ha;
- if (!stbi__jpeg_decode_block(z, data,
- z->huff_dc + z->img_comp[n].hd,
- z->huff_ac + ha, z->fast_ac[ha],
- n, z->dequant[z->img_comp[n].tq]))
+ if (!stbi__jpeg_decode_block(
+ z, data, z->huff_dc + z->img_comp[n].hd,
+ z->huff_ac + ha, z->fast_ac[ha], n,
+ z->dequant[z->img_comp[n].tq]
+ ))
return 0;
- z->idct_block_kernel(z->img_comp[n].data
- + z->img_comp[n].w2 * y2 + x2,
- z->img_comp[n].w2, data);
+ z->idct_block_kernel(
+ z->img_comp[n].data + z->img_comp[n].w2 * y2 + x2,
+ z->img_comp[n].w2, data
+ );
}
}
}
@@ -3162,12 +3231,14 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) {
= z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
if (z->spec_start == 0) {
if (!stbi__jpeg_decode_block_prog_dc(
- z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ z, data, &z->huff_dc[z->img_comp[n].hd], n
+ ))
return 0;
} else {
int ha = z->img_comp[n].ha;
- if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha],
- z->fast_ac[ha]))
+ if (!stbi__jpeg_decode_block_prog_ac(
+ z, data, &z->huff_ac[ha], z->fast_ac[ha]
+ ))
return 0;
}
// every data block is an MCU, so countdown the restart interval
@@ -3195,7 +3266,8 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg * z) {
short * data = z->img_comp[n].coeff
+ 64 * (x2 + y2 * z->img_comp[n].coeff_w);
if (!stbi__jpeg_decode_block_prog_dc(
- z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ z, data, &z->huff_dc[z->img_comp[n].hd], n
+ ))
return 0;
}
}
@@ -3231,9 +3303,10 @@ static void stbi__jpeg_finish(stbi__jpeg * z) {
short * data
= z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
- z->idct_block_kernel(z->img_comp[n].data + z->img_comp[n].w2 * j * 8
- + i * 8,
- z->img_comp[n].w2, data);
+ z->idct_block_kernel(
+ z->img_comp[n].data + z->img_comp[n].w2 * j * 8 + i * 8,
+ z->img_comp[n].w2, data
+ );
}
}
}
@@ -3283,7 +3356,8 @@ static int stbi__process_marker(stbi__jpeg * z, int m) {
if (n > 256)
return stbi__err(
"bad DHT header",
- "Corrupt JPEG"); // Loop over i < n would write past end of values!
+ "Corrupt JPEG"
+ ); // Loop over i < n would write past end of values!
L -= 17;
if (tc == 0) {
if (!stbi__build_huffman(z->huff_dc + th, sizes)) return 0;
@@ -3410,13 +3484,16 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) {
if (Lf < 11) return stbi__err("bad SOF len", "Corrupt JPEG"); // JPEG
p = stbi__get8(s);
if (p != 8)
- return stbi__err("only 8-bit",
- "JPEG format not supported: 8-bit only"); // JPEG baseline
+ return stbi__err(
+ "only 8-bit",
+ "JPEG format not supported: 8-bit only"
+ ); // JPEG baseline
s->img_y = stbi__get16be(s);
if (s->img_y == 0)
return stbi__err(
"no header height",
- "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+ "JPEG format not supported: delayed height"
+ ); // Legal, but we don't handle it--but neither does IJG
s->img_x = stbi__get16be(s);
if (s->img_x == 0) return stbi__err("0 width", "Corrupt JPEG"); // JPEG requires
if (s->img_y > STBI_MAX_DIMENSIONS)
@@ -3493,8 +3570,9 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) {
z->img_comp[i].linebuf = NULL;
z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
if (z->img_comp[i].raw_data == NULL)
- return stbi__free_jpeg_components(z, i + 1,
- stbi__err("outofmem", "Out of memory"));
+ return stbi__free_jpeg_components(
+ z, i + 1, stbi__err("outofmem", "Out of memory")
+ );
// align blocks for idct using mmx/sse
z->img_comp[i].data = (stbi_uc *) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
if (z->progressive) {
@@ -3504,8 +3582,9 @@ static int stbi__process_frame_header(stbi__jpeg * z, int scan) {
z->img_comp[i].raw_coeff
= stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
if (z->img_comp[i].raw_coeff == NULL)
- return stbi__free_jpeg_components(z, i + 1,
- stbi__err("outofmem", "Out of memory"));
+ return stbi__free_jpeg_components(
+ z, i + 1, stbi__err("outofmem", "Out of memory")
+ );
z->img_comp[i].coeff = (short *) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
}
}
@@ -3603,13 +3682,14 @@ static int stbi__decode_jpeg_image(stbi__jpeg * j) {
// static jfif-centered resampling (across block boundaries)
-typedef stbi_uc * (*resample_row_func)(stbi_uc * out, stbi_uc * in0, stbi_uc * in1, int w,
- int hs);
+typedef stbi_uc * (*resample_row_func)(
+ stbi_uc * out, stbi_uc * in0, stbi_uc * in1, int w, int hs
+);
#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
-static stbi_uc * resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w,
- int hs) {
+static stbi_uc *
+resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) {
STBI_NOTUSED(out);
STBI_NOTUSED(in_far);
STBI_NOTUSED(w);
@@ -3617,8 +3697,8 @@ static stbi_uc * resample_row_1(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_f
return in_near;
}
-static stbi_uc * stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far,
- int w, int hs) {
+static stbi_uc *
+stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) {
// need to generate two samples vertically for every one in input
int i;
STBI_NOTUSED(hs);
@@ -3626,8 +3706,8 @@ static stbi_uc * stbi__resample_row_v_2(stbi_uc * out, stbi_uc * in_near, stbi_u
return out;
}
-static stbi_uc * stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far,
- int w, int hs) {
+static stbi_uc *
+stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) {
// need to generate two samples horizontally for every one in input
int i;
stbi_uc * input = in_near;
@@ -3656,8 +3736,8 @@ static stbi_uc * stbi__resample_row_h_2(stbi_uc * out, stbi_uc * in_near, stbi_u
#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
-static stbi_uc * stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far,
- int w, int hs) {
+static stbi_uc *
+stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) {
// need to generate 2x2 samples for every one in input
int i, t0, t1;
if (w == 1) {
@@ -3681,8 +3761,9 @@ static stbi_uc * stbi__resample_row_hv_2(stbi_uc * out, stbi_uc * in_near, stbi_
}
#if defined(STBI_SSE2) || defined(STBI_NEON)
-static stbi_uc * stbi__resample_row_hv_2_simd(stbi_uc * out, stbi_uc * in_near,
- stbi_uc * in_far, int w, int hs) {
+static stbi_uc * stbi__resample_row_hv_2_simd(
+ stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs
+) {
// need to generate 2x2 samples for every one in input
int i = 0, t0, t1;
@@ -3797,8 +3878,8 @@ static stbi_uc * stbi__resample_row_hv_2_simd(stbi_uc * out, stbi_uc * in_near,
}
#endif
-static stbi_uc * stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far,
- int w, int hs) {
+static stbi_uc *
+stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, stbi_uc * in_far, int w, int hs) {
// resample with nearest-neighbor
int i, j;
STBI_NOTUSED(in_far);
@@ -3810,8 +3891,10 @@ static stbi_uc * stbi__resample_row_generic(stbi_uc * out, stbi_uc * in_near, st
// this is a reduced-precision calculation of YCbCr-to-RGB introduced
// to make sure the code produces the same results in both SIMD and scalar
#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8)
-static void stbi__YCbCr_to_RGB_row(stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb,
- const stbi_uc * pcr, int count, int step) {
+static void stbi__YCbCr_to_RGB_row(
+ stbi_uc * out, const stbi_uc * y, const stbi_uc * pcb, const stbi_uc * pcr, int count,
+ int step
+) {
int i;
for (i = 0; i < count; ++i) {
int y_fixed = (y[i] << 20) + (1 << 19); // rounding
@@ -3846,8 +3929,10 @@ static void stbi__YCbCr_to_RGB_row(stbi_uc * out, const stbi_uc * y, const stbi_
}
#if defined(STBI_SSE2) || defined(STBI_NEON)
-static void stbi__YCbCr_to_RGB_simd(stbi_uc * out, stbi_uc const * y, stbi_uc const * pcb,
- stbi_uc const * pcr, int count, int step) {
+static void stbi__YCbCr_to_RGB_simd(
+ stbi_uc * out, stbi_uc const * y, stbi_uc const * pcb, stbi_uc const * pcr, int count,
+ int step
+) {
int i = 0;
#ifdef STBI_SSE2
@@ -4031,8 +4116,8 @@ static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) {
return (stbi_uc) ((t + (t >> 8)) >> 8);
}
-static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * comp,
- int req_comp) {
+static stbi_uc *
+load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int * comp, int req_comp) {
int n, decode_n, is_rgb;
z->s->img_n = 0; // make stbi__cleanup_jpeg safe
@@ -4107,8 +4192,10 @@ static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int *
for (k = 0; k < decode_n; ++k) {
stbi__resample * r = &res_comp[k];
int y_bot = r->ystep >= (r->vs >> 1);
- coutput[k] = r->resample(z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0,
- y_bot ? r->line0 : r->line1, r->w_lores, r->hs);
+ coutput[k] = r->resample(
+ z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0,
+ y_bot ? r->line0 : r->line1, r->w_lores, r->hs
+ );
if (++r->ystep >= r->vs) {
r->ystep = 0;
r->line0 = r->line1;
@@ -4206,8 +4293,9 @@ static stbi_uc * load_jpeg_image(stbi__jpeg * z, int * out_x, int * out_y, int *
}
}
-static void * stbi__jpeg_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__jpeg_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
unsigned char * result;
stbi__jpeg * j = (stbi__jpeg *) stbi__malloc(sizeof(stbi__jpeg));
if (!j) return stbi__errpuc("outofmem", "Out of memory");
@@ -4432,8 +4520,10 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf * a, stbi__zhuffman * z)
return stbi__zhuffman_decode_slowpath(a, z);
}
-static int stbi__zexpand(stbi__zbuf * z, char * zout,
- int n) // need to make room for n bytes
+static int stbi__zexpand(
+ stbi__zbuf * z, char * zout,
+ int n
+) // need to make room for n bytes
{
char * q;
unsigned int cur, limit, old_limit;
@@ -4500,7 +4590,8 @@ static int stbi__parse_huffman_block(stbi__zbuf * a) {
if (z >= 286)
return stbi__err(
"bad huffman code",
- "Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
+ "Corrupt PNG"
+ ); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
z -= 257;
len = stbi__zlength_base[z];
if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
@@ -4508,7 +4599,8 @@ static int stbi__parse_huffman_block(stbi__zbuf * a) {
if (z < 0 || z >= 30)
return stbi__err(
"bad huffman code",
- "Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
+ "Corrupt PNG"
+ ); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
dist = stbi__zdist_base[z];
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
if (zout - a->zout_start < dist) return stbi__err("bad dist", "Corrupt PNG");
@@ -4617,8 +4709,10 @@ static int stbi__parse_zlib_header(stbi__zbuf * a) {
if ((cmf * 256 + flg) % 31 != 0)
return stbi__err("bad zlib header", "Corrupt PNG"); // zlib spec
if (flg & 32)
- return stbi__err("no preset dict",
- "Corrupt PNG"); // preset dictionary not allowed in png
+ return stbi__err(
+ "no preset dict",
+ "Corrupt PNG"
+ ); // preset dictionary not allowed in png
if (cm != 8)
return stbi__err("bad compression",
"Corrupt PNG"); // DEFLATE required for png
@@ -4692,8 +4786,9 @@ static int stbi__do_zlib(stbi__zbuf * a, char * obuf, int olen, int exp, int par
return stbi__parse_zlib(a, parse_header);
}
-STBIDEF char * stbi_zlib_decode_malloc_guesssize(const char * buffer, int len,
- int initial_size, int * outlen) {
+STBIDEF char * stbi_zlib_decode_malloc_guesssize(
+ const char * buffer, int len, int initial_size, int * outlen
+) {
stbi__zbuf a;
char * p = (char *) stbi__malloc(initial_size);
if (p == NULL) return NULL;
@@ -4712,9 +4807,9 @@ STBIDEF char * stbi_zlib_decode_malloc(char const * buffer, int len, int * outle
return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
}
-STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(const char * buffer, int len,
- int initial_size, int * outlen,
- int parse_header) {
+STBIDEF char * stbi_zlib_decode_malloc_guesssize_headerflag(
+ const char * buffer, int len, int initial_size, int * outlen, int parse_header
+) {
stbi__zbuf a;
char * p = (char *) stbi__malloc(initial_size);
if (p == NULL) return NULL;
@@ -4752,8 +4847,8 @@ STBIDEF char * stbi_zlib_decode_noheader_malloc(char const * buffer, int len, in
}
}
-STBIDEF int stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer,
- int ilen) {
+STBIDEF int
+stbi_zlib_decode_noheader_buffer(char * obuffer, int olen, const char * ibuffer, int ilen) {
stbi__zbuf a;
a.zbuffer = (stbi_uc *) ibuffer;
a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
@@ -4831,8 +4926,8 @@ static const stbi_uc stbi__depth_scale_table[9] = {0, 0xff, 0x55, 0, 0x11, 0, 0,
// adds an extra all-255 alpha channel
// dest == src is legal
// img_n must be 1 or 3
-static void stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__uint32 x,
- int img_n) {
+static void
+stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__uint32 x, int img_n) {
int i;
// must process data backwards since we allow dest==src
if (img_n == 1) {
@@ -4852,9 +4947,10 @@ static void stbi__create_png_alpha_expand8(stbi_uc * dest, stbi_uc * src, stbi__
}
// create the png data from post-deflated data
-static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32 raw_len,
- int out_n, stbi__uint32 x, stbi__uint32 y, int depth,
- int color) {
+static int stbi__create_png_image_raw(
+ stbi__png * a, stbi_uc * raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x,
+ stbi__uint32 y, int depth, int color
+) {
int bytes = (depth == 16 ? 2 : 1);
stbi__context * s = a->s;
stbi__uint32 i, j, stride = x * out_n * bytes;
@@ -4869,8 +4965,10 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32
int width = x;
STBI_ASSERT(out_n == s->img_n || out_n == s->img_n + 1);
- a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes,
- 0); // extra bytes to write off the end into
+ a->out = (stbi_uc *) stbi__malloc_mad3(
+ x, y, output_bytes,
+ 0
+ ); // extra bytes to write off the end into
if (!a->out) return stbi__err("outofmem", "Out of memory");
// note: error exits here don't need to clean up a->out individually,
@@ -4937,11 +5035,13 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32
case STBI__F_paeth:
for (k = 0; k < filter_bytes; ++k)
cur[k] = STBI__BYTECAST(
- raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)
+ raw[k] + prior[k]
+ ); // prior[k] == stbi__paeth(0,prior[k],0)
for (k = filter_bytes; k < nk; ++k)
- cur[k] = STBI__BYTECAST(raw[k]
- + stbi__paeth(cur[k - filter_bytes], prior[k],
- prior[k - filter_bytes]));
+ cur[k] = STBI__BYTECAST(
+ raw[k]
+ + stbi__paeth(cur[k - filter_bytes], prior[k], prior[k - filter_bytes])
+ );
break;
case STBI__F_avg_first:
memcpy(cur, raw, filter_bytes);
@@ -5022,16 +5122,18 @@ static int stbi__create_png_image_raw(stbi__png * a, stbi_uc * raw, stbi__uint32
return 1;
}
-static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data,
- stbi__uint32 image_data_len, int out_n, int depth, int color,
- int interlaced) {
+static int stbi__create_png_image(
+ stbi__png * a, stbi_uc * image_data, stbi__uint32 image_data_len, int out_n, int depth,
+ int color, int interlaced
+) {
int bytes = (depth == 16 ? 2 : 1);
int out_bytes = out_n * bytes;
stbi_uc * final;
int p;
if (!interlaced)
- return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x,
- a->s->img_y, depth, color);
+ return stbi__create_png_image_raw(
+ a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color
+ );
// de-interlacing
final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
@@ -5047,8 +5149,9 @@ static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data,
y = (a->s->img_y - yorig[p] + yspc[p] - 1) / yspc[p];
if (x && y) {
stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
- if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth,
- color)) {
+ if (!stbi__create_png_image_raw(
+ a, image_data, image_data_len, out_n, x, y, depth, color
+ )) {
STBI_FREE(final);
return 0;
}
@@ -5056,8 +5159,10 @@ static int stbi__create_png_image(stbi__png * a, stbi_uc * image_data,
for (i = 0; i < x; ++i) {
int out_y = j * yspc[p] + yorig[p];
int out_x = i * xspc[p] + xorig[p];
- memcpy(final + out_y * a->s->img_x * out_bytes + out_x * out_bytes,
- a->out + (j * x + i) * out_bytes, out_bytes);
+ memcpy(
+ final + out_y * a->s->img_x * out_bytes + out_x * out_bytes,
+ a->out + (j * x + i) * out_bytes, out_bytes
+ );
}
}
STBI_FREE(a->out);
@@ -5270,8 +5375,9 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) {
z->depth = stbi__get8(s);
if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8
&& z->depth != 16)
- return stbi__err("1/2/4/8/16-bit only",
- "PNG not supported: 1/2/4/8/16-bit only");
+ return stbi__err(
+ "1/2/4/8/16-bit only", "PNG not supported: 1/2/4/8/16-bit only"
+ );
color = stbi__get8(s);
if (color > 6) return stbi__err("bad ctype", "Corrupt PNG");
if (color == 3 && z->depth == 16) return stbi__err("bad ctype", "Corrupt PNG");
@@ -5385,15 +5491,17 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) {
raw_len = bpl * s->img_y * s->img_n /* pixels */
+ s->img_y /* filter mode per row */;
z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag(
- (char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
+ (char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone
+ );
if (z->expanded == NULL) return 0; // zlib should set error
STBI_FREE(z->idata);
z->idata = NULL;
if ((req_comp == s->img_n + 1 && req_comp != 3 && !pal_img_n) || has_trans)
s->img_out_n = s->img_n + 1;
else s->img_out_n = s->img_n;
- if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth,
- color, interlace))
+ if (!stbi__create_png_image(
+ z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace
+ ))
return 0;
if (has_trans) {
if (z->depth == 16) {
@@ -5432,8 +5540,9 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) {
invalid_chunk[2] = STBI__BYTECAST(c.type >> 8);
invalid_chunk[3] = STBI__BYTECAST(c.type >> 0);
#endif
- return stbi__err(invalid_chunk,
- "PNG not supported: unknown PNG chunk type");
+ return stbi__err(
+ invalid_chunk, "PNG not supported: unknown PNG chunk type"
+ );
}
stbi__skip(s, c.length);
break;
@@ -5443,25 +5552,30 @@ static int stbi__parse_png_file(stbi__png * z, int scan, int req_comp) {
}
}
-static void * stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_comp,
- stbi__result_info * ri) {
+static void *
+stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_comp, stbi__result_info * ri) {
void * result = NULL;
if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
if (p->depth <= 8) ri->bits_per_channel = 8;
else if (p->depth == 16) ri->bits_per_channel = 16;
else
- return stbi__errpuc("bad bits_per_channel",
- "PNG not supported: unsupported color depth");
+ return stbi__errpuc(
+ "bad bits_per_channel", "PNG not supported: unsupported color depth"
+ );
result = p->out;
p->out = NULL;
if (req_comp && req_comp != p->s->img_out_n) {
if (ri->bits_per_channel == 8)
- result = stbi__convert_format((unsigned char *) result, p->s->img_out_n,
- req_comp, p->s->img_x, p->s->img_y);
+ result = stbi__convert_format(
+ (unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x,
+ p->s->img_y
+ );
else
- result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n,
- req_comp, p->s->img_x, p->s->img_y);
+ result = stbi__convert_format16(
+ (stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x,
+ p->s->img_y
+ );
p->s->img_out_n = req_comp;
if (result == NULL) return result;
}
@@ -5479,8 +5593,9 @@ static void * stbi__do_png(stbi__png * p, int * x, int * y, int * n, int req_com
return result;
}
-static void * stbi__png_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__png_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
stbi__png p;
p.s = s;
return stbi__do_png(&p, x, y, comp, req_comp, ri);
@@ -5667,12 +5782,16 @@ static void * stbi__bmp_parse_header(stbi__context * s, stbi__bmp_data * info) {
if (compress == 1 || compress == 2)
return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
if (compress >= 4)
- return stbi__errpuc("BMP JPEG/PNG",
- "BMP type not supported: unsupported "
- "compression"); // this includes PNG/JPEG modes
+ return stbi__errpuc(
+ "BMP JPEG/PNG",
+ "BMP type not supported: unsupported "
+ "compression"
+ ); // this includes PNG/JPEG modes
if (compress == 3 && info->bpp != 16 && info->bpp != 32)
- return stbi__errpuc("bad BMP",
- "bad BMP"); // bitfields requires 16 or 32 bits/pixel
+ return stbi__errpuc(
+ "bad BMP",
+ "bad BMP"
+ ); // bitfields requires 16 or 32 bits/pixel
stbi__get32le(s); // discard sizeof
stbi__get32le(s); // discard hres
stbi__get32le(s); // discard vres
@@ -5723,8 +5842,9 @@ static void * stbi__bmp_parse_header(stbi__context * s, stbi__bmp_data * info) {
return (void *) 1;
}
-static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__bmp_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
stbi_uc * out;
unsigned int mr = 0, mg = 0, mb = 0, ma = 0, all_a;
stbi_uc pal[256][4];
@@ -5804,8 +5924,9 @@ static void * stbi__bmp_load(stbi__context * s, int * x, int * y, int * comp, in
if (info.hsz != 12) stbi__get8(s);
pal[i][3] = 255;
}
- stbi__skip(s, info.offset - info.extra_read - info.hsz
- - psize * (info.hsz == 12 ? 3 : 4));
+ stbi__skip(
+ s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)
+ );
if (info.bpp == 1) width = (s->img_x + 7) >> 3;
else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
else if (info.bpp == 8) width = s->img_x;
@@ -6023,8 +6144,9 @@ static int stbi__tga_info(stbi__context * s, int * x, int * y, int * comp) {
}
tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
} else {
- tga_comp = stbi__tga_get_comp(tga_bits_per_pixel,
- (tga_image_type == 3) || (tga_image_type == 11), NULL);
+ tga_comp = stbi__tga_get_comp(
+ tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL
+ );
}
if (!tga_comp) {
stbi__rewind(s);
@@ -6087,8 +6209,9 @@ static void stbi__tga_read_rgb16(stbi__context * s, stbi_uc * out) {
// so let's treat all 15 and 16bit TGAs as RGB with no alpha.
}
-static void * stbi__tga_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__tga_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
// read in the TGA header stuff
int tga_offset = stbi__get8(s);
int tga_indexed = stbi__get8(s);
@@ -6325,8 +6448,10 @@ static int stbi__psd_decode_rle(stbi__context * s, stbi_uc * p, int pixelCount)
return 1;
}
-static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri, int bpc) {
+static void * stbi__psd_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri,
+ int bpc
+) {
int pixelCount;
int channelCount, compression;
int channel, i;
@@ -6349,8 +6474,9 @@ static void * stbi__psd_load(stbi__context * s, int * x, int * y, int * comp, in
// Read the number of channels (R, G, B, A, etc).
channelCount = stbi__get16be(s);
if (channelCount < 0 || channelCount > 16)
- return stbi__errpuc("wrong channel count",
- "Unsupported number of channels in PSD image");
+ return stbi__errpuc(
+ "wrong channel count", "Unsupported number of channels in PSD image"
+ );
// Read the rows and columns of the image.
h = stbi__get32be(s);
@@ -6575,8 +6701,8 @@ static void stbi__copyval(int channel, stbi_uc * dest, const stbi_uc * src) {
if (channel & mask) dest[i] = src[i];
}
-static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, int * comp,
- stbi_uc * result) {
+static stbi_uc *
+stbi__pic_load_core(stbi__context * s, int width, int height, int * comp, stbi_uc * result) {
int act_comp = 0, num_packets = 0, y, chained;
stbi__pic_packet packets[10];
@@ -6632,8 +6758,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i
count = stbi__get8(s);
if (stbi__at_eof(s))
- return stbi__errpuc("bad file",
- "file too short (pure read count)");
+ return stbi__errpuc(
+ "bad file", "file too short (pure read count)"
+ );
if (count > left) count = (stbi_uc) left;
@@ -6650,8 +6777,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i
while (left > 0) {
int count = stbi__get8(s), i;
if (stbi__at_eof(s))
- return stbi__errpuc("bad file",
- "file too short (mixed read count)");
+ return stbi__errpuc(
+ "bad file", "file too short (mixed read count)"
+ );
if (count >= 128) { // Repeated
stbi_uc value[4];
@@ -6684,8 +6812,9 @@ static stbi_uc * stbi__pic_load_core(stbi__context * s, int width, int height, i
return result;
}
-static void * stbi__pic_load(stbi__context * s, int * px, int * py, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__pic_load(
+ stbi__context * s, int * px, int * py, int * comp, int req_comp, stbi__result_info * ri
+) {
stbi_uc * result;
int i, x, y, internal_comp;
STBI_NOTUSED(ri);
@@ -6780,8 +6909,9 @@ static int stbi__gif_test(stbi__context * s) {
return r;
}
-static void stbi__gif_parse_colortable(stbi__context * s, stbi_uc pal[256][4], int num_entries,
- int transp) {
+static void stbi__gif_parse_colortable(
+ stbi__context * s, stbi_uc pal[256][4], int num_entries, int transp
+) {
int i;
for (i = 0; i < num_entries; ++i) {
pal[i][2] = stbi__get8(s);
@@ -6957,8 +7087,9 @@ static stbi_uc * stbi__process_gif_raster(stbi__context * s, stbi__gif * g) {
// this function is designed to support animated gifs, although stb_image doesn't support it
// two back is the image from two frames ago, used for a very specific disposal format
-static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * comp,
- int req_comp, stbi_uc * two_back) {
+static stbi_uc * stbi__gif_load_next(
+ stbi__context * s, stbi__gif * g, int * comp, int req_comp, stbi_uc * two_back
+) {
int dispose;
int first_frame;
int pi;
@@ -6983,8 +7114,10 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com
// background colour is only used for pixels that are not rendered first frame, after that "background"
// color refers to the color that was there the previous frame.
memset(g->out, 0x00, 4 * pcount);
- memset(g->background, 0x00,
- 4 * pcount); // state of the background (starts transparent)
+ memset(
+ g->background, 0x00,
+ 4 * pcount
+ ); // state of the background (starts transparent)
memset(g->history, 0x00,
pcount); // pixels that were affected previous frame
first_frame = 1;
@@ -7066,8 +7199,10 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com
}
if (g->lflags & 0x80) {
- stbi__gif_parse_colortable(s, g->lpal, 2 << (g->lflags & 7),
- g->eflags & 0x01 ? g->transparent : -1);
+ stbi__gif_parse_colortable(
+ s, g->lpal, 2 << (g->lflags & 7),
+ g->eflags & 0x01 ? g->transparent : -1
+ );
g->color_table = (stbi_uc *) g->lpal;
} else if (g->flags & 0x80) {
g->color_table = (stbi_uc *) g->pal;
@@ -7101,8 +7236,8 @@ static stbi_uc * stbi__gif_load_next(stbi__context * s, stbi__gif * g, int * com
if (len == 4) {
g->eflags = stbi__get8(s);
g->delay = 10
- * stbi__get16le(
- s); // delay - 1/100th of a second, saving as 1/1000ths.
+ * stbi__get16le(s
+ ); // delay - 1/100th of a second, saving as 1/1000ths.
// unset old transparent
if (g->transparent >= 0) {
@@ -7148,8 +7283,9 @@ static void * stbi__load_gif_main_outofmem(stbi__gif * g, stbi_uc * out, int **
return stbi__errpuc("outofmem", "Out of memory");
}
-static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int * y, int * z,
- int * comp, int req_comp) {
+static void * stbi__load_gif_main(
+ stbi__context * s, int ** delays, int * x, int * y, int * z, int * comp, int req_comp
+) {
if (stbi__gif_test(s)) {
int layers = 0;
stbi_uc * u = 0;
@@ -7188,8 +7324,9 @@ static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int
}
if (delays) {
- int * new_delays = (int *) STBI_REALLOC_SIZED(*delays, delays_size,
- sizeof(int) * layers);
+ int * new_delays = (int *) STBI_REALLOC_SIZED(
+ *delays, delays_size, sizeof(int) * layers
+ );
if (!new_delays) return stbi__load_gif_main_outofmem(&g, out, delays);
*delays = new_delays;
delays_size = layers * sizeof(int);
@@ -7231,8 +7368,9 @@ static void * stbi__load_gif_main(stbi__context * s, int ** delays, int * x, int
}
}
-static void * stbi__gif_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__gif_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
stbi_uc * u = 0;
stbi__gif g;
memset(&g, 0, sizeof(g));
@@ -7336,8 +7474,9 @@ static void stbi__hdr_convert(float * output, stbi_uc * input, int req_comp) {
}
}
-static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static float * stbi__hdr_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
char buffer[STBI__HDR_BUFLEN];
char * token;
int valid = 0;
@@ -7404,8 +7543,9 @@ static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, i
stbi_uc rgbe[4];
main_decode_loop:
stbi__getn(s, rgbe, 4);
- stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe,
- req_comp);
+ stbi__hdr_convert(
+ hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp
+ );
}
}
} else {
@@ -7472,8 +7612,9 @@ static float * stbi__hdr_load(stbi__context * s, int * x, int * y, int * comp, i
}
}
for (i = 0; i < width; ++i)
- stbi__hdr_convert(hdr_data + (j * width + i) * req_comp, scanline + i * 4,
- req_comp);
+ stbi__hdr_convert(
+ hdr_data + (j * width + i) * req_comp, scanline + i * 4, req_comp
+ );
}
if (scanline) STBI_FREE(scanline);
}
@@ -7689,8 +7830,9 @@ static int stbi__pnm_test(stbi__context * s) {
return 1;
}
-static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, int req_comp,
- stbi__result_info * ri) {
+static void * stbi__pnm_load(
+ stbi__context * s, int * x, int * y, int * comp, int req_comp, stbi__result_info * ri
+) {
stbi_uc * out;
STBI_NOTUSED(ri);
@@ -7710,8 +7852,9 @@ static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, in
if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0))
return stbi__errpuc("too large", "PNM too large");
- out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8,
- 0);
+ out = (stbi_uc *) stbi__malloc_mad4(
+ s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0
+ );
if (!out) return stbi__errpuc("outofmem", "Out of memory");
if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
STBI_FREE(out);
@@ -7720,8 +7863,9 @@ static void * stbi__pnm_load(stbi__context * s, int * x, int * y, int * comp, in
if (req_comp && req_comp != s->img_n) {
if (ri->bits_per_channel == 16) {
- out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp,
- s->img_x, s->img_y);
+ out = (stbi_uc *) stbi__convert_format16(
+ (stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y
+ );
} else {
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
}
@@ -7753,8 +7897,10 @@ static int stbi__pnm_getinteger(stbi__context * s, char * c) {
value = value * 10 + (*c - '0');
*c = (char) stbi__get8(s);
if ((value > 214748364) || (value == 214748364 && *c > '7'))
- return stbi__err("integer parse overflow",
- "Parsing an integer in the PPM header overflowed a 32-bit int");
+ return stbi__err(
+ "integer parse overflow",
+ "Parsing an integer in the PPM header overflowed a 32-bit int"
+ );
}
return value;
@@ -7795,8 +7941,9 @@ static int stbi__pnm_info(stbi__context * s, int * x, int * y, int * comp) {
maxv = stbi__pnm_getinteger(s, &c); // read max value
if (maxv > 65535)
- return stbi__err("max value > 65535",
- "PPM image supports only 8-bit and 16-bit images");
+ return stbi__err(
+ "max value > 65535", "PPM image supports only 8-bit and 16-bit images"
+ );
else if (maxv > 255) return 16;
else return 8;
}
@@ -7902,15 +8049,16 @@ STBIDEF int stbi_is_16_bit_from_file(FILE * f) {
}
#endif // !STBI_NO_STDIO
-STBIDEF int stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y,
- int * comp) {
+STBIDEF int
+stbi_info_from_memory(stbi_uc const * buffer, int len, int * x, int * y, int * comp) {
stbi__context s;
stbi__start_mem(&s, buffer, len);
return stbi__info_main(&s, x, y, comp);
}
-STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const * c, void * user, int * x,
- int * y, int * comp) {
+STBIDEF int stbi_info_from_callbacks(
+ stbi_io_callbacks const * c, void * user, int * x, int * y, int * comp
+) {
stbi__context s;
stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
return stbi__info_main(&s, x, y, comp);
diff --git a/readme.md b/readme.md
index d309b30..373c682 100644
--- a/readme.md
+++ b/readme.md
@@ -33,6 +33,7 @@ This project uses the following libraries
|Google Test (`GTest`)|1.15.2|
|Berkeley DB (`libdb`)|5.3.21|
|Where Am I?|(latest git `master` version)
+|fontconfig|2.15.0|
> [!NOTE]
> Most of these libraries are likely available from your package manager if you
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c3f29da..696856c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -9,10 +9,12 @@ project(crepe C CXX)
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
+find_package(SDL2_ttf REQUIRED)
find_package(SoLoud REQUIRED)
find_package(GTest REQUIRED)
find_package(whereami REQUIRED)
find_library(BERKELEY_DB db)
+find_library(FONTCONFIG_LIB fontconfig)
add_library(crepe SHARED)
add_executable(test_main EXCLUDE_FROM_ALL)
@@ -24,9 +26,11 @@ target_include_directories(crepe
target_link_libraries(crepe
PRIVATE soloud
PUBLIC SDL2
+ PUBLIC SDL2_ttf
PUBLIC SDL2_image
PUBLIC ${BERKELEY_DB}
PUBLIC whereami
+ PUBLIC ${FONTCONFIG_LIB}
)
add_subdirectory(crepe)
@@ -40,5 +44,6 @@ install(
target_link_libraries(test_main
PRIVATE gtest
+ PRIVATE gmock
PUBLIC crepe
)
diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt
index 7e176e7..6cbb9fe 100644
--- a/src/crepe/CMakeLists.txt
+++ b/src/crepe/CMakeLists.txt
@@ -1,21 +1,21 @@
target_sources(crepe PUBLIC
Particle.cpp
- ComponentManager.cpp
Component.cpp
Collider.cpp
+ Resource.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
- ComponentManager.h
- ComponentManager.hpp
Component.h
Collider.h
ValueBroker.h
ValueBroker.hpp
+ Resource.h
)
add_subdirectory(api)
add_subdirectory(facade)
+add_subdirectory(manager)
add_subdirectory(system)
add_subdirectory(util)
diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp
index bbec488..77e11c8 100644
--- a/src/crepe/Collider.cpp
+++ b/src/crepe/Collider.cpp
@@ -2,4 +2,4 @@
using namespace crepe;
-Collider::Collider(game_object_id_t id) : Component(id) {}
+Collider::Collider(game_object_id_t id, const vec2 & offset) : Component(id), offset(offset) {}
diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h
index 827f83d..4344f15 100644
--- a/src/crepe/Collider.h
+++ b/src/crepe/Collider.h
@@ -1,14 +1,26 @@
#pragma once
#include "Component.h"
+#include "types.h"
namespace crepe {
+/**
+ * \brief Base collider class
+ */
class Collider : public Component {
public:
- Collider(game_object_id_t id);
+ Collider(game_object_id_t id, const vec2 & offset);
- int size;
+public:
+ /**
+ * \brief Offset of the collider relative to the rigidbody position.
+ *
+ * The `offset` defines the positional shift applied to the collider relative to the position of the rigidbody it is attached to.
+ * This allows the collider to be placed at a different position than the rigidbody.
+ *
+ */
+ vec2 offset;
};
} // namespace crepe
diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp
index acfd35c..ae76e65 100644
--- a/src/crepe/Component.cpp
+++ b/src/crepe/Component.cpp
@@ -1,5 +1,17 @@
#include "Component.h"
using namespace crepe;
+using namespace std;
Component::Component(game_object_id_t id) : game_object_id(id) {}
+
+Component & Component::operator=(const Component & other) {
+ this->active = other.active;
+ return *this;
+}
+
+unique_ptr<Component> Component::save() const {
+ return unique_ptr<Component>(new Component(*this));
+}
+
+void Component::restore(const Component & snapshot) { *this = snapshot; }
diff --git a/src/crepe/Component.h b/src/crepe/Component.h
index dc17721..52e06d5 100644
--- a/src/crepe/Component.h
+++ b/src/crepe/Component.h
@@ -1,5 +1,7 @@
#pragma once
+#include <memory>
+
#include "types.h"
namespace crepe {
@@ -8,7 +10,7 @@ class ComponentManager;
/**
* \brief Base class for all components
- *
+ *
* This class is the base class for all components. It provides a common interface for all
* components.
*/
@@ -16,7 +18,12 @@ class Component {
public:
//! Whether the component is active
bool active = true;
- //! The id of the GameObject this component belongs to
+ /**
+ * \brief The id of the GameObject this component belongs to
+ *
+ * \note Only systems are supposed to use this member, but since friend
+ * relations aren't inherited this needs to be public.
+ */
const game_object_id_t game_object_id;
protected:
@@ -24,14 +31,36 @@ protected:
* \param id The id of the GameObject this component belongs to
*/
Component(game_object_id_t id);
- //! Only the ComponentManager can create components
+ //! Only ComponentManager can create components
friend class ComponentManager;
- Component(const Component &) = delete;
+ // components are never moved
Component(Component &&) = delete;
- virtual Component & operator=(const Component &) = delete;
virtual Component & operator=(Component &&) = delete;
+protected:
+ /**
+ * \name ReplayManager (Memento) functions
+ * \{
+ */
+ /**
+ * \brief Save a snapshot of this component's state
+ * \note This function should only be implemented on components that should be saved/restored
+ * by ReplayManager.
+ * \returns Unique pointer to a deep copy of this component
+ */
+ virtual std::unique_ptr<Component> save() const;
+ //! Copy constructor (used by \c save())
+ Component(const Component &) = default;
+ /**
+ * \brief Restore this component from a snapshot
+ * \param snapshot Data to fill this component with (as returned by \c save())
+ */
+ virtual void restore(const Component & snapshot);
+ //! Copy assignment operator (used by \c restore())
+ virtual Component & operator=(const Component &);
+ //! \}
+
public:
virtual ~Component() = default;
diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp
deleted file mode 100644
index e310577..0000000
--- a/src/crepe/ComponentManager.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-#include "api/GameObject.h"
-#include "util/Log.h"
-
-#include "ComponentManager.h"
-
-using namespace crepe;
-using namespace std;
-
-ComponentManager::ComponentManager() { dbg_trace(); }
-ComponentManager::~ComponentManager() { dbg_trace(); }
-
-void ComponentManager::delete_all_components_of_id(game_object_id_t id) {
- // Loop through all the types (in the unordered_map<>)
- for (auto & [type, componentArray] : this->components) {
- // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
- if (id < componentArray.size()) {
- // Clear the components at this specific id
- componentArray[id].clear();
- }
- }
-}
-
-void ComponentManager::delete_all_components() {
- this->components.clear();
- this->next_id = 0;
-}
-
-GameObject ComponentManager::new_object(const string & name, const string & tag,
- const Vector2 & position, double rotation,
- double scale) {
- GameObject object{*this, this->next_id, name, tag, position, rotation, scale};
- this->next_id++;
- return object;
-}
diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h
deleted file mode 100644
index 0956d1e..0000000
--- a/src/crepe/ComponentManager.h
+++ /dev/null
@@ -1,148 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <typeindex>
-#include <unordered_map>
-#include <vector>
-
-#include "api/Vector2.h"
-
-#include "Component.h"
-#include "types.h"
-
-namespace crepe {
-
-class GameObject;
-
-/**
- * \brief Manages all components
- *
- * This class manages all components. It provides methods to add, delete and get components.
- */
-class ComponentManager {
- // TODO: This relation should be removed! I (loek) believe that the scene manager should
- // create/destroy components because the GameObject's are stored in concrete Scene classes,
- // which will in turn call GameObject's destructor, which will in turn call
- // ComponentManager::delete_components_by_id or something. This is a pretty major change, so
- // here is a comment and temporary fix instead :tada:
- friend class SceneManager;
-
-public:
- ComponentManager(); // dbg_trace
- ~ComponentManager(); // dbg_trace
-
- /**
- * \brief Create a new game object using the component manager
- *
- * \param name Metadata::name (required)
- * \param tag Metadata::tag (optional, empty by default)
- * \param position Transform::position (optional, origin by default)
- * \param rotation Transform::rotation (optional, 0 by default)
- * \param scale Transform::scale (optional, 1 by default)
- *
- * \returns GameObject interface
- *
- * \note This method automatically assigns a new entity ID
- */
- GameObject new_object(const std::string & name, const std::string & tag = "",
- const Vector2 & position = {0, 0}, double rotation = 0,
- double scale = 1);
-
-protected:
- /**
- * GameObject is used as an interface to add/remove components, and the game programmer is
- * supposed to use it instead of interfacing with the component manager directly.
- */
- friend class GameObject;
- /**
- * \brief Add a component to the ComponentManager
- *
- * This method adds a component to the ComponentManager. The component is created with the
- * given arguments and added to the ComponentManager.
- *
- * \tparam T The type of the component
- * \tparam Args The types of the arguments
- * \param id The id of the GameObject this component belongs to
- * \param args The arguments to create the component
- * \return The created component
- */
- template <typename T, typename... Args>
- T & add_component(game_object_id_t id, Args &&... args);
- /**
- * \brief Delete all components of a specific type and id
- *
- * This method deletes all components of a specific type and id.
- *
- * \tparam T The type of the component
- * \param id The id of the GameObject this component belongs to
- */
- template <typename T>
- void delete_components_by_id(game_object_id_t id);
- /**
- * \brief Delete all components of a specific type
- *
- * This method deletes all components of a specific type.
- *
- * \tparam T The type of the component
- */
- template <typename T>
- void delete_components();
- /**
- * \brief Delete all components of a specific id
- *
- * This method deletes all components of a specific id.
- *
- * \param id The id of the GameObject this component belongs to
- */
- void delete_all_components_of_id(game_object_id_t id);
- /**
- * \brief Delete all components
- *
- * This method deletes all components.
- */
- void delete_all_components();
-
-public:
- /**
- * \brief Get all components of a specific type and id
- *
- * This method gets all components of a specific type and id.
- *
- * \tparam T The type of the component
- * \param id The id of the GameObject this component belongs to
- * \return A vector of all components of the specific type and id
- */
- template <typename T>
- RefVector<T> get_components_by_id(game_object_id_t id) const;
- /**
- * \brief Get all components of a specific type
- *
- * This method gets all components of a specific type.
- *
- * \tparam T The type of the component
- * \return A vector of all components of the specific type
- */
- template <typename T>
- RefVector<T> get_components_by_type() const;
-
-private:
- /**
- * \brief The components
- *
- * This unordered_map stores all components. The key is the type of the component and the
- * value is a vector of vectors of unique pointers to the components.
- *
- * Every component type has its own vector of vectors of unique pointers to the components.
- * The first vector is for the ids of the GameObjects and the second vector is for the
- * components (because a GameObject might have multiple components).
- */
- std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>>
- components;
-
- //! ID of next GameObject allocated by \c ComponentManager::new_object
- game_object_id_t next_id = 0;
-};
-
-} // namespace crepe
-
-#include "ComponentManager.hpp"
diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp
index 1068cbf..27fa97f 100644
--- a/src/crepe/Particle.cpp
+++ b/src/crepe/Particle.cpp
@@ -2,8 +2,9 @@
using namespace crepe;
-void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity,
- double angle) {
+void Particle::reset(
+ unsigned int lifespan, const vec2 & position, const vec2 & velocity, float angle
+) {
// Initialize the particle state
this->time_in_life = 0;
this->lifespan = lifespan;
@@ -15,16 +16,17 @@ void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2
this->force_over_time = {0, 0};
}
-void Particle::update() {
+void Particle::update(double dt) {
// Deactivate particle if it has exceeded its lifespan
- if (++time_in_life >= lifespan) {
+ time_in_life += dt;
+ if (time_in_life >= lifespan) {
this->active = false;
return;
}
// Update velocity based on accumulated force and update position
- this->velocity += force_over_time;
- this->position += velocity;
+ this->velocity += force_over_time * dt;
+ this->position += velocity * dt;
}
void Particle::stop_movement() {
diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h
index 19859fe..c013de5 100644
--- a/src/crepe/Particle.h
+++ b/src/crepe/Particle.h
@@ -2,7 +2,7 @@
#include <cstdint>
-#include "api/Vector2.h"
+#include "types.h"
namespace crepe {
@@ -14,23 +14,21 @@ namespace crepe {
* can also be reset or stopped.
*/
class Particle {
- // TODO: add friend particleSsytem and rendersystem. Unit test will fail.
-
public:
//! Position of the particle in 2D space.
- Vector2 position;
+ vec2 position;
//! Velocity vector indicating the speed and direction of the particle.
- Vector2 velocity;
+ vec2 velocity;
//! Accumulated force affecting the particle over time.
- Vector2 force_over_time;
+ vec2 force_over_time;
//! Total lifespan of the particle in milliseconds.
- uint32_t lifespan;
+ float lifespan;
//! Active state of the particle; true if it is in use, false otherwise.
bool active = false;
//! The time the particle has been alive, in milliseconds.
- uint32_t time_in_life = 0;
+ float time_in_life = 0;
//! The angle at which the particle is oriented or moving.
- double angle = 0;
+ float angle = 0;
/**
* \brief Resets the particle with new properties.
@@ -43,15 +41,16 @@ public:
* \param velocity The initial velocity of the particle.
* \param angle The angle of the particle's trajectory or orientation.
*/
- void reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity,
- double angle);
+ void
+ reset(unsigned int lifespan, const vec2 & position, const vec2 & velocity, float angle);
/**
* \brief Updates the particle's state.
*
* Advances the particle's position based on its velocity and applies accumulated forces.
* Deactivates the particle if its lifespan has expired.
+ * \param dt The amount of fixed delta time that has passed.
*/
- void update();
+ void update(double dt);
/**
* \brief Stops the particle's movement.
*
diff --git a/src/crepe/Resource.cpp b/src/crepe/Resource.cpp
new file mode 100644
index 0000000..85913ed
--- /dev/null
+++ b/src/crepe/Resource.cpp
@@ -0,0 +1,6 @@
+#include "Resource.h"
+#include "manager/Mediator.h"
+
+using namespace crepe;
+
+Resource::Resource(const Asset & asset, Mediator & mediator) {}
diff --git a/src/crepe/Resource.h b/src/crepe/Resource.h
new file mode 100644
index 0000000..d65206b
--- /dev/null
+++ b/src/crepe/Resource.h
@@ -0,0 +1,30 @@
+#pragma once
+
+namespace crepe {
+
+class ResourceManager;
+class Asset;
+class Mediator;
+
+/**
+ * \brief Resource interface
+ *
+ * Resource is an interface class used to represent a (deserialized) game resource (e.g.
+ * textures, sounds). Resources are always created from \ref Asset "assets" by ResourceManager.
+ *
+ * The game programmer has the ability to use the ResourceManager to keep instances of concrete
+ * resources between scenes, preventing them from being reinstantiated during a scene
+ * transition.
+ */
+class Resource {
+public:
+ Resource(const Asset & src, Mediator & mediator);
+ virtual ~Resource() = default;
+
+ Resource(const Resource &) = delete;
+ Resource(Resource &&) = delete;
+ Resource & operator=(const Resource &) = delete;
+ Resource & operator=(Resource &&) = delete;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/AI.cpp b/src/crepe/api/AI.cpp
new file mode 100644
index 0000000..2fedaf4
--- /dev/null
+++ b/src/crepe/api/AI.cpp
@@ -0,0 +1,98 @@
+#include <stdexcept>
+#include <type_traits>
+
+#include "AI.h"
+#include "types.h"
+
+namespace crepe {
+
+AI::AI(game_object_id_t id, float max_force) : Component(id), max_force(max_force) {}
+
+void AI::make_circle_path(
+ float radius, const vec2 & center, float start_angle, bool clockwise
+) {
+ if (radius <= 0) {
+ throw std::runtime_error("Radius must be greater than 0");
+ }
+
+ // The step size is determined by the radius (step size is in radians)
+ float step = RADIUS_TO_STEP / radius;
+ // Force at least MIN_STEP steps (in case of a small radius)
+ if (step > 2 * M_PI / MIN_STEP) {
+ step = 2 * M_PI / MIN_STEP;
+ }
+ // The path node distance is determined by the step size and the radius
+ this->path_node_distance = radius * step * PATH_NODE_DISTANCE_FACTOR;
+
+ if (clockwise) {
+ for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) {
+ path.push_back(vec2 {
+ static_cast<float>(center.x + radius * cos(i)),
+ static_cast<float>(center.y + radius * sin(i))
+ });
+ }
+ } else {
+ for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) {
+ path.push_back(vec2 {
+ static_cast<float>(center.x + radius * cos(i)),
+ static_cast<float>(center.y + radius * sin(i))
+ });
+ }
+ }
+}
+
+void AI::make_oval_path(
+ float radius_x, float radius_y, const vec2 & center, float start_angle, bool clockwise,
+ float rotation
+) {
+ if (radius_x <= 0 && radius_y <= 0) {
+ throw std::runtime_error("Radius must be greater than 0");
+ }
+
+ float max_radius = std::max(radius_x, radius_y);
+ // The step size is determined by the radius (step size is in radians)
+ float step = RADIUS_TO_STEP / max_radius;
+ // Force at least MIN_STEP steps (in case of a small radius)
+ if (step > 2 * M_PI / MIN_STEP) {
+ step = 2 * M_PI / MIN_STEP;
+ }
+ // The path node distance is determined by the step size and the radius
+ this->path_node_distance = max_radius * step * PATH_NODE_DISTANCE_FACTOR;
+
+ std::function<vec2(vec2, vec2)> rotate_point = [rotation](vec2 point, vec2 center) {
+ float s = sin(rotation);
+ float c = cos(rotation);
+
+ // Translate point back to origin
+ point.x -= center.x;
+ point.y -= center.y;
+
+ // Rotate point
+ float xnew = point.x * c - point.y * s;
+ float ynew = point.x * s + point.y * c;
+
+ // Translate point back
+ point.x = xnew + center.x;
+ point.y = ynew + center.y;
+
+ return point;
+ };
+
+ if (clockwise) {
+ for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) {
+ vec2 point
+ = {static_cast<float>(center.x + radius_x * cos(i)),
+ static_cast<float>(center.y + radius_y * sin(i))};
+ path.push_back(rotate_point(point, center));
+ }
+ } else {
+ for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) {
+ vec2 point
+ = {static_cast<float>(center.x + radius_x * cos(i)),
+ static_cast<float>(center.y + radius_y * sin(i))};
+ path.push_back(rotate_point(point, center));
+ }
+ }
+}
+
+} // namespace crepe
diff --git a/src/crepe/api/AI.h b/src/crepe/api/AI.h
new file mode 100644
index 0000000..bee11b3
--- /dev/null
+++ b/src/crepe/api/AI.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "Component.h"
+#include "types.h"
+
+namespace crepe {
+
+/**
+ * \brief The AI component is used to control the movement of an entity using AI.
+ *
+ * The AI component can be used to control the movement of an entity. The AI component can be used
+ * to implement different behaviors such as seeking, fleeing, arriving, and path following.
+ */
+class AI : public Component {
+public:
+ //! The different types of behaviors that can be used
+ enum BehaviorTypeMask {
+ SEEK = 0x00002,
+ FLEE = 0x00004,
+ ARRIVE = 0x00008,
+ PATH_FOLLOW = 0x00010,
+ };
+
+public:
+ /**
+ * \param id The id of the game object
+ * \param max_force The maximum force that can be applied to the entity
+ */
+ AI(game_object_id_t id, float max_force);
+
+ /**
+ * \brief Check if a behavior is on (aka activated)
+ *
+ * \param behavior The behavior to check
+ * \return true if the behavior is on, false otherwise
+ */
+ bool on(BehaviorTypeMask behavior) const { return (flags & behavior); }
+ //! Turn on the seek behavior
+ void seek_on() { flags |= SEEK; }
+ //! Turn off the seek behavior
+ void seek_off() { flags &= ~SEEK; }
+ //! Turn on the flee behavior
+ void flee_on() { flags |= FLEE; }
+ //! Turn off the flee behavior
+ void flee_off() { flags &= ~FLEE; }
+ //! Turn on the arrive behavior
+ void arrive_on() { flags |= ARRIVE; }
+ //! Turn off the arrive behavior
+ void arrive_off() { flags &= ~ARRIVE; }
+ //! Turn on the path follow behavior
+ void path_follow_on() { flags |= PATH_FOLLOW; }
+ //! Turn off the path follow behavior
+ void path_follow_off() { flags &= ~PATH_FOLLOW; }
+
+ /**
+ * \brief Add a path node (for the path following behavior)
+ *
+ * \note The path is not relative to the entity's position (it is an absolute path)
+ *
+ * \param node The path node to add
+ */
+ void add_path_node(const vec2 & node) { path.push_back(node); }
+ /**
+ * \brief Make a circle path (for the path following behavior)
+ *
+ * \note The path is not relative to the entity's position (it is an absolute path)
+ *
+ * \param radius The radius of the circle (in game units)
+ * \param center The center of the circle (in game units)
+ * \param start_angle The start angle of the circle (in radians)
+ * \param clockwise The direction of the circle
+ */
+ void make_circle_path(
+ float radius, const vec2 & center = {0, 0}, float start_angle = 0,
+ bool clockwise = true
+ );
+ /**
+ * \brief Make an oval path (for the path following behavior)
+ *
+ * \note The path is not relative to the entity's position (it is an absolute path)
+ *
+ * \param radius_x The x radius of the oval (in game units)
+ * \param radius_y The y radius of the oval (in game units)
+ * \param center The center of the oval (in game units)
+ * \param start_angle The start angle of the oval (in radians)
+ * \param clockwise The direction of the oval
+ * \param rotation The rotation of the oval (in radians)
+ */
+ void make_oval_path(
+ float radius_x, float radius_y, const vec2 & center = {0, 0}, float start_angle = 0,
+ bool clockwise = true, float rotation = 0
+ );
+
+public:
+ //! The maximum force that can be applied to the entity (higher values will make the entity adjust faster)
+ float max_force;
+
+ //! The target to seek at
+ vec2 seek_target;
+ //! The target to arrive at
+ vec2 arrive_target;
+ //! The target to flee from
+ vec2 flee_target;
+ //! The distance at which the entity will start to flee from the target
+ float square_flee_panic_distance = 200.0f * 200.0f;
+ //! The deceleration rate for the arrive behavior (higher values will make the entity decelerate faster (less overshoot))
+ float arrive_deceleration = 40.0f;
+ //! The path to follow (for the path following behavior)
+ std::vector<vec2> path;
+ //! The distance from the path node at which the entity will move to the next node (automatically set by make_circle_path())
+ float path_node_distance = 400.0f;
+ //! Looping behavior for the path
+ bool path_loop = true;
+
+private:
+ //! The flags for the behaviors
+ int flags = 0;
+ //! The current path index
+ size_t path_index = 0;
+
+ //! The AISystem is the only class that should access the flags and path_index variables
+ friend class AISystem;
+
+ //! The minimum amount of steps for the path following behavior
+ static constexpr int MIN_STEP = 16;
+ //! The radius to step size ratio for the path following behavior
+ static constexpr float RADIUS_TO_STEP = 400.0f;
+ //! The path node distance factor for the path following behavior
+ static constexpr float PATH_NODE_DISTANCE_FACTOR = 0.75f;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp
index 464b0fd..c558d86 100644
--- a/src/crepe/api/Animator.cpp
+++ b/src/crepe/api/Animator.cpp
@@ -1,5 +1,5 @@
-#include "util/Log.h"
+#include "util/dbg.h"
#include "Animator.h"
#include "Component.h"
@@ -7,18 +7,53 @@
using namespace crepe;
-Animator::Animator(game_object_id_t id, Sprite & ss, int row, int col, int col_animator)
+Animator::Animator(
+ game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size,
+ const uvec2 & grid_size, const Animator::Data & data
+)
: Component(id),
- spritesheet(ss),
- row(row),
- col(col) {
+ spritesheet(spritesheet),
+ grid_size(grid_size),
+ data(data) {
dbg_trace();
- animator_rect = spritesheet.sprite_rect;
- animator_rect.h /= col;
- animator_rect.w /= row;
- animator_rect.x = 0;
- animator_rect.y = col_animator * animator_rect.h;
- this->active = false;
+ this->spritesheet.mask.w = single_frame_size.x;
+ this->spritesheet.mask.h = single_frame_size.y;
+ this->spritesheet.mask.x = 0;
+ this->spritesheet.mask.y = 0;
+
+ this->spritesheet.aspect_ratio
+ = static_cast<float>(single_frame_size.x) / single_frame_size.y;
}
+
Animator::~Animator() { dbg_trace(); }
+
+void Animator::loop() { this->data.looping = true; }
+
+void Animator::play() { this->active = true; }
+
+void Animator::pause() { this->active = false; }
+
+void Animator::stop() {
+ this->active = false;
+ this->data.col = 0;
+ this->data.row = 0;
+}
+void Animator::set_fps(int fps) { this->data.fps = fps; }
+
+void Animator::set_cycle_range(int start, int end) {
+ this->data.cycle_start = start, this->data.cycle_end = end;
+}
+
+void Animator::set_anim(int col) {
+ Animator::Data & ctx = this->data;
+ this->spritesheet.mask.x = ctx.row = 0;
+ ctx.col = col;
+ this->spritesheet.mask.y = ctx.col * this->spritesheet.mask.h;
+}
+
+void Animator::next_anim() {
+ Animator::Data & ctx = this->data;
+ ctx.row = ++ctx.row % this->grid_size.x;
+ this->spritesheet.mask.x = ctx.row * this->spritesheet.mask.w;
+}
diff --git a/src/crepe/api/Animator.h b/src/crepe/api/Animator.h
index 53f4b91..95539d3 100644
--- a/src/crepe/api/Animator.h
+++ b/src/crepe/api/Animator.h
@@ -1,5 +1,8 @@
#pragma once
+#include "../manager/LoopTimerManager.h"
+#include "../types.h"
+
#include "Component.h"
#include "Sprite.h"
@@ -16,62 +19,95 @@ class SDLContext;
* sheet. It can be used to play animations, loop them, or stop them.
*/
class Animator : public Component {
+public:
+ struct Data {
+ //! frames per second for animation
+ unsigned int fps = 1;
+ //! The current col being animated.
+ unsigned int col = 0;
+ //! The current row being animated.
+ unsigned int row = 0;
+ //! should the animation loop
+ bool looping = false;
+ //! starting frame for cycling
+ unsigned int cycle_start = 0;
+ //! end frame for cycling (-1 = use last frame)
+ int cycle_end = -1;
+ };
public:
- //TODO: need to implement this
+ //! Animator will repeat the animation
void loop();
+ //! starts the animation
+ void play();
+ //! pauses the animation
+ void pause();
+ /**
+ * \brief stops the animation
+ *
+ * sets the active on false and resets all the current rows and columns
+ */
void stop();
+ /**
+ * \brief set frames per second
+ *
+ * \param fps frames per second
+ */
+ void set_fps(int fps);
+ /**
+ * \brief set the range in the row
+ *
+ * \param start of row animation
+ * \param end of row animation
+ */
+ void set_cycle_range(int start, int end);
+ /**
+ * \brief select which column to animate from
+ *
+ * \param col animation column
+ */
+ void set_anim(int col);
+ //! will go to the next animaiton of current row
+ void next_anim();
public:
/**
* \brief Constructs an Animator object that will control animations for a sprite sheet.
*
* \param id The unique identifier for the component, typically assigned automatically.
- * \param spritesheet A reference to the Sprite object which holds the sprite sheet for
- * animation.
- * \param row The maximum number of rows in the sprite sheet.
- * \param col The maximum number of columns in the sprite sheet.
- * \param col_animate The specific col index of the sprite sheet to animate. This allows
- * selecting which col to animate from multiple col in the sheet.
+ * \param spritesheet the reference to the spritesheet
+ * \param single_frame_size the width and height in pixels of a single frame inside the
+ * spritesheet
+ * \param grid_size the max rows and columns inside the given spritesheet
+ * \param data extra animation data for more control
*
* This constructor sets up the Animator with the given parameters, and initializes the
* animation system.
*/
- Animator(uint32_t id, Sprite & spritesheet, int row, int col, int col_animate);
-
+ Animator(
+ game_object_id_t id, Sprite & spritesheet, const ivec2 & single_frame_size,
+ const uvec2 & grid_size, const Animator::Data & data
+ );
~Animator(); // dbg_trace
- Animator(const Animator &) = delete;
- Animator(Animator &&) = delete;
- Animator & operator=(const Animator &) = delete;
- Animator & operator=(Animator &&) = delete;
+
+public:
+ Animator::Data data;
private:
- //! A reference to the Sprite sheet containing the animation frames.
+ //! A reference to the Sprite sheet containing.
Sprite & spritesheet;
- //! The maximum number of columns in the sprite sheet.
- const int col;
-
- //! The maximum number of rows in the sprite sheet.
- const int row;
+ //! The maximum number of rows and columns inside the spritesheet
+ const uvec2 grid_size;
- //! The current col being animated.
- int curr_col = 0;
+ // the time elapsed from a frame duration
+ duration_t elapsed_time = {};
- //! The current row being animated.
- int curr_row = 0;
+ // frame counter
+ unsigned int frame = 0;
- Rect animator_rect;
-
- //TODO: Is this necessary?
- //int fps;
-
-private:
- //! AnimatorSystem adjust the private member parameters of Animator;
- friend class AnimatorSystem;
-
- //! SDLContext reads the Animator member var's
- friend class SDLContext;
+ //! Uses the spritesheet
+ friend AnimatorSystem;
};
+
} // namespace crepe
-//
diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp
index e148367..bab82e7 100644
--- a/src/crepe/api/Asset.cpp
+++ b/src/crepe/api/Asset.cpp
@@ -50,5 +50,5 @@ string Asset::whereami() const noexcept {
bool Asset::operator==(const Asset & other) const noexcept { return this->src == other.src; }
size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept {
- return std::hash<string>{}(asset.get_path());
+ return std::hash<string> {}(asset.get_path());
};
diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h
index bfd0ac7..d802e83 100644
--- a/src/crepe/api/Asset.h
+++ b/src/crepe/api/Asset.h
@@ -43,13 +43,13 @@ private:
/**
* \brief Locate asset path, or throw exception if it cannot be found
*
- * This function resolves asset locations relative to crepe::Config::root_pattern if it is
+ * This function resolves asset locations relative to Config::asset::root_pattern if it is
* set and \p src is a relative path. If \p src is an absolute path, it is canonicalized.
* This function only returns if the file can be found.
*
* \param src Arbitrary path to resource file
*
- * \returns \p src if crepe::Config::root_pattern is empty
+ * \returns \p src if Config::asset::root_pattern is empty
* \returns Canonical path to \p src
*
* \throws std::runtime_error if root_pattern cannot be found
diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp
deleted file mode 100644
index 3925758..0000000
--- a/src/crepe/api/AssetManager.cpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "util/Log.h"
-
-#include "AssetManager.h"
-
-using namespace crepe;
-
-AssetManager & AssetManager::get_instance() {
- static AssetManager instance;
- return instance;
-}
-
-AssetManager::~AssetManager() {
- dbg_trace();
- this->asset_cache.clear();
-}
-
-AssetManager::AssetManager() { dbg_trace(); }
diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h
deleted file mode 100644
index fee6780..0000000
--- a/src/crepe/api/AssetManager.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-
-#include <any>
-#include <memory>
-#include <string>
-#include <unordered_map>
-
-namespace crepe {
-
-/**
- * \brief The AssetManager is responsible for storing and managing assets over multiple scenes.
- *
- * The AssetManager ensures that assets are loaded once and can be accessed across different
- * scenes. It caches assets to avoid reloading them every time a scene is loaded. Assets are
- * retained in memory until the AssetManager is destroyed, at which point the cached assets are
- * cleared.
- */
-class AssetManager {
-
-private:
- //! A cache that holds all the assets, accessible by their file path, over multiple scenes.
- std::unordered_map<std::string, std::any> asset_cache;
-
-private:
- AssetManager();
- virtual ~AssetManager();
-
-public:
- AssetManager(const AssetManager &) = delete;
- AssetManager(AssetManager &&) = delete;
- AssetManager & operator=(const AssetManager &) = delete;
- AssetManager & operator=(AssetManager &&) = delete;
-
- /**
- * \brief Retrieves the singleton instance of the AssetManager.
- *
- * \return A reference to the single instance of the AssetManager.
- */
- static AssetManager & get_instance();
-
-public:
- /**
- * \brief Caches an asset by loading it from the given file path.
- *
- * \param file_path The path to the asset file to load.
- * \param reload If true, the asset will be reloaded from the file, even if it is already
- * cached.
- * \tparam T The type of asset to cache (e.g., texture, sound, etc.).
- *
- * \return A shared pointer to the cached asset.
- *
- * This template function caches the asset at the given file path. If the asset is already
- * cached and `reload` is false, the existing cached version will be returned. Otherwise, the
- * asset will be reloaded and added to the cache.
- */
- template <typename T>
- std::shared_ptr<T> cache(const std::string & file_path, bool reload = false);
-};
-
-} // namespace crepe
-
-#include "AssetManager.hpp"
diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp
deleted file mode 100644
index 1c0e978..0000000
--- a/src/crepe/api/AssetManager.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "AssetManager.h"
-
-namespace crepe {
-
-template <typename asset>
-std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, bool reload) {
- auto it = asset_cache.find(file_path);
-
- if (!reload && it != asset_cache.end()) {
- return std::any_cast<std::shared_ptr<asset>>(it->second);
- }
-
- std::shared_ptr<asset> new_asset = std::make_shared<asset>(file_path.c_str());
-
- asset_cache[file_path] = new_asset;
-
- return new_asset;
-}
-
-} // namespace crepe
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
new file mode 100644
index 0000000..7b05cb1
--- /dev/null
+++ b/src/crepe/api/AudioSource.cpp
@@ -0,0 +1,15 @@
+#include "AudioSource.h"
+
+using namespace crepe;
+using namespace std;
+
+AudioSource::AudioSource(game_object_id_t id, const Asset & src)
+ : Component(id),
+ source(src) {}
+
+void AudioSource::play(bool looping) {
+ this->loop = looping;
+ this->oneshot_play = true;
+}
+
+void AudioSource::stop() { this->oneshot_stop = true; }
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
new file mode 100644
index 0000000..eaa56e8
--- /dev/null
+++ b/src/crepe/api/AudioSource.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "../Component.h"
+#include "../facade/SoundHandle.h"
+#include "../types.h"
+
+#include "Asset.h"
+#include "GameObject.h"
+
+namespace crepe {
+
+class AudioSystem;
+
+//! Audio source component
+class AudioSource : public Component {
+ //! AudioSource components are handled by AudioSystem
+ friend class AudioSystem;
+
+protected:
+ /**
+ * \param source Sound sample to load
+ */
+ AudioSource(game_object_id_t id, const Asset & source);
+ //! Only ComponentManager creates components
+ friend class ComponentManager;
+
+public:
+ // std::unique_ptr needs to be able to destoy this component
+ virtual ~AudioSource() = default;
+
+public:
+ //! Start this audio source
+ void play(bool looping = false);
+ //! Stop this audio source
+ void stop();
+
+public:
+ //! Play when this component becomes active
+ bool play_on_awake = false;
+ //! Repeat the current audio clip during playback
+ bool loop = false;
+ //! Normalized volume (0.0 - 1.0)
+ float volume = 1.0;
+
+private:
+ //! This audio source's clip
+ const Asset source;
+
+ /**
+ * \name One-shot state variables
+ *
+ * These variables trigger function calls when set to true, and are unconditionally reset on
+ * every system update.
+ *
+ * \{
+ */
+ //! Play this sample
+ bool oneshot_play = false;
+ //! Stop this sample
+ bool oneshot_stop = false;
+ //! \}
+ /**
+ * \name State diffing variables
+ * \{
+ */
+ typeof(active) last_active = false;
+ typeof(volume) last_volume = volume;
+ typeof(loop) last_loop = loop;
+ //! \}
+ //! This source's voice handle
+ SoundHandle voice {};
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp
index 7bbace0..af7572c 100644
--- a/src/crepe/api/BehaviorScript.cpp
+++ b/src/crepe/api/BehaviorScript.cpp
@@ -4,12 +4,12 @@
using namespace crepe;
-BehaviorScript::BehaviorScript(game_object_id_t id, ComponentManager & mgr)
+BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator)
: Component(id),
- component_manager(mgr) {}
+ mediator(mediator) {}
template <>
BehaviorScript & GameObject::add_component<BehaviorScript>() {
- ComponentManager & mgr = this->component_manager;
- return mgr.add_component<BehaviorScript>(this->id, mgr);
+ ComponentManager & mgr = this->mediator.component_manager;
+ return mgr.add_component<BehaviorScript>(this->id, this->mediator);
}
diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h
index 9d85d4c..3909b96 100644
--- a/src/crepe/api/BehaviorScript.h
+++ b/src/crepe/api/BehaviorScript.h
@@ -23,14 +23,13 @@ class BehaviorScript : public Component {
protected:
/**
* \param id Parent \c GameObject id
- * \param component_manager Reference to component manager (passed through to \c Script
- * instance)
+ * \param mediator Mediator reference
*
* \note Calls to this constructor (should) always pass through \c GameObject::add_component,
* which has an exception for this specific component type. This was done so the user does
* not have to pass references used within \c Script to each \c BehaviorScript instance.
*/
- BehaviorScript(game_object_id_t id, ComponentManager & component_manager);
+ BehaviorScript(game_object_id_t id, Mediator & mediator);
//! Only ComponentManager is allowed to instantiate BehaviorScript
friend class ComponentManager;
@@ -39,11 +38,14 @@ public:
* \brief Set the concrete script of this component
*
* \tparam T Concrete script type (derived from \c crepe::Script)
+ * \tparam Args Arguments for concrete script constructor
+ *
+ * \param args Arguments for concrete script constructor (forwarded using perfect forwarding)
*
* \returns Reference to BehaviorScript component (`*this`)
*/
- template <class T>
- BehaviorScript & set_script();
+ template <class T, typename... Args>
+ BehaviorScript & set_script(Args &&... args);
protected:
//! Script instance
@@ -52,8 +54,8 @@ protected:
friend class ScriptSystem;
protected:
- //! Reference to component manager (passed to Script)
- ComponentManager & component_manager;
+ //! Reference mediator
+ Mediator & mediator;
};
/**
diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp
index d80321d..353d5e2 100644
--- a/src/crepe/api/BehaviorScript.hpp
+++ b/src/crepe/api/BehaviorScript.hpp
@@ -2,21 +2,20 @@
#include <type_traits>
-#include "../util/Log.h"
-
#include "BehaviorScript.h"
#include "Script.h"
namespace crepe {
-template <class T>
-BehaviorScript & BehaviorScript::set_script() {
- dbg_trace();
+template <class T, typename... Args>
+BehaviorScript & BehaviorScript::set_script(Args &&... args) {
static_assert(std::is_base_of<Script, T>::value);
- Script * s = new T();
- s->game_object_id = this->game_object_id;
- s->component_manager_ref = &this->component_manager;
- this->script = std::unique_ptr<Script>(s);
+ this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...));
+
+ this->script->game_object_id = this->game_object_id;
+ this->script->active = this->active;
+ this->script->mediator = this->mediator;
+
return *this;
}
diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp
new file mode 100644
index 0000000..f6b358d
--- /dev/null
+++ b/src/crepe/api/BoxCollider.cpp
@@ -0,0 +1,11 @@
+#include "BoxCollider.h"
+
+#include "../Collider.h"
+
+using namespace crepe;
+
+BoxCollider::BoxCollider(
+ game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset
+)
+ : Collider(game_object_id, offset),
+ dimensions(dimensions) {}
diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h
new file mode 100644
index 0000000..229b90f
--- /dev/null
+++ b/src/crepe/api/BoxCollider.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "../Collider.h"
+#include "Vector2.h"
+#include "types.h"
+
+namespace crepe {
+
+/**
+ * \brief A class representing a box-shaped collider.
+ *
+ * This class is used for collision detection with other colliders (e.g., CircleCollider).
+ */
+class BoxCollider : public Collider {
+public:
+ BoxCollider(
+ game_object_id_t game_object_id, const vec2 & dimensions, const vec2 & offset = {0, 0}
+ );
+
+ //! Width and height of the box collider
+ vec2 dimensions;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/Button.cpp b/src/crepe/api/Button.cpp
new file mode 100644
index 0000000..8eadd89
--- /dev/null
+++ b/src/crepe/api/Button.cpp
@@ -0,0 +1,11 @@
+#include "Button.h"
+
+namespace crepe {
+
+Button::Button(
+ game_object_id_t id, const vec2 & dimensions, const Data & data, const vec2 & offset
+)
+ : UIObject(id, dimensions, offset),
+ data(data) {}
+
+} // namespace crepe
diff --git a/src/crepe/api/Button.h b/src/crepe/api/Button.h
new file mode 100644
index 0000000..e986c04
--- /dev/null
+++ b/src/crepe/api/Button.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "../types.h"
+
+#include "UIObject.h"
+
+namespace crepe {
+
+/**
+ * \brief Button component.
+ *
+ * This component creates a clickable surface at the transform location with the specified width and height.
+ *
+ * The Button can be used in scripts by subscribing a EventHandler to the following events:
+ * - ButtonPressEvent
+ * - ButtonEnterEvent
+ * - ButtonExitEvent
+ * \see EventManager
+ *
+ */
+class Button : public UIObject {
+public:
+ struct Data {
+ //! variable indicating if transform is relative to camera(false) or world(true)
+ bool world_space = false;
+ };
+
+public:
+ /**
+ * \brief Constructs a Button with the specified game object ID and dimensions.
+ *
+ * \param id The unique ID of the game object associated with this button.
+ * \param dimensions The width and height of the UIObject
+ * \param offset The offset relative this GameObjects Transform
+ * \param data additional data the button has
+ */
+ Button(
+ game_object_id_t id, const vec2 & dimensions, const Data & data,
+ const vec2 & offset = {0, 0}
+ );
+ /**
+ * \brief Get the maximum number of instances for this component
+ *
+ * Since the button Event transfers the GameObject Metadata it will be the same for each button so only one button is allowed per GameObject
+ *
+ * \return 1
+ */
+ virtual int get_instances_max() const { return 1; }
+
+public:
+ Data data;
+
+private:
+ //! friend relation hover variable
+ friend class InputSystem;
+ //! Indicates whether the mouse is currently hovering over the button
+ bool hover = false;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index d6b6801..2bee3fb 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -1,33 +1,31 @@
target_sources(crepe PUBLIC
- # AudioSource.cpp
+ AudioSource.cpp
BehaviorScript.cpp
GameObject.cpp
Rigidbody.cpp
ParticleEmitter.cpp
Transform.cpp
Color.cpp
- Texture.cpp
- AssetManager.cpp
Sprite.cpp
- SaveManager.cpp
Config.cpp
Metadata.cpp
- Scene.cpp
- SceneManager.cpp
- Vector2.cpp
Camera.cpp
Animator.cpp
- EventManager.cpp
- IKeyListener.cpp
- IMouseListener.cpp
- LoopManager.cpp
- LoopTimer.cpp
+ BoxCollider.cpp
+ CircleCollider.cpp
+ Engine.cpp
Asset.cpp
EventHandler.cpp
+ Script.cpp
+ Button.cpp
+ UIObject.cpp
+ AI.cpp
+ Text.cpp
+ Scene.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
- # AudioSource.h
+ AudioSource.h
BehaviorScript.h
Config.h
Script.h
@@ -37,25 +35,23 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
Rigidbody.h
Sprite.h
Vector2.h
+ Vector2.hpp
Color.h
- Texture.h
- AssetManager.h
- AssetManager.hpp
- SaveManager.h
Scene.h
+ Scene.hpp
Metadata.h
- SceneManager.h
- SceneManager.hpp
Camera.h
Animator.h
- EventManager.h
- EventManager.hpp
+ BoxCollider.h
+ CircleCollider.h
EventHandler.h
EventHandler.hpp
Event.h
- IKeyListener.h
- IMouseListener.h
- LoopManager.h
- LoopTimer.h
+ Engine.h
+ Engine.hpp
Asset.h
+ Button.h
+ UIObject.h
+ AI.h
+ Text.h
)
diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp
index 5835bdd..b1466b5 100644
--- a/src/crepe/api/Camera.cpp
+++ b/src/crepe/api/Camera.cpp
@@ -1,14 +1,18 @@
-#include "util/Log.h"
+#include "util/dbg.h"
#include "Camera.h"
-#include "Color.h"
#include "Component.h"
+#include "types.h"
using namespace crepe;
-Camera::Camera(game_object_id_t id, const Color & bg_color)
+Camera::Camera(
+ game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size, const Data & data
+)
: Component(id),
- bg_color(bg_color) {
+ screen(screen),
+ viewport_size(viewport_size),
+ data(data) {
dbg_trace();
}
diff --git a/src/crepe/api/Camera.h b/src/crepe/api/Camera.h
index e0cda34..3191b04 100644
--- a/src/crepe/api/Camera.h
+++ b/src/crepe/api/Camera.h
@@ -2,6 +2,7 @@
#include "Color.h"
#include "Component.h"
+#include "types.h"
namespace crepe {
@@ -13,40 +14,56 @@ namespace crepe {
* position, and zoom level. It controls what part of the game world is visible on the screen.
*/
class Camera : public Component {
+public:
+ struct Data {
+ /**
+ * \bg_color background color of the game
+ *
+ * This will make the background the same color as the given value.
+ */
+ const Color bg_color = Color::BLACK;
+
+ /**
+ * \zoom Zooming level of the game
+ *
+ * zoom = 1 --> no zoom.
+ * zoom < 1 --> zoom out
+ * zoom > 1 --> zoom in
+ */
+ double zoom = 1;
+
+ //! offset postion from the game object transform component
+ vec2 postion_offset;
+ };
public:
/**
* \brief Constructs a Camera with the specified ID and background color.
* \param id Unique identifier for the camera component.
- * \param bg_color Background color for the camera view.
+ * \param screen is the actual screen size in pixels
+ * \param viewport_size is the view of the world in game units
+ * \param data the camera component data
*/
- Camera(game_object_id_t id, const Color & bg_color);
+ Camera(
+ game_object_id_t id, const ivec2 & screen, const vec2 & viewport_size,
+ const Camera::Data & data
+ );
~Camera(); // dbg_trace only
public:
- //! Background color of the camera view.
- Color bg_color;
-
- //! Aspect ratio height for the camera.
- double aspect_height = 480;
-
- //! Aspect ratio width for the camera.
- double aspect_width = 640;
-
- //! X-coordinate of the camera position.
- double x = 0.0;
+ Camera::Data data;
- //! Y-coordinate of the camera position.
- double y = 0.0;
+ //! screen the display size in pixels ( output resolution )
+ const ivec2 screen;
- //! Zoom level of the camera view.
- double zoom = 1.0;
+ //! viewport is the area of the world visible through the camera (in world units)
+ const vec2 viewport_size;
public:
/**
* \brief Gets the maximum number of camera instances allowed.
* \return Maximum instance count as an integer.
*/
- virtual int get_instances_max() const { return 10; }
+ virtual int get_instances_max() const { return 1; }
};
} // namespace crepe
diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp
new file mode 100644
index 0000000..e72800c
--- /dev/null
+++ b/src/crepe/api/CircleCollider.cpp
@@ -0,0 +1,9 @@
+#include "CircleCollider.h"
+
+using namespace crepe;
+
+CircleCollider::CircleCollider(
+ game_object_id_t game_object_id, float radius, const vec2 & offset
+)
+ : Collider(game_object_id, offset),
+ radius(radius) {}
diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h
index e77a592..e6ad4fa 100644
--- a/src/crepe/api/CircleCollider.h
+++ b/src/crepe/api/CircleCollider.h
@@ -1,14 +1,24 @@
#pragma once
+
+#include "Vector2.h"
+
#include "../Collider.h"
namespace crepe {
+/**
+ * \brief A class representing a circle-shaped collider.
+ *
+ * This class is used for collision detection with other colliders (e.g., BoxCollider).
+ */
class CircleCollider : public Collider {
public:
- CircleCollider(game_object_id_t game_object_id, int radius)
- : Collider(game_object_id),
- radius(radius) {}
- int radius;
+ CircleCollider(
+ game_object_id_t game_object_id, float radius, const vec2 & offset = {0, 0}
+ );
+
+ //! Radius of the circle collider.
+ float radius;
};
} // namespace crepe
diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp
index 29bd77a..d0e3b35 100644
--- a/src/crepe/api/Color.cpp
+++ b/src/crepe/api/Color.cpp
@@ -2,11 +2,13 @@
using namespace crepe;
-const Color Color::WHITE{0xff, 0xff, 0xff};
-const Color Color::RED{0xff, 0x00, 0x00};
-const Color Color::GREEN{0x00, 0xff, 0x00};
-const Color Color::BLUE{0x00, 0x00, 0xff};
-const Color Color::BLACK{0x00, 0x00, 0x00};
-const Color Color::CYAN{0x00, 0xff, 0xff};
-const Color Color::YELLOW{0xff, 0xff, 0x00};
-const Color Color::MAGENTA{0xff, 0x00, 0xff};
+const Color Color::WHITE {0xff, 0xff, 0xff};
+const Color Color::RED {0xff, 0x00, 0x00};
+const Color Color::GREEN {0x00, 0xff, 0x00};
+const Color Color::BLUE {0x00, 0x00, 0xff};
+const Color Color::BLACK {0x00, 0x00, 0x00};
+const Color Color::CYAN {0x00, 0xff, 0xff};
+const Color Color::YELLOW {0xff, 0xff, 0x00};
+const Color Color::MAGENTA {0xff, 0x00, 0xff};
+const Color Color::GREY {0x80, 0x80, 0x80};
+const Color Color::GOLD {249, 205, 91};
diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h
index 84edb5c..dbfd0ed 100644
--- a/src/crepe/api/Color.h
+++ b/src/crepe/api/Color.h
@@ -18,6 +18,8 @@ struct Color {
static const Color MAGENTA;
static const Color YELLOW;
static const Color BLACK;
+ static const Color GREY;
+ static const Color GOLD;
};
} // namespace crepe
diff --git a/src/crepe/api/Components.h b/src/crepe/api/Components.h
new file mode 100644
index 0000000..fa0663d
--- /dev/null
+++ b/src/crepe/api/Components.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "AI.h"
+#include "Animator.h"
+#include "AudioSource.h"
+#include "BehaviorScript.h"
+#include "BoxCollider.h"
+#include "Button.h"
+#include "Camera.h"
+#include "CircleCollider.h"
+#include "Metadata.h"
+#include "ParticleEmitter.h"
+#include "Rigidbody.h"
+#include "Sprite.h"
+#include "Text.h"
+#include "Transform.h"
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index 13eabd1..ab8bb59 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -1,5 +1,8 @@
#pragma once
+#include <string>
+
+#include "../types.h"
#include "../util/Log.h"
namespace crepe {
@@ -7,27 +10,14 @@ namespace crepe {
/**
* \brief Global configuration interface
*
- * This class stores engine default settings. Properties on this class are only supposed to be
- * modified *before* execution is handed over from the game programmer to the engine (i.e. the
- * main loop is started).
+ * This struct stores both engine default settings and global configuration parameters.
*/
-class Config {
-public:
+struct Config final {
//! Retrieve handle to global Config instance
static Config & get_instance();
-private:
- Config() = default;
-
- // singleton
- Config(const Config &) = delete;
- Config(Config &&) = delete;
- Config & operator=(const Config &) = delete;
- Config & operator=(Config &&) = delete;
-
-public:
//! Logging-related settings
- struct {
+ struct log { // NOLINT
/**
* \brief Log level
*
@@ -35,7 +25,7 @@ public:
*/
Log::Level level = Log::Level::INFO;
/**
- * \brief Colored log output
+ * \brief Enable colored log output
*
* Enables log coloring using ANSI escape codes.
*/
@@ -43,7 +33,7 @@ public:
} log;
//! Save manager
- struct {
+ struct savemgr { // NOLINT
/**
* \brief Save file location
*
@@ -53,18 +43,26 @@ public:
std::string location = "save.crepe.db";
} savemgr;
- //! physics-related settings
- struct {
+ //! Physics-related settings
+ struct physics { // NOLINT
/**
* \brief gravity value of physics system
*
* Gravity value of game.
*/
- double gravity = 1;
+ float gravity = 10;
} physics;
+ //! Default window settings
+ struct window_settings { // NOLINT
+ //! Default window size (in pixels)
+ ivec2 default_size = {1280, 720};
+ //! Default window title
+ std::string window_title = "crepe window";
+ } window_settings;
+
//! Asset loading options
- struct {
+ struct asset { // NOLINT
/**
* \brief Pattern to match for Asset base directory
*
@@ -76,6 +74,28 @@ public:
*/
std::string root_pattern = ".crepe-root";
} asset;
+ //! Default font options
+ struct {
+ /**
+ * \brief Default font size
+ *
+ * Using the SDL_ttf library the font size needs to be set when loading the font.
+ * This config option is the font size at which all fonts will be loaded initially.
+ *
+ */
+ unsigned int size = 100;
+ } font;
+ //! Configuration for click tolerance.
+ struct {
+ //! The maximum number of pixels the mouse can move between MouseDown and MouseUp events to be considered a click.
+ int click_tolerance = 5;
+ } input;
+
+ //! Audio system settings
+ struct {
+ //! Max amount of simultanious voices
+ unsigned int voices = 32;
+ } audio;
};
} // namespace crepe
diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp
new file mode 100644
index 0000000..0bbe51f
--- /dev/null
+++ b/src/crepe/api/Engine.cpp
@@ -0,0 +1,68 @@
+#include "../util/Log.h"
+
+#include "Engine.h"
+
+using namespace crepe;
+using namespace std;
+
+int Engine::main() noexcept {
+ try {
+ this->setup();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what());
+ return EXIT_FAILURE;
+ }
+
+ try {
+ this->loop();
+ } catch (const exception & e) {
+ Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what());
+ this->event_manager.trigger_event<ShutDownEvent>();
+ }
+
+ return EXIT_SUCCESS;
+}
+
+void Engine::setup() {
+ this->loop_timer.start();
+ this->scene_manager.load_next_scene();
+
+ this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) {
+ this->game_running = false;
+
+ // propagate to possible user ShutDownEvent listeners
+ return false;
+ });
+}
+
+void Engine::loop() {
+ LoopTimerManager & timer = this->loop_timer;
+ SystemManager & systems = this->system_manager;
+
+ while (this->game_running) {
+ timer.update();
+
+ while (timer.get_lag() >= timer.get_fixed_delta_time()) {
+ try {
+ systems.fixed_update();
+ } catch (const exception & e) {
+ Log::logf(
+ Log::Level::WARNING, "Uncaught exception in fixed update function: {}\n",
+ e.what()
+ );
+ }
+ timer.advance_fixed_elapsed_time();
+ }
+
+ try {
+ systems.frame_update();
+ this->scene_manager.load_next_scene();
+ } catch (const exception & e) {
+ Log::logf(
+ Log::Level::WARNING, "Uncaught exception in frame update function: {}\n",
+ e.what()
+ );
+ }
+ timer.enforce_frame_rate();
+ }
+}
diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h
new file mode 100644
index 0000000..452a856
--- /dev/null
+++ b/src/crepe/api/Engine.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "../facade/SDLContext.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/EventManager.h"
+#include "../manager/LoopTimerManager.h"
+#include "../manager/ReplayManager.h"
+#include "../manager/ResourceManager.h"
+#include "../manager/SaveManager.h"
+#include "../manager/SceneManager.h"
+#include "../manager/SystemManager.h"
+
+namespace crepe {
+
+/**
+ * \brief Main game entrypoint
+ *
+ * This class is responsible for managing the game loop, including initialization and updating.
+ */
+class Engine {
+public:
+ /**
+ * \brief Engine entrypoint
+ *
+ * This function is called by the game programmer after registering all scenes
+ *
+ * \returns process exit code
+ */
+ int main() noexcept;
+
+ //! \copydoc SceneManager::add_scene
+ template <typename T>
+ void add_scene();
+
+private:
+ /**
+ * \brief Setup function for one-time initialization.
+ *
+ * This function initializes necessary components for the game.
+ */
+ void setup();
+ /**
+ * \brief Main game loop function.
+ *
+ * This function runs the main loop, handling game updates and rendering.
+ */
+ void loop();
+
+ //! Game loop condition
+ bool game_running = true;
+
+private:
+ //! Global context
+ Mediator mediator;
+
+ //! SystemManager
+ SystemManager system_manager {mediator};
+
+ //! SDLContext instance
+ SDLContext sdl_context {mediator};
+
+ //! Resource manager instance
+ ResourceManager resource_manager {mediator};
+
+ //! Component manager instance
+ ComponentManager component_manager {mediator};
+ //! Scene manager instance
+ SceneManager scene_manager {mediator};
+ //! LoopTimerManager instance
+ LoopTimerManager loop_timer {mediator};
+ //! EventManager instance
+ EventManager event_manager {mediator};
+ //! Save manager instance
+ SaveManager save_manager {mediator};
+ //! ReplayManager instance
+ ReplayManager replay_manager {mediator};
+};
+
+} // namespace crepe
+
+#include "Engine.hpp"
diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp
new file mode 100644
index 0000000..f2fdc0a
--- /dev/null
+++ b/src/crepe/api/Engine.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Engine.h"
+
+namespace crepe {
+
+template <class T>
+void Engine::add_scene() {
+ this->scene_manager.add_scene<T>();
+}
+
+} // namespace crepe
diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h
index 06cf7f3..7d4df21 100644
--- a/src/crepe/api/Event.h
+++ b/src/crepe/api/Event.h
@@ -1,20 +1,23 @@
-// TODO discussing the location of these events
#pragma once
+// TODO discussing the location of these events
#include <string>
+#include "types.h"
+
#include "KeyCodes.h"
+namespace crepe {
+
/**
- * \brief Base class for all event types in the system.
+ * \brief Base struct for all event types in the system.
*/
-class Event {};
+struct Event {};
/**
* \brief Event triggered when a key is pressed.
*/
-class KeyPressEvent : public Event {
-public:
+struct KeyPressEvent : public Event {
//! false if first time press, true if key is repeated
bool repeat = false;
@@ -25,8 +28,7 @@ public:
/**
* \brief Event triggered when a key is released.
*/
-class KeyReleaseEvent : public Event {
-public:
+struct KeyReleaseEvent : public Event {
//! The key that was released.
Keycode key = Keycode::NONE;
};
@@ -34,13 +36,9 @@ public:
/**
* \brief Event triggered when a mouse button is pressed.
*/
-class MousePressEvent : public Event {
-public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+struct MousePressEvent : public Event {
+ //! mouse position in world coordinates (game units).
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was pressed.
MouseButton button = MouseButton::NONE;
@@ -49,13 +47,9 @@ public:
/**
* \brief Event triggered when a mouse button is clicked (press and release).
*/
-class MouseClickEvent : public Event {
-public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+struct MouseClickEvent : public Event {
+ //! mouse position in world coordinates (game units).
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was clicked.
MouseButton button = MouseButton::NONE;
@@ -64,13 +58,9 @@ public:
/**
* \brief Event triggered when a mouse button is released.
*/
-class MouseReleaseEvent : public Event {
-public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
-
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+struct MouseReleaseEvent : public Event {
+ //! mouse position in world coordinates (game units).
+ vec2 mouse_pos = {0, 0};
//! The mouse button that was released.
MouseButton button = MouseButton::NONE;
@@ -79,34 +69,77 @@ public:
/**
* \brief Event triggered when the mouse is moved.
*/
-class MouseMoveEvent : public Event {
-public:
- //! X-coordinate of the mouse position at the time of the event.
- int mouse_x = 0;
+struct MouseMoveEvent : public Event {
+ //! mouse position in world coordinates (game units).
+ vec2 mouse_pos = {0, 0};
+ //! The change in mouse position relative to the last position (in pixels).
+ ivec2 mouse_delta = {0, 0};
+};
- //! Y-coordinate of the mouse position at the time of the event.
- int mouse_y = 0;
+/**
+ * \brief Event triggered when the mouse is moved.
+ */
+struct MouseScrollEvent : public Event {
+ //! mouse position in world coordinates (game units) when the scroll happened.
+ vec2 mouse_pos = {0, 0};
+ //! scroll direction (-1 = down, 1 = up)
+ int scroll_direction = 0;
+ //! scroll amount in y axis (from and away from the person).
+ float scroll_delta = 0;
};
/**
- * \brief Event triggered during a collision between objects.
+ * \brief Event triggered to indicate the application is shutting down.
+ */
+struct ShutDownEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window is overlapped by another window.
+ *
+ * When two windows overlap the bottom window gets distorted and that window has to be redrawn.
*/
-class CollisionEvent : public Event {
-public:
- //! Data describing the collision (currently not implemented).
- // Collision collisionData;
+struct WindowExposeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window is resized.
+ */
+struct WindowResizeEvent : public Event {
+ //! new window dimensions
+ ivec2 dimensions = {0, 0};
};
/**
- * \brief Event triggered when text is submitted, e.g., from a text input.
+ * \brief Event triggered to indicate the window is moved.
*/
-class TextSubmitEvent : public Event {
-public:
- //! The submitted text.
- std::string text = "";
+struct WindowMoveEvent : public Event {
+ //! The change in position relative to the last position (in pixels).
+ ivec2 delta_move = {0, 0};
};
/**
- * \brief Event triggered to indicate the application is shutting down.
+ * \brief Event triggered to indicate the window is minimized.
+ */
+struct WindowMinimizeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window is maximized
*/
-class ShutDownEvent : public Event {};
+struct WindowMaximizeEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window gained focus
+ *
+ * This event is triggered when the window receives focus, meaning it becomes the active window
+ * for user interaction.
+ */
+struct WindowFocusGainEvent : public Event {};
+
+/**
+ * \brief Event triggered to indicate the window lost focus
+ *
+ * This event is triggered when the window loses focus, meaning it is no longer the active window
+ * for user interaction.
+ */
+struct WindowFocusLostEvent : public Event {};
+
+} // namespace crepe
diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h
index ef659fd..7bb501b 100644
--- a/src/crepe/api/EventHandler.h
+++ b/src/crepe/api/EventHandler.h
@@ -8,12 +8,12 @@
namespace crepe {
/**
* \brief A type alias for an event handler function.
- *
- * The EventHandler is a std::function that takes an EventType reference and returns a boolean value
+ *
+ * The EventHandler is a std::function that takes an EventType reference and returns a boolean value
* indicating whether the event is handled.
- *
+ *
* \tparam EventType The type of event this handler will handle.
- *
+ *
* Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether.
*/
template <typename EventType>
@@ -22,70 +22,70 @@ using EventHandler = std::function<bool(const EventType & e)>;
/**
* \class IEventHandlerWrapper
* \brief An abstract base class for event handler wrappers.
- *
+ *
* This class provides the interface for handling events. Derived classes must implement the
* `call()` method to process events
*/
class IEventHandlerWrapper {
public:
/**
- * \brief Virtual destructor for IEventHandlerWrapper.
- */
+ * \brief Virtual destructor for IEventHandlerWrapper.
+ */
virtual ~IEventHandlerWrapper() = default;
/**
- * \brief Executes the handler with the given event.
- *
- * This method calls the `call()` method of the derived class, passing the event to the handler.
- *
- * \param e The event to be processed.
- * \return A boolean value indicating whether the event is handled.
- */
+ * \brief Executes the handler with the given event.
+ *
+ * This method calls the `call()` method of the derived class, passing the event to the handler.
+ *
+ * \param e The event to be processed.
+ * \return A boolean value indicating whether the event is handled.
+ */
bool exec(const Event & e);
private:
/**
- * \brief The method responsible for handling the event.
- *
- * This method is implemented by derived classes to process the event.
- *
- * \param e The event to be processed.
- * \return A boolean value indicating whether the event is handled.
- */
+ * \brief The method responsible for handling the event.
+ *
+ * This method is implemented by derived classes to process the event.
+ *
+ * \param e The event to be processed.
+ * \return A boolean value indicating whether the event is handled.
+ */
virtual bool call(const Event & e) = 0;
};
/**
* \class EventHandlerWrapper
* \brief A wrapper for event handler functions.
- *
- * This class wraps an event handler function of a specific event type. It implements the
- * `call()` and `get_type()` methods to allow the handler to be executed and its type to be
+ *
+ * This class wraps an event handler function of a specific event type. It implements the
+ * `call()` and `get_type()` methods to allow the handler to be executed and its type to be
* queried.
- *
+ *
* \tparam EventType The type of event this handler will handle.
*/
template <typename EventType>
class EventHandlerWrapper : public IEventHandlerWrapper {
public:
/**
- * \brief Constructs an EventHandlerWrapper with a given handler.
- *
- * The constructor takes an event handler function and stores it in the wrapper.
- *
- * \param handler The event handler function.
- */
+ * \brief Constructs an EventHandlerWrapper with a given handler.
+ *
+ * The constructor takes an event handler function and stores it in the wrapper.
+ *
+ * \param handler The event handler function.
+ */
explicit EventHandlerWrapper(const EventHandler<EventType> & handler);
private:
/**
- * \brief Calls the stored event handler with the event.
- *
- * This method casts the event to the appropriate type and calls the handler.
- *
- * \param e The event to be handled.
- * \return A boolean value indicating whether the event is handled.
- */
+ * \brief Calls the stored event handler with the event.
+ *
+ * This method casts the event to the appropriate type and calls the handler.
+ *
+ * \param e The event to be handled.
+ * \return A boolean value indicating whether the event is handled.
+ */
bool call(const Event & e) override;
//! The event handler function.
EventHandler<EventType> handler;
diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp
index 391dcca..050e57e 100644
--- a/src/crepe/api/EventHandler.hpp
+++ b/src/crepe/api/EventHandler.hpp
@@ -1,3 +1,5 @@
+#pragma once
+
#include <typeindex>
#include "EventHandler.h"
diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp
index 4874426..100e210 100644
--- a/src/crepe/api/GameObject.cpp
+++ b/src/crepe/api/GameObject.cpp
@@ -7,20 +7,19 @@
using namespace crepe;
using namespace std;
-GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id,
- const std::string & name, const std::string & tag,
- const Vector2 & position, double rotation, double scale)
+GameObject::GameObject(
+ Mediator & mediator, game_object_id_t id, const std::string & name,
+ const std::string & tag, const vec2 & position, double rotation, double scale
+)
: id(id),
- component_manager(component_manager) {
-
- // Add Transform and Metadata components
- ComponentManager & mgr = this->component_manager;
- mgr.add_component<Transform>(this->id, position, rotation, scale);
- mgr.add_component<Metadata>(this->id, name, tag);
-}
+ mediator(mediator),
+ transform(mediator.component_manager->add_component<Transform>(
+ this->id, position, rotation, scale
+ )),
+ metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {}
void GameObject::set_parent(const GameObject & parent) {
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
// Set parent on own Metadata component
RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id);
@@ -30,3 +29,9 @@ void GameObject::set_parent(const GameObject & parent) {
RefVector<Metadata> parent_metadata = mgr.get_components_by_id<Metadata>(parent.id);
parent_metadata.at(0).get().children.push_back(this->id);
}
+
+void GameObject::set_persistent(bool persistent) {
+ ComponentManager & mgr = this->mediator.component_manager;
+
+ mgr.set_persistent(this->id, persistent);
+}
diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h
index 34ef8bb..043913a 100644
--- a/src/crepe/api/GameObject.h
+++ b/src/crepe/api/GameObject.h
@@ -2,16 +2,17 @@
#include <string>
-#include "Vector2.h"
#include "types.h"
namespace crepe {
-class ComponentManager;
+class Mediator;
+class Transform;
+class Metadata;
/**
* \brief Represents a GameObject
- *
+ *
* This class represents a GameObject. The GameObject class is only used as an interface for
* the game programmer. The actual implementation is done in the ComponentManager.
*/
@@ -20,8 +21,8 @@ private:
/**
* This constructor creates a new GameObject. It creates a new Transform and Metadata
* component and adds them to the ComponentManager.
- *
- * \param component_manager Reference to component_manager
+ *
+ * \param mediator Reference to mediator
* \param id The id of the GameObject
* \param name The name of the GameObject
* \param tag The tag of the GameObject
@@ -29,29 +30,38 @@ private:
* \param rotation The rotation of the GameObject
* \param scale The scale of the GameObject
*/
- GameObject(ComponentManager & component_manager, game_object_id_t id,
- const std::string & name, const std::string & tag, const Vector2 & position,
- double rotation, double scale);
+ GameObject(
+ Mediator & mediator, game_object_id_t id, const std::string & name,
+ const std::string & tag, const vec2 & position, double rotation, double scale
+ );
//! ComponentManager instances GameObject
friend class ComponentManager;
public:
+ //! The id of the GameObject
+ const game_object_id_t id;
+ //! This entity's transform
+ Transform & transform;
+ //! This entity's metadata
+ Metadata & metadata;
+
+public:
/**
* \brief Set the parent of this GameObject
- *
+ *
* This method sets the parent of this GameObject. It sets the parent in the Metadata
* component of this GameObject and adds this GameObject to the children list of the parent
* GameObject.
- *
+ *
* \param parent The parent GameObject
*/
void set_parent(const GameObject & parent);
/**
* \brief Add a component to the GameObject
- *
+ *
* This method adds a component to the GameObject. It forwards the arguments to the
* ComponentManager.
- *
+ *
* \tparam T The type of the component
* \tparam Args The types of the arguments
* \param args The arguments to create the component
@@ -59,13 +69,18 @@ public:
*/
template <typename T, typename... Args>
T & add_component(Args &&... args);
-
-public:
- //! The id of the GameObject
- const game_object_id_t id;
+ /**
+ * \brief Components will not be deleted if this method is called
+ *
+ * This method sets the persistent flag of the GameObject to true. If the persistent
+ * flag is set to true, the GameObject will not be deleted when the scene is changed.
+ *
+ * \param persistent The persistent flag
+ */
+ void set_persistent(bool persistent = true);
protected:
- ComponentManager & component_manager;
+ Mediator & mediator;
};
} // namespace crepe
diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp
index 17b17d7..69f7d73 100644
--- a/src/crepe/api/GameObject.hpp
+++ b/src/crepe/api/GameObject.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "../ComponentManager.h"
+#include "../manager/ComponentManager.h"
#include "GameObject.h"
@@ -8,7 +8,7 @@ namespace crepe {
template <typename T, typename... Args>
T & GameObject::add_component(Args &&... args) {
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
return mgr.add_component<T>(this->id, std::forward<Args>(args)...);
}
diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp
deleted file mode 100644
index 8642655..0000000
--- a/src/crepe/api/IKeyListener.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "IKeyListener.h"
-
-using namespace crepe;
-
-// Constructor with specified channel
-IKeyListener::IKeyListener(event_channel_t channel)
- : event_manager(EventManager::get_instance()) {
- this->press_id = event_manager.subscribe<KeyPressEvent>(
- [this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel);
- this->release_id = event_manager.subscribe<KeyReleaseEvent>(
- [this](const KeyReleaseEvent & event) { return this->on_key_released(event); },
- channel);
-}
-
-// Destructor, unsubscribe events
-IKeyListener::~IKeyListener() {
- event_manager.unsubscribe(this->press_id);
- event_manager.unsubscribe(this->release_id);
-}
diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h
deleted file mode 100644
index 328a4c2..0000000
--- a/src/crepe/api/IKeyListener.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include "Event.h"
-#include "EventHandler.h"
-#include "EventManager.h"
-
-namespace crepe {
-
-/**
- * \class IKeyListener
- * \brief Interface for keyboard event handling in the application.
- */
-class IKeyListener {
-public:
- /**
- * \brief Constructs an IKeyListener with a specified channel.
- * \param channel The channel ID for event handling.
- */
- IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL);
- virtual ~IKeyListener();
- IKeyListener(const IKeyListener &) = delete;
- IKeyListener & operator=(const IKeyListener &) = delete;
- IKeyListener & operator=(IKeyListener &&) = delete;
- IKeyListener(IKeyListener &&) = delete;
-
- /**
- * \brief Pure virtual function to handle key press events.
- * \param event The key press event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_key_pressed(const KeyPressEvent & event) = 0;
-
- /**
- * \brief Pure virtual function to handle key release events.
- * \param event The key release event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_key_released(const KeyReleaseEvent & event) = 0;
-
-private:
- //! Key press event id
- subscription_t press_id = -1;
- //! Key release event id
- subscription_t release_id = -1;
- //! EventManager reference
- EventManager & event_manager;
-};
-
-} // namespace crepe
diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp
deleted file mode 100644
index 989aeb3..0000000
--- a/src/crepe/api/IMouseListener.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "IMouseListener.h"
-
-using namespace crepe;
-
-IMouseListener::IMouseListener(event_channel_t channel)
- : event_manager(EventManager::get_instance()) {
- this->click_id = event_manager.subscribe<MouseClickEvent>(
- [this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); },
- channel);
-
- this->press_id = event_manager.subscribe<MousePressEvent>(
- [this](const MousePressEvent & event) { return this->on_mouse_pressed(event); },
- channel);
-
- this->release_id = event_manager.subscribe<MouseReleaseEvent>(
- [this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); },
- channel);
-
- this->move_id = event_manager.subscribe<MouseMoveEvent>(
- [this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel);
-}
-
-IMouseListener::~IMouseListener() {
- // Unsubscribe event handlers
- event_manager.unsubscribe(this->click_id);
- event_manager.unsubscribe(this->press_id);
- event_manager.unsubscribe(this->release_id);
- event_manager.unsubscribe(this->move_id);
-}
diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h
deleted file mode 100644
index 15e1619..0000000
--- a/src/crepe/api/IMouseListener.h
+++ /dev/null
@@ -1,72 +0,0 @@
-#pragma once
-
-#include "Event.h"
-#include "EventHandler.h"
-#include "EventManager.h"
-
-namespace crepe {
-
-/**
- * \class IMouseListener
- * \brief Interface for mouse event handling in the application.
- */
-class IMouseListener {
-public:
- /**
- * \brief Constructs an IMouseListener with a specified channel.
- * \param channel The channel ID for event handling.
- */
- IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL);
- virtual ~IMouseListener();
- IMouseListener & operator=(const IMouseListener &) = delete;
- IMouseListener(const IMouseListener &) = delete;
- IMouseListener & operator=(const IMouseListener &&) = delete;
- IMouseListener(IMouseListener &&) = delete;
-
- /**
- * \brief Move assignment operator (deleted).
- */
- IMouseListener & operator=(IMouseListener &&) = delete;
-
- /**
- * \brief Handles a mouse click event.
- * \param event The mouse click event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0;
-
- /**
- * \brief Handles a mouse press event.
- * \param event The mouse press event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_mouse_pressed(const MousePressEvent & event) = 0;
-
- /**
- * \brief Handles a mouse release event.
- * \param event The mouse release event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0;
-
- /**
- * \brief Handles a mouse move event.
- * \param event The mouse move event to handle.
- * \return True if the event was handled, false otherwise.
- */
- virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0;
-
-private:
- //! Mouse click event id
- subscription_t click_id = -1;
- //! Mouse press event id
- subscription_t press_id = -1;
- //! Mouse release event id
- subscription_t release_id = -1;
- //! Mouse move event id
- subscription_t move_id = -1;
- //! EventManager reference
- EventManager & event_manager;
-};
-
-} //namespace crepe
diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h
index 9e173e0..1b9573a 100644
--- a/src/crepe/api/KeyCodes.h
+++ b/src/crepe/api/KeyCodes.h
@@ -1,5 +1,9 @@
#pragma once
+#include <unordered_map>
+
+namespace crepe {
+
//! Enumeration for mouse button inputs, including standard and extended buttons.
enum class MouseButton {
NONE = 0, //!< No mouse button input.
@@ -85,9 +89,9 @@ enum class Keycode {
PRINT_SCREEN = 283, //!< Print Screen key.
PAUSE = 284, //!< Pause key.
/**
- * \name Function keys (F1-F25).
- * \{
- */
+ * \name Function keys (F1-F25).
+ * \{
+ */
F1 = 290,
F2 = 291,
F3 = 292,
@@ -115,9 +119,9 @@ enum class Keycode {
F25 = 314,
/// \}
/**
- * \name Keypad digits and operators.
- * \{
- */
+ * \name Keypad digits and operators.
+ * \{
+ */
KP0 = 320,
KP1 = 321,
KP2 = 322,
@@ -137,9 +141,9 @@ enum class Keycode {
KP_EQUAL = 336,
/// \}
/**
- * \name Modifier keys.
- * \{
- */
+ * \name Modifier keys.
+ * \{
+ */
LEFT_SHIFT = 340,
LEFT_CONTROL = 341,
LEFT_ALT = 342,
@@ -151,3 +155,6 @@ enum class Keycode {
/// \}
MENU = 348, //!< Menu key.
};
+//! Typedef for keyboard state.
+typedef std::unordered_map<Keycode, bool> keyboard_state_t;
+} // namespace crepe
diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp
deleted file mode 100644
index a64366f..0000000
--- a/src/crepe/api/LoopManager.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include "../facade/SDLContext.h"
-
-#include "../system/AnimatorSystem.h"
-#include "../system/CollisionSystem.h"
-#include "../system/ParticleSystem.h"
-#include "../system/PhysicsSystem.h"
-#include "../system/RenderSystem.h"
-#include "../system/ScriptSystem.h"
-
-#include "LoopManager.h"
-#include "LoopTimer.h"
-
-using namespace crepe;
-using namespace std;
-
-LoopManager::LoopManager() {
- this->load_system<AnimatorSystem>();
- this->load_system<CollisionSystem>();
- this->load_system<ParticleSystem>();
- this->load_system<PhysicsSystem>();
- this->load_system<RenderSystem>();
- this->load_system<ScriptSystem>();
-}
-
-void LoopManager::process_input() {
- SDLContext::get_instance().handle_events(this->game_running);
-}
-
-void LoopManager::start() {
- this->setup();
- this->loop();
-}
-void LoopManager::set_running(bool running) { this->game_running = running; }
-
-void LoopManager::fixed_update() {}
-
-void LoopManager::loop() {
- LoopTimer & timer = LoopTimer::get_instance();
- timer.start();
-
- while (game_running) {
- timer.update();
-
- while (timer.get_lag() >= timer.get_fixed_delta_time()) {
- this->process_input();
- this->fixed_update();
- timer.advance_fixed_update();
- }
-
- this->update();
- this->render();
-
- timer.enforce_frame_rate();
- }
-}
-
-void LoopManager::setup() {
- this->game_running = true;
- LoopTimer::get_instance().start();
- LoopTimer::get_instance().set_fps(60);
-}
-
-void LoopManager::render() {
- if (this->game_running) {
- this->get_system<RenderSystem>().update();
- }
-}
-
-void LoopManager::update() { LoopTimer & timer = LoopTimer::get_instance(); }
diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h
deleted file mode 100644
index 13e6dac..0000000
--- a/src/crepe/api/LoopManager.h
+++ /dev/null
@@ -1,119 +0,0 @@
-#pragma once
-
-#include <memory>
-
-#include "../ComponentManager.h"
-#include "../system/System.h"
-#include "api/SceneManager.h"
-
-namespace crepe {
-
-/**
- * \brief Main game loop manager
- *
- * This class is responsible for managing the game loop, including initialization and updating.
- */
-class LoopManager {
-public:
- void start();
- LoopManager();
-
- /**
- * \brief Add a new concrete scene to the scene manager
- *
- * \tparam T Type of concrete scene
- */
- template <typename T>
- void add_scene();
-
-private:
- /**
- * \brief Setup function for one-time initialization.
- *
- * This function initializes necessary components for the game.
- */
- void setup();
- /**
- * \brief Main game loop function.
- *
- * This function runs the main loop, handling game updates and rendering.
- */
- void loop();
-
- /**
- * \brief Function for handling input-related system calls.
- *
- * Processes user inputs from keyboard and mouse.
- */
- void process_input();
-
- /**
- * \brief Per-frame update.
- *
- * Updates the game state based on the elapsed time since the last frame.
- */
- void update();
-
- /**
- * \brief Late update which is called after update().
- *
- * This function can be used for final adjustments before rendering.
- */
- void late_update();
-
- /**
- * \brief Fixed update executed at a fixed rate.
- *
- * This function updates physics and game logic based on LoopTimer's fixed_delta_time.
- */
- void fixed_update();
-
- /**
- * \brief Set game running variable
- *
- * \param running running (false = game shutdown, true = game running)
- */
- void set_running(bool running);
-
- /**
- * \brief Function for executing render-related systems.
- *
- * Renders the current state of the game to the screen.
- */
- void render();
-
- bool game_running = false;
-
-private:
- //! Component manager instance
- ComponentManager component_manager{};
- //! Scene manager instance
- SceneManager scene_manager{component_manager};
-
-private:
- /**
- * \brief Collection of System instances
- *
- * This map holds System instances indexed by the system's class typeid. It is filled in the
- * constructor of \c LoopManager using LoopManager::load_system.
- */
- std::unordered_map<std::type_index, std::unique_ptr<System>> systems;
- /**
- * \brief Initialize a system
- * \tparam T System type (must be derivative of \c System)
- */
- template <class T>
- void load_system();
- /**
- * \brief Retrieve a reference to ECS system
- * \tparam T System type
- * \returns Reference to system instance
- * \throws std::runtime_error if the System is not initialized
- */
- template <class T>
- T & get_system();
-};
-
-} // namespace crepe
-
-#include "LoopManager.hpp"
diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/api/LoopManager.hpp
deleted file mode 100644
index 9cf470b..0000000
--- a/src/crepe/api/LoopManager.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <cassert>
-#include <format>
-#include <memory>
-
-#include "../system/System.h"
-
-#include "LoopManager.h"
-
-namespace crepe {
-
-template <class T>
-void LoopManager::add_scene() {
- this->scene_manager.add_scene<T>();
-}
-
-template <class T>
-T & LoopManager::get_system() {
- using namespace std;
- static_assert(is_base_of<System, T>::value,
- "get_system must recieve a derivative class of System");
-
- const type_info & type = typeid(T);
- if (!this->systems.contains(type))
- throw runtime_error(format("LoopManager: {} is not initialized", type.name()));
-
- System * system = this->systems.at(type).get();
- T * concrete_system = dynamic_cast<T *>(system);
- assert(concrete_system != nullptr);
-
- return *concrete_system;
-}
-
-template <class T>
-void LoopManager::load_system() {
- using namespace std;
- static_assert(is_base_of<System, T>::value,
- "load_system must recieve a derivative class of System");
-
- System * system = new T(this->component_manager);
- this->systems[typeid(T)] = unique_ptr<System>(system);
-}
-
-} // namespace crepe
diff --git a/src/crepe/api/LoopTimer.cpp b/src/crepe/api/LoopTimer.cpp
deleted file mode 100644
index a9800b7..0000000
--- a/src/crepe/api/LoopTimer.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-#include <chrono>
-
-#include "../facade/SDLContext.h"
-#include "../util/Log.h"
-
-#include "LoopTimer.h"
-
-using namespace crepe;
-
-LoopTimer::LoopTimer() { dbg_trace(); }
-
-LoopTimer & LoopTimer::get_instance() {
- static LoopTimer instance;
- return instance;
-}
-
-void LoopTimer::start() {
- this->last_frame_time = std::chrono::steady_clock::now();
- this->elapsed_time = std::chrono::milliseconds(0);
- this->elapsed_fixed_time = std::chrono::milliseconds(0);
- this->delta_time = std::chrono::milliseconds(0);
-}
-
-void LoopTimer::update() {
- auto current_frame_time = std::chrono::steady_clock::now();
- // Convert to duration in seconds for delta time
- this->delta_time = std::chrono::duration_cast<std::chrono::duration<double>>(
- current_frame_time - last_frame_time);
-
- if (this->delta_time > this->maximum_delta_time) {
- this->delta_time = this->maximum_delta_time;
- }
-
- this->delta_time *= this->game_scale;
- this->elapsed_time += this->delta_time;
- this->last_frame_time = current_frame_time;
-}
-
-double LoopTimer::get_delta_time() const { return this->delta_time.count(); }
-
-double LoopTimer::get_current_time() const { return this->elapsed_time.count(); }
-
-void LoopTimer::advance_fixed_update() { this->elapsed_fixed_time += this->fixed_delta_time; }
-
-double LoopTimer::get_fixed_delta_time() const { return this->fixed_delta_time.count(); }
-
-void LoopTimer::set_fps(int fps) {
- this->fps = fps;
- // target time per frame in seconds
- this->frame_target_time = std::chrono::seconds(1) / fps;
-}
-
-int LoopTimer::get_fps() const { return this->fps; }
-
-void LoopTimer::set_game_scale(double value) { this->game_scale = value; }
-
-double LoopTimer::get_game_scale() const { return this->game_scale; }
-void LoopTimer::enforce_frame_rate() {
- std::chrono::steady_clock::time_point current_frame_time
- = std::chrono::steady_clock::now();
- std::chrono::milliseconds frame_duration
- = std::chrono::duration_cast<std::chrono::milliseconds>(current_frame_time
- - this->last_frame_time);
-
- if (frame_duration < this->frame_target_time) {
- std::chrono::milliseconds delay_time
- = std::chrono::duration_cast<std::chrono::milliseconds>(this->frame_target_time
- - frame_duration);
- if (delay_time.count() > 0) {
- SDLContext::get_instance().delay(delay_time.count());
- }
- }
-
- this->last_frame_time = current_frame_time;
-}
-
-double LoopTimer::get_lag() const {
- return (this->elapsed_time - this->elapsed_fixed_time).count();
-}
diff --git a/src/crepe/api/LoopTimer.h b/src/crepe/api/LoopTimer.h
deleted file mode 100644
index f277d7b..0000000
--- a/src/crepe/api/LoopTimer.h
+++ /dev/null
@@ -1,144 +0,0 @@
-#pragma once
-
-#include <chrono>
-
-namespace crepe {
-
-class LoopTimer {
-public:
- /**
- * \brief Get the singleton instance of LoopTimer.
- *
- * \return A reference to the LoopTimer instance.
- */
- static LoopTimer & get_instance();
-
- /**
- * \brief Get the current delta time for the current frame.
- *
- * \return Delta time in seconds since the last frame.
- */
- double get_delta_time() const;
-
- /**
- * \brief Get the current game time.
- *
- * \note The current game time may vary from real-world elapsed time. It is the cumulative
- * sum of each frame's delta time.
- *
- * \return Elapsed game time in seconds.
- */
- double get_current_time() const;
-
- /**
- * \brief Set the target frames per second (FPS).
- *
- * \param fps The desired frames rendered per second.
- */
- void set_fps(int fps);
-
- /**
- * \brief Get the current frames per second (FPS).
- *
- * \return Current FPS.
- */
- int get_fps() const;
-
- /**
- * \brief Get the current game scale.
- *
- * \return The current game scale, where 0 = paused, 1 = normal speed, and values > 1 speed
- * up the game.
- */
- double get_game_scale() const;
-
- /**
- * \brief Set the game scale.
- *
- * \param game_scale The desired game scale (0 = pause, 1 = normal speed, > 1 = speed up).
- */
- void set_game_scale(double game_scale);
-
-private:
- friend class LoopManager;
-
- /**
- * \brief Start the loop timer.
- *
- * Initializes the timer to begin tracking frame times.
- */
- void start();
-
- /**
- * \brief Enforce the frame rate limit.
- *
- * Ensures that the game loop does not exceed the target FPS by delaying frame updates as
- * necessary.
- */
- void enforce_frame_rate();
-
- /**
- * \brief Get the fixed delta time for consistent updates.
- *
- * Fixed delta time is used for operations that require uniform time steps, such as physics
- * calculations.
- *
- * \return Fixed delta time in seconds.
- */
- double get_fixed_delta_time() const;
-
- /**
- * \brief Get the accumulated lag in the game loop.
- *
- * Lag represents the difference between the target frame time and the actual frame time,
- * useful for managing fixed update intervals.
- *
- * \return Accumulated lag in seconds.
- */
- double get_lag() const;
-
- /**
- * \brief Construct a new LoopTimer object.
- *
- * Private constructor for singleton pattern to restrict instantiation outside the class.
- */
- LoopTimer();
-
- /**
- * \brief Update the timer to the current frame.
- *
- * Calculates and updates the delta time for the current frame and adds it to the cumulative
- * game time.
- */
- void update();
-
- /**
- * \brief Advance the game loop by a fixed update interval.
- *
- * This method progresses the game state by a consistent, fixed time step, allowing for
- * stable updates independent of frame rate fluctuations.
- */
- void advance_fixed_update();
-
-private:
- //! Current frames per second
- int fps = 50;
- //! Current game scale
- double game_scale = 1;
- //! Maximum delta time in seconds to avoid large jumps
- std::chrono::duration<double> maximum_delta_time{0.25};
- //! Delta time for the current frame in seconds
- std::chrono::duration<double> delta_time{0.0};
- //! Target time per frame in seconds
- std::chrono::duration<double> frame_target_time = std::chrono::seconds(1) / fps;
- //! Fixed delta time for fixed updates in seconds
- std::chrono::duration<double> fixed_delta_time = std::chrono::seconds(1) / 50;
- //! Total elapsed game time in seconds
- std::chrono::duration<double> elapsed_time{0.0};
- //! Total elapsed time for fixed updates in seconds
- std::chrono::duration<double> elapsed_fixed_time{0.0};
- //! Time of the last frame
- std::chrono::steady_clock::time_point last_frame_time;
-};
-
-} // namespace crepe
diff --git a/src/crepe/api/Metadata.h b/src/crepe/api/Metadata.h
index 235d42f..f404703 100644
--- a/src/crepe/api/Metadata.h
+++ b/src/crepe/api/Metadata.h
@@ -9,7 +9,7 @@ namespace crepe {
/**
* \brief Metadata component
- *
+ *
* This class represents the Metadata component. It stores the name, tag, parent and children
* of a GameObject.
*/
diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp
index 90b77a0..341c1e2 100644
--- a/src/crepe/api/ParticleEmitter.cpp
+++ b/src/crepe/api/ParticleEmitter.cpp
@@ -1,11 +1,29 @@
#include "ParticleEmitter.h"
+#include "api/Sprite.h"
using namespace crepe;
+using namespace std;
-ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Data & data)
+ParticleEmitter::ParticleEmitter(
+ game_object_id_t game_object_id, const Sprite & sprite, const Data & data
+)
: Component(game_object_id),
+ sprite(sprite),
data(data) {
for (size_t i = 0; i < this->data.max_particles; i++) {
- this->data.particles.emplace_back();
+ this->particles.emplace_back();
}
}
+
+unique_ptr<Component> ParticleEmitter::save() const {
+ return unique_ptr<Component> {new ParticleEmitter(*this)};
+}
+
+void ParticleEmitter::restore(const Component & snapshot) {
+ *this = static_cast<const ParticleEmitter &>(snapshot);
+}
+
+ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) {
+ this->particles = other.particles;
+ return *this;
+}
diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h
index 33112e1..1edd2b5 100644
--- a/src/crepe/api/ParticleEmitter.h
+++ b/src/crepe/api/ParticleEmitter.h
@@ -1,10 +1,14 @@
#pragma once
+#include <cmath>
#include <vector>
+#include "system/ParticleSystem.h"
+#include "system/RenderSystem.h"
+
#include "Component.h"
#include "Particle.h"
-#include "Vector2.h"
+#include "types.h"
namespace crepe {
@@ -26,15 +30,18 @@ public:
*/
struct Boundary {
//! boundary width (midpoint is emitter location)
- double width = 0.0;
+ float width = INFINITY;
//! boundary height (midpoint is emitter location)
- double height = 0.0;
+ float height = INFINITY;
//! boundary offset from particle emitter location
- Vector2 offset;
+ vec2 offset;
//! reset on exit or stop velocity and set max postion
bool reset_on_exit = false;
};
+ //! sprite reference of displayed sprite
+ const Sprite & sprite;
+
/**
* \brief Holds parameters that control particle emission.
*
@@ -42,32 +49,28 @@ public:
* and the sprite used for rendering particles.
*/
struct Data {
- //! position of the emitter
- Vector2 position;
+ //! offset of the emitter relative to transform
+ vec2 offset;
//! maximum number of particles
- const unsigned int max_particles = 0;
- //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored)
- double emission_rate = 0;
+ const unsigned int max_particles = 256;
+ //! rate of particle emission per second
+ float emission_rate = 50;
//! min speed of the particles
- double min_speed = 0;
+ float min_speed = 100;
//! min speed of the particles
- double max_speed = 0;
+ float max_speed = 100;
//! min angle of particle emission
- double min_angle = 0;
+ float min_angle = 0;
//! max angle of particle emission
- double max_angle = 0;
- //! begin Lifespan of particle (only visual)
- double begin_lifespan = 0.0;
- //! end Lifespan of particle
- double end_lifespan = 0.0;
+ float max_angle = 0;
+ //! begin Lifespan of particle in seconds (only visual)
+ float begin_lifespan = 0.0;
+ //! end Lifespan of particle in seconds
+ float end_lifespan = 10.0;
//! force over time (physics)
- Vector2 force_over_time;
+ vec2 force_over_time;
//! particle boundary
Boundary boundary;
- //! collection of particles
- std::vector<Particle> particles;
- //! sprite reference
- const Sprite & sprite;
};
public:
@@ -75,11 +78,27 @@ public:
* \param game_object_id Identifier for the game object using this emitter.
* \param data Configuration data defining particle properties.
*/
- ParticleEmitter(game_object_id_t game_object_id, const Data & data);
+ ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite, const Data & data);
public:
//! Configuration data for particle emission settings.
Data data;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ ParticleEmitter(const ParticleEmitter &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual ParticleEmitter & operator=(const ParticleEmitter &);
+
+private:
+ //! Only ParticleSystem can move and read particles
+ friend ParticleSystem;
+ //! Only RenderSystem can read particles
+ friend RenderSystem;
+ //! Saves time left over from last update event.
+ float spawn_accumulator = 0;
+ //! collection of particles
+ std::vector<Particle> particles;
};
} // namespace crepe
diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp
index 6b87695..8213afb 100644
--- a/src/crepe/api/Rigidbody.cpp
+++ b/src/crepe/api/Rigidbody.cpp
@@ -6,10 +6,8 @@ crepe::Rigidbody::Rigidbody(game_object_id_t id, const Data & data)
: Component(id),
data(data) {}
-void crepe::Rigidbody::add_force_linear(const Vector2 & force) {
+void crepe::Rigidbody::add_force_linear(const vec2 & force) {
this->data.linear_velocity += force;
}
-void crepe::Rigidbody::add_force_angular(double force) {
- this->data.angular_velocity += force;
-}
+void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; }
diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h
index 3e5c7a3..b63d941 100644
--- a/src/crepe/api/Rigidbody.h
+++ b/src/crepe/api/Rigidbody.h
@@ -1,14 +1,18 @@
#pragma once
+#include <cmath>
+#include <set>
+#include <string>
+
#include "../Component.h"
-#include "Vector2.h"
+#include "types.h"
namespace crepe {
/**
* \brief Rigidbody class
- *
+ *
* This class is used by the physics sytem and collision system. It configures how to system
* interact with the gameobject for movement and collisions.
*/
@@ -16,7 +20,7 @@ class Rigidbody : public Component {
public:
/**
* \brief BodyType enum
- *
+ *
* This enum provides three bodytypes the physics sytem and collision system use.
*/
enum class BodyType {
@@ -29,54 +33,141 @@ public:
};
/**
* \brief PhysicsConstraints to constrain movement
- *
+ *
* This struct configures the movement constraint for this object. If a constraint is enabled
* the systems will not move the object.
*/
struct PhysicsConstraints {
- //! X constraint
+ //! Prevent movement along X axis
bool x = false;
- //! Y constraint
+ //! Prevent movement along Y axis
bool y = false;
- //! rotation constraint
+ //! Prevent rotation
bool rotation = false;
};
public:
- /**
+ /**
* \brief struct for Rigidbody data
- *
+ *
* This struct holds the data for the Rigidbody.
*/
struct Data {
//! objects mass
- double mass = 0.0;
- //! gravtiy scale
- double gravity_scale = 0.0;
- //! Changes if physics apply
+ float mass = 1;
+ /**
+ * \brief Gravity scale factor.
+ *
+ * The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default
+ * gravity force, allowing for fine-grained control over how the object responds to gravity.
+ *
+ */
+ float gravity_scale = 0;
+
+ //! Defines the type of the physics body, which determines how the physics system interacts with the object.
BodyType body_type = BodyType::DYNAMIC;
- //! linear velocity of object
- Vector2 linear_velocity;
- //! maximum linear velocity of object
- Vector2 max_linear_velocity;
- //! linear damping of object
- Vector2 linear_damping;
- //! angular velocity of object
- double angular_velocity = 0.0;
- //! max angular velocity of object
- double max_angular_velocity = 0.0;
- //! angular damping of object
- double angular_damping = 0.0;
- //! movements constraints of object
+
+ /**
+ * \name Linear (positional) motion
+ *
+ * These variables define the linear motion (movement along the position) of an object.
+ * The linear velocity is applied to the object's position in each update of the PhysicsSystem.
+ * The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient
+ * that can scale the velocity over time.
+ *
+ * \{
+ */
+ //! Linear velocity of the object (speed and direction).
+ vec2 linear_velocity;
+ //! Maximum linear velocity of the object. This limits the object's speed.
+ float max_linear_velocity = INFINITY;
+ //! Linear velocity coefficient. This scales the object's velocity for adjustment or damping.
+ vec2 linear_velocity_coefficient = {1, 1};
+ //! \}
+
+ /**
+ * \name Angular (rotational) motion
+ *
+ * These variables define the angular motion (rotation) of an object.
+ * The angular velocity determines how quickly the object rotates, while the maximum angular velocity
+ * sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling
+ * to the angular velocity, which can be used to simulate friction or other effects that slow down rotation.
+ *
+ * \{
+ */
+ //! Angular velocity of the object, representing the rate of rotation (in degrees).
+ float angular_velocity = 0;
+ //! Maximum angular velocity of the object. This limits the maximum rate of rotation.
+ float max_angular_velocity = INFINITY;
+ //! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping.
+ float angular_velocity_coefficient = 1;
+ //! \}
+
+ /**
+ * \brief Movement constraints for an object.
+ *
+ * The `PhysicsConstraints` struct defines the constraints that restrict an object's movement
+ * in certain directions or prevent rotation. These constraints effect only the physics system
+ * to prevent the object from moving or rotating in specified ways.
+ *
+ */
PhysicsConstraints constraints;
- //! if gravity applies
- bool use_gravity = true;
- //! if object bounces
- bool bounce = false;
+
+ /**
+ * \brief Elasticity factor of the material (bounce factor).
+ *
+ * The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision.
+ * It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value
+ * above 0.0.
+ *
+ */
+ float elasticity_coefficient = 0.0;
+
+ /**
+ * \brief Enables collision handling for objects colliding with kinematic objects.
+ *
+ * Enables collision handling for objects colliding with kinematic objects in the collision system.
+ * If `kinematic_collision` is true, dynamic objects cannot pass through this kinematic object.
+ * This ensures that kinematic objects delegate collision handling to the collision system.
+ */
+ bool kinematic_collision = true;
+
+ /**
+ * \brief Defines the collision layers a GameObject interacts with.
+ *
+ * The `collision_layers` represent the set of layers the GameObject can detect collisions with.
+ * Each element in this set corresponds to a layer ID. The GameObject will only collide with other
+ * GameObjects that belong to one these layers.
+ */
+ std::set<int> collision_layers = {0};
+
+ /**
+ * \brief Specifies the collision layer of the GameObject.
+ *
+ * The `collision_layer` indicates the single layer that this GameObject belongs to.
+ * This determines which layers other objects must match to detect collisions with this object.
+ */
+ int collision_layer = 0;
+
+ /**
+ * \brief Defines the collision layers of a GameObject.
+ *
+ * The `collision_names` specifies where the GameObject will collide with.
+ * Each element represents a name from the Metadata of the gameobject.
+ */
+ std::set<std::string> collision_names;
+
+ /**
+ * \brief Defines the collision layers of a GameObject.
+ *
+ * The `collision_tags` specifies where the GameObject will collide with.
+ * Each element represents a tag from the Metadata of the gameobject.
+ */
+ std::set<std::string> collision_tags;
};
public:
- /**
+ /**
* \param game_object_id id of the gameobject the rigibody is added to.
* \param data struct to configure the rigidbody.
*/
@@ -85,18 +176,27 @@ public:
Data data;
public:
- /**
+ /**
* \brief add a linear force to the Rigidbody.
- *
+ *
* \param force Vector2 that is added to the linear force.
*/
- void add_force_linear(const Vector2 & force);
- /**
+ void add_force_linear(const vec2 & force);
+ /**
* \brief add a angular force to the Rigidbody.
- *
+ *
* \param force Vector2 that is added to the angular force.
*/
- void add_force_angular(double force);
+ void add_force_angular(float force);
+
+protected:
+ /**
+ * Ensures there is at most one Rigidbody component per entity.
+ * \return Always returns 1, indicating this constraint.
+ */
+ virtual int get_instances_max() const { return 1; }
+ //! ComponentManager instantiates all components
+ friend class ComponentManager;
};
} // namespace crepe
diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp
index 849945e..84da7e8 100644
--- a/src/crepe/api/Scene.cpp
+++ b/src/crepe/api/Scene.cpp
@@ -2,4 +2,16 @@
using namespace crepe;
-Scene::Scene(ComponentManager & mgr) : component_manager(mgr) {}
+SaveManager & Scene::get_save_manager() const { return mediator->save_manager; }
+
+GameObject Scene::new_object(
+ const std::string & name, const std::string & tag, const vec2 & position, double rotation,
+ double scale
+) {
+ // Forward the call to ComponentManager's new_object method
+ return mediator->component_manager->new_object(name, tag, position, rotation, scale);
+}
+
+void Scene::set_persistent(const Asset & asset, bool persistent) {
+ mediator->resource_manager->set_persistent(asset, persistent);
+}
diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h
index 869bf6f..b50a0fc 100644
--- a/src/crepe/api/Scene.h
+++ b/src/crepe/api/Scene.h
@@ -2,24 +2,30 @@
#include <string>
+#include "../manager/ComponentManager.h"
+#include "../manager/Mediator.h"
+#include "../manager/ResourceManager.h"
+#include "../util/Log.h"
+#include "../util/OptionalRef.h"
+
+#include "GameObject.h"
+
namespace crepe {
class SceneManager;
class ComponentManager;
+class Asset;
/**
* \brief Represents a Scene
- *
+ *
* This class represents a Scene. The Scene class is only used as an interface for the game
* programmer.
*/
class Scene {
protected:
- //TODO: Use Loek's custom reference class to set ComponentManger via SceneManager instead of via constructor
- /**
- * \param mgr Reference to the ComponentManager
- */
- Scene(ComponentManager & mgr);
+ // NOTE: This must be the only constructor on Scene, see "Late references" below
+ Scene() = default;
//! SceneManager instances Scene
friend class SceneManager;
@@ -35,9 +41,55 @@ public:
*/
virtual std::string get_name() const = 0;
-protected:
- //! Reference to the ComponentManager
- ComponentManager & component_manager;
+ // TODO: Late references should ALWAYS be private! This is currently kept as-is so unit tests
+ // keep passing, but this reference should not be directly accessible by the user!!!
+
+private:
+ /**
+ * \name Late references
+ *
+ * These references are set by SceneManager immediately after calling the constructor of Scene.
+ *
+ * \note Scene must have a constructor without arguments so the game programmer doesn't need to
+ * manually add `using Scene::Scene` to their concrete scene class, if they want to add a
+ * constructor with arguments (e.g. for passing references to their own concrete Scene classes).
+ *
+ * \{
+ */
+ //! Mediator reference
+ OptionalRef<Mediator> mediator;
+ //! \}
+
+public:
+ /**
+ * \brief Retrieve the reference to the SaveManager instance
+ *
+ * \returns A reference to the SaveManager instance held by the Mediator.
+ */
+ SaveManager & get_save_manager() const;
+
+ //! \copydoc ComponentManager::new_object
+ GameObject new_object(
+ const std::string & name, const std::string & tag = "", const vec2 & position = {0, 0},
+ double rotation = 0, double scale = 1
+ );
+
+ //! \copydoc ResourceManager::set_persistent
+ void set_persistent(const Asset & asset, bool persistent);
+ /**
+ * \name Logging functions
+ * \see Log
+ * \{
+ */
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args);
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(std::format_string<Args...> fmt, Args &&... args);
+ //! \}
};
} // namespace crepe
+
+#include "Scene.hpp"
diff --git a/src/crepe/api/Scene.hpp b/src/crepe/api/Scene.hpp
new file mode 100644
index 0000000..14635df
--- /dev/null
+++ b/src/crepe/api/Scene.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../util/Log.h"
+
+#include "Scene.h"
+
+namespace crepe {
+
+template <class... Args>
+void Scene::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(level, fmt, std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void Scene::logf(std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(fmt, std::forward<Args>(args)...);
+}
+
+} // namespace crepe
diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp
new file mode 100644
index 0000000..06b535f
--- /dev/null
+++ b/src/crepe/api/Script.cpp
@@ -0,0 +1,78 @@
+#include <string>
+
+#include "../facade/SDLContext.h"
+#include "../manager/SceneManager.h"
+
+#include "Script.h"
+
+using namespace crepe;
+using namespace std;
+
+Script::~Script() {
+ EventManager & mgr = this->mediator->event_manager;
+ for (auto id : this->listeners) {
+ mgr.unsubscribe(id);
+ }
+}
+
+template <>
+void Script::subscribe(const EventHandler<CollisionEvent> & callback) {
+ this->subscribe_internal(callback, this->game_object_id);
+}
+
+template <>
+void Script::subscribe(const EventHandler<ButtonExitEvent> & callback) {
+ this->subscribe_internal(callback, this->game_object_id);
+}
+
+template <>
+void Script::subscribe(const EventHandler<ButtonPressEvent> & callback) {
+ this->subscribe_internal(callback, this->game_object_id);
+}
+
+template <>
+void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback) {
+ this->subscribe_internal(callback, this->game_object_id);
+}
+
+void Script::set_next_scene(const string & name) {
+ SceneManager & mgr = this->mediator->scene_manager;
+ mgr.set_next_scene(name);
+}
+
+SaveManager & Script::get_save_manager() const { return this->mediator->save_manager; }
+
+LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; }
+
+void Script::replay::record_start() {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.record_start();
+}
+
+recording_t Script::replay::record_end() {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.record_end();
+}
+
+void Script::replay::play(recording_t recording) {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.play(recording);
+}
+
+void Script::replay::release(recording_t recording) {
+ ReplayManager & mgr = this->mediator->replay_manager;
+ return mgr.release(recording);
+}
+
+const keyboard_state_t & Script::get_keyboard_state() const {
+ SDLContext & sdl_context = this->mediator->sdl_context;
+ return sdl_context.get_keyboard_state();
+}
+
+bool Script::get_key_state(Keycode key) const noexcept {
+ try {
+ return this->get_keyboard_state().at(key);
+ } catch (...) {
+ return false;
+ }
+}
diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h
index 839d937..b000d9d 100644
--- a/src/crepe/api/Script.h
+++ b/src/crepe/api/Script.h
@@ -2,7 +2,16 @@
#include <vector>
+#include "../api/KeyCodes.h"
+#include "../manager/EventManager.h"
+#include "../manager/LoopTimerManager.h"
+#include "../manager/Mediator.h"
+#include "../manager/ReplayManager.h"
+#include "../system/CollisionSystem.h"
+#include "../system/InputSystem.h"
#include "../types.h"
+#include "../util/Log.h"
+#include "../util/OptionalRef.h"
namespace crepe {
@@ -18,11 +27,21 @@ class ComponentManager;
*
* \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as
* member or lambda methods in derivative user script classes and registered in \c init().
+ *
+ * \warning Concrete scripts are allowed do create a custom constructor, but the utility
+ * functions should not be called inside the constructor as they rely on late references that
+ * are only available after the constructor returns.
+ *
+ * \see feature_script
*/
class Script {
protected:
/**
- * \brief Script initialization function
+ * \name Interface functions
+ * \{
+ */
+ /**
+ * \brief Script initialization function (empty by default)
*
* This function is called during the ScriptSystem::update() routine *before*
* Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component
@@ -30,59 +49,266 @@ protected:
*/
virtual void init() {}
/**
- * \brief Script update function
+ * \brief Script fixed update function (empty by default)
+ *
+ * \param delta_time Time since last fixed update
+ *
+ * \note This function is called during the ScriptSystem::update() routine if the \c
+ * BehaviorScript component holding this script instance is active.
+ */
+ virtual void fixed_update(duration_t delta_time) {}
+ /**
+ * \brief Script frame update function (empty by default)
+ *
+ * \param delta_time Time since last frame update
*
- * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript
- * component holding this script instance is active.
+ * \note This function is called during the ScriptSystem::update() routine if the \c
+ * BehaviorScript component holding this script instance is active.
*/
- virtual void update() {}
+ virtual void frame_update(duration_t delta_time) {}
+ //! \}
+
//! ScriptSystem calls \c init() and \c update()
friend class crepe::ScriptSystem;
protected:
/**
- * \brief Get single component of type \c T on this game object (utility)
- *
+ * \name Component query functions
+ * \see ComponentManager
+ * \{
+ */
+ /**
+ * \brief Get single component of type \c T on this game object
* \tparam T Type of component
- *
* \returns Reference to component
- *
- * \throws nullptr if this game object does not have a component matching type \c T
+ * \throws std::runtime_error if this game object does not have a component with type \c T
*/
template <typename T>
T & get_component() const;
- // TODO: make get_component calls for component types that can have more than 1 instance
- // cause compile-time errors
-
/**
- * \brief Get all components of type \c T on this game object (utility)
- *
+ * \brief Get all components of type \c T on this game object
* \tparam T Type of component
- *
* \returns List of component references
*/
template <typename T>
RefVector<T> get_components() const;
+ //! \copydoc ComponentManager::get_components_by_id
+ template <typename T>
+ RefVector<T> get_components_by_id(game_object_id_t id) const;
+ //! \copydoc ComponentManager::get_components_by_name
+ template <typename T>
+ RefVector<T> get_components_by_name(const std::string & name) const;
+ //! \copydoc ComponentManager::get_components_by_tag
+ template <typename T>
+ RefVector<T> get_components_by_tag(const std::string & tag) const;
+ //! \}
+
+ /**
+ * \name Logging functions
+ * \see Log
+ * \{
+ */
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args);
+ //! \copydoc Log::logf
+ template <class... Args>
+ void logf(std::format_string<Args...> fmt, Args &&... args);
+ // \}
+
+ /**
+ * \name Event manager functions
+ * \see EventManager
+ * \{
+ */
+ //! \copydoc EventManager::subscribe
+ template <typename EventType>
+ void subscribe(const EventHandler<EventType> & callback, event_channel_t channel);
+ //! \copydoc EventManager::subscribe
+ template <typename EventType>
+ void subscribe(const EventHandler<EventType> & callback);
+ //! \copydoc EventManager::trigger_event
+ template <typename EventType>
+ void trigger_event(
+ const EventType & event = {}, event_channel_t channel = EventManager::CHANNEL_ALL
+ );
+ //! \copydoc EventManager::queue_event
+ template <typename EventType>
+ void queue_event(
+ const EventType & event = {}, event_channel_t channel = EventManager::CHANNEL_ALL
+ );
+ //! \}
+
+ /**
+ * \name Scene-related functions
+ * \see SceneManager
+ * \{
+ */
+ //! \copydoc SceneManager::set_next_scene
+ void set_next_scene(const std::string & name);
+ //! \}
+
+ /**
+ * \name Save data management functions
+ * \see SaveManager
+ * \{
+ */
+ //! Retrieve SaveManager reference
+ SaveManager & get_save_manager() const;
+ //! \}
+
+ /**
+ * \name Timing functions
+ * \see LoopTimerManager
+ * \{
+ */
+ //! Retrieve LoopTimerManager reference
+ LoopTimerManager & get_loop_timer() const;
+ //! \}
+
+ //! Replay management functions
+ struct replay { // NOLINT
+ //! \copydoc ReplayManager::record_start
+ void record_start();
+ //! \copydoc ReplayManager::record_end
+ recording_t record_end();
+ //! \copydoc ReplayManager::play
+ void play(recording_t);
+ //! \copydoc ReplayManager::release
+ void release(recording_t);
+
+ private:
+ OptionalRef<Mediator> & mediator;
+ replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {}
+ friend class Script;
+ } replay {mediator};
+
+ /**
+ * \brief Utility function to retrieve the keyboard state
+ * \see SDLContext::get_keyboard_state
+ *
+ * \return current keyboard state map with Keycode as key and bool as value(true = pressed, false = not pressed)
+ */
+ const keyboard_state_t & get_keyboard_state() const;
+ /**
+ * \brief Utility function to retrieve a single key state.
+ * \see SDLContext::get_keyboard_state
+ *
+ * \return Keycode state (true if pressed, false if not pressed).
+ */
+ bool get_key_state(Keycode key) const noexcept;
+
+private:
+ /**
+ * \brief Internal subscribe function
+ *
+ * This function exists so certain template specializations of Script::subscribe can be
+ * explicitly deleted, and does the following:
+ * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript
+ * component is still active
+ * - Store the subscriber handle returned by the event manager so this listener is
+ * automatically unsubscribed at the end of this Script instance's life
+ *
+ * \tparam EventType concrete Event class
+ * \param callback User-provided callback function
+ * \param channel Event channel (may have been overridden by template specializations)
+ */
+ template <typename EventType>
+ void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel);
protected:
- // NOTE: Script must have a constructor without arguments so the game programmer doesn't need
- // to manually add `using Script::Script` to their concrete script class.
+ // NOTE: This must be the only constructor on Script, see "Late references" below
Script() = default;
//! Only \c BehaviorScript instantiates Script
friend class BehaviorScript;
+public:
+ // std::unique_ptr destroys script
+ virtual ~Script();
+
+private:
+ Script(const Script &) = delete;
+ Script(Script &&) = delete;
+ Script & operator=(const Script &) = delete;
+ Script & operator=(Script &&) = delete;
+
private:
- // These references are set by BehaviorScript immediately after calling the constructor of
- // Script.
- game_object_id_t game_object_id = -1;
- ComponentManager * component_manager_ref = nullptr;
- // TODO: use OptionalRef instead of pointer
+ /**
+ * \name Late references
+ *
+ * These references are set by BehaviorScript immediately after calling the constructor of
+ * Script.
+ *
+ * \note Script must have a constructor without arguments so the game programmer doesn't need
+ * to manually add `using Script::Script` to their concrete script class if they want to
+ * implement a non-default constructor (e.g. for passing references to their own concrete
+ * Script classes).
+ *
+ * \{
+ */
+ //! Game object ID of game object parent BehaviorScript is attached to
+ game_object_id_t game_object_id;
+ //! Reference to parent component
+ OptionalRef<bool> active;
+ //! Mediator reference
+ OptionalRef<Mediator> mediator;
+ //! \}
private:
//! Flag to indicate if \c init() has been called already
bool initialized = false;
+ //! List of subscribed events
+ std::vector<subscription_t> listeners;
};
+/**
+ * \brief Subscribe to CollisionEvent for the current GameObject
+ *
+ * This is a template specialization for Script::subscribe which automatically sets the event
+ * channel so the callback handler is only called for CollisionEvent events that apply to the
+ * current GameObject the parent BehaviorScript is attached to.
+ */
+template <>
+void Script::subscribe(const EventHandler<CollisionEvent> & callback);
+template <>
+void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t)
+ = delete;
+/**
+ * \brief Subscribe to ButtonPressEvent for the current GameObject
+ *
+ * This is a template specialization for Script::subscribe which automatically sets the event
+ * channel so the callback handler is only called for ButtonPressEvent events that apply to the
+ * current GameObject the parent BehaviorScript is attached to.
+ */
+template <>
+void Script::subscribe(const EventHandler<ButtonPressEvent> & callback);
+template <>
+void Script::subscribe(const EventHandler<ButtonPressEvent> & callback, event_channel_t)
+ = delete;
+/**
+ * \brief Subscribe to ButtonExitEvent for the current GameObject
+ *
+ * This is a template specialization for Script::subscribe which automatically sets the event
+ * channel so the callback handler is only called for ButtonExitEvent events that apply to the
+ * current GameObject the parent BehaviorScript is attached to.
+ */
+template <>
+void Script::subscribe(const EventHandler<ButtonExitEvent> & callback);
+template <>
+void Script::subscribe(const EventHandler<ButtonExitEvent> & callback, event_channel_t)
+ = delete;
+/**
+ * \brief Subscribe to ButtonEnterEvent for the current GameObject
+ *
+ * This is a template specialization for Script::subscribe which automatically sets the event
+ * channel so the callback handler is only called for ButtonEnterEvent events that apply to the
+ * current GameObject the parent BehaviorScript is attached to.
+ */
+template <>
+void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback);
+template <>
+void Script::subscribe(const EventHandler<ButtonEnterEvent> & callback, event_channel_t)
+ = delete;
} // namespace crepe
#include "Script.hpp"
diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp
index a85d814..c7fa6ff 100644
--- a/src/crepe/api/Script.hpp
+++ b/src/crepe/api/Script.hpp
@@ -1,6 +1,7 @@
#pragma once
-#include "../ComponentManager.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/ReplayManager.h"
#include "BehaviorScript.h"
#include "Script.h"
@@ -13,15 +14,95 @@ T & Script::get_component() const {
RefVector<T> all_components = this->get_components<T>();
if (all_components.size() < 1)
throw runtime_error(
- format("Script: no component found with type = {}", typeid(T).name()));
+ format("Script: no component found with type = {}", typeid(T).name())
+ );
return all_components.back().get();
}
template <typename T>
RefVector<T> Script::get_components() const {
- auto & mgr = *this->component_manager_ref;
- return mgr.get_components_by_id<T>(this->game_object_id);
+ return this->get_components_by_id<T>(this->game_object_id);
+}
+
+template <class... Args>
+void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(level, fmt, std::forward<Args>(args)...);
+}
+
+template <class... Args>
+void Script::logf(std::format_string<Args...> fmt, Args &&... args) {
+ Log::logf(fmt, std::forward<Args>(args)...);
+}
+
+template <typename EventType>
+void Script::subscribe_internal(
+ const EventHandler<EventType> & callback, event_channel_t channel
+) {
+ EventManager & mgr = this->mediator->event_manager;
+ subscription_t listener = mgr.subscribe<EventType>(
+ [this, callback](const EventType & data) -> bool {
+ // check if (parent) BehaviorScript component is active
+ bool & active = this->active;
+ if (!active) return false;
+
+ // check if replay manager is playing (if initialized)
+ try {
+ ReplayManager & replay = this->mediator->replay_manager;
+ if (replay.get_state() == ReplayManager::PLAYING) return false;
+ } catch (const std::runtime_error &) {
+ }
+
+ // call user-provided callback
+ return callback(data);
+ },
+ channel
+ );
+ this->listeners.push_back(listener);
+}
+
+template <typename EventType>
+void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) {
+ this->subscribe_internal(callback, channel);
+}
+
+template <typename EventType>
+void Script::subscribe(const EventHandler<EventType> & callback) {
+ this->subscribe_internal(callback, EventManager::CHANNEL_ALL);
+}
+
+template <typename EventType>
+void Script::trigger_event(const EventType & event, event_channel_t channel) {
+ EventManager & mgr = this->mediator->event_manager;
+ mgr.trigger_event(event, channel);
+}
+
+template <typename EventType>
+void Script::queue_event(const EventType & event, event_channel_t channel) {
+ EventManager & mgr = this->mediator->event_manager;
+ mgr.queue_event(event, channel);
+}
+
+template <typename T>
+RefVector<T> Script::get_components_by_id(game_object_id_t id) const {
+ Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+
+ return mgr.get_components_by_id<T>(id);
+}
+template <typename T>
+RefVector<T> Script::get_components_by_name(const std::string & name) const {
+ Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+
+ return mgr.get_components_by_name<T>(name);
+}
+template <typename T>
+RefVector<T> Script::get_components_by_tag(const std::string & tag) const {
+ Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+
+ return mgr.get_components_by_tag<T>(tag);
}
} // namespace crepe
diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp
index bd2d5cf..3c77e2e 100644
--- a/src/crepe/api/Sprite.cpp
+++ b/src/crepe/api/Sprite.cpp
@@ -1,25 +1,34 @@
-#include <memory>
+#include <cmath>
-#include "../util/Log.h"
-#include "facade/SDLContext.h"
+#include "../util/dbg.h"
+#include "api/Asset.h"
#include "Component.h"
#include "Sprite.h"
-#include "Texture.h"
+#include "types.h"
using namespace std;
using namespace crepe;
-Sprite::Sprite(game_object_id_t id, const shared_ptr<Texture> image, const Color & color,
- const FlipSettings & flip)
+Sprite::Sprite(game_object_id_t id, const Asset & texture, const Sprite::Data & data)
: Component(id),
- color(color),
- flip(flip),
- sprite_image(image) {
- dbg_trace();
+ source(texture),
+ data(data) {
- this->sprite_rect.w = sprite_image->get_width();
- this->sprite_rect.h = sprite_image->get_height();
+ dbg_trace();
}
Sprite::~Sprite() { dbg_trace(); }
+
+unique_ptr<Component> Sprite::save() const { return unique_ptr<Component>(new Sprite(*this)); }
+
+void Sprite::restore(const Component & snapshot) {
+ *this = static_cast<const Sprite &>(snapshot);
+}
+
+Sprite & Sprite::operator=(const Sprite & snapshot) {
+ this->active = snapshot.active;
+ this->data = snapshot.data;
+ this->mask = snapshot.mask;
+ return *this;
+}
diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h
index 74a55d4..3565bed 100644
--- a/src/crepe/api/Sprite.h
+++ b/src/crepe/api/Sprite.h
@@ -1,26 +1,13 @@
#pragma once
-#include <memory>
-
#include "../Component.h"
+#include "api/Asset.h"
#include "Color.h"
-#include "Texture.h"
+#include "types.h"
namespace crepe {
-struct Rect {
- int w = 0;
- int h = 0;
- int x = 0;
- int y = 0;
-};
-
-struct FlipSettings {
- bool flip_x = false;
- bool flip_y = false;
-};
-
class SDLContext;
class Animator;
class AnimatorSystem;
@@ -32,58 +19,112 @@ class AnimatorSystem;
* flip settings, and is managed in layers with defined sorting orders.
*/
class Sprite : public Component {
+public:
+ //! settings to flip the image
+ struct FlipSettings {
+ //! horizantal flip
+ bool flip_x = false;
+ //! vertical flip
+ bool flip_y = false;
+ };
+
+ //! Sprite data that does not have to be set in the constructor
+ struct Data {
+ /**
+ * \brief Sprite tint (multiplied)
+ *
+ * The sprite texture's pixels are multiplied by this color before being displayed
+ * (including alpha channel for transparency).
+ */
+ Color color = Color::WHITE;
+
+ //! Flip settings for the sprite
+ FlipSettings flip;
+
+ //! Layer sorting level of the sprite
+ int sorting_in_layer = 0;
+
+ //! Order within the sorting layer
+ int order_in_layer = 0;
+
+ /**
+ * \brief width and height of the sprite in game units
+ *
+ * - if exclusively width is specified, the height is calculated using the texture's aspect
+ * ratio
+ * - if exclusively height is specified, the width is calculated using the texture's aspect
+ * ratio
+ * - if both are specified the texture is streched to fit the specified size
+ */
+ vec2 size = {0, 0};
+
+ //! independent sprite angle. rotating clockwise direction in degrees
+ float angle_offset = 0;
+
+ //! independent sprite scale multiplier
+ float scale_offset = 1;
+
+ //! independent sprite offset position
+ vec2 position_offset;
+
+ /**
+ * \brief gives the user the option to render this in world space or in camera space
+ *
+ * - if true will this be rendered in world space this means that the sprite can be
+ * rendered off the screen
+ * - if false --> will the sprite be rendered in camera space. this means that the
+ * coordinates given on the \c Sprite and \c Transform will be inside the camera
+ */
+ bool world_space = true;
+ };
public:
- // TODO: Loek comment in github #27 will be looked another time
- // about shared_ptr Texture
/**
- * \brief Constructs a Sprite with specified parameters.
* \param game_id Unique identifier for the game object this sprite belongs to.
- * \param image Shared pointer to the texture for this sprite.
- * \param color Color tint applied to the sprite.
- * \param flip Flip settings for horizontal and vertical orientation.
- */
- Sprite(game_object_id_t id, const std::shared_ptr<Texture> image, const Color & color,
- const FlipSettings & flip);
-
- /**
- * \brief Destroys the Sprite instance.
+ * \param texture asset of the image
+ * \param ctx all the sprite data
*/
+ Sprite(game_object_id_t id, const Asset & texture, const Data & data);
~Sprite();
//! Texture used for the sprite
- const std::shared_ptr<Texture> sprite_image;
- //! Color tint of the sprite
- Color color;
- //! Flip settings for the sprite
- FlipSettings flip;
- //! Layer sorting level of the sprite
- uint8_t sorting_in_layer = 0;
- //! Order within the sorting layer
- uint8_t order_in_layer = 0;
+ const Asset source;
-public:
- /**
- * \brief Gets the maximum number of instances allowed for this sprite.
- * \return Maximum instance count as an integer.
- *
- * For now is this number randomly picked. I think it will eventually be 1.
- */
- virtual int get_instances_max() const { return 10; }
+ Data data;
private:
- //! Reads the sprite_rect of sprite
+ //! Reads the mask of sprite
friend class SDLContext;
- //! Reads the all the variables plus the sprite_rect
+ //! Reads the all the variables plus the mask
friend class Animator;
- //! Reads the all the variables plus the sprite_rect
+ //! Reads the all the variables plus the mask
friend class AnimatorSystem;
+ /**
+ * \aspect_ratio the ratio of the sprite image
+ *
+ * - this value will only be set by the \c Animator component for the ratio of the Animation
+ * - if \c Animator component is not added it will not use this ratio (because 0) and will use aspect_ratio of the Asset.
+ */
+ float aspect_ratio = 0;
+
+ struct Rect {
+ int w = 0;
+ int h = 0;
+ int x = 0;
+ int y = 0;
+ };
//! Render area of the sprite this will also be adjusted by the AnimatorSystem if an Animator
- // object is present in GameObject
- Rect sprite_rect;
+ // object is present in GameObject. this is in sprite pixels
+ Rect mask;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ Sprite(const Sprite &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual Sprite & operator=(const Sprite &);
};
} // namespace crepe
diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp
new file mode 100644
index 0000000..e5cc39d
--- /dev/null
+++ b/src/crepe/api/Text.cpp
@@ -0,0 +1,27 @@
+#include "../types.h"
+
+#include "Text.h"
+
+using namespace crepe;
+using namespace std;
+
+Text::Text(
+ game_object_id_t id, const vec2 & dimensions, const std::string & font_family,
+ const Data & data, const vec2 & offset, const std::string & text
+)
+ : UIObject(id, dimensions, offset),
+ text(text),
+ data(data),
+ font_family(font_family) {}
+
+unique_ptr<Component> Text::save() const { return unique_ptr<Component>(new Text(*this)); }
+
+void Text::restore(const Component & snapshot) { *this = static_cast<const Text &>(snapshot); }
+
+Text & Text::operator=(const Text & snapshot) {
+ this->active = snapshot.active;
+ this->data = snapshot.data;
+ this->text = snapshot.text;
+ this->font_family = snapshot.font_family;
+ return *this;
+}
diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h
new file mode 100644
index 0000000..859490e
--- /dev/null
+++ b/src/crepe/api/Text.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <optional>
+#include <string>
+
+#include "../types.h"
+
+#include "Asset.h"
+#include "Color.h"
+#include "UIObject.h"
+
+namespace crepe {
+/**
+ * \brief Text UIObject component for displaying text
+ *
+ * This class can be used to display text on screen. By setting the font_family to a font already stored on the current device it will automatically be loaded in.
+ */
+class Text : public UIObject {
+public:
+ //! Text data that does not have to be set in the constructor
+ struct Data {
+ //! variable indicating if transform is relative to camera(false) or world(true)
+ bool world_space = false;
+
+ //! Label text color.
+ Color text_color = Color::BLACK;
+ };
+
+public:
+ /**
+ *
+ * \param dimensions Width and height of the UIObject.
+ * \param offset Offset of the UIObject relative to its transform
+ * \param text The text to be displayed.
+ * \param font_family The font style name to be displayed.
+ * \param data Data struct containing extra text parameters.
+ * \param font Optional font asset that can be passed or left empty.
+ */
+ Text(
+ game_object_id_t id, const vec2 & dimensions, const std::string & font_family,
+ const Data & data, const vec2 & offset = {0, 0}, const std::string & text = ""
+ );
+
+ //! Label text.
+ std::string text = "";
+ //! font family name
+ std::string font_family = "";
+ //! Font asset variable if this is not set, it will use the font_family to create an asset.
+ std::optional<Asset> font;
+ //! Data instance
+ Data data;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ Text(const Text &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual Text & operator=(const Text &);
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp
deleted file mode 100644
index 9be9421..0000000
--- a/src/crepe/api/Texture.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include <SDL2/SDL_render.h>
-
-#include "../facade/SDLContext.h"
-#include "../util/Log.h"
-
-#include "Asset.h"
-#include "Texture.h"
-
-using namespace crepe;
-using namespace std;
-
-Texture::Texture(unique_ptr<Asset> res) {
- dbg_trace();
- this->load(std::move(res));
-}
-
-Texture::Texture(const char * src) {
- dbg_trace();
- this->load(make_unique<Asset>(src));
-}
-
-Texture::~Texture() {
- dbg_trace();
- this->texture.reset();
-}
-
-void Texture::load(unique_ptr<Asset> res) {
- SDLContext & ctx = SDLContext::get_instance();
- this->texture = std::move(ctx.texture_from_path(res->get_path()));
-}
-
-int Texture::get_width() const {
- if (this->texture == nullptr) return 0;
- return SDLContext::get_instance().get_width(*this);
-}
-int Texture::get_height() const {
- if (this->texture == nullptr) return 0;
- return SDLContext::get_instance().get_height(*this);
-}
diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h
deleted file mode 100644
index 6965223..0000000
--- a/src/crepe/api/Texture.h
+++ /dev/null
@@ -1,75 +0,0 @@
-#pragma once
-
-// FIXME: this header can't be included because this is an API header, and SDL2 development
-// headers won't be bundled with crepe. Why is this facade in the API namespace?
-
-#include <SDL2/SDL_render.h>
-#include <functional>
-#include <memory>
-
-#include "Asset.h"
-
-namespace crepe {
-
-class SDLContext;
-class Animator;
-
-/**
- * \class Texture
- * \brief Manages texture loading and properties.
- *
- * The Texture class is responsible for loading an image from a source and providing access to
- * its dimensions. Textures can be used for rendering.
- */
-class Texture {
-
-public:
- /**
- * \brief Constructs a Texture from a file path.
- * \param src Path to the image file to be loaded as a texture.
- */
- Texture(const char * src);
-
- /**
- * \brief Constructs a Texture from an Asset resource.
- * \param res Unique pointer to an Asset resource containing texture data.
- */
- Texture(std::unique_ptr<Asset> res);
-
- /**
- * \brief Destroys the Texture instance, freeing associated resources.
- */
- ~Texture();
- // FIXME: this constructor shouldn't be necessary because this class doesn't manage memory
-
- /**
- * \brief Gets the width of the texture.
- * \return Width of the texture in pixels.
- */
- int get_width() const;
-
- /**
- * \brief Gets the height of the texture.
- * \return Height of the texture in pixels.
- */
- int get_height() const;
-
-private:
- /**
- * \brief Loads the texture from an Asset resource.
- * \param res Unique pointer to an Asset resource to load the texture from.
- */
- void load(std::unique_ptr<Asset> res);
-
-private:
- //! The texture of the class from the library
- std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture;
-
- //! Grants SDLContext access to private members.
- friend class SDLContext;
-
- //! Grants Animator access to private members.
- friend class Animator;
-};
-
-} // namespace crepe
diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp
index cd944bd..b70174c 100644
--- a/src/crepe/api/Transform.cpp
+++ b/src/crepe/api/Transform.cpp
@@ -1,13 +1,22 @@
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "Transform.h"
using namespace crepe;
+using namespace std;
-Transform::Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale)
+Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale)
: Component(id),
position(point),
rotation(rotation),
scale(scale) {
dbg_trace();
}
+
+unique_ptr<Component> Transform::save() const {
+ return unique_ptr<Component> {new Transform(*this)};
+}
+
+void Transform::restore(const Component & snapshot) {
+ *this = static_cast<const Transform &>(snapshot);
+}
diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h
index 18aa293..a6f3486 100644
--- a/src/crepe/api/Transform.h
+++ b/src/crepe/api/Transform.h
@@ -1,25 +1,24 @@
#pragma once
-#include "api/Vector2.h"
-
#include "Component.h"
+#include "types.h"
namespace crepe {
/**
* \brief Transform component
- *
+ *
* This class represents the Transform component. It stores the position, rotation and scale of
* a GameObject.
*/
class Transform : public Component {
public:
//! Translation (shift)
- Vector2 position = {0, 0};
- //! Rotation, in degrees
- double rotation = 0;
+ vec2 position = {0, 0};
+ //! Rotation, in degrees clockwise
+ float rotation = 0;
//! Multiplication factor
- double scale = 0;
+ float scale = 0;
protected:
/**
@@ -28,7 +27,7 @@ protected:
* \param rotation The rotation of the GameObject
* \param scale The scale of the GameObject
*/
- Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale);
+ Transform(game_object_id_t id, const vec2 & point, double rotation, double scale);
/**
* There is always exactly one transform component per entity
* \return 1
@@ -36,6 +35,12 @@ protected:
virtual int get_instances_max() const { return 1; }
//! ComponentManager instantiates all components
friend class ComponentManager;
+
+protected:
+ virtual std::unique_ptr<Component> save() const;
+ Transform(const Transform &) = default;
+ virtual void restore(const Component & snapshot);
+ virtual Transform & operator=(const Transform &) = default;
};
} // namespace crepe
diff --git a/src/crepe/api/UIObject.cpp b/src/crepe/api/UIObject.cpp
new file mode 100644
index 0000000..d239b89
--- /dev/null
+++ b/src/crepe/api/UIObject.cpp
@@ -0,0 +1,8 @@
+#include "UIObject.h"
+
+using namespace crepe;
+
+UIObject::UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset)
+ : Component(id),
+ dimensions(dimensions),
+ offset(offset) {}
diff --git a/src/crepe/api/UIObject.h b/src/crepe/api/UIObject.h
new file mode 100644
index 0000000..0d9b1f7
--- /dev/null
+++ b/src/crepe/api/UIObject.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "../Component.h"
+
+namespace crepe {
+
+/**
+ * \brief Represents a UI object in the game, derived from the Component class.
+ */
+class UIObject : public Component {
+public:
+ /**
+ * \brief Constructs a UiObject with the specified game object ID.
+ * \param id The unique ID of the game object associated with this UI object.
+ * \param dimensions width and height of the UIObject
+ * \param offset Offset relative to the GameObject Transform
+ */
+ UIObject(game_object_id_t id, const vec2 & dimensions, const vec2 & offset = {0, 0});
+ //! Width and height of the UIObject
+ vec2 dimensions;
+ //! Position offset relative to this GameObjects Transform
+ vec2 offset;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp
deleted file mode 100644
index 30b968e..0000000
--- a/src/crepe/api/Vector2.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "Vector2.h"
-
-using namespace crepe;
-
-Vector2 Vector2::operator-(const Vector2 & other) const { return {x - other.x, y - other.y}; }
-
-Vector2 Vector2::operator+(const Vector2 & other) const { return {x + other.x, y + other.y}; }
-
-Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; }
-
-Vector2 & Vector2::operator*=(const Vector2 & other) {
- x *= other.x;
- y *= other.y;
- return *this;
-}
-
-Vector2 & Vector2::operator+=(const Vector2 & other) {
- x += other.x;
- y += other.y;
- return *this;
-}
-
-Vector2 & Vector2::operator+=(double other) {
- x += other;
- y += other;
- return *this;
-}
-
-Vector2 Vector2::operator-() const { return {-x, -y}; }
-
-bool Vector2::operator==(const Vector2 & other) const { return x == other.x && y == other.y; }
-
-bool Vector2::operator!=(const Vector2 & other) const { return !(*this == other); }
diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h
index 2fb6136..6613641 100644
--- a/src/crepe/api/Vector2.h
+++ b/src/crepe/api/Vector2.h
@@ -1,40 +1,110 @@
#pragma once
+#include <format>
+
namespace crepe {
//! 2D vector
+template <class T>
struct Vector2 {
//! X component of the vector
- double x = 0;
+ T x = 0;
//! Y component of the vector
- double y = 0;
+ T y = 0;
//! Subtracts another vector from this vector and returns the result.
- Vector2 operator-(const Vector2 & other) const;
+ Vector2<T> operator-(const Vector2<T> & other) const;
+
+ //! Subtracts a scalar value from both components of this vector and returns the result.
+ Vector2<T> operator-(T scalar) const;
//! Adds another vector to this vector and returns the result.
- Vector2 operator+(const Vector2 & other) const;
+ Vector2<T> operator+(const Vector2<T> & other) const;
+
+ //! Adds a scalar value to both components of this vector and returns the result.
+ Vector2<T> operator+(T scalar) const;
+
+ //! Multiplies this vector by another vector element-wise and returns the result.
+ Vector2<T> operator*(const Vector2<T> & other) const;
//! Multiplies this vector by a scalar and returns the result.
- Vector2 operator*(double scalar) const;
+ Vector2<T> operator*(T scalar) const;
- //! Multiplies this vector by another vector element-wise and updates this vector.
- Vector2 & operator*=(const Vector2 & other);
+ //! Divides this vector by another vector element-wise and returns the result.
+ Vector2<T> operator/(const Vector2<T> & other) const;
+
+ //! Divides this vector by a scalar and returns the result.
+ Vector2<T> operator/(T scalar) const;
//! Adds another vector to this vector and updates this vector.
- Vector2 & operator+=(const Vector2 & other);
+ Vector2<T> & operator+=(const Vector2<T> & other);
//! Adds a scalar value to both components of this vector and updates this vector.
- Vector2 & operator+=(double other);
+ Vector2<T> & operator+=(T other);
+
+ //! Subtracts another vector from this vector and updates this vector.
+ Vector2<T> & operator-=(const Vector2<T> & other);
+
+ //! Subtracts a scalar value from both components of this vector and updates this vector.
+ Vector2<T> & operator-=(T other);
+
+ //! Multiplies this vector by another vector element-wise and updates this vector.
+ Vector2<T> & operator*=(const Vector2<T> & other);
+
+ //! Multiplies this vector by a scalar and updates this vector.
+ Vector2<T> & operator*=(T other);
+
+ //! Divides this vector by another vector element-wise and updates this vector.
+ Vector2<T> & operator/=(const Vector2<T> & other);
+
+ //! Divides this vector by a scalar and updates this vector.
+ Vector2<T> & operator/=(T other);
//! Returns the negation of this vector.
- Vector2 operator-() const;
+ Vector2<T> operator-() const;
//! Checks if this vector is equal to another vector.
- bool operator==(const Vector2 & other) const;
+ bool operator==(const Vector2<T> & other) const;
//! Checks if this vector is not equal to another vector.
- bool operator!=(const Vector2 & other) const;
+ bool operator!=(const Vector2<T> & other) const;
+
+ //! Truncates the vector to a maximum length.
+ void truncate(T max);
+
+ //! Normalizes the vector (resulting in vector with a length of 1).
+ void normalize();
+
+ //! Returns the length of the vector.
+ T length() const;
+
+ //! Returns the squared length of the vector.
+ T length_squared() const;
+
+ //! Returns the dot product (inwendig product) of this vector and another vector.
+ T dot(const Vector2<T> & other) const;
+
+ //! Returns the distance between this vector and another vector.
+ T distance(const Vector2<T> & other) const;
+
+ //! Returns the squared distance between this vector and another vector.
+ T distance_squared(const Vector2<T> & other) const;
+
+ //! Returns the perpendicular vector to this vector.
+ Vector2<T> perpendicular() const;
+
+ //! Checks if both components of the vector are NaN.
+ bool is_nan() const;
+
+ //! Rotate this vector clockwise by \c deg degrees
+ Vector2<T> rotate(float deg) const;
};
} // namespace crepe
+
+template <typename T>
+struct std::formatter<crepe::Vector2<T>> : std::formatter<std::string> {
+ format_context::iterator format(crepe::Vector2<T> vec, format_context & ctx) const;
+};
+
+#include "Vector2.hpp"
diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp
new file mode 100644
index 0000000..30441d2
--- /dev/null
+++ b/src/crepe/api/Vector2.hpp
@@ -0,0 +1,186 @@
+#pragma once
+
+#include <cmath>
+
+#include "Vector2.h"
+
+namespace crepe {
+
+template <class T>
+Vector2<T> Vector2<T>::operator-(const Vector2<T> & other) const {
+ return {x - other.x, y - other.y};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator-(T scalar) const {
+ return {x - scalar, y - scalar};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator+(const Vector2<T> & other) const {
+ return {x + other.x, y + other.y};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator+(T scalar) const {
+ return {x + scalar, y + scalar};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator*(const Vector2<T> & other) const {
+ return {x * other.x, y * other.y};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator*(T scalar) const {
+ return {x * scalar, y * scalar};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator/(const Vector2<T> & other) const {
+ return {x / other.x, y / other.y};
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator/(T scalar) const {
+ return {x / scalar, y / scalar};
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator+=(const Vector2<T> & other) {
+ x += other.x;
+ y += other.y;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator+=(T other) {
+ x += other;
+ y += other;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator-=(const Vector2<T> & other) {
+ x -= other.x;
+ y -= other.y;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator-=(T other) {
+ x -= other;
+ y -= other;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator*=(const Vector2<T> & other) {
+ x *= other.x;
+ y *= other.y;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator*=(T other) {
+ x *= other;
+ y *= other;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator/=(const Vector2<T> & other) {
+ x /= other.x;
+ y /= other.y;
+ return *this;
+}
+
+template <class T>
+Vector2<T> & Vector2<T>::operator/=(T other) {
+ x /= other;
+ y /= other;
+ return *this;
+}
+
+template <class T>
+Vector2<T> Vector2<T>::operator-() const {
+ return {-x, -y};
+}
+
+template <class T>
+bool Vector2<T>::operator==(const Vector2<T> & other) const {
+ return x == other.x && y == other.y;
+}
+
+template <class T>
+bool Vector2<T>::operator!=(const Vector2<T> & other) const {
+ return !(*this == other);
+}
+
+template <class T>
+void Vector2<T>::truncate(T max) {
+ if (length() > max) {
+ normalize();
+ *this *= max;
+ }
+}
+
+template <class T>
+void Vector2<T>::normalize() {
+ T len = length();
+ if (len > 0) {
+ *this /= len;
+ }
+}
+
+template <class T>
+T Vector2<T>::length() const {
+ return std::sqrt(x * x + y * y);
+}
+
+template <class T>
+T Vector2<T>::length_squared() const {
+ return x * x + y * y;
+}
+
+template <class T>
+T Vector2<T>::dot(const Vector2<T> & other) const {
+ return x * other.x + y * other.y;
+}
+
+template <class T>
+T Vector2<T>::distance(const Vector2<T> & other) const {
+ return (*this - other).length();
+}
+
+template <class T>
+T Vector2<T>::distance_squared(const Vector2<T> & other) const {
+ return (*this - other).length_squared();
+}
+
+template <class T>
+Vector2<T> Vector2<T>::perpendicular() const {
+ return {-y, x};
+}
+
+template <class T>
+bool Vector2<T>::is_nan() const {
+ return std::isnan(x) && std::isnan(y);
+}
+
+template <class T>
+Vector2<T> Vector2<T>::rotate(float deg) const {
+ float rad = -deg / 180 * M_PI;
+ return {
+ x * std::cos(rad) - y * std::sin(rad),
+ x * std::sin(rad) + y * std::cos(rad),
+ };
+}
+
+} // namespace crepe
+
+template <typename T>
+std::format_context::iterator
+std::formatter<crepe::Vector2<T>>::format(crepe::Vector2<T> vec, format_context & ctx) const {
+ return formatter<string>::format(std::format("{{{}, {}}}", vec.x, vec.y), ctx);
+}
diff --git a/src/crepe/facade/CMakeLists.txt b/src/crepe/facade/CMakeLists.txt
index 4cc53bc..243ae46 100644
--- a/src/crepe/facade/CMakeLists.txt
+++ b/src/crepe/facade/CMakeLists.txt
@@ -1,14 +1,20 @@
target_sources(crepe PUBLIC
Sound.cpp
+ Texture.cpp
SoundContext.cpp
SDLContext.cpp
DB.cpp
+ FontFacade.cpp
+ Font.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
Sound.h
+ Texture.h
SoundContext.h
SDLContext.h
DB.h
+ FontFacade.h
+ Font.h
)
diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp
index 95cf606..7a3e473 100644
--- a/src/crepe/facade/DB.cpp
+++ b/src/crepe/facade/DB.cpp
@@ -1,6 +1,6 @@
#include <cstring>
-#include "util/Log.h"
+#include "util/dbg.h"
#include "DB.h"
@@ -21,12 +21,6 @@ DB::DB(const string & path) {
const char * file = path.empty() ? NULL : path.c_str();
ret = this->db->open(this->db.get(), NULL, file, NULL, libdb::DB_BTREE, DB_CREATE, 0);
if (ret != 0) throw runtime_error(format("db->open: {}", libdb::db_strerror(ret)));
-
- // create cursor
- libdb::DBC * cursor;
- ret = this->db->cursor(this->db.get(), NULL, &cursor, 0);
- if (ret != 0) throw runtime_error(format("db->cursor: {}", libdb::db_strerror(ret)));
- this->cursor = {cursor, [](libdb::DBC * cursor) { cursor->close(cursor); }};
}
libdb::DBT DB::to_thing(const string & thing) const noexcept {
@@ -42,10 +36,10 @@ string DB::get(const string & key) {
libdb::DBT db_val;
memset(&db_val, 0, sizeof(libdb::DBT));
- int ret = this->cursor->get(this->cursor.get(), &db_key, &db_val, DB_FIRST);
+ int ret = this->db->get(this->db.get(), NULL, &db_key, &db_val, 0);
if (ret == 0) return {static_cast<char *>(db_val.data), db_val.size};
- string err = format("cursor->get: {}", libdb::db_strerror(ret));
+ string err = format("db->get: {}", libdb::db_strerror(ret));
if (ret == DB_NOTFOUND) throw out_of_range(err);
else throw runtime_error(err);
}
@@ -54,7 +48,7 @@ void DB::set(const string & key, const string & value) {
libdb::DBT db_key = this->to_thing(key);
libdb::DBT db_val = this->to_thing(value);
int ret = this->db->put(this->db.get(), NULL, &db_key, &db_val, 0);
- if (ret != 0) throw runtime_error(format("cursor->get: {}", libdb::db_strerror(ret)));
+ if (ret != 0) throw runtime_error(format("db->get: {}", libdb::db_strerror(ret)));
}
bool DB::has(const std::string & key) {
diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h
index 115c0f1..84cdf19 100644
--- a/src/crepe/facade/DB.h
+++ b/src/crepe/facade/DB.h
@@ -61,8 +61,6 @@ public:
private:
//! RAII wrapper around \c DB struct
std::unique_ptr<libdb::DB, std::function<void(libdb::DB *)>> db;
- //! RAII wrapper around \c DBC struct
- std::unique_ptr<libdb::DBC, std::function<void(libdb::DBC *)>> cursor;
private:
/**
diff --git a/src/crepe/facade/EventData.h b/src/crepe/facade/EventData.h
new file mode 100644
index 0000000..a7526b4
--- /dev/null
+++ b/src/crepe/facade/EventData.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "../api/KeyCodes.h"
+#include "../types.h"
+namespace crepe {
+//! EventType enum for passing eventType
+enum EventType {
+ NONE = 0,
+ MOUSE_DOWN,
+ MOUSE_UP,
+ MOUSE_MOVE,
+ MOUSE_WHEEL,
+ KEY_UP,
+ KEY_DOWN,
+ SHUTDOWN,
+ WINDOW_MINIMIZE,
+ WINDOW_MAXIMIZE,
+ WINDOW_FOCUS_GAIN,
+ WINDOW_FOCUS_LOST,
+ WINDOW_MOVE,
+ WINDOW_RESIZE,
+ WINDOW_EXPOSE,
+};
+
+//! Struct for storing key data.
+struct KeyData {
+ Keycode key = Keycode::NONE;
+ bool key_repeat = false;
+};
+
+//! Struct for storing mouse data.
+struct MouseData {
+ MouseButton mouse_button = MouseButton::NONE;
+ ivec2 mouse_position = {-1, -1};
+ int scroll_direction = -1;
+ float scroll_delta = INFINITY;
+ ivec2 rel_mouse_move = {-1, -1};
+};
+
+//! Struct for storing window data.
+struct WindowData {
+ ivec2 move_delta;
+ ivec2 resize_dimension;
+};
+
+//! EventData struct for passing event data from facade
+struct EventData {
+ EventType event_type = EventType::NONE;
+ union {
+ KeyData key_data;
+ MouseData mouse_data;
+ WindowData window_data;
+ } data;
+};
+} // namespace crepe
diff --git a/src/crepe/facade/Font.cpp b/src/crepe/facade/Font.cpp
new file mode 100644
index 0000000..771002f
--- /dev/null
+++ b/src/crepe/facade/Font.cpp
@@ -0,0 +1,21 @@
+#include <SDL2/SDL_ttf.h>
+
+#include "../api/Asset.h"
+#include "../api/Config.h"
+
+#include "Font.h"
+
+using namespace std;
+using namespace crepe;
+
+Font::Font(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ const Config & config = Config::get_instance();
+ const std::string FONT_PATH = src.get_path();
+ TTF_Font * loaded_font = TTF_OpenFont(FONT_PATH.c_str(), config.font.size);
+ if (loaded_font == NULL) {
+ throw runtime_error(format("Font: {} (path: {})", TTF_GetError(), FONT_PATH));
+ }
+ this->font = {loaded_font, [](TTF_Font * close_font) { TTF_CloseFont(close_font); }};
+}
+
+TTF_Font * Font::get_font() const { return this->font.get(); }
diff --git a/src/crepe/facade/Font.h b/src/crepe/facade/Font.h
new file mode 100644
index 0000000..b208d96
--- /dev/null
+++ b/src/crepe/facade/Font.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <SDL2/SDL_ttf.h>
+#include <memory>
+
+#include "../Resource.h"
+#include "../api/Config.h"
+
+namespace crepe {
+
+class Asset;
+/**
+ * \brief Resource for managing font creation and destruction
+ *
+ * This class is a wrapper around an SDL_ttf font instance, encapsulating font loading and usage.
+ * It loads a font from an Asset and manages its lifecycle. The font is automatically unloaded
+ * when this object is destroyed.
+ */
+class Font : public Resource {
+
+public:
+ /**
+ * \param src The Asset containing the font file path and metadata to load the font.
+ * \param mediator The Mediator object used for managing the SDL context or related systems.
+ */
+ Font(const Asset & src, Mediator & mediator);
+ /**
+ * \brief Gets the underlying TTF_Font resource.
+ *
+ * This function returns the raw pointer to the SDL_ttf TTF_Font object that represents
+ * the loaded font. This can be used with SDL_ttf functions to render text.
+ *
+ * \return The raw TTF_Font object wrapped in a unique pointer.
+ */
+ TTF_Font * get_font() const;
+
+private:
+ //! The SDL_ttf font object with custom deleter.
+ std::unique_ptr<TTF_Font, std::function<void(TTF_Font *)>> font = nullptr;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/FontFacade.cpp b/src/crepe/facade/FontFacade.cpp
new file mode 100644
index 0000000..e284f5a
--- /dev/null
+++ b/src/crepe/facade/FontFacade.cpp
@@ -0,0 +1,44 @@
+#include <fontconfig/fontconfig.h>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+#include "FontFacade.h"
+
+using namespace std;
+using namespace crepe;
+
+FontFacade::FontFacade() {
+ if (!FcInit()) throw runtime_error("Failed to initialize Fontconfig.");
+}
+
+FontFacade::~FontFacade() { FcFini(); }
+
+Asset FontFacade::get_font_asset(const string & font_family) {
+ FcPattern * raw_pattern
+ = FcNameParse(reinterpret_cast<const FcChar8 *>(font_family.c_str()));
+ if (raw_pattern == NULL) throw runtime_error("Failed to create font pattern.");
+
+ unique_ptr<FcPattern, function<void(FcPattern *)>> pattern {
+ raw_pattern, [](FcPattern * p) { FcPatternDestroy(p); }
+ };
+
+ FcConfig * config = FcConfigGetCurrent();
+ if (config == NULL) throw runtime_error("Failed to get current Fontconfig configuration.");
+
+ FcResult result;
+ FcPattern * raw_matched_pattern = FcFontMatch(config, pattern.get(), &result);
+ if (raw_matched_pattern == NULL) throw runtime_error("No matching font found.");
+
+ unique_ptr<FcPattern, function<void(FcPattern *)>> matched_pattern
+ = {raw_matched_pattern, [](FcPattern * p) { FcPatternDestroy(p); }};
+
+ FcChar8 * file_path = nullptr;
+ FcResult res = FcPatternGetString(matched_pattern.get(), FC_FILE, 0, &file_path);
+ if (res != FcResultMatch || file_path == NULL)
+ throw runtime_error("Failed to get font file path.");
+
+ string font_file_path = reinterpret_cast<const char *>(file_path);
+ return Asset(font_file_path);
+}
diff --git a/src/crepe/facade/FontFacade.h b/src/crepe/facade/FontFacade.h
new file mode 100644
index 0000000..9761070
--- /dev/null
+++ b/src/crepe/facade/FontFacade.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <memory>
+
+#include "../api/Asset.h"
+
+namespace crepe {
+
+/**
+ *
+ * \brief Font facade class for converting font family names to absolute file paths
+ *
+ */
+class FontFacade {
+public:
+ FontFacade();
+ ~FontFacade();
+ FontFacade(const FontFacade & other) = delete;
+ FontFacade & operator=(const FontFacade & other) = delete;
+ FontFacade(FontFacade && other) noexcept = delete;
+ FontFacade & operator=(FontFacade && other) noexcept = delete;
+ /**
+ *
+ * \brief Facade function to convert a font_family into an asset.
+ *
+ * This function uses the FontConfig library to convert a font family name (Arial, Inter, Helvetica) and converts it to the font source path.
+ * This function returns a default font path if the font_family name doesnt exist or cant be found
+ * \param font_family Name of the font family name.
+ * \return Asset with filepath to the corresponding font.
+ */
+ Asset get_font_asset(const std::string & font_family);
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp
index 00523a6..6c93fb2 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -1,44 +1,50 @@
#include <SDL2/SDL.h>
+#include <SDL2/SDL_blendmode.h>
+#include <SDL2/SDL_hints.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_keycode.h>
+#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_surface.h>
+#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_video.h>
+#include <array>
#include <cmath>
#include <cstddef>
#include <functional>
#include <memory>
#include <stdexcept>
-#include <string>
#include "../api/Camera.h"
+#include "../api/Color.h"
+#include "../api/Config.h"
#include "../api/Sprite.h"
-#include "../api/Texture.h"
-#include "../api/Transform.h"
-#include "../api/Vector2.h"
-#include "../util/Log.h"
+#include "../util/dbg.h"
+#include "api/Text.h"
+#include "api/Transform.h"
+#include "facade/Font.h"
+#include "manager/Mediator.h"
+#include "util/AbsolutePosition.h"
#include "SDLContext.h"
+#include "Texture.h"
+#include "types.h"
using namespace crepe;
using namespace std;
-SDLContext & SDLContext::get_instance() {
- static SDLContext instance;
- return instance;
-}
-
-SDLContext::SDLContext() {
+SDLContext::SDLContext(Mediator & mediator) {
dbg_trace();
- // FIXME: read window defaults from config manager
-
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));
}
- SDL_Window * tmp_window
- = SDL_CreateWindow("Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- this->viewport.w, this->viewport.h, 0);
+
+ auto & cfg = Config::get_instance().window_settings;
+ SDL_Window * tmp_window = SDL_CreateWindow(
+ cfg.window_title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ cfg.default_size.x, cfg.default_size.y, 0
+ );
if (!tmp_window) {
throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError()));
}
@@ -47,8 +53,8 @@ SDLContext::SDLContext() {
SDL_Renderer * tmp_renderer
= SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED);
if (!tmp_renderer) {
- throw runtime_error(
- format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError()));
+ throw runtime_error(format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())
+ );
}
this->game_renderer
@@ -58,6 +64,12 @@ SDLContext::SDLContext() {
if (!(IMG_Init(img_flags) & img_flags)) {
throw runtime_error("SDLContext: SDL_image could not initialize!");
}
+
+ if (TTF_Init() == -1) {
+ throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError()));
+ }
+
+ mediator.sdl_context = *this;
}
SDLContext::~SDLContext() {
@@ -69,96 +81,242 @@ SDLContext::~SDLContext() {
// TODO: how are we going to ensure that these are called from the same
// thread that SDL_Init() was called on? This has caused problems for me
// before.
+ TTF_Quit();
IMG_Quit();
SDL_Quit();
}
-void SDLContext::handle_events(bool & running) {
- //TODO: wouter i need events
- /*
- SDL_Event event;
- SDL_PollEvent(&event);
- switch (event.type) {
- case SDL_QUIT:
- running = false;
- break;
- case SDL_KEYDOWN:
- triggerEvent(KeyPressedEvent(getCustomKey(event.key.keysym.sym)));
- break;
- case SDL_MOUSEBUTTONDOWN:
- int x, y;
- SDL_GetMouseState(&x, &y);
- triggerEvent(MousePressedEvent(x, y));
- break;
+
+Keycode SDLContext::sdl_to_keycode(SDL_Scancode sdl_key) {
+ if (!lookup_table.contains(sdl_key)) return Keycode::NONE;
+ return lookup_table.at(sdl_key);
+}
+
+const keyboard_state_t & SDLContext::get_keyboard_state() {
+ SDL_PumpEvents();
+ const Uint8 * current_state = SDL_GetKeyboardState(nullptr);
+
+ for (int i = 0; i < SDL_NUM_SCANCODES; ++i) {
+
+ Keycode key = sdl_to_keycode(static_cast<SDL_Scancode>(i));
+ if (key != Keycode::NONE) {
+ this->keyboard_state[key] = current_state[i] != 0;
+ }
}
- */
+ return this->keyboard_state;
+}
+
+MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) {
+ static const std::array<MouseButton, 5> MOUSE_BUTTON_LOOKUP_TABLE = [] {
+ std::array<MouseButton, 5> table {};
+ table.fill(MouseButton::NONE);
+
+ table[SDL_BUTTON_LEFT] = MouseButton::LEFT_MOUSE;
+ table[SDL_BUTTON_RIGHT] = MouseButton::RIGHT_MOUSE;
+ table[SDL_BUTTON_MIDDLE] = MouseButton::MIDDLE_MOUSE;
+ table[SDL_BUTTON_X1] = MouseButton::X1_MOUSE;
+ table[SDL_BUTTON_X2] = MouseButton::X2_MOUSE;
+
+ return table;
+ }();
+
+ if (sdl_button >= MOUSE_BUTTON_LOOKUP_TABLE.size()) {
+ // Return NONE for invalid or unmapped button
+ return MouseButton::NONE;
+ }
+
+ return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button];
}
void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); }
-void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); }
-
-SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const {
- return SDL_Rect{
- .x = sprite.sprite_rect.x,
- .y = sprite.sprite_rect.y,
- .w = sprite.sprite_rect.w,
- .h = sprite.sprite_rect.h,
- };
+void SDLContext::present_screen() {
+ SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255);
+ SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[0]);
+ SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[1]);
+ SDL_RenderPresent(this->game_renderer.get());
}
-SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const Vector2 & pos,
- const double & scale, const Camera & cam) const {
- double adjusted_x = (pos.x - cam.x) * cam.zoom;
- double adjusted_y = (pos.y - cam.y) * cam.zoom;
- double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom;
- double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom;
+SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {
+
+ const Sprite::Data & data = ctx.sprite.data;
+
+ float aspect_ratio
+ = (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio;
+
+ vec2 size = data.size;
+ vec2 screen_pos = ctx.pos;
- return SDL_Rect{
- .x = static_cast<int>(adjusted_x),
- .y = static_cast<int>(adjusted_y),
- .w = static_cast<int>(adjusted_w),
- .h = static_cast<int>(adjusted_h),
+ if (data.size.x == 0 && data.size.y != 0) {
+ size.x = data.size.y * aspect_ratio;
+ }
+ if (data.size.y == 0 && data.size.x != 0) {
+ size.y = data.size.x / aspect_ratio;
+ }
+ size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset;
+
+ if (ctx.sprite.data.world_space) {
+ screen_pos = (screen_pos - cam_aux_data.cam_pos + cam_aux_data.zoomed_viewport / 2)
+ * cam_aux_data.render_scale
+ - size / 2 + cam_aux_data.bar_size;
+ } else {
+ screen_pos
+ = (screen_pos + cam_aux_data.zoomed_viewport / 2) * cam_aux_data.render_scale
+ - size / 2 + cam_aux_data.bar_size;
+ }
+
+ return SDL_FRect {
+ .x = screen_pos.x,
+ .y = screen_pos.y,
+ .w = size.x,
+ .h = size.y,
};
}
-void SDLContext::draw_particle(const Sprite & sprite, const Vector2 & pos,
- const double & angle, const double & scale,
- const Camera & camera) {
-
+void SDLContext::draw(const RenderContext & ctx) {
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
+ const Sprite::Data & data = ctx.sprite.data;
SDL_RendererFlip render_flip
- = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x)
- | (SDL_FLIP_VERTICAL * sprite.flip.flip_y));
+ = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x)
+ | (SDL_FLIP_VERTICAL * data.flip.flip_y));
+
+ SDL_Rect srcrect;
+ SDL_Rect * srcrect_ptr = NULL;
+ if (ctx.sprite.mask.w != 0 || ctx.sprite.mask.h != 0) {
+ srcrect.w = ctx.sprite.mask.w;
+ srcrect.h = ctx.sprite.mask.h;
+ srcrect.x = ctx.sprite.mask.x;
+ srcrect.y = ctx.sprite.mask.y;
+ srcrect_ptr = &srcrect;
+ }
- SDL_Rect srcrect = this->get_src_rect(sprite);
- SDL_Rect dstrect = this->get_dst_rect(sprite, pos, scale, camera);
+ SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData {
+ .sprite = ctx.sprite,
+ .texture = ctx.texture,
+ .pos = ctx.pos,
+ .img_scale = ctx.scale,
+ });
- SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect,
- &dstrect, angle, NULL, render_flip);
+ double angle = ctx.angle + data.angle_offset;
+
+ this->set_color_texture(ctx.texture, ctx.sprite.data.color);
+ SDL_RenderCopyExF(
+ this->game_renderer.get(), ctx.texture.get_img(), srcrect_ptr, &dstrect, angle, NULL,
+ render_flip
+ );
}
-void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) {
+void SDLContext::draw_text(const RenderText & data) {
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
- SDL_RendererFlip render_flip
- = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x)
- | (SDL_FLIP_VERTICAL * sprite.flip.flip_y));
+ const Text & text = data.text;
+ const Font & font = data.font;
+ vec2 absoluut_pos = AbsolutePosition::get_position(data.transform, data.text.offset);
+ std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> font_surface;
+ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> font_texture;
- SDL_Rect srcrect = this->get_src_rect(sprite);
- SDL_Rect dstrect = this->get_dst_rect(sprite, transform.position, transform.scale, cam);
+ SDL_Color color {
+ .r = text.data.text_color.r,
+ .g = text.data.text_color.g,
+ .b = text.data.text_color.b,
+ .a = text.data.text_color.a,
+ };
+ SDL_Surface * tmp_font_surface
+ = TTF_RenderText_Solid(font.get_font(), text.text.c_str(), color);
+ if (tmp_font_surface == NULL) {
+ throw runtime_error(format("draw_text: font surface error: {}", SDL_GetError()));
+ }
+ font_surface = {tmp_font_surface, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }};
- SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect,
- &dstrect, transform.rotation, NULL, render_flip);
-}
+ SDL_Texture * tmp_font_texture
+ = SDL_CreateTextureFromSurface(this->game_renderer.get(), font_surface.get());
+ if (tmp_font_texture == NULL) {
+ throw runtime_error(format("draw_text: font texture error: {}", SDL_GetError()));
+ }
+ font_texture
+ = {tmp_font_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }};
+
+ vec2 size = text.dimensions * cam_aux_data.render_scale * data.transform.scale;
+ vec2 screen_pos = absoluut_pos;
+ if (text.data.world_space) {
+ screen_pos = (screen_pos - cam_aux_data.cam_pos + (cam_aux_data.zoomed_viewport) / 2)
+ * cam_aux_data.render_scale
+ - size / 2 + cam_aux_data.bar_size;
+ } else {
+ screen_pos
+ = (screen_pos + (cam_aux_data.zoomed_viewport) / 2) * cam_aux_data.render_scale
+ - size / 2 + cam_aux_data.bar_size;
+ }
-void SDLContext::set_camera(const Camera & cam) {
- this->viewport.w = static_cast<int>(cam.aspect_width);
- this->viewport.h = static_cast<int>(cam.aspect_height);
- this->viewport.x = static_cast<int>(cam.x) - (this->viewport.w / 2);
- this->viewport.y = static_cast<int>(cam.y) - (this->viewport.h / 2);
+ SDL_FRect dstrect {
+ .x = screen_pos.x,
+ .y = screen_pos.y,
+ .w = size.x,
+ .h = size.y,
+ };
- SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g,
- cam.bg_color.b, cam.bg_color.a);
+ SDL_RenderCopyExF(
+ this->game_renderer.get(), font_texture.get(), NULL, &dstrect, data.transform.rotation,
+ NULL, SDL_FLIP_NONE
+ );
}
-uint64_t SDLContext::get_ticks() const { return SDL_GetTicks64(); }
+void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {
+
+ const Camera::Data & cam_data = cam.data;
+ // resize window
+ int w, h;
+ SDL_GetWindowSize(this->game_window.get(), &w, &h);
+ if (w != cam.screen.x || h != cam.screen.y) {
+ SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y);
+ }
+
+ vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport;
+ vec2 & bar_size = this->cam_aux_data.bar_size;
+ vec2 & render_scale = this->cam_aux_data.render_scale;
+ this->cam_aux_data.cam_pos = new_pos;
+
+ zoomed_viewport = cam.viewport_size * cam_data.zoom;
+ float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y;
+ float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y;
+
+ // calculate black bars
+ if (screen_aspect > viewport_aspect) {
+ // pillarboxing
+ float scale = cam.screen.y / zoomed_viewport.y;
+ float adj_width = zoomed_viewport.x * scale;
+ float bar_width = (cam.screen.x - adj_width) / 2;
+ this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y};
+ this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y};
+
+ bar_size = {bar_width, 0};
+ render_scale.x = render_scale.y = scale;
+ } else {
+ // letterboxing
+ float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom);
+ float adj_height = cam.viewport_size.y * scale;
+ float bar_height = (cam.screen.y - adj_height) / 2;
+ this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height};
+ this->black_bars[1]
+ = {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height};
+
+ bar_size = {0, bar_height};
+ render_scale.x = render_scale.y = scale;
+ }
+
+ SDL_SetRenderDrawColor(
+ this->game_renderer.get(), cam_data.bg_color.r, cam_data.bg_color.g,
+ cam_data.bg_color.b, cam_data.bg_color.a
+ );
+
+ SDL_Rect bg = {
+ .x = 0,
+ .y = 0,
+ .w = cam.screen.x,
+ .h = cam.screen.y,
+ };
+
+ // fill bg color
+ SDL_RenderFillRect(this->game_renderer.get(), &bg);
+}
std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>
SDLContext::texture_from_path(const std::string & path) {
@@ -180,16 +338,156 @@ SDLContext::texture_from_path(const std::string & path) {
std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture;
img_texture = {tmp_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }};
+ SDL_SetTextureBlendMode(img_texture.get(), SDL_BLENDMODE_BLEND);
return img_texture;
}
-int SDLContext::get_width(const Texture & ctx) const {
- int w;
- SDL_QueryTexture(ctx.texture.get(), NULL, NULL, &w, NULL);
- return w;
+
+ivec2 SDLContext::get_size(const Texture & ctx) {
+ ivec2 size;
+ SDL_QueryTexture(ctx.get_img(), NULL, NULL, &size.x, &size.y);
+ return size;
}
-int SDLContext::get_height(const Texture & ctx) const {
- int h;
- SDL_QueryTexture(ctx.texture.get(), NULL, NULL, NULL, &h);
- return h;
+
+std::vector<EventData> SDLContext::get_events() {
+ std::vector<EventData> event_list;
+ SDL_Event event;
+ const CameraAuxiliaryData & cam = this->cam_aux_data;
+ while (SDL_PollEvent(&event)) {
+ ivec2 mouse_pos;
+ mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x;
+ mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y;
+ switch (event.type) {
+ case SDL_QUIT:
+ event_list.push_back({.event_type = EventType::SHUTDOWN});
+ break;
+ case SDL_KEYDOWN:
+ event_list.push_back(EventData{
+ .event_type = EventType::KEY_DOWN,
+ .data = {
+ .key_data = {
+ .key = this->sdl_to_keycode(event.key.keysym.scancode),
+ .key_repeat = event.key.repeat != 0,
+ },
+ },
+ });
+ break;
+
+ case SDL_KEYUP:
+ event_list.push_back(EventData{
+ .event_type = EventType::KEY_UP,
+ .data = {
+ .key_data = {
+ .key = this->sdl_to_keycode(event.key.keysym.scancode),
+ .key_repeat = event.key.repeat != 0,
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_DOWN,
+ .data = {
+ .mouse_data = {
+ .mouse_button = this->sdl_to_mousebutton(event.button.button),
+ .mouse_position = mouse_pos,
+ },
+ },
+ });
+ break;
+ case SDL_MOUSEBUTTONUP:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_UP,
+ .data = {
+ .mouse_data = {
+ .mouse_button = this->sdl_to_mousebutton(event.button.button),
+ .mouse_position = mouse_pos,
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEMOTION:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_MOVE,
+ .data = {
+ .mouse_data = {
+ .mouse_position = mouse_pos,
+ .rel_mouse_move = {event.motion.xrel, event.motion.yrel},
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEWHEEL:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_WHEEL,
+ .data = {
+ .mouse_data = {
+ .mouse_position = mouse_pos,
+ .scroll_direction = event.wheel.y < 0 ? -1 : 1,
+ .scroll_delta = event.wheel.preciseY,
+ },
+ },
+ });
+ break;
+ case SDL_WINDOWEVENT:
+ this->handle_window_event(event.window, event_list);
+ break;
+ }
+ }
+
+ return event_list;
+}
+
+void SDLContext::handle_window_event(
+ const SDL_WindowEvent & window_event, std::vector<EventData> & event_list
+) {
+ switch (window_event.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ event_list.push_back(EventData {EventType::WINDOW_EXPOSE});
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ event_list.push_back(EventData{
+ .event_type = EventType::WINDOW_RESIZE,
+ .data = {
+ .window_data = {
+ .resize_dimension = {window_event.data1, window_event.data2}
+ },
+ },
+ });
+ break;
+ case SDL_WINDOWEVENT_MOVED:
+ event_list.push_back(EventData{
+ .event_type = EventType::WINDOW_MOVE,
+ .data = {
+ .window_data = {
+ .move_delta = {window_event.data1, window_event.data2}
+ },
+ },
+ });
+ break;
+
+ case SDL_WINDOWEVENT_MINIMIZED:
+ event_list.push_back(EventData {EventType::WINDOW_MINIMIZE});
+ break;
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ event_list.push_back(EventData {EventType::WINDOW_MAXIMIZE});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ event_list.push_back(EventData {EventType::WINDOW_FOCUS_GAIN});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ event_list.push_back(EventData {EventType::WINDOW_FOCUS_LOST});
+ break;
+ }
+}
+
+void SDLContext::set_color_texture(const Texture & texture, const Color & color) {
+ SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b);
+ SDL_SetTextureAlphaMod(texture.get_img(), color.a);
+}
+
+Asset SDLContext::get_font_from_name(const std::string & font_family) {
+ return this->font_facade.get_font_asset(font_family);
}
-void SDLContext::delay(int ms) const { SDL_Delay(ms); }
diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h
index 841ffc9..bc118f9 100644
--- a/src/crepe/facade/SDLContext.h
+++ b/src/crepe/facade/SDLContext.h
@@ -1,5 +1,6 @@
#pragma once
+#include <SDL2/SDL.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
@@ -8,53 +9,149 @@
#include <functional>
#include <memory>
#include <string>
+#include <unordered_map>
-#include "../api/Sprite.h"
-#include "../api/Transform.h"
+#include "../types.h"
+#include "EventData.h"
#include "api/Camera.h"
-#include "api/Vector2.h"
+#include "api/Color.h"
+#include "api/KeyCodes.h"
+#include "api/Sprite.h"
+#include "api/Transform.h"
-namespace crepe {
+#include "EventData.h"
+#include "FontFacade.h"
+#include "types.h"
-// TODO: SDL_Keycode is defined in a header not distributed with crepe, which means this
-// typedef is unusable when crepe is packaged. Wouter will fix this later.
-typedef SDL_Keycode CREPE_KEYCODES;
+namespace crepe {
+class Texture;
+class Text;
+class Font;
+class Mediator;
/**
- * \class SDLContext
* \brief Facade for the SDL library
- *
+ *
* SDLContext is a singleton that handles the SDL window and renderer, provides methods for
* event handling, and rendering to the screen. It is never used directly by the user
*/
class SDLContext {
-
public:
- /**
- * \brief Gets the singleton instance of SDLContext.
- * \return Reference to the SDLContext instance.
- */
- static SDLContext & get_instance();
+ //! data that the camera component cannot hold
+ struct CameraAuxiliaryData {
+
+ //! zoomed in viewport in game_units
+ vec2 zoomed_viewport;
+
+ /**
+ * \brief scaling factor
+ *
+ * depending on the black bars type will the scaling be different.
+ * - letterboxing --> scaling on the y-as
+ * - pillarboxing --> scaling on the x-as
+ */
+ vec2 render_scale;
+
+ /**
+ * \brief size of calculated black bars
+ *
+ * depending on the black bars type will the size be different
+ * - lettorboxing --> {0, bar_height}
+ * - pillarboxing --> {bar_width , 0}
+ */
+ vec2 bar_size;
+
+ //! Calculated camera position
+ vec2 cam_pos;
+ };
+
+ //! rendering data needed to render on screen
+ struct RenderContext {
+ const Sprite & sprite;
+ const Texture & texture;
+ const vec2 & pos;
+ const double & angle;
+ const double & scale;
+ };
+
+ struct RenderText {
+ const Text & text;
+ const Font & font;
+ const Transform & transform;
+ };
+public:
SDLContext(const SDLContext &) = delete;
SDLContext(SDLContext &&) = delete;
SDLContext & operator=(const SDLContext &) = delete;
SDLContext & operator=(SDLContext &&) = delete;
-private:
- //! will only use handle_events
- friend class LoopManager;
+public:
/**
- * \brief Handles SDL events such as window close and input.
- * \param running Reference to a boolean flag that controls the main loop.
+ * \brief Constructs an SDLContext instance.
+ * Initializes SDL, creates a window and renderer.
*/
- void handle_events(bool & running);
+ SDLContext(Mediator & mediator);
-private:
- //! Will only use get_ticks
- friend class AnimatorSystem;
- //! Will only use delay
- friend class LoopTimer;
+ /**
+ * \brief Destroys the SDLContext instance.
+ * Cleans up SDL resources, including the window and renderer.
+ */
+ ~SDLContext();
+
+public:
+ /**
+ * \brief Retrieves a list of all events from the SDL context.
+ *
+ * This method retrieves all the events from the SDL context that are currently
+ * available. It is primarily used by the InputSystem to process various
+ * input events such as mouse clicks, mouse movements, and keyboard presses.
+ *
+ * \return Events that occurred since last call to `get_events()`
+ */
+ std::vector<EventData> get_events();
+ /**
+ * \brief Fills event_list with triggered window events
+ *
+ * This method checks if any window events are triggered and adds them to the event_list.
+ *
+ */
+ void handle_window_event(
+ const SDL_WindowEvent & window_event, std::vector<EventData> & event_list
+ );
+ /**
+ * \brief Converts an SDL scan code to the custom Keycode type.
+ *
+ * This method maps an SDL scan code to the corresponding `Keycode` enum value,
+ * which is used internally by the system to identify the keys.
+ *
+ * \param sdl_key The SDL scan code to convert.
+ * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.
+ */
+ Keycode sdl_to_keycode(SDL_Scancode sdl_key);
+
+ /**
+ * \brief Converts an SDL mouse button code to the custom MouseButton type.
+ *
+ * This method maps an SDL mouse button code to the corresponding `MouseButton`
+ * enum value, which is used internally by the system to identify mouse buttons.
+ *
+ * \param sdl_button The SDL mouse button code to convert.
+ * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized
+ */
+ MouseButton sdl_to_mousebutton(Uint8 sdl_button);
+ /**
+ * \brief Gets the current state of the keyboard.
+ *
+ * Updates the internal keyboard state by checking the current key states using
+ * SDL's `SDL_GetKeyboardState()`, and returns a reference to the `keyboard_state_t`.
+ *
+ * \return A constant reference to the `keyboard_state_t`, which holds the state
+ * of each key (true = pressed, false = not pressed).
+ */
+ const keyboard_state_t & get_keyboard_state();
+
+public:
/**
* \brief Gets the current SDL ticks since the program started.
* \return Current ticks in milliseconds as a constant uint64_t.
@@ -70,23 +167,7 @@ private:
*/
void delay(int ms) const;
-private:
- /**
- * \brief Constructs an SDLContext instance.
- * Initializes SDL, creates a window and renderer.
- */
- SDLContext();
-
- /**
- * \brief Destroys the SDLContext instance.
- * Cleans up SDL resources, including the window and renderer.
- */
- ~SDLContext();
-
-private:
- //! Will use the funtions: texture_from_path, get_width,get_height.
- friend class Texture;
-
+public:
/**
* \brief Loads a texture from a file path.
* \param path Path to the image file.
@@ -95,33 +176,25 @@ private:
std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>
texture_from_path(const std::string & path);
/**
- * \brief Gets the width of a texture.
+ * \brief Gets the size of a texture.
* \param texture Reference to the Texture object.
- * \return Width of the texture as an integer.
+ * \return Width and height of the texture as an integer in pixels.
*/
- int get_width(const Texture &) const;
+ ivec2 get_size(const Texture & ctx);
+public:
/**
- * \brief Gets the height of a texture.
- * \param texture Reference to the Texture object.
- * \return Height of the texture as an integer.
+ * \brief Draws a sprite to the screen using the specified transform and camera.
+ * \param RenderContext Reference to rendering data to draw
*/
- int get_height(const Texture &) const;
-
-private:
- //! Will use draw,clear_screen, present_screen, camera.
- friend class RenderSystem;
+ void draw(const RenderContext & ctx);
/**
- * \brief Draws a sprite to the screen using the specified transform and camera.
- * \param sprite Reference to the Sprite to draw.
- * \param transform Reference to the Transform for positioning.
- * \param camera Reference to the Camera for view adjustments.
+ * \brief draws a text to the screen
+ *
+ * \param data Reference to the rendering data needed to draw
*/
- void draw(const Sprite & sprite, const Transform & transform, const Camera & camera);
-
- void draw_particle(const Sprite & sprite, const Vector2 & pos, const double & angle,
- const double & scale, const Camera & camera);
+ void draw_text(const RenderText & data);
//! Clears the screen, preparing for a new frame.
void clear_screen();
@@ -130,31 +203,38 @@ private:
void present_screen();
/**
- * \brief sets the background of the camera (will be adjusted in future PR)
- * \param camera Reference to the Camera object.
+ * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and
+ * adjusting window size.
+ *
+ * \note only supports windowed mode.
+ * \param camera Reference to the current Camera object in the scene.
+ * \param new_pos new camera position from transform and offset
*/
- void set_camera(const Camera & camera);
+ void update_camera_view(const Camera & camera, const vec2 & new_pos);
+
+public:
+ //! the data needed to construct a sdl dst rectangle
+ struct DestinationRectangleData {
+ const Sprite & sprite;
+ const Texture & texture;
+ const vec2 & pos;
+ const double & img_scale;
+ };
-private:
/**
- * \brief calculates the sqaure size of the image
+ * \brief calculates the sqaure size of the image for destination
*
- * \param sprite Reference to the sprite to calculate the rectangle
- * \return sdl rectangle to draw a src image
+ * \param data needed to calculate a destination rectangle
+ * \return sdl rectangle to draw a dst image to draw on the screen
*/
- SDL_Rect get_src_rect(const Sprite & sprite) const;
+ SDL_FRect get_dst_rect(const DestinationRectangleData & data) const;
/**
- * \brief calculates the sqaure size of the image for an destination
+ * \brief Set an additional color value multiplied into render copy operations.
*
- * \param sprite Reference to the sprite to calculate the rectangle
- * \param pos the pos in pixel positions
- * \param scale the multiplier to increase of decrease for the specified sprite
- * \param cam Reference to the current camera in the scene to calculate the position based
- * on the camera
- * \return sdl rectangle to draw a dst image to draw on the screen
+ * \param texture the given texture to adjust
+ * \param color the color data for the texture
*/
- SDL_Rect get_dst_rect(const Sprite & sprite, const Vector2 & pos, const double & scale,
- const Camera & cam) const;
+ void set_color_texture(const Texture & texture, const Color & color);
private:
//! sdl Window
@@ -163,8 +243,133 @@ private:
//! renderer for the crepe engine
std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> game_renderer;
- //! viewport for the camera window
- SDL_Rect viewport = {0, 0, 640, 480};
+ //! black bars rectangle to draw
+ SDL_FRect black_bars[2] = {};
+
+ /**
+ * \cam_aux_data extra data that the component cannot hold.
+ *
+ * - this is defined in this class because get_events() needs this information aswell
+ */
+ CameraAuxiliaryData cam_aux_data;
+
+private:
+ //! instance of the font_facade
+ FontFacade font_facade {};
+
+public:
+ /**
+ * \brief Function to Get asset from font_family
+ *
+ * This function uses the FontFacade function to convert a font_family to an asset.
+ *
+ * \param font_family name of the font style that needs to be used (will return an asset with default font path of the font_family doesnt exist)
+ *
+ * \return asset with the font style absolute path
+ */
+ Asset get_font_from_name(const std::string & font_family);
+ //! variable to store the state of each key (true = pressed, false = not pressed)
+ keyboard_state_t keyboard_state;
+ //! lookup table for converting SDL_SCANCODES to Keycodes
+ const std::unordered_map<SDL_Scancode, Keycode> lookup_table
+ = {{SDL_SCANCODE_SPACE, Keycode::SPACE},
+ {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE},
+ {SDL_SCANCODE_COMMA, Keycode::COMMA},
+ {SDL_SCANCODE_MINUS, Keycode::MINUS},
+ {SDL_SCANCODE_PERIOD, Keycode::PERIOD},
+ {SDL_SCANCODE_SLASH, Keycode::SLASH},
+ {SDL_SCANCODE_0, Keycode::D0},
+ {SDL_SCANCODE_1, Keycode::D1},
+ {SDL_SCANCODE_2, Keycode::D2},
+ {SDL_SCANCODE_3, Keycode::D3},
+ {SDL_SCANCODE_4, Keycode::D4},
+ {SDL_SCANCODE_5, Keycode::D5},
+ {SDL_SCANCODE_6, Keycode::D6},
+ {SDL_SCANCODE_7, Keycode::D7},
+ {SDL_SCANCODE_8, Keycode::D8},
+ {SDL_SCANCODE_9, Keycode::D9},
+ {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON},
+ {SDL_SCANCODE_EQUALS, Keycode::EQUAL},
+ {SDL_SCANCODE_A, Keycode::A},
+ {SDL_SCANCODE_B, Keycode::B},
+ {SDL_SCANCODE_C, Keycode::C},
+ {SDL_SCANCODE_D, Keycode::D},
+ {SDL_SCANCODE_E, Keycode::E},
+ {SDL_SCANCODE_F, Keycode::F},
+ {SDL_SCANCODE_G, Keycode::G},
+ {SDL_SCANCODE_H, Keycode::H},
+ {SDL_SCANCODE_I, Keycode::I},
+ {SDL_SCANCODE_J, Keycode::J},
+ {SDL_SCANCODE_K, Keycode::K},
+ {SDL_SCANCODE_L, Keycode::L},
+ {SDL_SCANCODE_M, Keycode::M},
+ {SDL_SCANCODE_N, Keycode::N},
+ {SDL_SCANCODE_O, Keycode::O},
+ {SDL_SCANCODE_P, Keycode::P},
+ {SDL_SCANCODE_Q, Keycode::Q},
+ {SDL_SCANCODE_R, Keycode::R},
+ {SDL_SCANCODE_S, Keycode::S},
+ {SDL_SCANCODE_T, Keycode::T},
+ {SDL_SCANCODE_U, Keycode::U},
+ {SDL_SCANCODE_V, Keycode::V},
+ {SDL_SCANCODE_W, Keycode::W},
+ {SDL_SCANCODE_X, Keycode::X},
+ {SDL_SCANCODE_Y, Keycode::Y},
+ {SDL_SCANCODE_Z, Keycode::Z},
+ {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET},
+ {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH},
+ {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET},
+ {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT},
+ {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE},
+ {SDL_SCANCODE_RETURN, Keycode::ENTER},
+ {SDL_SCANCODE_TAB, Keycode::TAB},
+ {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE},
+ {SDL_SCANCODE_INSERT, Keycode::INSERT},
+ {SDL_SCANCODE_DELETE, Keycode::DELETE},
+ {SDL_SCANCODE_RIGHT, Keycode::RIGHT},
+ {SDL_SCANCODE_LEFT, Keycode::LEFT},
+ {SDL_SCANCODE_DOWN, Keycode::DOWN},
+ {SDL_SCANCODE_UP, Keycode::UP},
+ {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP},
+ {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN},
+ {SDL_SCANCODE_HOME, Keycode::HOME},
+ {SDL_SCANCODE_END, Keycode::END},
+ {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK},
+ {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK},
+ {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK},
+ {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN},
+ {SDL_SCANCODE_PAUSE, Keycode::PAUSE},
+ {SDL_SCANCODE_F1, Keycode::F1},
+ {SDL_SCANCODE_F2, Keycode::F2},
+ {SDL_SCANCODE_F3, Keycode::F3},
+ {SDL_SCANCODE_F4, Keycode::F4},
+ {SDL_SCANCODE_F5, Keycode::F5},
+ {SDL_SCANCODE_F6, Keycode::F6},
+ {SDL_SCANCODE_F7, Keycode::F7},
+ {SDL_SCANCODE_F8, Keycode::F8},
+ {SDL_SCANCODE_F9, Keycode::F9},
+ {SDL_SCANCODE_F10, Keycode::F10},
+ {SDL_SCANCODE_F11, Keycode::F11},
+ {SDL_SCANCODE_F12, Keycode::F12},
+ {SDL_SCANCODE_KP_0, Keycode::KP0},
+ {SDL_SCANCODE_KP_1, Keycode::KP1},
+ {SDL_SCANCODE_KP_2, Keycode::KP2},
+ {SDL_SCANCODE_KP_3, Keycode::KP3},
+ {SDL_SCANCODE_KP_4, Keycode::KP4},
+ {SDL_SCANCODE_KP_5, Keycode::KP5},
+ {SDL_SCANCODE_KP_6, Keycode::KP6},
+ {SDL_SCANCODE_KP_7, Keycode::KP7},
+ {SDL_SCANCODE_KP_8, Keycode::KP8},
+ {SDL_SCANCODE_KP_9, Keycode::KP9},
+ {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT},
+ {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL},
+ {SDL_SCANCODE_LALT, Keycode::LEFT_ALT},
+ {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER},
+ {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT},
+ {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL},
+ {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT},
+ {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER},
+ {SDL_SCANCODE_MENU, Keycode::MENU}};
};
} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 4d3abf5..b1e6463 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -1,59 +1,13 @@
-#include "../util/Log.h"
+#include "../api/Asset.h"
+#include "../util/dbg.h"
#include "Sound.h"
-#include "SoundContext.h"
using namespace crepe;
using namespace std;
-Sound::Sound(unique_ptr<Asset> res) {
+Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ this->sample.load(src.get_path().c_str());
dbg_trace();
- this->load(std::move(res));
-}
-
-Sound::Sound(const char * src) {
- dbg_trace();
- this->load(make_unique<Asset>(src));
-}
-
-void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); }
-
-void Sound::play() {
- SoundContext & ctx = SoundContext::get_instance();
- if (ctx.engine.getPause(this->handle)) {
- // resume if paused
- ctx.engine.setPause(this->handle, false);
- } else {
- // or start new sound
- this->handle = ctx.engine.play(this->sample, this->volume);
- ctx.engine.setLooping(this->handle, this->looping);
- }
-}
-
-void Sound::pause() {
- SoundContext & ctx = SoundContext::get_instance();
- if (ctx.engine.getPause(this->handle)) return;
- ctx.engine.setPause(this->handle, true);
-}
-
-void Sound::rewind() {
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.seek(this->handle, 0);
-}
-
-void Sound::set_volume(float volume) {
- this->volume = volume;
-
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.setVolume(this->handle, this->volume);
-}
-
-void Sound::set_looping(bool looping) {
- this->looping = looping;
-
- SoundContext & ctx = SoundContext::get_instance();
- if (!ctx.engine.isValidVoiceHandle(this->handle)) return;
- ctx.engine.setLooping(this->handle, this->looping);
}
+Sound::~Sound() { dbg_trace(); }
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
index 4c68f32..4a5d692 100644
--- a/src/crepe/facade/Sound.h
+++ b/src/crepe/facade/Sound.h
@@ -1,84 +1,31 @@
#pragma once
-#include <memory>
#include <soloud/soloud.h>
#include <soloud/soloud_wav.h>
-#include "../api/Asset.h"
+#include "../Resource.h"
namespace crepe {
+class SoundContext;
+class Mediator;
+
/**
* \brief Sound resource facade
*
- * This class is a wrapper around a \c SoLoud::Wav instance, which holds a
- * single sample. It is part of the sound facade.
+ * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is
+ * part of the sound facade.
*/
-class Sound {
-public:
- /**
- * \brief Pause this sample
- *
- * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead
- * position is saved, such that calling \c play() after this function makes the sound resume.
- */
- void pause();
- /**
- * \brief Play this sample
- *
- * Resume playback if this sound is paused, or start from the beginning of the sample.
- *
- * \note This class only saves a reference to the most recent 'voice' of this sound. Calling
- * \c play() while the sound is already playing causes multiple instances of the sample to
- * play simultaniously. The sample started last is the one that is controlled afterwards.
- */
- void play();
- /**
- * \brief Reset playhead position
- *
- * Resets the playhead position so that calling \c play() after this function makes it play
- * from the start of the sample. If the sound is not paused before calling this function,
- * this function will stop playback.
- */
- void rewind();
- /**
- * \brief Set playback volume / gain
- *
- * \param volume Volume (0 = muted, 1 = full volume)
- */
- void set_volume(float volume);
- /**
- * \brief Get playback volume / gain
- *
- * \return Volume
- */
- float get_volume() const { return this->volume; }
- /**
- * \brief Set looping behavior for this sample
- *
- * \param looping Looping behavior (false = one-shot, true = loop)
- */
- void set_looping(bool looping);
- /**
- * \brief Get looping behavior
- *
- * \return true if looping, false if one-shot
- */
- bool get_looping() const { return this->looping; }
-
+class Sound : public Resource {
public:
- Sound(const char * src);
- Sound(std::unique_ptr<Asset> res);
-
-private:
- void load(std::unique_ptr<Asset> res);
+ Sound(const Asset & src, Mediator & mediator);
+ ~Sound(); // dbg_trace
private:
+ //! Deserialized resource (soloud)
SoLoud::Wav sample;
- SoLoud::handle handle;
-
- float volume = 1.0f;
- bool looping = false;
+ //! SoundContext uses \c sample
+ friend class SoundContext;
};
} // namespace crepe
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
index deb2b62..5091e07 100644
--- a/src/crepe/facade/SoundContext.cpp
+++ b/src/crepe/facade/SoundContext.cpp
@@ -1,20 +1,36 @@
-#include "../util/Log.h"
+#include "../util/dbg.h"
#include "SoundContext.h"
using namespace crepe;
-SoundContext & SoundContext::get_instance() {
- static SoundContext instance;
- return instance;
-}
-
SoundContext::SoundContext() {
dbg_trace();
- engine.init();
+ this->engine.init();
+ this->engine.setMaxActiveVoiceCount(this->config.audio.voices);
}
SoundContext::~SoundContext() {
dbg_trace();
- engine.deinit();
+ this->engine.deinit();
+}
+
+SoundHandle SoundContext::play(Sound & resource) {
+ SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f);
+ SoundHandle handle = this->next_handle;
+ this->registry[handle] = real_handle;
+ this->next_handle++;
+ return handle;
+}
+
+void SoundContext::stop(const SoundHandle & handle) {
+ this->engine.stop(this->registry[handle]);
+}
+
+void SoundContext::set_volume(const SoundHandle & handle, float volume) {
+ this->engine.setVolume(this->registry[handle], volume);
+}
+
+void SoundContext::set_loop(const SoundHandle & handle, bool loop) {
+ this->engine.setLooping(this->registry[handle], loop);
}
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
index d703c16..d986c59 100644
--- a/src/crepe/facade/SoundContext.h
+++ b/src/crepe/facade/SoundContext.h
@@ -2,30 +2,80 @@
#include <soloud/soloud.h>
+#include "../api/Config.h"
+
#include "Sound.h"
+#include "SoundHandle.h"
namespace crepe {
/**
* \brief Sound engine facade
*
- * This class is a wrapper around a \c SoLoud::Soloud instance, which provides
- * the methods for playing \c Sound instances. It is part of the sound facade.
+ * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for
+ * playing \c Sound instances. It is part of the sound facade.
*/
class SoundContext {
-private:
- // singleton
+public:
SoundContext();
virtual ~SoundContext();
+
SoundContext(const SoundContext &) = delete;
SoundContext(SoundContext &&) = delete;
SoundContext & operator=(const SoundContext &) = delete;
SoundContext & operator=(SoundContext &&) = delete;
+ /**
+ * \brief Play a sample
+ *
+ * Plays a Sound from the beginning of the sample and returns a handle to control it later.
+ *
+ * \param resource Sound instance to play
+ *
+ * \returns Handle to control this voice
+ */
+ virtual SoundHandle play(Sound & resource);
+ /**
+ * \brief Stop a voice immediately if it is still playing
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ */
+ virtual void stop(const SoundHandle & handle);
+ /**
+ * \brief Change the volume of a voice
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ * \param volume New gain value (0=silent, 1=default)
+ */
+ virtual void set_volume(const SoundHandle & handle, float volume);
+ /**
+ * \brief Set the looping behavior of a voice
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ * \param loop Looping behavior (false=oneshot, true=loop)
+ */
+ virtual void set_loop(const SoundHandle & handle, bool loop);
+
private:
- static SoundContext & get_instance();
+ //! Abstracted class
SoLoud::Soloud engine;
- friend class Sound;
+
+ //! Config reference
+ Config & config = Config::get_instance();
+
+ //! Sound handle registry
+ std::unordered_map<SoundHandle, SoLoud::handle> registry;
+ //! Unique handle counter
+ SoundHandle next_handle = 0;
};
} // namespace crepe
diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h
new file mode 100644
index 0000000..b7925fc
--- /dev/null
+++ b/src/crepe/facade/SoundHandle.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <cstddef>
+
+namespace crepe {
+
+/**
+ * \brief Voice handle returned by
+ */
+typedef size_t SoundHandle;
+
+} // namespace crepe
diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp
new file mode 100644
index 0000000..06caa54
--- /dev/null
+++ b/src/crepe/facade/Texture.cpp
@@ -0,0 +1,30 @@
+#include "../Resource.h"
+#include "../facade/SDLContext.h"
+#include "../manager/Mediator.h"
+#include "../types.h"
+#include "../util/dbg.h"
+
+#include "SDLContext.h"
+#include "Texture.h"
+
+using namespace crepe;
+using namespace std;
+
+Texture::Texture(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ dbg_trace();
+ SDLContext & ctx = mediator.sdl_context;
+ this->texture = ctx.texture_from_path(src.get_path());
+ this->size = ctx.get_size(*this);
+ this->aspect_ratio = static_cast<float>(this->size.x) / this->size.y;
+}
+
+Texture::~Texture() {
+ dbg_trace();
+ this->texture.reset();
+}
+
+const ivec2 & Texture::get_size() const noexcept { return this->size; }
+
+const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; }
+
+SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); }
diff --git a/src/crepe/facade/Texture.h b/src/crepe/facade/Texture.h
new file mode 100644
index 0000000..cdacac4
--- /dev/null
+++ b/src/crepe/facade/Texture.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <SDL2/SDL_render.h>
+#include <memory>
+
+#include "../Resource.h"
+
+#include "types.h"
+
+namespace crepe {
+
+class Mediator;
+class Asset;
+
+/**
+ * \class Texture
+ * \brief Manages texture loading and properties.
+ *
+ * The Texture class is responsible for loading an image from a source and providing access to
+ * its dimensions. Textures can be used for rendering.
+ */
+class Texture : public Resource {
+
+public:
+ /**
+ * \brief Constructs a Texture from an Asset resource.
+ * \param src Asset with texture data to load.
+ * \param mediator use the SDLContext reference to load the image
+ */
+ Texture(const Asset & src, Mediator & mediator);
+
+ /**
+ * \brief Destroys the Texture instance
+ */
+ ~Texture();
+
+ /**
+ * \brief get width and height of image in pixels
+ * \return pixel size width and height
+ *
+ */
+ const ivec2 & get_size() const noexcept;
+
+ /**
+ * \brief aspect_ratio of image
+ * \return ratio
+ *
+ */
+ const float & get_ratio() const noexcept;
+
+ /**
+ * \brief get the image texture
+ * \return SDL_Texture
+ *
+ */
+ SDL_Texture * get_img() const noexcept;
+
+private:
+ //! The texture of the class from the library
+ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture;
+
+ // texture size in pixel
+ ivec2 size;
+
+ //! ratio of image
+ float aspect_ratio;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt
new file mode 100644
index 0000000..48e444f
--- /dev/null
+++ b/src/crepe/manager/CMakeLists.txt
@@ -0,0 +1,30 @@
+target_sources(crepe PUBLIC
+ ComponentManager.cpp
+ EventManager.cpp
+ Manager.cpp
+ SaveManager.cpp
+ SceneManager.cpp
+ LoopTimerManager.cpp
+ ResourceManager.cpp
+ ReplayManager.cpp
+ SystemManager.cpp
+)
+
+target_sources(crepe PUBLIC FILE_SET HEADERS FILES
+ ComponentManager.h
+ ComponentManager.hpp
+ EventManager.h
+ EventManager.hpp
+ Manager.h
+ Mediator.h
+ SaveManager.h
+ SceneManager.h
+ SceneManager.hpp
+ LoopTimerManager.h
+ ResourceManager.h
+ ResourceManager.hpp
+ ReplayManager.h
+ SystemManager.h
+ SystemManager.hpp
+)
+
diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp
new file mode 100644
index 0000000..245419d
--- /dev/null
+++ b/src/crepe/manager/ComponentManager.cpp
@@ -0,0 +1,103 @@
+#include "../api/GameObject.h"
+#include "../api/Metadata.h"
+#include "../types.h"
+#include "../util/dbg.h"
+
+#include "ComponentManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ComponentManager::ComponentManager(Mediator & mediator) : Manager(mediator) {
+ mediator.component_manager = *this;
+ dbg_trace();
+}
+ComponentManager::~ComponentManager() { dbg_trace(); }
+
+void ComponentManager::delete_all_components_of_id(game_object_id_t id) {
+ // Do not delete persistent objects
+ if (this->persistent[id]) {
+ return;
+ }
+
+ // Loop through all the types (in the unordered_map<>)
+ for (auto & [type, component_array] : this->components) {
+ // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
+ if (id < component_array.size()) {
+ // Clear the components at this specific id
+ component_array[id].clear();
+ }
+ }
+}
+
+void ComponentManager::delete_all_components() {
+ // Loop through all the types (in the unordered_map<>)
+ for (auto & [type, component_array] : this->components) {
+ // Loop through all the ids (in the vector<>)
+ for (game_object_id_t id = 0; id < component_array.size(); id++) {
+ // Do not delete persistent objects
+ if (!this->persistent[id]) {
+ // Clear the components at this specific id
+ component_array[id].clear();
+ }
+ }
+ }
+
+ this->next_id = 0;
+}
+
+GameObject ComponentManager::new_object(
+ const string & name, const string & tag, const vec2 & position, double rotation,
+ double scale
+) {
+ // Find the first available id (taking persistent objects into account)
+ while (this->persistent[this->next_id]) {
+ this->next_id++;
+ }
+
+ GameObject object {this->mediator, this->next_id, name, tag, position, rotation, scale};
+ this->next_id++;
+
+ return object;
+}
+
+void ComponentManager::set_persistent(game_object_id_t id, bool persistent) {
+ this->persistent[id] = persistent;
+}
+
+set<game_object_id_t> ComponentManager::get_objects_by_name(const string & name) const {
+ return this->get_objects_by_predicate<Metadata>([name](const Metadata & data) {
+ return data.name == name;
+ });
+}
+
+set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) const {
+ return this->get_objects_by_predicate<Metadata>([tag](const Metadata & data) {
+ return data.tag == tag;
+ });
+}
+
+ComponentManager::Snapshot ComponentManager::save() {
+ Snapshot snapshot {};
+ for (const auto & [type, by_id_index] : this->components) {
+ for (game_object_id_t id = 0; id < by_id_index.size(); id++) {
+ const auto & components = by_id_index[id];
+ for (size_t index = 0; index < components.size(); index++) {
+ const Component & component = *components[index];
+ snapshot.components.push_back(SnapshotComponent {
+ .type = type,
+ .id = id,
+ .index = index,
+ .component = component.save(),
+ });
+ }
+ }
+ }
+ return snapshot;
+}
+
+void ComponentManager::restore(const Snapshot & snapshot) {
+ for (const SnapshotComponent & info : snapshot.components) {
+ this->components[info.type][info.id][info.index]->restore(*info.component);
+ }
+}
diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h
new file mode 100644
index 0000000..2eb1f7e
--- /dev/null
+++ b/src/crepe/manager/ComponentManager.h
@@ -0,0 +1,253 @@
+#pragma once
+
+#include <memory>
+#include <set>
+#include <typeindex>
+#include <unordered_map>
+#include <vector>
+
+#include "../Component.h"
+#include "../types.h"
+
+#include "Manager.h"
+
+namespace crepe {
+
+class GameObject;
+
+/**
+ * \brief Manages all components
+ *
+ * This class manages all components. It provides methods to add, delete and get components.
+ */
+class ComponentManager : public Manager {
+public:
+ ComponentManager(Mediator & mediator);
+ ~ComponentManager(); // dbg_trace
+
+ /**
+ * \brief Create a new game object using the component manager
+ *
+ * \param name Metadata::name (required)
+ * \param tag Metadata::tag (optional, empty by default)
+ * \param position Transform::position (optional, origin by default)
+ * \param rotation Transform::rotation (optional, 0 by default)
+ * \param scale Transform::scale (optional, 1 by default)
+ *
+ * \returns GameObject interface
+ *
+ * \note This method automatically assigns a new entity ID
+ */
+ GameObject new_object(
+ const std::string & name, const std::string & tag = "", const vec2 & position = {0, 0},
+ double rotation = 0, double scale = 1
+ );
+
+public:
+ /**
+ * \brief Add a component to the ComponentManager
+ *
+ * This method adds a component to the ComponentManager. The component is created with the
+ * given arguments and added to the ComponentManager.
+ *
+ * \tparam T The type of the component
+ * \tparam Args The types of the arguments
+ * \param id The id of the GameObject this component belongs to
+ * \param args The arguments to create the component
+ * \return The created component
+ */
+ template <typename T, typename... Args>
+ T & add_component(game_object_id_t id, Args &&... args);
+ /**
+ * \brief Delete all components of a specific type and id
+ *
+ * This method deletes all components of a specific type and id.
+ *
+ * \tparam T The type of the component
+ * \param id The id of the GameObject this component belongs to
+ */
+ template <typename T>
+ void delete_components_by_id(game_object_id_t id);
+ /**
+ * \brief Delete all components of a specific type
+ *
+ * This method deletes all components of a specific type.
+ *
+ * \tparam T The type of the component
+ */
+ template <typename T>
+ void delete_components();
+ /**
+ * \brief Delete all components of a specific id
+ *
+ * This method deletes all components of a specific id.
+ *
+ * \param id The id of the GameObject this component belongs to
+ */
+ void delete_all_components_of_id(game_object_id_t id);
+ /**
+ * \brief Delete all components
+ *
+ * This method deletes all components.
+ */
+ void delete_all_components();
+ /**
+ * \brief Set a GameObject as persistent
+ *
+ * This method sets a GameObject as persistent. If a GameObject is persistent, its
+ * components will not be deleted.
+ *
+ * \param id The id of the GameObject to set as persistent
+ * \param persistent The persistent flag
+ */
+ void set_persistent(game_object_id_t id, bool persistent);
+
+public:
+ /**
+ * \brief Get all components of a specific type and id
+ *
+ * This method gets all components of a specific type and id.
+ *
+ * \tparam T The type of the component
+ * \param id The id of the GameObject this component belongs to
+ * \return A vector of all components of the specific type and id
+ */
+ template <typename T>
+ RefVector<T> get_components_by_id(game_object_id_t id) const;
+ /**
+ * \brief Get all components of a specific type
+ *
+ * This method gets all components of a specific type.
+ *
+ * \tparam T The type of the component
+ * \return A vector of all components of the specific type
+ */
+ template <typename T>
+ RefVector<T> get_components_by_type() const;
+ /**
+ * \brief Get all components of a specific type on a GameObject with name \c name
+ *
+ * \tparam T The type of the component
+ * \param name Metadata::name for the same game_object_id as the returned components
+ * \return Components matching criteria
+ */
+ template <typename T>
+ RefVector<T> get_components_by_name(const std::string & name) const;
+ /**
+ * \brief Get all components of a specific type on a GameObject with tag \c tag
+ *
+ * \tparam T The type of the component
+ * \param name Metadata::tag for the same game_object_id as the returned components
+ * \return Components matching criteria
+ */
+ template <typename T>
+ RefVector<T> get_components_by_tag(const std::string & tag) const;
+
+ //! Snapshot of single component (including path in \c components)
+ struct SnapshotComponent {
+ //! \c components path
+ std::type_index type;
+ //! \c components path
+ game_object_id_t id;
+ //! \c components path
+ size_t index;
+ //! Actual component snapshot
+ std::unique_ptr<Component> component;
+ };
+ //! Snapshot of the entire component manager state
+ struct Snapshot {
+ //! All components
+ std::vector<SnapshotComponent> components;
+ // TODO: some kind of hash code that ensures components exist in all the same places as
+ // this snapshot
+ };
+ /**
+ * \name ReplayManager (Memento) functions
+ * \{
+ */
+ /**
+ * \brief Save a snapshot of the component manager state
+ * \returns Deep copy of the component manager's internal state
+ */
+ Snapshot save();
+ /**
+ * \brief Restore component manager from a snapshot
+ * \param snapshot Snapshot to restore from (as returned by \c save())
+ */
+ void restore(const Snapshot & snapshot);
+ //! \}
+
+private:
+ /**
+ * \brief Get object IDs by predicate function
+ *
+ * This function calls the predicate function \c pred for all components matching type \c T,
+ * and adds their parent game_object_id to a \c std::set if the predicate returns true.
+ *
+ * \tparam T The type of the component to check the predicate against
+ * \param pred Predicate function
+ *
+ * \note The predicate function may be called for multiple components with the same \c
+ * game_object_id. In this case, the ID is added if *any* call returns \c true.
+ *
+ * \returns game_object_id for all components where the predicate returned true
+ */
+ template <typename T>
+ std::set<game_object_id_t>
+ get_objects_by_predicate(const std::function<bool(const T &)> & pred) const;
+
+ /**
+ * \brief Get components of type \c T for multiple game object IDs
+ *
+ * \tparam T The type of the components to return
+ * \param ids The object IDs
+ *
+ * \return All components matching type \c T and one of the IDs in \c ids
+ */
+ template <typename T>
+ RefVector<T> get_components_by_ids(const std::set<game_object_id_t> & ids) const;
+
+ /**
+ * \brief Get object IDs for objects with name \c name
+ *
+ * \param name Object name to match
+ * \returns Object IDs where Metadata::name is equal to \c name
+ */
+ std::set<game_object_id_t> get_objects_by_name(const std::string & name) const;
+ /**
+ * \brief Get object IDs for objects with tag \c tag
+ *
+ * \param tag Object tag to match
+ * \returns Object IDs where Metadata::tag is equal to \c tag
+ */
+ std::set<game_object_id_t> get_objects_by_tag(const std::string & tag) const;
+
+private:
+ //! By Component \c std::type_index (readability helper type)
+ template <typename T>
+ using by_type = std::unordered_map<std::type_index, T>;
+ //! By \c game_object_id index (readability helper type)
+ template <typename T>
+ using by_id_index = std::vector<T>;
+ /**
+ * \brief The components
+ *
+ * This unordered_map stores all components. The key is the type of the component and the
+ * value is a vector of vectors of unique pointers to the components.
+ *
+ * Every component type has its own vector of vectors of unique pointers to the components.
+ * The first vector is for the ids of the GameObjects and the second vector is for the
+ * components (because a GameObject might have multiple components).
+ */
+ by_type<by_id_index<std::vector<std::unique_ptr<Component>>>> components;
+
+ //! Persistent flag for each GameObject
+ std::unordered_map<game_object_id_t, bool> persistent;
+
+ //! ID of next GameObject allocated by \c ComponentManager::new_object
+ game_object_id_t next_id = 0;
+};
+
+} // namespace crepe
+
+#include "ComponentManager.hpp"
diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp
index 4d5eaf4..6d32edb 100644
--- a/src/crepe/ComponentManager.hpp
+++ b/src/crepe/manager/ComponentManager.hpp
@@ -11,8 +11,10 @@ template <class T, typename... Args>
T & ComponentManager::add_component(game_object_id_t id, Args &&... args) {
using namespace std;
- static_assert(is_base_of<Component, T>::value,
- "add_component must recieve a derivative class of Component");
+ static_assert(
+ is_base_of<Component, T>::value,
+ "add_component must recieve a derivative class of Component"
+ );
// Determine the type of T (this is used as the key of the unordered_map<>)
type_index type = typeid(T);
@@ -40,8 +42,8 @@ T & ComponentManager::add_component(game_object_id_t id, Args &&... args) {
// Check if the vector size is not greater than get_instances_max
int max_instances = instance->get_instances_max();
if (max_instances != -1 && components[type][id].size() >= max_instances) {
- throw std::runtime_error(
- "Exceeded maximum number of instances for this component type");
+ throw std::runtime_error("Exceeded maximum number of instances for this component type"
+ );
}
// store its unique_ptr in the vector<>
@@ -54,6 +56,11 @@ template <typename T>
void ComponentManager::delete_components_by_id(game_object_id_t id) {
using namespace std;
+ // Do not delete persistent objects
+ if (this->persistent[id]) {
+ return;
+ }
+
// Determine the type of T (this is used as the key of the unordered_map<>)
type_index type = typeid(T);
@@ -77,39 +84,40 @@ void ComponentManager::delete_components() {
if (this->components.find(type) == this->components.end()) return;
- this->components[type].clear();
+ // Loop through the whole vector<> of this specific type
+ for (game_object_id_t i = 0; i < this->components[type].size(); ++i) {
+ // Do not delete persistent objects
+ if (!this->persistent[i]) {
+ this->components[type][i].clear();
+ }
+ }
}
template <typename T>
RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const {
using namespace std;
- // Determine the type of T (this is used as the key of the unordered_map<>)
- type_index type = typeid(T);
-
- // Create an empty vector<>
- RefVector<T> component_vector;
-
- if (this->components.find(type) == this->components.end()) return component_vector;
-
- // Get the correct vector<>
- const vector<vector<unique_ptr<Component>>> & component_array = this->components.at(type);
-
- // Make sure that the id (that we are looking for) is within the boundaries of the vector<>
- if (id >= component_array.size()) return component_vector;
+ static_assert(
+ is_base_of<Component, T>::value,
+ "get_components_by_id must recieve a derivative class of Component"
+ );
- // Loop trough the whole vector<>
- for (const unique_ptr<Component> & component_ptr : component_array[id]) {
- // Cast the unique_ptr to a raw pointer
- T * casted_component = static_cast<T *>(component_ptr.get());
-
- if (casted_component == nullptr) continue;
-
- // Add the dereferenced raw pointer to the vector<>
- component_vector.push_back(*casted_component);
+ type_index type = typeid(T);
+ if (!this->components.contains(type)) return {};
+
+ const by_id_index<vector<unique_ptr<Component>>> & components_by_id
+ = this->components.at(type);
+ if (id >= components_by_id.size()) return {};
+
+ RefVector<T> out = {};
+ const vector<unique_ptr<Component>> & components = components_by_id.at(id);
+ for (auto & component_ptr : components) {
+ if (component_ptr == nullptr) continue;
+ Component & component = *component_ptr.get();
+ out.push_back(static_cast<T &>(component));
}
- return component_vector;
+ return out;
}
template <typename T>
@@ -147,4 +155,46 @@ RefVector<T> ComponentManager::get_components_by_type() const {
return component_vector;
}
+template <typename T>
+std::set<game_object_id_t>
+ComponentManager::get_objects_by_predicate(const std::function<bool(const T &)> & pred) const {
+ using namespace std;
+
+ set<game_object_id_t> objects = {};
+ RefVector<T> components = this->get_components_by_type<T>();
+
+ for (const T & component : components) {
+ game_object_id_t id = dynamic_cast<const Component &>(component).game_object_id;
+ if (objects.contains(id)) continue;
+ if (!pred(component)) continue;
+ objects.insert(id);
+ }
+
+ return objects;
+}
+
+template <typename T>
+RefVector<T> ComponentManager::get_components_by_ids(const std::set<game_object_id_t> & ids
+) const {
+ using namespace std;
+
+ RefVector<T> out = {};
+ for (game_object_id_t id : ids) {
+ RefVector<T> components = get_components_by_id<T>(id);
+ out.insert(out.end(), components.begin(), components.end());
+ }
+
+ return out;
+}
+
+template <typename T>
+RefVector<T> ComponentManager::get_components_by_name(const std::string & name) const {
+ return this->get_components_by_ids<T>(this->get_objects_by_name(name));
+}
+
+template <typename T>
+RefVector<T> ComponentManager::get_components_by_tag(const std::string & tag) const {
+ return this->get_components_by_ids<T>(this->get_objects_by_tag(tag));
+}
+
} // namespace crepe
diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/manager/EventManager.cpp
index 20f0dd3..6aa49ee 100644
--- a/src/crepe/api/EventManager.cpp
+++ b/src/crepe/manager/EventManager.cpp
@@ -3,11 +3,9 @@
using namespace crepe;
using namespace std;
-EventManager & EventManager::get_instance() {
- static EventManager instance;
- return instance;
+EventManager::EventManager(Mediator & mediator) : Manager(mediator) {
+ this->mediator.event_manager = *this;
}
-
void EventManager::dispatch_events() {
for (auto & event : this->events_queue) {
this->handle_event(event.type, event.channel, *event.event.get());
diff --git a/src/crepe/api/EventManager.h b/src/crepe/manager/EventManager.h
index 348a04d..5766a0c 100644
--- a/src/crepe/api/EventManager.h
+++ b/src/crepe/manager/EventManager.h
@@ -5,8 +5,10 @@
#include <unordered_map>
#include <vector>
-#include "Event.h"
-#include "EventHandler.h"
+#include "../api/Event.h"
+#include "../api/EventHandler.h"
+
+#include "Manager.h"
namespace crepe {
@@ -22,78 +24,70 @@ typedef size_t subscription_t;
typedef size_t event_channel_t;
/**
- * \class EventManager
* \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling.
- *
+ *
* The `EventManager` acts as a centralized event system. It allows for registering callbacks
* for specific event types, triggering events synchronously, queueing events for later
* processing, and managing subscriptions via unique identifiers.
*/
-class EventManager {
+class EventManager : public Manager {
public:
static constexpr const event_channel_t CHANNEL_ALL = -1;
-
/**
- * \brief Get the singleton instance of the EventManager.
- *
- * This method returns the unique instance of the EventManager, creating it if it
- * doesn't already exist. Ensures only one instance is active in the program.
- *
- * \return Reference to the singleton instance of the EventManager.
+ * \param mediator A reference to a Mediator object used for transfering managers.
*/
- static EventManager & get_instance();
-
+ EventManager(Mediator & mediator);
/**
* \brief Subscribe to a specific event type.
- *
+ *
* Registers a callback for a given event type and optional channel. Each callback
* is assigned a unique subscription ID that can be used for later unsubscription.
- *
+ *
* \tparam EventType The type of the event to subscribe to.
* \param callback The callback function to be invoked when the event is triggered.
* \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels).
* \return A unique subscription ID associated with the registered callback.
*/
template <typename EventType>
- subscription_t subscribe(const EventHandler<EventType> & callback,
- event_channel_t channel = CHANNEL_ALL);
+ subscription_t
+ subscribe(const EventHandler<EventType> & callback, event_channel_t channel = CHANNEL_ALL);
/**
* \brief Unsubscribe a previously registered callback.
- *
+ *
* Removes a callback from the subscription list based on its unique subscription ID.
- *
+ *
* \param event_id The unique subscription ID of the callback to remove.
*/
void unsubscribe(subscription_t event_id);
/**
* \brief Trigger an event immediately.
- *
+ *
* Synchronously invokes all registered callbacks for the given event type on the specified channel.
- *
+ *
* \tparam EventType The type of the event to trigger.
* \param event The event instance to pass to the callbacks.
* \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels).
*/
template <typename EventType>
- void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL);
+ void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL);
/**
* \brief Queue an event for later processing.
- *
+ *
* Adds an event to the event queue to be processed during the next call to `dispatch_events`.
- *
+ *
* \tparam EventType The type of the event to queue.
* \param event The event instance to queue.
* \param channel The channel to associate with the event (default is CHANNEL_ALL).
*/
template <typename EventType>
- void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL);
+ void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL);
/**
* \brief Process all queued events.
- *
+ *
* Iterates through the event queue and triggers callbacks for each queued event.
* Events are removed from the queue once processed.
*/
@@ -101,25 +95,18 @@ public:
/**
* \brief Clear all subscriptions.
- *
+ *
* Removes all registered event handlers and clears the subscription list.
*/
void clear();
private:
/**
- * \brief Default constructor for the EventManager.
- *
- * Constructor is private to enforce the singleton pattern.
- */
- EventManager() = default;
-
- /**
* \struct QueueEntry
* \brief Represents an entry in the event queue.
*/
struct QueueEntry {
- std::unique_ptr<Event> event; ///< The event instance.
+ std::unique_ptr<Event, std::function<void(Event *)>> event; ///< The event instance.
event_channel_t channel = CHANNEL_ALL; ///< The channel associated with the event.
std::type_index type; ///< The type of the event.
};
diff --git a/src/crepe/api/EventManager.hpp b/src/crepe/manager/EventManager.hpp
index a5f4556..1f44943 100644
--- a/src/crepe/api/EventManager.hpp
+++ b/src/crepe/manager/EventManager.hpp
@@ -5,24 +5,31 @@
namespace crepe {
template <typename EventType>
-subscription_t EventManager::subscribe(const EventHandler<EventType> & callback,
- event_channel_t channel) {
+subscription_t
+EventManager::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) {
subscription_counter++;
std::type_index event_type = typeid(EventType);
std::unique_ptr<EventHandlerWrapper<EventType>> handler
= std::make_unique<EventHandlerWrapper<EventType>>(callback);
std::vector<CallbackEntry> & handlers = this->subscribers[event_type];
- handlers.emplace_back(CallbackEntry{
- .callback = std::move(handler), .channel = channel, .id = subscription_counter});
+ handlers.emplace_back(CallbackEntry {
+ .callback = std::move(handler), .channel = channel, .id = subscription_counter
+ });
return subscription_counter;
}
template <typename EventType>
void EventManager::queue_event(const EventType & event, event_channel_t channel) {
- static_assert(std::is_base_of<Event, EventType>::value,
- "EventType must derive from Event");
+ static_assert(
+ std::is_base_of<Event, EventType>::value, "EventType must derive from Event"
+ );
this->events_queue.push_back(QueueEntry{
- .event = std::make_unique<EventType>(event),
+ // unique_ptr w/ custom destructor implementation is used because the base Event interface
+ // can't be polymorphic (= have default virtual destructor)
+ .event = {
+ new EventType(event),
+ [](Event * ev) { delete static_cast<EventType *>(ev); },
+ },
.channel = channel,
.type = typeid(EventType),
});
diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp
new file mode 100644
index 0000000..b4cd07f
--- /dev/null
+++ b/src/crepe/manager/LoopTimerManager.cpp
@@ -0,0 +1,91 @@
+#include <chrono>
+#include <thread>
+
+#include "../util/dbg.h"
+
+#include "LoopTimerManager.h"
+
+using namespace crepe;
+using namespace std::chrono;
+using namespace std::chrono_literals;
+
+LoopTimerManager::LoopTimerManager(Mediator & mediator) : Manager(mediator) {
+ this->mediator.loop_timer = *this;
+ dbg_trace();
+}
+
+void LoopTimerManager::start() {
+ this->last_frame_time = std::chrono::steady_clock::now();
+
+ this->elapsed_time = elapsed_time_t {0};
+ this->elapsed_fixed_time = elapsed_time_t {0};
+ this->delta_time = duration_t {0};
+}
+
+void LoopTimerManager::update() {
+ time_point_t current_frame_time = std::chrono::steady_clock::now();
+ // Convert to duration in seconds for delta time
+ this->delta_time = current_frame_time - last_frame_time;
+
+ if (this->delta_time > this->maximum_delta_time) {
+ this->delta_time = this->maximum_delta_time;
+ }
+ if (this->delta_time > 0s) {
+ this->actual_fps = static_cast<unsigned>(1.0 / this->delta_time.count());
+ } else {
+ this->actual_fps = 0;
+ }
+ this->elapsed_time += duration_cast<elapsed_time_t>(this->delta_time);
+ this->last_frame_time = current_frame_time;
+}
+
+duration_t LoopTimerManager::get_delta_time() const {
+ return this->delta_time * this->time_scale;
+}
+
+elapsed_time_t LoopTimerManager::get_elapsed_time() const { return this->elapsed_time; }
+
+void LoopTimerManager::advance_fixed_elapsed_time() {
+ this->elapsed_fixed_time
+ += std::chrono::duration_cast<elapsed_time_t>(this->fixed_delta_time);
+}
+
+void LoopTimerManager::set_target_framerate(unsigned fps) {
+ this->target_fps = fps;
+ //check if fps is lower or equals 0
+ if (fps <= 0) return;
+ // target time per frame in seconds
+ this->frame_target_time = duration_t(1s) / this->target_fps;
+}
+
+unsigned LoopTimerManager::get_fps() const { return this->actual_fps; }
+
+void LoopTimerManager::set_time_scale(double value) { this->time_scale = value; }
+
+float LoopTimerManager::get_time_scale() const { return this->time_scale; }
+
+void LoopTimerManager::enforce_frame_rate() {
+ time_point_t current_frame_time = std::chrono::steady_clock::now();
+ duration_t frame_duration = current_frame_time - this->last_frame_time;
+ // Check if frame duration is less than the target frame time
+ if (frame_duration < this->frame_target_time) {
+ duration_t delay_time = this->frame_target_time - frame_duration;
+ if (delay_time > 0s) {
+ std::this_thread::sleep_for(delay_time);
+ }
+ }
+}
+
+duration_t LoopTimerManager::get_lag() const {
+ return this->elapsed_time - this->elapsed_fixed_time;
+}
+
+duration_t LoopTimerManager::get_scaled_fixed_delta_time() const {
+ return this->fixed_delta_time * this->time_scale;
+}
+
+void LoopTimerManager::set_fixed_delta_time(float seconds) {
+ this->fixed_delta_time = duration_t(seconds);
+}
+
+duration_t LoopTimerManager::get_fixed_delta_time() const { return this->fixed_delta_time; }
diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h
new file mode 100644
index 0000000..279d6b2
--- /dev/null
+++ b/src/crepe/manager/LoopTimerManager.h
@@ -0,0 +1,177 @@
+#pragma once
+
+#include <chrono>
+
+#include "Manager.h"
+
+namespace crepe {
+
+class Engine;
+
+typedef std::chrono::duration<float> duration_t;
+typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t;
+
+/**
+ * \brief Manages timing and frame rate for the game loop.
+ *
+ * The LoopTimerManager class is responsible for calculating and managing timing functions
+ * such as delta time, frames per second (FPS), fixed time steps, and time scaling. It ensures
+ * consistent frame updates and supports game loop operations, such as handling fixed updates
+ * for physics and other time-sensitive operations.
+ */
+class LoopTimerManager : public Manager {
+public:
+ /**
+ * \param mediator A reference to a Mediator object used for transfering managers.
+ */
+ LoopTimerManager(Mediator & mediator);
+ /**
+ * \brief Get the current delta time for the current frame.
+ *
+ * This value represents the estimated frame duration of the current frame.
+ * This value can be used in the frame_update to convert pixel based values to time based values.
+ *
+ * \return Delta time in seconds since the last frame.
+ */
+ duration_t get_delta_time() const;
+
+ /**
+ * \brief Get the current elapsed time (total time passed )
+ *
+ * \note The current game time may vary from real-world elapsed time. It is the cumulative
+ * sum of each frame's delta time.
+ *
+ * \return Elapsed game time in seconds.
+ */
+ elapsed_time_t get_elapsed_time() const;
+
+ /**
+ * \brief Set the target frames per second (FPS).
+ *
+ * \param fps The desired frames rendered per second.
+ */
+ void set_target_framerate(unsigned fps);
+
+ /**
+ * \brief Get the current frames per second (FPS).
+ *
+ * \return Current FPS.
+ */
+ unsigned int get_fps() const;
+
+ /**
+ * \brief Get the current time scale.
+ *
+ * \return The current time scale, where (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up).
+ * up the game.
+ */
+ float get_time_scale() const;
+
+ /**
+ * \brief Set the time scale.
+ *
+ * time_scale is a value that changes the delta time that can be retrieved using get_delta_time function.
+ *
+ * \param time_scale The desired time scale (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up).
+ */
+ void set_time_scale(double time_scale);
+
+ /**
+ * \brief Get the fixed delta time in seconds without scaling by the time scale.
+ *
+ * This value is used in the LoopManager to determine how many times
+ * the fixed_update should be called within a given interval.
+ *
+ * \return The unscaled fixed delta time in seconds.
+ */
+ duration_t get_fixed_delta_time() const;
+
+ /**
+ * \brief Set the fixed_delta_time in seconds.
+ *
+ * \param seconds fixed_delta_time in seconds.
+ *
+ * The fixed_delta_time value is used to determine how many times per second the fixed_update and process_input functions are called.
+ *
+ */
+ void set_fixed_delta_time(float seconds);
+
+ /**
+ * \brief Retrieves the scaled fixed delta time in seconds.
+ *
+ * The scaled fixed delta time is the timing value used within the `fixed_update` function.
+ * It is adjusted by the time_scale to account for any changes in the simulation's
+ * speed.
+ *
+ * \return The fixed delta time, scaled by the current time scale, in seconds.
+ */
+ duration_t get_scaled_fixed_delta_time() const;
+
+private:
+ //! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update.
+ friend class Engine;
+ /**
+ * \brief Start the loop timer.
+ *
+ * Initializes the timer to begin tracking frame times.
+ */
+ void start();
+ /**
+ * \brief Enforce the frame rate limit.
+ *
+ * Ensures that the game loop does not exceed the target FPS by delaying frame updates as
+ * necessary.
+ */
+ void enforce_frame_rate();
+ /**
+ * \brief Get the accumulated lag in the game loop.
+ *
+ * Lag represents the difference between the target frame time and the actual frame time,
+ * useful for managing fixed update intervals.
+ *
+ * \return Accumulated lag in seconds.
+ */
+ duration_t get_lag() const;
+
+ /**
+ * \brief Update the timer to the current frame.
+ *
+ * Calculates and updates the delta time for the current frame and adds it to the cumulative
+ * game time.
+ */
+ void update();
+
+ /**
+ * \brief Progress the elapsed fixed time by the fixed delta time interval.
+ *
+ * This method advances the game's fixed update loop by adding the fixed_delta_time
+ * to elapsed_fixed_time, ensuring the fixed update catches up with the elapsed time.
+ */
+ void advance_fixed_elapsed_time();
+
+private:
+ //! Target frames per second.
+ unsigned int target_fps = 60;
+ //! Actual frames per second.
+ unsigned int actual_fps = 0;
+ //! Time scale for speeding up or slowing down the game (0 = pause, < 1 = slow down, 1 = normal speed, > 1 = speed up).
+ float time_scale = 1;
+ //! Maximum delta time in seconds to avoid large jumps.
+ duration_t maximum_delta_time {0.25};
+ //! Delta time for the current frame in seconds.
+ duration_t delta_time {0.0};
+ //! Target time per frame in seconds
+ duration_t frame_target_time {1.0 / target_fps};
+ //! Fixed delta time for fixed updates in seconds.
+ duration_t fixed_delta_time {1.0 / 50.0};
+ //! Total elapsed game time in microseconds.
+ elapsed_time_t elapsed_time {0};
+ //! Total elapsed time for fixed updates in microseconds.
+ elapsed_time_t elapsed_fixed_time {0};
+
+ typedef std::chrono::steady_clock::time_point time_point_t;
+ //! Time of the last frame.
+ time_point_t last_frame_time;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/Manager.cpp b/src/crepe/manager/Manager.cpp
new file mode 100644
index 0000000..1182785
--- /dev/null
+++ b/src/crepe/manager/Manager.cpp
@@ -0,0 +1,5 @@
+#include "Manager.h"
+
+using namespace crepe;
+
+Manager::Manager(Mediator & mediator) : mediator(mediator) {}
diff --git a/src/crepe/manager/Manager.h b/src/crepe/manager/Manager.h
new file mode 100644
index 0000000..84d80fe
--- /dev/null
+++ b/src/crepe/manager/Manager.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "Mediator.h"
+
+namespace crepe {
+
+/**
+ * \brief Base manager class
+ *
+ * Managers are used for various tasks that fall outside the ECS system category. All managers
+ * are required to register themselves to the mediator passed to the constructor, and this
+ * mutable reference is saved for convenience, even though not all managers use the mediator
+ * directly.
+ */
+class Manager {
+public:
+ Manager(Mediator & mediator);
+ virtual ~Manager() = default;
+
+protected:
+ Mediator & mediator;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h
new file mode 100644
index 0000000..842f1de
--- /dev/null
+++ b/src/crepe/manager/Mediator.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "../util/OptionalRef.h"
+
+namespace crepe {
+
+class ComponentManager;
+class SceneManager;
+class EventManager;
+class LoopTimerManager;
+class SaveManager;
+class ResourceManager;
+class SDLContext;
+class ReplayManager;
+class SystemManager;
+
+/**
+ * Struct to pass references to classes that would otherwise need to be singletons down to
+ * other classes within the engine hierarchy. Made to prevent constant changes to subclasses to
+ * pass specific references through dependency injection. All references on this struct
+ * *should* be explicitly checked for availability as this struct does not guarantee anything.
+ *
+ * \note Dereferencing members of this struct should be deferred. If you are a user of this
+ * class, keep a reference to this mediator instead of just picking references from it when you
+ * receive an instance.
+ *
+ * \warning This class should never be directly accessible from the API
+ */
+struct Mediator {
+ OptionalRef<SDLContext> sdl_context;
+ OptionalRef<ComponentManager> component_manager;
+ OptionalRef<SceneManager> scene_manager;
+ OptionalRef<EventManager> event_manager;
+ OptionalRef<LoopTimerManager> loop_timer;
+ OptionalRef<SaveManager> save_manager;
+ OptionalRef<ResourceManager> resource_manager;
+ OptionalRef<ReplayManager> replay_manager;
+ OptionalRef<SystemManager> system_manager;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp
new file mode 100644
index 0000000..090a94e
--- /dev/null
+++ b/src/crepe/manager/ReplayManager.cpp
@@ -0,0 +1,70 @@
+#include <format>
+
+#include "Manager.h"
+#include "ReplayManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) {
+ mediator.replay_manager = *this;
+}
+
+void ReplayManager::record_start() {
+ if (this->state == RECORDING) this->release(this->id);
+ this->id++;
+ this->memory[this->id] = make_unique<Recording>();
+ this->recording = *this->memory.at(this->id);
+ this->state = RECORDING;
+}
+
+recording_t ReplayManager::record_end() {
+ this->state = IDLE;
+ return this->id;
+}
+
+void ReplayManager::play(recording_t handle) {
+ if (!this->memory.contains(handle))
+ throw out_of_range(format("ReplayManager: no recording for handle {}", handle));
+ this->recording = *this->memory.at(handle);
+ this->recording->frame = 0;
+ this->state = PLAYING;
+}
+
+void ReplayManager::release(recording_t handle) {
+ if (!this->memory.contains(handle)) return;
+ this->memory.erase(handle);
+}
+
+void ReplayManager::frame_record() {
+ if (this->state != RECORDING)
+ throw runtime_error("ReplayManager: frame_step called while not playing");
+
+ ComponentManager & components = this->mediator.component_manager;
+ Recording & recording = this->recording;
+
+ recording.frames.push_back(components.save());
+ recording.frame++;
+}
+
+bool ReplayManager::frame_step() {
+ if (this->state != PLAYING)
+ throw runtime_error("ReplayManager: frame_step called while not playing");
+
+ ComponentManager & components = this->mediator.component_manager;
+ Recording & recording = this->recording;
+
+ ComponentManager::Snapshot & frame = recording.frames.at(recording.frame);
+
+ components.restore(frame);
+ recording.frame++;
+
+ if (recording.frame < recording.frames.size()) return false;
+ // end of recording
+ recording.frame = 0;
+ this->state = IDLE;
+ this->recording.clear();
+ return true;
+}
+
+ReplayManager::State ReplayManager::get_state() const { return this->state; }
diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h
new file mode 100644
index 0000000..f06a58b
--- /dev/null
+++ b/src/crepe/manager/ReplayManager.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <unordered_map>
+
+#include "../util/OptionalRef.h"
+
+#include "ComponentManager.h"
+#include "Manager.h"
+
+namespace crepe {
+
+//! Handle to recording held by ReplayManager
+typedef size_t recording_t;
+
+/**
+ * \brief Replay manager
+ *
+ * The replay manager is responsible for creating, storing and restoring ComponentManager
+ * snapshots. Sequential snapshots can be recorded and replayed in combination with
+ * ReplaySystem.
+ */
+class ReplayManager : public Manager {
+ // TODO: Delete recordings at end of scene
+
+public:
+ ReplayManager(Mediator & mediator);
+
+public:
+ //! Start a new recording
+ void record_start();
+ /**
+ * \brief End the latest recording started by \c record_start()
+ * \returns Handle to recording
+ */
+ recording_t record_end();
+ /**
+ * \brief Play a recording
+ * \param handle Handle to recording (as returned by \c record_end())
+ */
+ void play(recording_t handle);
+ /**
+ * \brief Delete a recording from memory
+ * \param handle Handle to recording (as returned by \c record_end())
+ */
+ void release(recording_t handle);
+
+public:
+ //! Internal state
+ enum State {
+ IDLE, //!< Not doing anything
+ RECORDING, //!< Currently recording
+ PLAYING, //!< Currently playing back a recording
+ };
+ //! Get current internal state
+ State get_state() const;
+
+public:
+ /**
+ * \brief Record a single frame to the current recording
+ *
+ * This function is called by ReplaySystem after the game programmer has called \c
+ * record_start()
+ */
+ void frame_record();
+ /**
+ * \brief Play the next frame of the current recording
+ *
+ * \returns `true` if the recording is finished playing
+ * \returns `false` if there are more frames
+ *
+ * This function also automatically resets the internal state from PLAYING to IDLE at the end
+ * of a recording.
+ */
+ bool frame_step();
+
+private:
+ /**
+ * \brief Recording data
+ */
+ struct Recording {
+ //! Current frame being shown
+ size_t frame = 0;
+ //! All frames in recording
+ std::vector<ComponentManager::Snapshot> frames;
+ };
+ //! Internal state
+ State state = IDLE;
+ //! Current recording handle
+ recording_t id = -1;
+ //! Current recording data
+ OptionalRef<Recording> recording;
+ //! Recording storage
+ std::unordered_map<recording_t, std::unique_ptr<Recording>> memory;
+};
+
+} // namespace crepe
diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp
new file mode 100644
index 0000000..5713183
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.cpp
@@ -0,0 +1,30 @@
+#include "util/dbg.h"
+
+#include "ResourceManager.h"
+
+using namespace crepe;
+using namespace std;
+
+ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) {
+ dbg_trace();
+ mediator.resource_manager = *this;
+}
+ResourceManager::~ResourceManager() { dbg_trace(); }
+
+void ResourceManager::clear() {
+ std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) {
+ const CacheEntry & entry = pair.second;
+ return entry.persistent == false;
+ });
+}
+
+void ResourceManager::clear_all() { this->resources.clear(); }
+
+void ResourceManager::set_persistent(const Asset & asset, bool persistent) {
+ this->get_entry(asset).persistent = persistent;
+}
+
+ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) {
+ if (!this->resources.contains(asset)) this->resources[asset] = {};
+ return this->resources.at(asset);
+}
diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h
new file mode 100644
index 0000000..84b275d
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+
+#include "../Resource.h"
+#include "../api/Asset.h"
+
+#include "Manager.h"
+
+namespace crepe {
+
+/**
+ * \brief Owner of concrete Resource instances
+ *
+ * ResourceManager caches concrete Resource instances per Asset. Concrete resources are
+ * destroyed at the end of scenes by default, unless the game programmer marks them as
+ * persistent.
+ */
+class ResourceManager : public Manager {
+public:
+ ResourceManager(Mediator & mediator);
+ virtual ~ResourceManager(); // dbg_trace
+
+private:
+ //! Cache entry
+ struct CacheEntry {
+ //! Concrete resource instance
+ std::unique_ptr<Resource> resource = nullptr;
+ //! Prevent ResourceManager::clear from removing this entry
+ bool persistent = false;
+ };
+ //! Internal cache
+ std::unordered_map<const Asset, CacheEntry> resources;
+ /**
+ * \brief Ensure a cache entry exists for this asset and return a mutable reference to it
+ *
+ * \param asset Asset the concrete resource is instantiated from
+ *
+ * \returns Mutable reference to cache entry
+ */
+ CacheEntry & get_entry(const Asset & asset);
+
+public:
+ /**
+ * \brief Mark a resource as persistent (i.e. used across multiple scenes)
+ *
+ * \param asset Asset the concrete resource is instantiated from
+ * \param persistent Whether this resource is persistent (true=keep, false=destroy)
+ */
+ void set_persistent(const Asset & asset, bool persistent);
+
+ /**
+ * \brief Retrieve reference to concrete Resource by Asset
+ *
+ * \param asset Asset the concrete resource is instantiated from
+ * \tparam Resource Concrete derivative of Resource
+ *
+ * This class instantiates the concrete resource if it is not yet stored in the internal
+ * cache, or returns a reference to the cached resource if it already exists.
+ *
+ * \returns Reference to concrete resource
+ *
+ * \throws std::runtime_error if the \c Resource parameter does not match with the actual
+ * type of the resource stored in the cache for this Asset
+ */
+ template <typename Resource>
+ Resource & get(const Asset & asset);
+
+ //! Clear non-persistent resources from cache
+ void clear();
+ //! Clear all resources from cache regardless of persistence
+ void clear_all();
+};
+
+} // namespace crepe
+
+#include "ResourceManager.hpp"
diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp
new file mode 100644
index 0000000..4ca6be0
--- /dev/null
+++ b/src/crepe/manager/ResourceManager.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <format>
+
+#include "ResourceManager.h"
+
+namespace crepe {
+
+template <typename T>
+T & ResourceManager::get(const Asset & asset) {
+ using namespace std;
+ static_assert(
+ is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource"
+ );
+
+ CacheEntry & entry = this->get_entry(asset);
+ if (entry.resource == nullptr) entry.resource = make_unique<T>(asset, this->mediator);
+
+ T * concrete_resource = dynamic_cast<T *>(entry.resource.get());
+ if (concrete_resource == nullptr)
+ throw runtime_error(format(
+ "ResourceManager: mismatch between requested type and "
+ "actual type of resource ({})",
+ asset.get_path()
+ ));
+
+ return *concrete_resource;
+}
+
+} // namespace crepe
diff --git a/src/crepe/api/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp
index c5f43ea..f313ed2 100644
--- a/src/crepe/api/SaveManager.cpp
+++ b/src/crepe/manager/SaveManager.cpp
@@ -1,13 +1,25 @@
+#include "../ValueBroker.h"
+#include "../api/Config.h"
#include "../facade/DB.h"
-#include "../util/Log.h"
-#include "Config.h"
#include "SaveManager.h"
-#include "ValueBroker.h"
using namespace std;
using namespace crepe;
+SaveManager::SaveManager(Mediator & mediator) : Manager(mediator) {
+ mediator.save_manager = *this;
+}
+
+DB & SaveManager::get_db() {
+ if (this->db == nullptr) {
+ Config & cfg = Config::get_instance();
+ this->db
+ = {new DB(cfg.savemgr.location), [](void * db) { delete static_cast<DB *>(db); }};
+ }
+ return *static_cast<DB *>(this->db.get());
+}
+
template <>
string SaveManager::serialize(const string & value) const noexcept {
return value;
@@ -90,22 +102,6 @@ int32_t SaveManager::deserialize(const string & value) const noexcept {
return deserialize<int64_t>(value);
}
-SaveManager::SaveManager() { dbg_trace(); }
-
-SaveManager & SaveManager::get_instance() {
- dbg_trace();
- static SaveManager instance;
- return instance;
-}
-
-DB & SaveManager::get_db() {
- Config & cfg = Config::get_instance();
- // TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the
- // default equivalent is on Windows using some third party library
- static DB db(cfg.savemgr.location);
- return db;
-}
-
bool SaveManager::has(const string & key) {
DB & db = this->get_db();
return db.has(key);
@@ -133,9 +129,32 @@ template void SaveManager::set(const string &, const float &);
template void SaveManager::set(const string &, const double &);
template <typename T>
+T SaveManager::get(const string & key) {
+ return this->deserialize<T>(this->get_db().get(key));
+}
+template uint8_t SaveManager::get(const string &);
+template int8_t SaveManager::get(const string &);
+template uint16_t SaveManager::get(const string &);
+template int16_t SaveManager::get(const string &);
+template uint32_t SaveManager::get(const string &);
+template int32_t SaveManager::get(const string &);
+template uint64_t SaveManager::get(const string &);
+template int64_t SaveManager::get(const string &);
+template float SaveManager::get(const string &);
+template double SaveManager::get(const string &);
+template string SaveManager::get(const string &);
+
+template <typename T>
ValueBroker<T> SaveManager::get(const string & key, const T & default_value) {
if (!this->has(key)) this->set<T>(key, default_value);
- return this->get<T>(key);
+ T value;
+ return {
+ [this, key](const T & target) { this->set<T>(key, target); },
+ [this, key, value]() mutable -> const T & {
+ value = this->get<T>(key);
+ return value;
+ },
+ };
}
template ValueBroker<uint8_t> SaveManager::get(const string &, const uint8_t &);
template ValueBroker<int8_t> SaveManager::get(const string &, const int8_t &);
@@ -148,26 +167,3 @@ template ValueBroker<int64_t> SaveManager::get(const string &, const int64_t &);
template ValueBroker<float> SaveManager::get(const string &, const float &);
template ValueBroker<double> SaveManager::get(const string &, const double &);
template ValueBroker<string> SaveManager::get(const string &, const string &);
-
-template <typename T>
-ValueBroker<T> SaveManager::get(const string & key) {
- T value;
- return {
- [this, key](const T & target) { this->set<T>(key, target); },
- [this, key, value]() mutable -> const T & {
- value = this->deserialize<T>(this->get_db().get(key));
- return value;
- },
- };
-}
-template ValueBroker<uint8_t> SaveManager::get(const string &);
-template ValueBroker<int8_t> SaveManager::get(const string &);
-template ValueBroker<uint16_t> SaveManager::get(const string &);
-template ValueBroker<int16_t> SaveManager::get(const string &);
-template ValueBroker<uint32_t> SaveManager::get(const string &);
-template ValueBroker<int32_t> SaveManager::get(const string &);
-template ValueBroker<uint64_t> SaveManager::get(const string &);
-template ValueBroker<int64_t> SaveManager::get(const string &);
-template ValueBroker<float> SaveManager::get(const string &);
-template ValueBroker<double> SaveManager::get(const string &);
-template ValueBroker<string> SaveManager::get(const string &);
diff --git a/src/crepe/api/SaveManager.h b/src/crepe/manager/SaveManager.h
index 3d8c852..1e34bc0 100644
--- a/src/crepe/api/SaveManager.h
+++ b/src/crepe/manager/SaveManager.h
@@ -1,9 +1,12 @@
#pragma once
+#include <functional>
#include <memory>
#include "../ValueBroker.h"
+#include "Manager.h"
+
namespace crepe {
class DB;
@@ -18,7 +21,7 @@ class DB;
*
* The underlying database is a key-value store.
*/
-class SaveManager {
+class SaveManager : public Manager {
public:
/**
* \brief Get a read/write reference to a value and initialize it if it does not yet exist
@@ -33,17 +36,17 @@ public:
ValueBroker<T> get(const std::string & key, const T & default_value);
/**
- * \brief Get a read/write reference to a value
+ * \brief Get a value directly
*
* \param key The value key
*
- * \return Read/write reference to the value
+ * \return The value
*
* \note Attempting to read this value before it is initialized (i.e. set) will result in an
* exception
*/
template <typename T>
- ValueBroker<T> get(const std::string & key);
+ T get(const std::string & key);
/**
* \brief Set a value directly
@@ -63,8 +66,8 @@ public:
*/
bool has(const std::string & key);
-private:
- SaveManager();
+public:
+ SaveManager(Mediator & mediator);
virtual ~SaveManager() = default;
private:
@@ -89,26 +92,13 @@ private:
template <typename T>
T deserialize(const std::string & value) const noexcept;
-public:
- // singleton
- static SaveManager & get_instance();
- SaveManager(const SaveManager &) = delete;
- SaveManager(SaveManager &&) = delete;
- SaveManager & operator=(const SaveManager &) = delete;
- SaveManager & operator=(SaveManager &&) = delete;
+protected:
+ //! Create or return DB
+ virtual DB & get_db();
private:
- /**
- * \brief Create an instance of DB and return its reference
- *
- * \returns DB instance
- *
- * This function exists because DB is a facade class, which can't directly be used in the API
- * without workarounds
- *
- * TODO: better solution
- */
- static DB & get_db();
+ //! Database
+ std::unique_ptr<void, std::function<void(void *)>> db = nullptr;
};
} // namespace crepe
diff --git a/src/crepe/api/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp
index 1f783ad..e6f92db 100644
--- a/src/crepe/api/SceneManager.cpp
+++ b/src/crepe/manager/SceneManager.cpp
@@ -1,14 +1,15 @@
#include <algorithm>
#include <memory>
-#include "../ComponentManager.h"
-
+#include "ComponentManager.h"
#include "SceneManager.h"
using namespace crepe;
using namespace std;
-SceneManager::SceneManager(ComponentManager & mgr) : component_manager(mgr) {}
+SceneManager::SceneManager(Mediator & mediator) : Manager(mediator) {
+ mediator.scene_manager = *this;
+}
void SceneManager::set_next_scene(const string & name) { next_scene = name; }
@@ -16,19 +17,24 @@ void SceneManager::load_next_scene() {
// next scene not set
if (this->next_scene.empty()) return;
- auto it = find_if(this->scenes.begin(), this->scenes.end(),
- [&next_scene = this->next_scene](unique_ptr<Scene> & scene) {
- return scene.get()->get_name() == next_scene;
- });
+ auto it = find_if(
+ this->scenes.begin(), this->scenes.end(),
+ [&next_scene = this->next_scene](unique_ptr<Scene> & scene) {
+ return scene.get()->get_name() == next_scene;
+ }
+ );
// next scene not found
if (it == this->scenes.end()) return;
unique_ptr<Scene> & scene = *it;
// Delete all components of the current scene
- ComponentManager & mgr = this->component_manager;
+ ComponentManager & mgr = this->mediator.component_manager;
mgr.delete_all_components();
// Load the new scene
scene->load_scene();
+
+ //clear the next scene
+ next_scene.clear();
}
diff --git a/src/crepe/api/SceneManager.h b/src/crepe/manager/SceneManager.h
index 45ba668..e0955c2 100644
--- a/src/crepe/api/SceneManager.h
+++ b/src/crepe/manager/SceneManager.h
@@ -3,7 +3,9 @@
#include <memory>
#include <vector>
-#include "Scene.h"
+#include "../api/Scene.h"
+
+#include "Manager.h"
namespace crepe {
@@ -15,10 +17,9 @@ class ComponentManager;
* This class manages scenes. It can add new scenes and load them. It also manages the current scene
* and the next scene.
*/
-class SceneManager {
+class SceneManager : public Manager {
public:
- //! \param mgr Reference to the ComponentManager
- SceneManager(ComponentManager & mgr);
+ SceneManager(Mediator & mediator);
public:
/**
@@ -26,8 +27,8 @@ public:
*
* \tparam T Type of concrete scene
*/
- template <typename T>
- void add_scene();
+ template <typename T, typename... Args>
+ void add_scene(Args &&... args);
/**
* \brief Set the next scene
*
@@ -44,8 +45,6 @@ private:
std::vector<std::unique_ptr<Scene>> scenes;
//! Next scene to load
std::string next_scene;
- //! Reference to the ComponentManager
- ComponentManager & component_manager;
};
} // namespace crepe
diff --git a/src/crepe/api/SceneManager.hpp b/src/crepe/manager/SceneManager.hpp
index 94e5946..dff4e51 100644
--- a/src/crepe/api/SceneManager.hpp
+++ b/src/crepe/manager/SceneManager.hpp
@@ -4,13 +4,17 @@
namespace crepe {
-template <typename T>
-void SceneManager::add_scene() {
+template <typename T, typename... Args>
+void SceneManager::add_scene(Args &&... args) {
using namespace std;
static_assert(is_base_of<Scene, T>::value, "T must be derived from Scene");
- Scene * scene = new T(this->component_manager);
- this->scenes.emplace_back(unique_ptr<Scene>(scene));
+ Scene * scene = new T(std::forward<Args>(args)...);
+ unique_ptr<Scene> unique_scene(scene);
+
+ unique_scene->mediator = this->mediator;
+
+ this->scenes.emplace_back(std::move(unique_scene));
// The first scene added, is the one that will be loaded at the beginning
if (next_scene.empty()) {
diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp
new file mode 100644
index 0000000..eabc022
--- /dev/null
+++ b/src/crepe/manager/SystemManager.cpp
@@ -0,0 +1,66 @@
+#include "../system/AISystem.h"
+#include "../system/AnimatorSystem.h"
+#include "../system/AudioSystem.h"
+#include "../system/CollisionSystem.h"
+#include "../system/EventSystem.h"
+#include "../system/InputSystem.h"
+#include "../system/ParticleSystem.h"
+#include "../system/PhysicsSystem.h"
+#include "../system/RenderSystem.h"
+#include "../system/ReplaySystem.h"
+#include "../system/ScriptSystem.h"
+
+#include "SystemManager.h"
+
+using namespace crepe;
+using namespace std;
+
+SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) {
+ this->load_system<InputSystem>();
+ this->load_system<EventSystem>();
+ this->load_system<ScriptSystem>();
+ this->load_system<ParticleSystem>();
+ this->load_system<AISystem>();
+ this->load_system<PhysicsSystem>();
+ this->load_system<CollisionSystem>();
+ this->load_system<AudioSystem>();
+ this->load_system<AnimatorSystem>();
+ this->load_system<RenderSystem>();
+ this->load_system<ReplaySystem>();
+
+ this->mediator.system_manager = *this;
+}
+
+void SystemManager::fixed_update() {
+ for (System & system : this->system_order) {
+ if (!system.active) continue;
+ system.fixed_update();
+ }
+}
+
+void SystemManager::frame_update() {
+ for (System & system : this->system_order) {
+ if (!system.active) continue;
+ system.frame_update();
+ }
+}
+
+SystemManager::Snapshot SystemManager::save() {
+ Snapshot snapshot;
+ for (auto & [type, system] : this->systems) {
+ snapshot[type] = system->active;
+ }
+ return snapshot;
+}
+
+void SystemManager::restore(const Snapshot & snapshot) {
+ for (auto & [type, active] : snapshot) {
+ this->systems[type]->active = active;
+ }
+}
+
+void SystemManager::disable_all() {
+ for (auto & [type, system] : this->systems) {
+ system->active = false;
+ }
+}
diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h
new file mode 100644
index 0000000..614d90c
--- /dev/null
+++ b/src/crepe/manager/SystemManager.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <memory>
+#include <typeindex>
+#include <unordered_map>
+#include <vector>
+
+#include "../system/System.h"
+
+#include "Manager.h"
+
+namespace crepe {
+
+/**
+ * \brief Collection of all systems
+ *
+ * This manager aggregates all systems and provides utility functions to retrieve references to
+ * and update systems.
+ */
+class SystemManager : public Manager {
+public:
+ SystemManager(Mediator &);
+
+ /**
+ * \brief Per-frame update.
+ *
+ * Updates the game state based on the elapsed time since the last frame.
+ */
+ void frame_update();
+
+ /**
+ * \brief Fixed update executed at a fixed rate.
+ *
+ * This function updates physics and game logic based on LoopTimer's fixed_delta_time.
+ */
+ void fixed_update();
+
+private:
+ /**
+ * \brief Collection of System instances
+ *
+ * This map holds System instances indexed by the system's class typeid. It is filled in the
+ * constructor of \c SystemManager using SystemManager::load_system.
+ */
+ std::unordered_map<std::type_index, std::unique_ptr<System>> systems;
+ /**
+ * \brief Collection of System instances
+ *
+ * This map holds System instances indexed by the system's class typeid. It is filled in the
+ * constructor of \c SystemManager using SystemManager::load_system.
+ */
+ std::vector<std::reference_wrapper<System>> system_order;
+ /**
+ * \brief Initialize a system
+ * \tparam T System type (must be derivative of \c System)
+ */
+ template <class T>
+ void load_system();
+
+public:
+ /**
+ * \brief Retrieve a reference to ECS system
+ * \tparam T System type
+ * \returns Reference to system instance
+ * \throws std::runtime_error if the System is not initialized
+ */
+ template <class T>
+ T & get_system();
+
+public:
+ /**
+ * \brief SystemManager snapshot
+ *
+ * The SystemManager snapshot only stores which systems are active
+ */
+ typedef std::unordered_map<std::type_index, bool> Snapshot;
+ /**
+ * \brief Save a snapshot of the systems' state
+ * \returns Copy of each system's active property
+ */
+ Snapshot save();
+ /**
+ * \brief Restore system active state from a snapshot
+ * \param snapshot Snapshot to restore from (as returned by \c save())
+ */
+ void restore(const Snapshot & snapshot);
+ //! Disable all systems
+ void disable_all();
+};
+
+} // namespace crepe
+
+#include "SystemManager.hpp"
diff --git a/src/crepe/manager/SystemManager.hpp b/src/crepe/manager/SystemManager.hpp
new file mode 100644
index 0000000..addd274
--- /dev/null
+++ b/src/crepe/manager/SystemManager.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <cassert>
+#include <format>
+#include <memory>
+
+#include "SystemManager.h"
+
+namespace crepe {
+
+template <class T>
+T & SystemManager::get_system() {
+ using namespace std;
+ static_assert(
+ is_base_of<System, T>::value, "get_system must recieve a derivative class of System"
+ );
+
+ const type_info & type = typeid(T);
+ if (!this->systems.contains(type))
+ throw runtime_error(format("SystemManager: {} is not initialized", type.name()));
+
+ System * system = this->systems.at(type).get();
+ T * concrete_system = dynamic_cast<T *>(system);
+ assert(concrete_system != nullptr);
+
+ return *concrete_system;
+}
+
+template <class T>
+void SystemManager::load_system() {
+ using namespace std;
+ static_assert(
+ is_base_of<System, T>::value, "load_system must recieve a derivative class of System"
+ );
+
+ const type_info & type = typeid(T);
+ if (this->systems.contains(type))
+ throw runtime_error(format("SystemManager: {} is already initialized", type.name()));
+ System * system = new T(this->mediator);
+ this->systems[type] = unique_ptr<System>(system);
+ this->system_order.push_back(*this->systems[type]);
+}
+
+} // namespace crepe
diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp
new file mode 100644
index 0000000..94445c7
--- /dev/null
+++ b/src/crepe/system/AISystem.cpp
@@ -0,0 +1,187 @@
+#include <algorithm>
+#include <cmath>
+
+#include "manager/ComponentManager.h"
+#include "manager/LoopTimerManager.h"
+#include "manager/Mediator.h"
+
+#include "AISystem.h"
+
+using namespace crepe;
+using namespace std::chrono;
+
+void AISystem::fixed_update() {
+ const Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+ LoopTimerManager & loop_timer = mediator.loop_timer;
+ RefVector<AI> ai_components = mgr.get_components_by_type<AI>();
+
+ float dt = loop_timer.get_scaled_fixed_delta_time().count();
+
+ // Loop through all AI components
+ for (AI & ai : ai_components) {
+ if (!ai.active) {
+ continue;
+ }
+
+ RefVector<Rigidbody> rigidbodies
+ = mgr.get_components_by_id<Rigidbody>(ai.game_object_id);
+ if (rigidbodies.empty()) {
+ throw std::runtime_error(
+ "AI component must be attached to a GameObject with a Rigidbody component"
+ );
+ }
+ Rigidbody & rigidbody = rigidbodies.front().get();
+ if (!rigidbody.active) {
+ continue;
+ }
+ if (rigidbody.data.mass <= 0) {
+ throw std::runtime_error("Mass must be greater than 0");
+ }
+
+ // Calculate the force to apply to the entity
+ vec2 force = this->calculate(ai, rigidbody);
+ // Calculate the acceleration (using the above calculated force)
+ vec2 acceleration = force / rigidbody.data.mass;
+ // Finally, update Rigidbody's velocity
+ rigidbody.data.linear_velocity += acceleration * dt;
+ }
+}
+
+vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) {
+ const Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+ RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id);
+ Transform & transform = transforms.front().get();
+
+ vec2 force;
+
+ // Run all the behaviors that are on, and stop if the force gets too high
+ if (ai.on(AI::BehaviorTypeMask::FLEE)) {
+ vec2 force_to_add = this->flee(ai, rigidbody, transform);
+
+ if (!this->accumulate_force(ai, force, force_to_add)) {
+ return force;
+ }
+ }
+ if (ai.on(AI::BehaviorTypeMask::ARRIVE)) {
+ vec2 force_to_add = this->arrive(ai, rigidbody, transform);
+
+ if (!this->accumulate_force(ai, force, force_to_add)) {
+ return force;
+ }
+ }
+ if (ai.on(AI::BehaviorTypeMask::SEEK)) {
+ vec2 force_to_add = this->seek(ai, rigidbody, transform);
+
+ if (!this->accumulate_force(ai, force, force_to_add)) {
+ return force;
+ }
+ }
+ if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) {
+ vec2 force_to_add = this->path_follow(ai, rigidbody, transform);
+
+ if (!this->accumulate_force(ai, force, force_to_add)) {
+ return force;
+ }
+ }
+
+ return force;
+}
+
+bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) {
+ float magnitude = running_total.length();
+ float magnitude_remaining = ai.max_force - magnitude;
+
+ if (magnitude_remaining <= 0.0f) {
+ // If the force is already at/above the max force, return false
+ return false;
+ }
+
+ float magnitude_to_add = force_to_add.length();
+ if (magnitude_to_add < magnitude_remaining) {
+ // If the force to add is less than the remaining force, add it
+ running_total += force_to_add;
+ } else {
+ // If the force to add is greater than the remaining force, add a fraction of it
+ force_to_add.normalize();
+ running_total += force_to_add * magnitude_remaining;
+ }
+
+ return true;
+}
+
+vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform)
+ const {
+ // Calculate the desired velocity
+ vec2 desired_velocity = ai.seek_target - transform.position;
+ desired_velocity.normalize();
+ desired_velocity *= rigidbody.data.max_linear_velocity;
+
+ return desired_velocity - rigidbody.data.linear_velocity;
+}
+
+vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform)
+ const {
+ // Calculate the desired velocity if the entity is within the panic distance
+ vec2 desired_velocity = transform.position - ai.flee_target;
+ if (desired_velocity.length_squared() > ai.square_flee_panic_distance) {
+ return vec2 {0, 0};
+ }
+ desired_velocity.normalize();
+ desired_velocity *= rigidbody.data.max_linear_velocity;
+
+ return desired_velocity - rigidbody.data.linear_velocity;
+}
+
+vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform)
+ const {
+ // Calculate the desired velocity (taking into account the deceleration rate)
+ vec2 to_target = ai.arrive_target - transform.position;
+ float distance = to_target.length();
+ if (distance > 0.0f) {
+ if (ai.arrive_deceleration <= 0.0f) {
+ throw std::runtime_error("Deceleration rate must be greater than 0");
+ }
+
+ float speed = distance / ai.arrive_deceleration;
+ speed = std::min(speed, rigidbody.data.max_linear_velocity);
+ vec2 desired_velocity = to_target * (speed / distance);
+
+ return desired_velocity - rigidbody.data.linear_velocity;
+ }
+
+ return vec2 {0, 0};
+}
+
+vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) {
+ if (ai.path.empty()) {
+ return vec2 {0, 0};
+ }
+
+ // Get the target node
+ vec2 target = ai.path.at(ai.path_index);
+ // Calculate the force to apply to the entity
+ vec2 to_target = target - transform.position;
+ if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) {
+ // If the entity is not close enough to the target node, seek it
+ ai.seek_target = target;
+ ai.arrive_target = target;
+ } else {
+ // If the entity is close enough to the target node, move to the next node
+ ai.path_index++;
+ if (ai.path_index >= ai.path.size()) {
+ if (ai.path_loop) {
+ // If the path is looping, reset the path index
+ ai.path_index = 0;
+ } else {
+ // If the path is not looping, arrive at the last node
+ ai.path_index = ai.path.size() - 1;
+ return this->arrive(ai, rigidbody, transform);
+ }
+ }
+ }
+
+ // Seek the target node
+ return this->seek(ai, rigidbody, transform);
+}
diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h
new file mode 100644
index 0000000..04807cf
--- /dev/null
+++ b/src/crepe/system/AISystem.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include "api/AI.h"
+#include "api/Rigidbody.h"
+
+#include "System.h"
+#include "api/Transform.h"
+#include "types.h"
+
+namespace crepe {
+
+/**
+ * \brief The AISystem is used to control the movement of entities using AI.
+ *
+ * The AISystem is used to control the movement of entities using AI. The AISystem can be used to
+ * implement different behaviors such as seeking, fleeing, arriving, and path following.
+ */
+class AISystem : public System {
+public:
+ using System::System;
+
+ //! Update the AI system
+ void fixed_update() override;
+
+private:
+ /**
+ * \brief Calculate the total force to apply to the entity
+ *
+ * \param ai The AI component
+ * \param rigidbody The Rigidbody component
+ */
+ vec2 calculate(AI & ai, const Rigidbody & rigidbody);
+ /**
+ * \brief Accumulate the force to apply to the entity
+ *
+ * \param ai The AI component
+ * \param running_total The running total of the force
+ * \param force_to_add The force to add
+ * \return true if the force was added, false otherwise
+ */
+ bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add);
+
+ /**
+ * \brief Calculate the seek force
+ *
+ * \param ai The AI component
+ * \param rigidbody The Rigidbody component
+ * \param transform The Transform component
+ * \return The seek force
+ */
+ vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const;
+ /**
+ * \brief Calculate the flee force
+ *
+ * \param ai The AI component
+ * \param rigidbody The Rigidbody component
+ * \param transform The Transform component
+ * \return The flee force
+ */
+ vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const;
+ /**
+ * \brief Calculate the arrive force
+ *
+ * \param ai The AI component
+ * \param rigidbody The Rigidbody component
+ * \param transform The Transform component
+ * \return The arrive force
+ */
+ vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const;
+ /**
+ * \brief Calculate the path follow force
+ *
+ * \param ai The AI component
+ * \param rigidbody The Rigidbody component
+ * \param transform The Transform component
+ * \return The path follow force
+ */
+ vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform);
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp
index 676e485..143d5d6 100644
--- a/src/crepe/system/AnimatorSystem.cpp
+++ b/src/crepe/system/AnimatorSystem.cpp
@@ -1,24 +1,44 @@
-#include <cstdint>
+#include <chrono>
-#include "api/Animator.h"
-#include "facade/SDLContext.h"
+#include "../api/Animator.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/LoopTimerManager.h"
#include "AnimatorSystem.h"
-#include "ComponentManager.h"
using namespace crepe;
+using namespace std::chrono;
-void AnimatorSystem::update() {
- ComponentManager & mgr = this->component_manager;
-
+void AnimatorSystem::frame_update() {
+ ComponentManager & mgr = this->mediator.component_manager;
+ LoopTimerManager & timer = this->mediator.loop_timer;
RefVector<Animator> animations = mgr.get_components_by_type<Animator>();
- uint64_t tick = SDLContext::get_instance().get_ticks();
+ duration_t elapsed_time = timer.get_delta_time();
+
for (Animator & a : animations) {
- if (a.active) {
- a.curr_row = (tick / 100) % a.row;
- a.animator_rect.x = (a.curr_row * a.animator_rect.w) + a.curr_col;
- a.spritesheet.sprite_rect = a.animator_rect;
+ if (!a.active) continue;
+ if (a.data.fps == 0) continue;
+
+ Animator::Data & ctx = a.data;
+
+ a.elapsed_time += elapsed_time;
+ duration_t frame_duration = 1000ms / ctx.fps;
+
+ int cycle_end = (ctx.cycle_end == -1) ? a.grid_size.x : ctx.cycle_end;
+ if (a.elapsed_time >= frame_duration) {
+ a.elapsed_time = 0ms;
+ a.frame++;
+ if (a.frame == cycle_end) {
+ a.frame = ctx.cycle_start;
+ if (!ctx.looping) {
+ a.active = false;
+ continue;
+ }
+ }
}
+
+ ctx.row = ctx.cycle_start + a.frame;
+ a.spritesheet.mask.x = ctx.row * a.spritesheet.mask.w;
}
}
diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h
index 56cc7b3..092e131 100644
--- a/src/crepe/system/AnimatorSystem.h
+++ b/src/crepe/system/AnimatorSystem.h
@@ -2,9 +2,6 @@
#include "System.h"
-//TODO:
-// control if flip works with animation system
-
namespace crepe {
/**
@@ -21,12 +18,11 @@ public:
/**
* \brief Updates the Animator components.
*
- * This method is called periodically (likely every frame) to update the state of all
+ * This method is called to update the state of all
* Animator components, moving the animations forward and managing their behavior (e.g.,
* looping).
*/
- void update() override;
- // FIXME: never say "likely" in the documentation lmao
+ void frame_update() override;
};
} // namespace crepe
diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp
new file mode 100644
index 0000000..3c2232f
--- /dev/null
+++ b/src/crepe/system/AudioSystem.cpp
@@ -0,0 +1,64 @@
+#include "AudioSystem.h"
+
+#include "../manager/ComponentManager.h"
+#include "../manager/ResourceManager.h"
+#include "../types.h"
+
+using namespace crepe;
+using namespace std;
+
+void AudioSystem::fixed_update() {
+ ComponentManager & component_manager = this->mediator.component_manager;
+ ResourceManager & resource_manager = this->mediator.resource_manager;
+ RefVector<AudioSource> components
+ = component_manager.get_components_by_type<AudioSource>();
+
+ for (AudioSource & component : components) {
+ Sound & resource = resource_manager.get<Sound>(component.source);
+
+ this->diff_update(component, resource);
+
+ this->update_last(component);
+ }
+}
+
+void AudioSystem::diff_update(AudioSource & component, Sound & resource) {
+ SoundContext & context = this->get_context();
+
+ if (component.active != component.last_active) {
+ if (!component.active) {
+ context.stop(component.voice);
+ return;
+ }
+ if (component.play_on_awake) component.oneshot_play = true;
+ }
+ if (!component.active) return;
+
+ if (component.oneshot_play) {
+ component.voice = context.play(resource);
+ context.set_loop(component.voice, component.loop);
+ context.set_volume(component.voice, component.volume);
+ component.oneshot_play = false;
+ }
+ if (component.oneshot_stop) {
+ context.stop(component.voice);
+ component.oneshot_stop = false;
+ }
+ if (component.volume != component.last_volume) {
+ context.set_volume(component.voice, component.volume);
+ }
+ if (component.loop != component.last_loop) {
+ context.set_loop(component.voice, component.loop);
+ }
+}
+
+void AudioSystem::update_last(AudioSource & component) {
+ component.last_active = component.active;
+ component.last_loop = component.loop;
+ component.last_volume = component.volume;
+}
+
+SoundContext & AudioSystem::get_context() {
+ if (this->context == nullptr) this->context = make_unique<SoundContext>();
+ return *this->context.get();
+}
diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h
new file mode 100644
index 0000000..56fc98c
--- /dev/null
+++ b/src/crepe/system/AudioSystem.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "../api/AudioSource.h"
+#include "../facade/Sound.h"
+#include "../facade/SoundContext.h"
+
+#include "System.h"
+
+namespace crepe {
+
+class AudioSystem : public System {
+public:
+ using System::System;
+ void fixed_update() override;
+
+private:
+ /**
+ * \brief Update `last_*` members of \c component
+ *
+ * Copies all component properties stored for comparison between AudioSystem::update() calls
+ *
+ * \param component AudioSource component to update
+ */
+ void update_last(AudioSource & component);
+
+ /**
+ * \brief Compare update component
+ *
+ * Compares properties of \c component and \c data, and calls SoundContext functions where
+ * applicable.
+ *
+ * \param component AudioSource component to update
+ * \param resource Sound instance for AudioSource's Asset
+ */
+ void diff_update(AudioSource & component, Sound & resource);
+
+protected:
+ /**
+ * \brief Get SoundContext
+ *
+ * SoundContext is retrieved through this function instead of being a direct member of
+ * AudioSystem to aid with testability.
+ */
+ virtual SoundContext & get_context();
+
+private:
+ //! SoundContext
+ std::unique_ptr<SoundContext> context = nullptr;
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt
index d658b25..52369d0 100644
--- a/src/crepe/system/CMakeLists.txt
+++ b/src/crepe/system/CMakeLists.txt
@@ -5,7 +5,12 @@ target_sources(crepe PUBLIC
PhysicsSystem.cpp
CollisionSystem.cpp
RenderSystem.cpp
+ AudioSystem.cpp
AnimatorSystem.cpp
+ InputSystem.cpp
+ EventSystem.cpp
+ ReplaySystem.cpp
+ AISystem.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -14,5 +19,10 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
PhysicsSystem.h
CollisionSystem.h
RenderSystem.h
+ AudioSystem.h
AnimatorSystem.h
+ InputSystem.h
+ EventSystem.h
+ ReplaySystem.h
+ AISystem.h
)
diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp
index c74ca1d..571ac70 100644
--- a/src/crepe/system/CollisionSystem.cpp
+++ b/src/crepe/system/CollisionSystem.cpp
@@ -1,5 +1,604 @@
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <emmintrin.h>
+#include <functional>
+#include <optional>
+#include <utility>
+#include <variant>
+
+#include "../manager/ComponentManager.h"
+#include "../manager/EventManager.h"
+#include "api/BoxCollider.h"
+#include "api/CircleCollider.h"
+#include "api/Event.h"
+#include "api/Metadata.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+#include "api/Vector2.h"
+#include "util/AbsolutePosition.h"
+#include "util/OptionalRef.h"
+
#include "CollisionSystem.h"
+#include "types.h"
using namespace crepe;
+using enum Rigidbody::BodyType;
+
+CollisionSystem::CollisionInfo CollisionSystem::CollisionInfo::operator-() const {
+ return {
+ .self = this->other,
+ .other = this->self,
+ .resolution = -this->resolution,
+ .resolution_direction = this->resolution_direction,
+ };
+}
+
+void CollisionSystem::fixed_update() {
+ std::vector<CollisionInternal> all_colliders;
+ game_object_id_t id = 0;
+ ComponentManager & mgr = this->mediator.component_manager;
+ RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>();
+ // Collisions can only happen on object with a rigidbody
+ for (Rigidbody & rigidbody : rigidbodies) {
+ if (!rigidbody.active) continue;
+ id = rigidbody.game_object_id;
+ Transform & transform = mgr.get_components_by_id<Transform>(id).front().get();
+ Metadata & metadata = mgr.get_components_by_id<Metadata>(id).front().get();
+ // Check if the boxcollider is active and has the same id as the rigidbody.
+ RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>();
+ for (BoxCollider & boxcollider : boxcolliders) {
+ if (boxcollider.game_object_id != id) continue;
+ if (!boxcollider.active) continue;
+ all_colliders.push_back(
+ {.id = id,
+ .collider = collider_variant {boxcollider},
+ .info = {transform, rigidbody, metadata}}
+ );
+ }
+ // Check if the circlecollider is active and has the same id as the rigidbody.
+ RefVector<CircleCollider> circlecolliders
+ = mgr.get_components_by_type<CircleCollider>();
+ for (CircleCollider & circlecollider : circlecolliders) {
+ if (circlecollider.game_object_id != id) continue;
+ if (!circlecollider.active) continue;
+ all_colliders.push_back(
+ {.id = id,
+ .collider = collider_variant {circlecollider},
+ .info = {transform, rigidbody, metadata}}
+ );
+ }
+ }
+
+ // Check between all colliders if there is a collision (collision handling)
+ std::vector<std::pair<CollisionInternal, CollisionInternal>> collided
+ = this->gather_collisions(all_colliders);
+
+ // For the object convert the info and call the collision handler if needed
+ for (auto & collision_pair : collided) {
+ // Convert internal struct to external struct
+ CollisionInfo info
+ = this->get_collision_info(collision_pair.first, collision_pair.second);
+ // Determine if and/or what collison handler is needed.
+ this->determine_collision_handler(info);
+ }
+}
+
+// Below is for collision detection
+std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>>
+CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) {
+
+ // TODO:
+ // If no colliders skip
+ // Check if colliders has rigidbody if not skip
+
+ // TODO:
+ // If amount is higer than lets say 16 for now use quadtree otwerwise skip
+ // Quadtree code
+ // Quadtree is placed over the input vector
+
+ // Return data of collided colliders which are variants
+ std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret;
+ //using visit to visit the variant to access the active and id.
+ for (size_t i = 0; i < colliders.size(); ++i) {
+ for (size_t j = i + 1; j < colliders.size(); ++j) {
+ if (colliders[i].id == colliders[j].id) continue;
+ if (!should_collide(colliders[i], colliders[j])) continue;
+ CollisionInternalType type
+ = get_collider_type(colliders[i].collider, colliders[j].collider);
+ if (!detect_collision(colliders[i], colliders[j], type)) continue;
+ //fet
+ collisions_ret.emplace_back(colliders[i], colliders[j]);
+ }
+ }
+ return collisions_ret;
+}
+
+bool CollisionSystem::should_collide(
+ const CollisionInternal & self, const CollisionInternal & other
+) const {
+
+ const Rigidbody::Data & self_rigidbody = self.info.rigidbody.data;
+ const Rigidbody::Data & other_rigidbody = other.info.rigidbody.data;
+ const Metadata & self_metadata = self.info.metadata;
+ const Metadata & other_metadata = other.info.metadata;
+
+ // Check collision layers
+ if (self_rigidbody.collision_layers.contains(other_rigidbody.collision_layer)) return true;
+ if (other_rigidbody.collision_layers.contains(self_rigidbody.collision_layer)) return true;
+
+ // Check names
+ if (self_rigidbody.collision_names.contains(other_metadata.name)) return true;
+ if (other_rigidbody.collision_names.contains(self_metadata.name)) return true;
+
+ // Check tags
+ if (self_rigidbody.collision_tags.contains(other_metadata.tag)) return true;
+ if (other_rigidbody.collision_tags.contains(self_metadata.tag)) return true;
+
+ return false;
+}
+
+CollisionSystem::CollisionInternalType CollisionSystem::get_collider_type(
+ const collider_variant & collider1, const collider_variant & collider2
+) const {
+ if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) {
+ if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) {
+ return CollisionInternalType::CIRCLE_CIRCLE;
+ } else {
+ return CollisionInternalType::CIRCLE_BOX;
+ }
+ } else {
+ if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) {
+ return CollisionInternalType::BOX_CIRCLE;
+ } else {
+ return CollisionInternalType::BOX_BOX;
+ }
+ }
+}
+
+bool CollisionSystem::detect_collision(
+ CollisionInternal & self, CollisionInternal & other, const CollisionInternalType & type
+) {
+ vec2 resolution;
+ switch (type) {
+ case CollisionInternalType::BOX_BOX: {
+ // Box-Box collision detection
+ const BoxColliderInternal BOX1
+ = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider),
+ .transform = self.info.transform,
+ .rigidbody = self.info.rigidbody};
+ const BoxColliderInternal BOX2
+ = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider),
+ .transform = other.info.transform,
+ .rigidbody = other.info.rigidbody};
+ // Get resolution vector from box-box collision detection
+ resolution = this->get_box_box_detection(BOX1, BOX2);
+ // If no collision (NaN values), return false
+ if (resolution.is_nan()) return false;
+ break;
+ }
+ case CollisionInternalType::BOX_CIRCLE: {
+ // Box-Circle collision detection
+ const BoxColliderInternal BOX1
+ = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider),
+ .transform = self.info.transform,
+ .rigidbody = self.info.rigidbody};
+ const CircleColliderInternal CIRCLE2
+ = {.collider
+ = std::get<std::reference_wrapper<CircleCollider>>(other.collider),
+ .transform = other.info.transform,
+ .rigidbody = other.info.rigidbody};
+ // Get resolution vector from box-circle collision detection
+ resolution = this->get_box_circle_detection(BOX1, CIRCLE2);
+ // If no collision (NaN values), return false
+ if (resolution.is_nan()) return false;
+ // Invert the resolution vector for proper collision response
+ resolution = -resolution;
+ break;
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE: {
+ // Circle-Circle collision detection
+ const CircleColliderInternal CIRCLE1
+ = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider),
+ .transform = self.info.transform,
+ .rigidbody = self.info.rigidbody};
+ const CircleColliderInternal CIRCLE2
+ = {.collider
+ = std::get<std::reference_wrapper<CircleCollider>>(other.collider),
+ .transform = other.info.transform,
+ .rigidbody = other.info.rigidbody};
+ // Get resolution vector from circle-circle collision detection
+ resolution = this->get_circle_circle_detection(CIRCLE1, CIRCLE2);
+ // If no collision (NaN values), return false
+ if (resolution.is_nan()) return false;
+ break;
+ }
+ case CollisionInternalType::CIRCLE_BOX: {
+ // Circle-Box collision detection
+ const CircleColliderInternal CIRCLE1
+ = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider),
+ .transform = self.info.transform,
+ .rigidbody = self.info.rigidbody};
+ const BoxColliderInternal BOX2
+ = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider),
+ .transform = other.info.transform,
+ .rigidbody = other.info.rigidbody};
+ // Get resolution vector from box-circle collision detection (order swapped)
+ resolution = this->get_box_circle_detection(BOX2, CIRCLE1);
+ // If no collision (NaN values), return false
+ if (resolution.is_nan()) return false;
+ break;
+ }
+ case CollisionInternalType::NONE:
+ // No collision detection needed if the type is NONE
+ return false;
+ break;
+ }
+ // Store the calculated resolution vector for the 'self' collider
+ self.resolution = resolution;
+ // Calculate the resolution direction based on the rigidbody data
+ self.resolution_direction
+ = this->resolution_correction(self.resolution, self.info.rigidbody.data);
+ // For the 'other' collider, the resolution is the opposite direction of 'self'
+ other.resolution = -self.resolution;
+ other.resolution_direction = self.resolution_direction;
+
+ // Return true if a collision was detected and resolution was calculated
+ return true;
+}
+
+vec2 CollisionSystem::get_box_box_detection(
+ const BoxColliderInternal & box1, const BoxColliderInternal & box2
+) const {
+ vec2 resolution {NAN, NAN};
+ // Get current positions of colliders
+ vec2 pos1 = AbsolutePosition::get_position(box1.transform, box1.collider.offset);
+ vec2 pos2 = AbsolutePosition::get_position(box2.transform, box2.collider.offset);
+
+ // Scale dimensions
+ vec2 scaled_box1 = box1.collider.dimensions * box1.transform.scale;
+ vec2 scaled_box2 = box2.collider.dimensions * box2.transform.scale;
+ vec2 delta = pos2 - pos1;
+
+ // Calculate half-extents (half width and half height)
+ float half_width1 = scaled_box1.x / 2.0;
+ float half_height1 = scaled_box1.y / 2.0;
+ float half_width2 = scaled_box2.x / 2.0;
+ float half_height2 = scaled_box2.y / 2.0;
+
+ if (pos1.x + half_width1 > pos2.x - half_width2
+ && pos1.x - half_width1 < pos2.x + half_width2
+ && pos1.y + half_height1 > pos2.y - half_height2
+ && pos1.y - half_height1 < pos2.y + half_height2) {
+ resolution = {0, 0};
+ float overlap_x = (half_width1 + half_width2) - std::abs(delta.x);
+ float overlap_y = (half_height1 + half_height2) - std::abs(delta.y);
+ if (overlap_x > 0 && overlap_y > 0) {
+ // Determine the direction of resolution
+ if (overlap_x < overlap_y) {
+ // Resolve along the X-axis (smallest overlap)
+ resolution.x = (delta.x > 0) ? -overlap_x : overlap_x;
+ } else if (overlap_y < overlap_x) {
+ // Resolve along the Y-axis (smallest overlap)
+ resolution.y = (delta.y > 0) ? -overlap_y : overlap_y;
+ } else {
+ // Equal overlap, resolve both directions with preference
+ resolution.x = (delta.x > 0) ? -overlap_x : overlap_x;
+ resolution.y = (delta.y > 0) ? -overlap_y : overlap_y;
+ }
+ }
+ }
+ return resolution;
+}
+
+vec2 CollisionSystem::get_box_circle_detection(
+ const BoxColliderInternal & box, const CircleColliderInternal & circle
+) const {
+ /// Get current positions of colliders
+ vec2 box_pos = AbsolutePosition::get_position(box.transform, box.collider.offset);
+ vec2 circle_pos = AbsolutePosition::get_position(circle.transform, circle.collider.offset);
+
+ // Scale dimensions
+ vec2 scaled_box = box.collider.dimensions * box.transform.scale;
+ float scaled_circle_radius = circle.collider.radius * circle.transform.scale;
+
+ // Calculate box half-extents
+ float half_width = scaled_box.x / 2.0f;
+ float half_height = scaled_box.y / 2.0f;
+
+ // Find the closest point on the box to the circle's center
+ float closest_x
+ = std::max(box_pos.x - half_width, std::min(circle_pos.x, box_pos.x + half_width));
+ float closest_y
+ = std::max(box_pos.y - half_height, std::min(circle_pos.y, box_pos.y + half_height));
+
+ float distance_x = circle_pos.x - closest_x;
+ float distance_y = circle_pos.y - closest_y;
+ float distance_squared = distance_x * distance_x + distance_y * distance_y;
+ if (distance_squared < scaled_circle_radius * scaled_circle_radius) {
+ vec2 delta = circle_pos - box_pos;
+
+ // Clamp circle center to the nearest point on the box
+ vec2 closest_point;
+ closest_point.x = std::clamp(delta.x, -half_width, half_width);
+ closest_point.y = std::clamp(delta.y, -half_height, half_height);
+
+ // Find the vector from the circle center to the closest point
+ vec2 closest_delta = delta - closest_point;
+
+ float distance
+ = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y);
+ vec2 collision_normal = closest_delta / distance;
+
+ // Compute penetration depth
+ float penetration_depth = scaled_circle_radius - distance;
+
+ // Compute the resolution vector
+ return vec2 {collision_normal * penetration_depth};
+ }
+ // No collision
+ return vec2 {NAN, NAN};
+}
+
+vec2 CollisionSystem::get_circle_circle_detection(
+ const CircleColliderInternal & circle1, const CircleColliderInternal & circle2
+) const {
+ // Get current positions of colliders
+ vec2 final_position1
+ = AbsolutePosition::get_position(circle1.transform, circle1.collider.offset);
+ vec2 final_position2
+ = AbsolutePosition::get_position(circle2.transform, circle2.collider.offset);
+
+ // Scale dimensions
+ float scaled_circle1 = circle1.collider.radius * circle1.transform.scale;
+ float scaled_circle2 = circle2.collider.radius * circle2.transform.scale;
+
+ float distance_x = final_position1.x - final_position2.x;
+ float distance_y = final_position1.y - final_position2.y;
+ float distance_squared = distance_x * distance_x + distance_y * distance_y;
+
+ // Calculate the sum of the radii
+ float radius_sum = scaled_circle1 + scaled_circle2;
+
+ // Check for collision (distance squared must be less than the square of the radius sum)
+ if (distance_squared < radius_sum * radius_sum) {
+ vec2 delta = final_position2 - final_position1;
+
+ // Compute the distance between the two circle centers
+ float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y);
+
+ // Compute the combined radii of the two circles
+ float combined_radius = scaled_circle1 + scaled_circle2;
+
+ // Compute the penetration depth
+ float penetration_depth = combined_radius - distance;
+
+ // Normalize the delta vector to get the collision direction
+ vec2 collision_normal = delta / distance;
+
+ // Compute the resolution vector
+ vec2 resolution = -collision_normal * penetration_depth;
+
+ return resolution;
+ }
+ // No collision
+ return vec2 {NAN, NAN};
+ ;
+}
+
+CollisionSystem::Direction
+CollisionSystem::resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody) {
+
+ // Calculate the other value to move back correctly
+ // If only X or Y has a value determine what is should be to move back.
+ Direction resolution_direction = Direction::NONE;
+ // If both are not zero a perfect corner has been hit
+ if (resolution.x != 0 && resolution.y != 0) {
+ resolution_direction = Direction::BOTH;
+ // If x is not zero a horizontal action was latest action.
+ } else if (resolution.x != 0) {
+ resolution_direction = Direction::X_DIRECTION;
+ // If both are 0 resolution y should not be changed (y_velocity can be 0 by kinematic object movement)
+ if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0)
+ resolution.y
+ = -rigidbody.linear_velocity.y * (resolution.x / rigidbody.linear_velocity.x);
+ } else if (resolution.y != 0) {
+ resolution_direction = Direction::Y_DIRECTION;
+ // If both are 0 resolution x should not be changed (x_velocity can be 0 by kinematic object movement)
+ if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0)
+ resolution.x
+ = -rigidbody.linear_velocity.x * (resolution.y / rigidbody.linear_velocity.y);
+ }
+
+ return resolution_direction;
+}
+
+CollisionSystem::CollisionInfo CollisionSystem::get_collision_info(
+ const CollisionInternal & in_self, const CollisionInternal & in_other
+) const {
+
+ crepe::CollisionSystem::ColliderInfo self {
+ .transform = in_self.info.transform,
+ .rigidbody = in_self.info.rigidbody,
+ .metadata = in_self.info.metadata,
+ };
+
+ crepe::CollisionSystem::ColliderInfo other {
+ .transform = in_other.info.transform,
+ .rigidbody = in_other.info.rigidbody,
+ .metadata = in_other.info.metadata,
+ };
+
+ struct CollisionInfo collision_info {
+ .self = self, .other = other, .resolution = in_self.resolution,
+ .resolution_direction = in_self.resolution_direction,
+ };
+ return collision_info;
+}
+
+void CollisionSystem::determine_collision_handler(const CollisionInfo & info) {
+ Rigidbody::BodyType self_type = info.self.rigidbody.data.body_type;
+ Rigidbody::BodyType other_type = info.other.rigidbody.data.body_type;
+ bool self_kinematic = info.self.rigidbody.data.kinematic_collision;
+ bool other_kinematic = info.other.rigidbody.data.kinematic_collision;
+ // Inverted collision info
+ CollisionInfo inverted = -info;
+ // If both objects are static skip handle call collision script
+ if (self_type == STATIC && other_type == STATIC) return;
+
+ // First body is not dynamic
+ if (self_type != DYNAMIC) {
+ bool static_collision = self_type == STATIC && other_type == DYNAMIC;
+ bool kinematic_collision
+ = self_type == KINEMATIC && other_type == DYNAMIC && self_kinematic;
+
+ // Handle collision
+ if (static_collision || kinematic_collision) this->static_collision_handler(inverted);
+ // Call scripts
+ this->call_collision_events(inverted);
+ return;
+ }
+
+ // Second body is not dynamic
+ if (other_type != DYNAMIC) {
+ bool static_collision = other_type == STATIC;
+ bool kinematic_collision = other_type == KINEMATIC && other_kinematic;
+ // Handle collision
+ if (static_collision || kinematic_collision) this->static_collision_handler(info);
+ // Call scripts
+ this->call_collision_events(info);
+ return;
+ }
+
+ // Dynamic
+ // Handle collision
+ this->dynamic_collision_handler(info);
+ // Call scripts
+ this->call_collision_events(info);
+}
+
+void CollisionSystem::static_collision_handler(const CollisionInfo & info) {
+
+ vec2 & transform_pos = info.self.transform.position;
+ float elasticity = info.self.rigidbody.data.elasticity_coefficient;
+ vec2 & rigidbody_vel = info.self.rigidbody.data.linear_velocity;
+
+ // Move object back using calculate move back value
+ transform_pos += info.resolution;
+
+ switch (info.resolution_direction) {
+ case Direction::BOTH:
+ //bounce
+ if (elasticity > 0) {
+ rigidbody_vel = -rigidbody_vel * elasticity;
+ }
+ //stop movement
+ else {
+ rigidbody_vel = {0, 0};
+ }
+ break;
+ case Direction::Y_DIRECTION:
+ // Bounce
+ if (elasticity > 0) {
+ rigidbody_vel.y = -rigidbody_vel.y * elasticity;
+ }
+ // Stop movement
+ else {
+ rigidbody_vel.y = 0;
+ transform_pos.x -= info.resolution.x;
+ }
+ break;
+ case Direction::X_DIRECTION:
+ // Bounce
+ if (elasticity > 0) {
+ rigidbody_vel.x = -rigidbody_vel.x * elasticity;
+ }
+ // Stop movement
+ else {
+ rigidbody_vel.x = 0;
+ transform_pos.y -= info.resolution.y;
+ }
+ break;
+ case Direction::NONE:
+ // Not possible
+ break;
+ }
+}
+
+void CollisionSystem::dynamic_collision_handler(const CollisionInfo & info) {
+
+ vec2 & self_transform_pos = info.self.transform.position;
+ vec2 & other_transform_pos = info.other.transform.position;
+ float self_elasticity = info.self.rigidbody.data.elasticity_coefficient;
+ float other_elasticity = info.other.rigidbody.data.elasticity_coefficient;
+ vec2 & self_rigidbody_vel = info.self.rigidbody.data.linear_velocity;
+ vec2 & other_rigidbody_vel = info.other.rigidbody.data.linear_velocity;
+
+ self_transform_pos += info.resolution / 2;
+ other_transform_pos += -(info.resolution / 2);
+
+ switch (info.resolution_direction) {
+ case Direction::BOTH:
+ if (self_elasticity > 0) {
+ self_rigidbody_vel = -self_rigidbody_vel * self_elasticity;
+ } else {
+ self_rigidbody_vel = {0, 0};
+ }
+
+ if (other_elasticity > 0) {
+ other_rigidbody_vel = -other_rigidbody_vel * other_elasticity;
+ } else {
+ other_rigidbody_vel = {0, 0};
+ }
+ break;
+ case Direction::Y_DIRECTION:
+ if (self_elasticity > 0) {
+ self_rigidbody_vel.y = -self_rigidbody_vel.y * self_elasticity;
+ }
+ // Stop movement
+ else {
+ self_rigidbody_vel.y = 0;
+ self_transform_pos.x -= info.resolution.x;
+ }
+
+ if (other_elasticity > 0) {
+ other_rigidbody_vel.y = -other_rigidbody_vel.y * other_elasticity;
+ }
+ // Stop movement
+ else {
+ other_rigidbody_vel.y = 0;
+ other_transform_pos.x -= info.resolution.x;
+ }
+ break;
+ case Direction::X_DIRECTION:
+ if (self_elasticity > 0) {
+ self_rigidbody_vel.x = -self_rigidbody_vel.x * self_elasticity;
+ }
+ // Stop movement
+ else {
+ self_rigidbody_vel.x = 0;
+ self_transform_pos.y -= info.resolution.y;
+ }
+
+ if (other_elasticity > 0) {
+ other_rigidbody_vel.x = -other_rigidbody_vel.x * other_elasticity;
+ }
+ // Stop movement
+ else {
+ other_rigidbody_vel.x = 0;
+ other_transform_pos.y -= info.resolution.y;
+ }
+ break;
+ case Direction::NONE:
+ // Not possible
+ break;
+ }
+}
-void CollisionSystem::update() {}
+void CollisionSystem::call_collision_events(const CollisionInfo & info) {
+ CollisionEvent data(info);
+ CollisionEvent data_inverted(-info);
+ EventManager & emgr = this->mediator.event_manager;
+ emgr.trigger_event<CollisionEvent>(data, info.self.transform.game_object_id);
+ emgr.trigger_event<CollisionEvent>(data_inverted, info.other.transform.game_object_id);
+}
diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h
index c1a70d8..ff2d35f 100644
--- a/src/crepe/system/CollisionSystem.h
+++ b/src/crepe/system/CollisionSystem.h
@@ -1,13 +1,303 @@
#pragma once
+#include <optional>
+#include <variant>
+#include <vector>
+
+#include "api/BoxCollider.h"
+#include "api/CircleCollider.h"
+#include "api/Event.h"
+#include "api/Metadata.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+#include "api/Vector2.h"
+
+#include "Collider.h"
#include "System.h"
namespace crepe {
+//! A system responsible for detecting and handling collisions between colliders.
class CollisionSystem : public System {
public:
using System::System;
- void update() override;
+
+private:
+ //! Enum representing movement directions during collision resolution.
+ enum class Direction {
+ //! No movement required.
+ NONE,
+ //! Movement in the X direction.
+ X_DIRECTION,
+ //! Movement in the Y direction.
+ Y_DIRECTION,
+ //! Movement in both X and Y directions.
+ BOTH,
+ };
+
+public:
+ //! Structure representing components of the collider
+ struct ColliderInfo {
+ Transform & transform;
+ Rigidbody & rigidbody;
+ Metadata & metadata;
+ };
+
+ /**
+ * \brief Structure representing detailed collision information between two colliders.
+ *
+ * Includes information about the colliding objects and the resolution data for handling the collision.
+ */
+ struct CollisionInfo {
+ ColliderInfo self;
+ ColliderInfo other;
+ //! The resolution vector for the collision.
+ vec2 resolution;
+ //! The direction of movement for resolving the collision.
+ Direction resolution_direction = Direction::NONE;
+ CollisionInfo operator-() const;
+ };
+
+private:
+ //! A variant type that can hold either a BoxCollider or a CircleCollider.
+ using collider_variant = std::variant<
+ std::reference_wrapper<BoxCollider>, std::reference_wrapper<CircleCollider>>;
+
+ //! Enum representing the types of collider pairs for collision detection.
+ enum class CollisionInternalType {
+ BOX_BOX,
+ CIRCLE_CIRCLE,
+ BOX_CIRCLE,
+ CIRCLE_BOX,
+ NONE,
+ };
+
+ /**
+ * \brief A structure to store the collision data of a single collider.
+ *
+ * This structure all components and id that are for needed within this system when calculating or handling collisions.
+ * The transform and rigidbody are mostly needed for location and rotation.
+ * In rigidbody additional info is written about what the body of the object is,
+ * and how it should respond on a collision.
+ */
+ struct CollisionInternal {
+ game_object_id_t id = 0;
+ collider_variant collider;
+ ColliderInfo info;
+ vec2 resolution;
+ Direction resolution_direction = Direction::NONE;
+ };
+
+ //! Structure of a collider with additional components
+ template <typename ColliderType>
+ struct ColliderInternal {
+ ColliderType & collider;
+ Transform & transform;
+ Rigidbody & rigidbody;
+ };
+ //! Predefined BoxColliderInternal. (System is only made for this type)
+ using BoxColliderInternal = ColliderInternal<BoxCollider>;
+ //! Predefined CircleColliderInternal. (System is only made for this type)
+ using CircleColliderInternal = ColliderInternal<CircleCollider>;
+
+public:
+ //! Updates the collision system by checking for collisions between colliders and handling them.
+ void fixed_update() override;
+
+private:
+ /**
+ * \brief Determines the type of collider pair from two colliders.
+ *
+ * Uses std::holds_alternative to identify the types of the provided colliders.
+ *
+ * \param collider1 First collider variant (BoxCollider or CircleCollider).
+ * \param collider2 Second collider variant (BoxCollider or CircleCollider).
+ * \return The combined type of the two colliders.
+ */
+ CollisionInternalType get_collider_type(
+ const collider_variant & collider1, const collider_variant & collider2
+ ) const;
+
+private:
+ /**
+ * \brief Converts internal collision data into user-accessible collision information.
+ *
+ * This function processes collision data from two colliding entities and packages it
+ * into a structured format that is accessible for further use,
+ * such as resolving collisions and triggering user-defined collision scripts.
+ *
+ * \param data1 Collision data for the first collider.
+ * \param data2 Collision data for the second collider.
+ */
+ CollisionInfo
+ get_collision_info(const CollisionInternal & data1, const CollisionInternal & data2) const;
+
+ /**
+ * \brief Corrects the collision resolution vector and determines its direction.
+ *
+ * This function adjusts the provided resolution vector based on the
+ * rigidbody's linear velocity to ensure consistent collision correction. If the resolution
+ * vector has only one non-zero component (either x or y), the missing component is computed
+ * based on the rigidbody's velocity. If both components are non-zero, it indicates a corner
+ * collision. The function also identifies the direction of the resolution and returns it.
+ *
+ * \param resolution resolution vector that needs to be corrected
+ * \param rigidbody rigidbody data used to correct resolution
+ * \return A Direction indicating the resolution direction
+ */
+ Direction resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody);
+
+ /**
+ * \brief Determines the appropriate collision handler for a given collision event.
+ *
+ * This function identifies the correct collision resolution process based on the body types
+ * of the colliders involved in the collision. It delegates
+ * collision handling to specific handlers and calls collision event scripts
+ * as needed.
+ *
+ * \param info Collision information containing data about both colliders.
+ */
+ void determine_collision_handler(const CollisionInfo & info);
+
+ /**
+ * \brief Calls both collision script
+ *
+ * Calls both collision script to let user add additonal handling or handle full collision.
+ *
+ * \param info Collision information containing data about both colliders.
+ */
+ void call_collision_events(const CollisionInfo & info);
+
+ /**
+ * \brief Handles collisions involving static objects.
+ *
+ * This function resolves collisions between static and dynamic objects by adjusting
+ * the position of the static object and modifying the velocity of the dynamic object
+ * if elasticity is enabled. The position of the static object is corrected
+ * based on the collision resolution, and the dynamic object's velocity is adjusted
+ * accordingly to reflect the collision response.
+ *
+ * The handling includes stopping movement, applying bouncing based on the elasticity
+ * coefficient, and adjusting the position of the dynamic object if needed.
+ *
+ * \param info Collision information containing data about both colliders.
+ */
+ void static_collision_handler(const CollisionInfo & info);
+
+ /**
+ * \brief Handles collisions involving dynamic objects.
+ *
+ * Resolves collisions between two dynamic objects by adjusting their positions and modifying
+ * their velocities based on the collision resolution. If elasticity is enabled,
+ * the velocity of both objects is reversed and scaled by the respective elasticity coefficient.
+ * The positions of the objects are adjusted based on the collision resolution.
+ *
+ * \param info Collision information containing data about both colliders.
+ */
+ void dynamic_collision_handler(const CollisionInfo & info);
+
+private:
+ /**
+ * \brief Checks for collisions between colliders.
+ *
+ * This function checks all active colliders and identifies pairs of colliding objects.
+ * For each identified collision, the appropriate collision data is returned as pairs for further processing.
+ *
+ * \param colliders A collection of all active colliders.
+ * \return A list of collision pairs with their associated data.
+ */
+ std::vector<std::pair<CollisionInternal, CollisionInternal>>
+ gather_collisions(std::vector<CollisionInternal> & colliders);
+
+ /**
+ * \brief Checks if the settings allow collision
+ *
+ * This function checks if there is any collison layer where each object is located in.
+ * After checking the layers it checks the names and at last the tags.
+ * if in all three sets nothing is found collision can not happen.
+ *
+ * \param this_rigidbody Rigidbody of first object
+ * \param other_rigidbody Rigidbody of second collider
+ * \param this_metadata Rigidbody of first object
+ * \param other_metadata Rigidbody of second object
+ * \return Returns true if there is at least one comparison found.
+ */
+ bool should_collide(
+ const CollisionInternal & self,
+ const CollisionInternal & other
+ ) const; //done
+
+ /**
+ * \brief Checks for collision between two colliders.
+ *
+ * This function determines whether two colliders are colliding based on their types.
+ * It calls the appropriate collision detection function based on the collider pair type and stores the collision resolution data.
+ * If a collision is detected, it returns true, otherwise false.
+ *
+ * \param first_info Collision data for the first collider.
+ * \param second_info Collision data for the second collider.
+ * \param type The type of collider pair.
+ * \return True if a collision is detected, otherwise false.
+ */
+ bool detect_collision(
+ CollisionInternal & first_info, CollisionInternal & second_info,
+ const CollisionInternalType & type
+ );
+
+ /**
+ * \brief Detects collisions between two BoxColliders.
+ *
+ * This function checks whether two `BoxCollider` are colliding based on their positions and scaled dimensions.
+ * If a collision is detected, it calculates the overlap along the X and Y axes and returns the resolution vector.
+ * If no collision is detected, it returns a vector with NaN values.
+
+ * \param box1 Information about the first BoxCollider.
+ * \param box2 Information about the second BoxCollider.
+ * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.
+ */
+ vec2 get_box_box_detection(
+ const BoxColliderInternal & box1, const BoxColliderInternal & box2
+ ) const;
+
+ /**
+ * \brief Check collision for box on circle collider
+ *
+ * This function detects if a collision occurs between a rectangular box and a circular collider.
+ * If a collision is detected, the function calculates the resolution vector to resolve the collision.
+ * If no collision is detected, it returns a vector with NaN values
+ *
+ * \param box1 Information about the BoxCollider.
+ * \param circle2 Information about the circleCollider.
+ * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.
+ */
+ vec2 get_box_circle_detection(
+ const BoxColliderInternal & box1, const CircleColliderInternal & circle2
+ ) const;
+
+ /**
+ * \brief Check collision for circle on circle collider
+ *
+ * This function detects if a collision occurs between two circular colliders.
+ * If a collision is detected, it calculates the resolution vector to resolve the collision.
+ * If no collision is detected, it returns a vector with NaN values.
+ *
+ * \param circle1 Information about the first circleCollider.
+ * \param circle2 Information about the second circleCollider.
+ * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.
+ */
+ vec2 get_circle_circle_detection(
+ const CircleColliderInternal & circle1, const CircleColliderInternal & circle2
+ ) const;
+};
+
+/**
+ * \brief Event triggered during a collision between objects.
+ */
+class CollisionEvent : public Event {
+public:
+ crepe::CollisionSystem::CollisionInfo info;
+ CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo)
+ : info(collisionInfo) {}
};
} // namespace crepe
diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp
new file mode 100644
index 0000000..7e168ab
--- /dev/null
+++ b/src/crepe/system/EventSystem.cpp
@@ -0,0 +1,9 @@
+#include "EventSystem.h"
+#include "../manager/EventManager.h"
+
+using namespace crepe;
+
+void EventSystem::fixed_update() {
+ EventManager & ev = this->mediator.event_manager;
+ ev.dispatch_events();
+}
diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h
new file mode 100644
index 0000000..0ae48d2
--- /dev/null
+++ b/src/crepe/system/EventSystem.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "System.h"
+
+namespace crepe {
+
+/**
+ * \brief EventManager dispatch helper system
+ */
+class EventSystem : public System {
+public:
+ using System::System;
+
+ /**
+ * \brief Dispatch queued events
+ * \see EventManager::dispatch_events
+ */
+ void fixed_update() override;
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp
new file mode 100644
index 0000000..be7eda6
--- /dev/null
+++ b/src/crepe/system/InputSystem.cpp
@@ -0,0 +1,225 @@
+#include "../api/Button.h"
+#include "../api/Config.h"
+#include "../facade/SDLContext.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/EventManager.h"
+
+#include "InputSystem.h"
+
+using namespace crepe;
+
+void InputSystem::fixed_update() {
+ ComponentManager & mgr = this->mediator.component_manager;
+ SDLContext & context = this->mediator.sdl_context;
+ std::vector<EventData> event_list = context.get_events();
+ const RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();
+ OptionalRef<Camera> curr_cam_ref;
+
+ // Find the active camera
+ for (Camera & cam : cameras) {
+ if (!cam.active) continue;
+ curr_cam_ref = cam;
+ break;
+ }
+ if (!curr_cam_ref) return;
+
+ Camera & current_cam = curr_cam_ref;
+ const Transform & cam_transform
+ = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front();
+
+ vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset
+ - (current_cam.viewport_size / 2);
+
+ for (const EventData & event : event_list) {
+ // Only calculate mouse coordinates for relevant events
+ if (event.event_type == EventType::MOUSE_DOWN
+ || event.event_type == EventType::MOUSE_UP
+ || event.event_type == EventType::MOUSE_MOVE
+ || event.event_type == EventType::MOUSE_WHEEL) {
+ this->handle_mouse_event(event, camera_origin, current_cam);
+
+ } else {
+ this->handle_non_mouse_event(event);
+ }
+ }
+}
+
+void InputSystem::handle_mouse_event(
+ const EventData & event, const vec2 & camera_origin, const Camera & current_cam
+) {
+ EventManager & event_mgr = this->mediator.event_manager;
+ vec2 adjusted_mouse;
+ adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x;
+ adjusted_mouse.y = event.data.mouse_data.mouse_position.y + camera_origin.y;
+ // Check if the mouse is within the viewport
+ if ((adjusted_mouse.x < camera_origin.x
+ || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x
+ || adjusted_mouse.y < camera_origin.y
+ || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y))
+ return;
+
+ // Handle mouse-specific events
+ switch (event.event_type) {
+ case EventType::MOUSE_DOWN:
+ event_mgr.queue_event<MousePressEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
+ });
+ this->last_mouse_down_position = adjusted_mouse;
+ this->last_mouse_button = event.data.mouse_data.mouse_button;
+ break;
+
+ case EventType::MOUSE_UP: {
+ event_mgr.queue_event<MouseReleaseEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
+ });
+ vec2 delta_move = adjusted_mouse - this->last_mouse_down_position;
+ int click_tolerance = Config::get_instance().input.click_tolerance;
+ if (this->last_mouse_button == event.data.mouse_data.mouse_button
+ && std::abs(delta_move.x) <= click_tolerance
+ && std::abs(delta_move.y) <= click_tolerance) {
+ event_mgr.queue_event<MouseClickEvent>({
+ .mouse_pos = adjusted_mouse,
+ .button = event.data.mouse_data.mouse_button,
+ });
+ this->handle_click(
+ event.data.mouse_data.mouse_button, adjusted_mouse, current_cam
+ );
+ }
+ break;
+ }
+
+ case EventType::MOUSE_MOVE:
+ event_mgr.queue_event<MouseMoveEvent>({
+ .mouse_pos = adjusted_mouse,
+ .mouse_delta = event.data.mouse_data.rel_mouse_move,
+ });
+ this->handle_move(event, adjusted_mouse, current_cam);
+ break;
+
+ case EventType::MOUSE_WHEEL:
+ event_mgr.queue_event<MouseScrollEvent>({
+ .mouse_pos = adjusted_mouse,
+ .scroll_direction = event.data.mouse_data.scroll_direction,
+ .scroll_delta = event.data.mouse_data.scroll_delta,
+ });
+ break;
+
+ default:
+ break;
+ }
+}
+
+void InputSystem::handle_non_mouse_event(const EventData & event) {
+ EventManager & event_mgr = this->mediator.event_manager;
+ switch (event.event_type) {
+ case EventType::KEY_DOWN:
+
+ event_mgr.queue_event<KeyPressEvent>(
+ {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key}
+ );
+ break;
+ case EventType::KEY_UP:
+ event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key});
+ break;
+ case EventType::SHUTDOWN:
+ event_mgr.queue_event<ShutDownEvent>({});
+ break;
+ case EventType::WINDOW_EXPOSE:
+ event_mgr.queue_event<WindowExposeEvent>({});
+ break;
+ case EventType::WINDOW_RESIZE:
+ event_mgr.queue_event<WindowResizeEvent>(
+ WindowResizeEvent {.dimensions = event.data.window_data.resize_dimension}
+ );
+ break;
+ case EventType::WINDOW_MOVE:
+ event_mgr.queue_event<WindowMoveEvent>(
+ {.delta_move = event.data.window_data.move_delta}
+ );
+ break;
+ case EventType::WINDOW_MINIMIZE:
+ event_mgr.queue_event<WindowMinimizeEvent>({});
+ break;
+ case EventType::WINDOW_MAXIMIZE:
+ event_mgr.queue_event<WindowMaximizeEvent>({});
+ break;
+ case EventType::WINDOW_FOCUS_GAIN:
+ event_mgr.queue_event<WindowFocusGainEvent>({});
+ break;
+ case EventType::WINDOW_FOCUS_LOST:
+ event_mgr.queue_event<WindowFocusLostEvent>({});
+ break;
+ default:
+ break;
+ }
+}
+
+void InputSystem::handle_move(
+ const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam
+) {
+ ComponentManager & mgr = this->mediator.component_manager;
+ EventManager & event_mgr = this->mediator.event_manager;
+ const RefVector<Button> buttons = mgr.get_components_by_type<Button>();
+
+ for (Button & button : buttons) {
+ if (!button.active) continue;
+
+ const Transform & transform
+ = mgr.get_components_by_id<Transform>(button.game_object_id).front();
+ const Transform & cam_transform
+ = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front();
+ const Metadata & metadata
+ = mgr.get_components_by_id<Metadata>(button.game_object_id).front();
+ bool was_hovering = button.hover;
+
+ if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) {
+ button.hover = true;
+ if (!was_hovering) {
+ event_mgr.trigger_event<ButtonEnterEvent>(metadata, metadata.game_object_id);
+ }
+ } else {
+ button.hover = false;
+ if (was_hovering) {
+ event_mgr.trigger_event<ButtonExitEvent>(metadata, metadata.game_object_id);
+ }
+ }
+ }
+}
+
+void InputSystem::handle_click(
+ const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam
+) {
+ ComponentManager & mgr = this->mediator.component_manager;
+ EventManager & event_mgr = this->mediator.event_manager;
+ const RefVector<Button> buttons = mgr.get_components_by_type<Button>();
+ const Transform & cam_transform
+ = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front();
+ for (Button & button : buttons) {
+ if (!button.active) continue;
+ const Metadata & metadata
+ = mgr.get_components_by_id<Metadata>(button.game_object_id).front();
+ const Transform & transform
+ = mgr.get_components_by_id<Transform>(button.game_object_id).front();
+ if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) {
+ event_mgr.trigger_event<ButtonPressEvent>(metadata, metadata.game_object_id);
+ }
+ }
+}
+
+bool InputSystem::is_mouse_inside_button(
+ const vec2 & mouse_pos, const Button & button, const Transform & transform,
+ const Transform & cam_transform
+) {
+ vec2 actual_pos = transform.position + button.offset;
+ if (!button.data.world_space) {
+ actual_pos += cam_transform.position;
+ }
+ vec2 half_dimensions = button.dimensions * transform.scale / 2;
+
+ return mouse_pos.x >= actual_pos.x - half_dimensions.x
+ && mouse_pos.x <= actual_pos.x + half_dimensions.x
+ && mouse_pos.y >= actual_pos.y - half_dimensions.y
+ && mouse_pos.y <= actual_pos.y + half_dimensions.y;
+}
diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h
new file mode 100644
index 0000000..be62367
--- /dev/null
+++ b/src/crepe/system/InputSystem.h
@@ -0,0 +1,139 @@
+#pragma once
+
+#include "../api/Event.h"
+#include "../api/Metadata.h"
+#include "../facade/EventData.h"
+#include "../types.h"
+
+#include "System.h"
+
+namespace crepe {
+
+class Camera;
+class Button;
+class Transform;
+//! Event triggered when a button is clicked
+class ButtonPressEvent : public Event {
+public:
+ //! Metadata of the button.
+ const Metadata & metadata;
+ /**
+ * \param metadata Metadata of the button pressed
+ */
+ ButtonPressEvent(const Metadata & metadata) : metadata(metadata) {};
+};
+//! Event triggered when the mouse enters a button
+class ButtonEnterEvent : public Event {
+public:
+ //! Metadata of the button.
+ const Metadata & metadata;
+ /**
+ * \param metadata Metadata of the button pressed
+ */
+ ButtonEnterEvent(const Metadata & metadata) : metadata(metadata) {};
+};
+//! Event triggered when the mouse leaves a button
+class ButtonExitEvent : public Event {
+public:
+ //! Metadata of the button.
+ const Metadata & metadata;
+ /**
+ * \param metadata Metadata of the button pressed
+ */
+ ButtonExitEvent(const Metadata & metadata) : metadata(metadata) {};
+};
+
+/**
+ * \brief Handles the processing of input events created by SDLContext
+ *
+ * This system processes events such as mouse clicks, mouse movement, and keyboard
+ * actions. It is responsible for detecting interactions with UI buttons and
+ * passing the corresponding events to the registered listeners.
+ */
+class InputSystem : public System {
+public:
+ using System::System;
+
+ /**
+ * \brief Updates the system, processing all input events.
+ * This method processes all events and triggers corresponding actions.
+ */
+ void fixed_update() override;
+
+private:
+ //! Stores the last position of the mouse when the button was pressed.
+ vec2 last_mouse_down_position;
+ // TODO: specify world/hud space and make regular `vec2`
+
+ //! Stores the last mouse button pressed.
+ MouseButton last_mouse_button = MouseButton::NONE;
+ /**
+ * \brief Handles mouse-related events.
+ * \param event The event data for the mouse event.
+ * \param camera_origin The origin position of the camera in world space.
+ * \param current_cam The currently active camera.
+ *
+ * This method processes mouse events, adjusts the mouse position to world coordinates,
+ * and triggers the appropriate mouse-specific event handling logic.
+ */
+ void handle_mouse_event(
+ const EventData & event, const vec2 & camera_origin, const Camera & current_cam
+ );
+ /**
+ * \brief Handles non-mouse-related events.
+ * \param event The event data for the non-mouse event.
+ *
+ * This method processes events that do not involve the mouse, such as keyboard events,
+ * window events, and shutdown events, and triggers the corresponding event actions.
+ */
+ void handle_non_mouse_event(const EventData & event);
+ /**
+ * \brief Handles the mouse click event.
+ * \param mouse_button The mouse button involved in the click.
+ * \param world_mouse_x The X coordinate of the mouse in world space.
+ * \param world_mouse_y The Y coordinate of the mouse in world space.
+ * \param current_cam The current active camera.
+ *
+ * This method processes the mouse click event and triggers the corresponding button action.
+ */
+ void handle_click(
+ const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam
+ );
+
+ /**
+ * \brief Handles the mouse movement event.
+ * \param event_data The event data containing information about the mouse movement.
+ * \param world_mouse_x The X coordinate of the mouse in world space.
+ * \param world_mouse_y The Y coordinate of the mouse in world space.
+ * \param current_cam The current active camera.
+ *
+ * This method processes the mouse movement event and updates the button hover state.
+ */
+ void handle_move(
+ const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam
+ );
+
+ /**
+ * \brief Checks if the mouse position is inside the bounds of the button.
+ * \param world_mouse_x The X coordinate of the mouse in world space.
+ * \param world_mouse_y The Y coordinate of the mouse in world space.
+ * \param button The button to check.
+ * \param transform The transform component of the button.
+ * \param cam_transform the transform of the current active camera
+ * \return True if the mouse is inside the button, false otherwise.
+ */
+ bool is_mouse_inside_button(
+ const vec2 & mouse_pos, const Button & button, const Transform & transform,
+ const Transform & cam_transform
+ );
+
+ /**
+ * \brief Handles the button press event, calling the on_click callback if necessary.
+ * \param button The button being pressed.
+ *
+ * This method triggers the on_click action for the button when it is pressed.
+ */
+ void handle_button_press(Button & button);
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp
index fcf7522..f026390 100644
--- a/src/crepe/system/ParticleSystem.cpp
+++ b/src/crepe/system/ParticleSystem.cpp
@@ -1,19 +1,25 @@
+#include <chrono>
#include <cmath>
#include <cstdlib>
#include <ctime>
-#include "api/ParticleEmitter.h"
-#include "api/Transform.h"
-#include "api/Vector2.h"
+#include "../api/ParticleEmitter.h"
+#include "../api/Transform.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/LoopTimerManager.h"
+#include "util/AbsolutePosition.h"
-#include "ComponentManager.h"
#include "ParticleSystem.h"
using namespace crepe;
-void ParticleSystem::update() {
+void ParticleSystem::fixed_update() {
// Get all emitters
- ComponentManager & mgr = this->component_manager;
+ const Mediator & mediator = this->mediator;
+ LoopTimerManager & loop_timer = mediator.loop_timer;
+ ComponentManager & mgr = mediator.component_manager;
+ float dt = loop_timer.get_scaled_fixed_delta_time().count();
+
RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();
for (ParticleEmitter & emitter : emitters) {
@@ -22,108 +28,99 @@ void ParticleSystem::update() {
= mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get();
// Emit particles based on emission_rate
- int updates = calculate_update(this->update_count, emitter.data.emission_rate);
- for (size_t i = 0; i < updates; i++) {
- emit_particle(emitter, transform);
+ emitter.spawn_accumulator += emitter.data.emission_rate * dt;
+ while (emitter.spawn_accumulator >= 1.0) {
+ this->emit_particle(emitter, transform);
+ emitter.spawn_accumulator -= 1.0;
}
// Update all particles
- for (Particle & particle : emitter.data.particles) {
+ for (Particle & particle : emitter.particles) {
if (particle.active) {
- particle.update();
+ particle.update(dt);
}
}
// Check if within boundary
- check_bounds(emitter, transform);
+ this->check_bounds(emitter, transform);
}
-
- this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT;
}
void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) {
- constexpr double DEG_TO_RAD = M_PI / 180.0;
+ constexpr float DEG_TO_RAD = M_PI / 180.0;
- Vector2 initial_position = emitter.data.position + transform.position;
- double random_angle
- = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle);
+ vec2 initial_position = AbsolutePosition::get_position(transform, emitter.data.offset);
+ float random_angle = this->generate_random_angle(
+ emitter.data.min_angle + transform.rotation,
+ emitter.data.max_angle + transform.rotation
+ );
- double random_speed
- = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed);
- double angle_radians = random_angle * DEG_TO_RAD;
+ float random_speed
+ = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed);
+ float angle_radians = random_angle * DEG_TO_RAD;
- Vector2 velocity
+ vec2 velocity
= {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)};
- for (Particle & particle : emitter.data.particles) {
+ for (Particle & particle : emitter.particles) {
if (!particle.active) {
- particle.reset(emitter.data.end_lifespan, initial_position, velocity,
- random_angle);
+ particle.reset(
+ emitter.data.end_lifespan, initial_position, velocity, random_angle
+ );
break;
}
}
}
-int ParticleSystem::calculate_update(int count, double emission) const {
- double integer_part = std::floor(emission);
- double fractional_part = emission - integer_part;
-
- if (fractional_part > 0) {
- int denominator = static_cast<int>(1.0 / fractional_part);
- return (count % denominator == 0) ? 1 : 0;
- }
-
- return static_cast<int>(emission);
-}
-
void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) {
- Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position;
- double half_width = emitter.data.boundary.width / 2.0;
- double half_height = emitter.data.boundary.height / 2.0;
-
- const double LEFT = offset.x - half_width;
- const double RIGHT = offset.x + half_width;
- const double TOP = offset.y - half_height;
- const double BOTTOM = offset.y + half_height;
-
- for (Particle & particle : emitter.data.particles) {
- const Vector2 & position = particle.position;
- bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP
- && position.y <= BOTTOM);
-
+ vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.offset;
+ float half_width = emitter.data.boundary.width / 2.0;
+ float half_height = emitter.data.boundary.height / 2.0;
+
+ float left = offset.x - half_width;
+ float right = offset.x + half_width;
+ float top = offset.y - half_height;
+ float bottom = offset.y + half_height;
+
+ for (Particle & particle : emitter.particles) {
+ const vec2 & position = particle.position;
+ bool within_bounds
+ = (position.x >= left && position.x <= right && position.y >= top
+ && position.y <= bottom);
+ //if not within bounds do a reset or stop velocity
if (!within_bounds) {
if (emitter.data.boundary.reset_on_exit) {
particle.active = false;
} else {
particle.velocity = {0, 0};
- if (position.x < LEFT) particle.position.x = LEFT;
- else if (position.x > RIGHT) particle.position.x = RIGHT;
- if (position.y < TOP) particle.position.y = TOP;
- else if (position.y > BOTTOM) particle.position.y = BOTTOM;
+ if (position.x < left) particle.position.x = left;
+ else if (position.x > right) particle.position.x = right;
+ if (position.y < top) particle.position.y = top;
+ else if (position.y > bottom) particle.position.y = bottom;
}
}
}
}
-double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const {
+float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const {
if (min_angle == max_angle) {
return min_angle;
} else if (min_angle < max_angle) {
return min_angle
- + static_cast<double>(std::rand() % static_cast<int>(max_angle - min_angle));
+ + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle));
} else {
- double angle_offset = (360 - min_angle) + max_angle;
- double random_angle
- = min_angle + static_cast<double>(std::rand() % static_cast<int>(angle_offset));
+ float angle_offset = (360 - min_angle) + max_angle;
+ float random_angle
+ = min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset));
return (random_angle >= 360) ? random_angle - 360 : random_angle;
}
}
-double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const {
+float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const {
if (min_speed == max_speed) {
return min_speed;
} else {
return min_speed
- + static_cast<double>(std::rand() % static_cast<int>(max_speed - min_speed));
+ + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed));
}
}
diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h
index c647284..4296ff3 100644
--- a/src/crepe/system/ParticleSystem.h
+++ b/src/crepe/system/ParticleSystem.h
@@ -20,31 +20,21 @@ public:
* \brief Updates all particle emitters by emitting particles, updating particle states, and
* checking bounds.
*/
- void update() override;
+ void fixed_update() override;
private:
/**
* \brief Emits a particle from the specified emitter based on its emission properties.
- *
+ *
* \param emitter Reference to the ParticleEmitter.
* \param transform Const reference to the Transform component associated with the emitter.
*/
void emit_particle(ParticleEmitter & emitter, const Transform & transform);
/**
- * \brief Calculates the number of times particles should be emitted based on emission rate
- * and update count.
- *
- * \param count Current update count.
- * \param emission Emission rate.
- * \return The number of particles to emit.
- */
- int calculate_update(int count, double emission) const;
-
- /**
* \brief Checks whether particles are within the emitter’s boundary, resets or stops
* particles if they exit.
- *
+ *
* \param emitter Reference to the ParticleEmitter.
* \param transform Const reference to the Transform component associated with the emitter.
*/
@@ -52,29 +42,21 @@ private:
/**
* \brief Generates a random angle for particle emission within the specified range.
- *
+ *
* \param min_angle Minimum emission angle in degrees.
* \param max_angle Maximum emission angle in degrees.
* \return Random angle in degrees.
*/
- double generate_random_angle(double min_angle, double max_angle) const;
+ float generate_random_angle(float min_angle, float max_angle) const;
/**
* \brief Generates a random speed for particle emission within the specified range.
- *
+ *
* \param min_speed Minimum emission speed.
* \param max_speed Maximum emission speed.
* \return Random speed.
*/
- double generate_random_speed(double min_speed, double max_speed) const;
-
-private:
- //! Counter to count updates to determine how many times emit_particle is
- // called.
- unsigned int update_count = 0;
- //! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000
- // updates).
- static constexpr unsigned int MAX_UPDATE_COUNT = 100;
+ float generate_random_speed(float min_speed, float max_speed) const;
};
} // namespace crepe
diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp
index bcde431..62f8132 100644
--- a/src/crepe/system/PhysicsSystem.cpp
+++ b/src/crepe/system/PhysicsSystem.cpp
@@ -1,89 +1,98 @@
#include <cmath>
-#include "../ComponentManager.h"
#include "../api/Config.h"
#include "../api/Rigidbody.h"
#include "../api/Transform.h"
#include "../api/Vector2.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/LoopTimerManager.h"
+#include "../manager/Mediator.h"
#include "PhysicsSystem.h"
using namespace crepe;
-void PhysicsSystem::update() {
- ComponentManager & mgr = this->component_manager;
+void PhysicsSystem::fixed_update() {
+ const Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+ LoopTimerManager & loop_timer = mediator.loop_timer;
RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>();
- RefVector<Transform> transforms = mgr.get_components_by_type<Transform>();
+ float dt = loop_timer.get_scaled_fixed_delta_time().count();
- double gravity = Config::get_instance().physics.gravity;
+ float gravity = Config::get_instance().physics.gravity;
for (Rigidbody & rigidbody : rigidbodies) {
if (!rigidbody.active) continue;
+ Transform & transform
+ = mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get();
switch (rigidbody.data.body_type) {
case Rigidbody::BodyType::DYNAMIC:
- for (Transform & transform : transforms) {
- if (transform.game_object_id == rigidbody.game_object_id) {
+ if (transform.game_object_id == rigidbody.game_object_id) {
+ // Add gravity
- // Add gravity
- if (rigidbody.data.use_gravity) {
- rigidbody.data.linear_velocity.y
- += (rigidbody.data.mass * rigidbody.data.gravity_scale
- * gravity);
- }
- // Add damping
- if (rigidbody.data.angular_damping != 0) {
- rigidbody.data.angular_velocity *= rigidbody.data.angular_damping;
- }
- if (rigidbody.data.linear_damping != Vector2{0, 0}) {
- rigidbody.data.linear_velocity *= rigidbody.data.linear_damping;
- }
+ if (rigidbody.data.mass <= 0) {
+ throw std::runtime_error("Mass must be greater than 0");
+ }
- // Max velocity check
- if (rigidbody.data.angular_velocity
- > rigidbody.data.max_angular_velocity) {
- rigidbody.data.angular_velocity
- = rigidbody.data.max_angular_velocity;
- } else if (rigidbody.data.angular_velocity
- < -rigidbody.data.max_angular_velocity) {
- rigidbody.data.angular_velocity
- = -rigidbody.data.max_angular_velocity;
- }
+ if (gravity <= 0) {
+ throw std::runtime_error("Config Gravity must be greater than 0");
+ }
- if (rigidbody.data.linear_velocity.x
- > rigidbody.data.max_linear_velocity.x) {
- rigidbody.data.linear_velocity.x
- = rigidbody.data.max_linear_velocity.x;
- } else if (rigidbody.data.linear_velocity.x
- < -rigidbody.data.max_linear_velocity.x) {
- rigidbody.data.linear_velocity.x
- = -rigidbody.data.max_linear_velocity.x;
- }
+ if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) {
+ rigidbody.data.linear_velocity.y
+ += (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity
+ * dt);
+ }
+ // Add coefficient rotation
+ if (rigidbody.data.angular_velocity_coefficient > 0) {
+ rigidbody.data.angular_velocity
+ *= std::pow(rigidbody.data.angular_velocity_coefficient, dt);
+ }
- if (rigidbody.data.linear_velocity.y
- > rigidbody.data.max_linear_velocity.y) {
- rigidbody.data.linear_velocity.y
- = rigidbody.data.max_linear_velocity.y;
- } else if (rigidbody.data.linear_velocity.y
- < -rigidbody.data.max_linear_velocity.y) {
- rigidbody.data.linear_velocity.y
- = -rigidbody.data.max_linear_velocity.y;
- }
+ // Add coefficient movement horizontal
+ if (rigidbody.data.linear_velocity_coefficient.x > 0
+ && !rigidbody.data.constraints.x) {
+ rigidbody.data.linear_velocity.x
+ *= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt);
+ }
- // Move object
- if (!rigidbody.data.constraints.rotation) {
- transform.rotation += rigidbody.data.angular_velocity;
- transform.rotation = std::fmod(transform.rotation, 360.0);
- if (transform.rotation < 0) {
- transform.rotation += 360.0;
- }
- }
- if (!rigidbody.data.constraints.x) {
- transform.position.x += rigidbody.data.linear_velocity.x;
- }
- if (!rigidbody.data.constraints.y) {
- transform.position.y += rigidbody.data.linear_velocity.y;
+ // Add coefficient movement horizontal
+ if (rigidbody.data.linear_velocity_coefficient.y > 0
+ && !rigidbody.data.constraints.y) {
+ rigidbody.data.linear_velocity.y
+ *= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt);
+ }
+
+ // Max velocity check
+ if (rigidbody.data.angular_velocity
+ > rigidbody.data.max_angular_velocity) {
+ rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity;
+ } else if (rigidbody.data.angular_velocity
+ < -rigidbody.data.max_angular_velocity) {
+ rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity;
+ }
+
+ // Set max velocity to maximum length
+ if (rigidbody.data.linear_velocity.length()
+ > rigidbody.data.max_linear_velocity) {
+ rigidbody.data.linear_velocity.normalize();
+ rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity;
+ }
+
+ // Move object
+ if (!rigidbody.data.constraints.rotation) {
+ transform.rotation += rigidbody.data.angular_velocity * dt;
+ transform.rotation = std::fmod(transform.rotation, 360.0);
+ if (transform.rotation < 0) {
+ transform.rotation += 360.0;
}
}
+ if (!rigidbody.data.constraints.x) {
+ transform.position.x += rigidbody.data.linear_velocity.x * dt;
+ }
+ if (!rigidbody.data.constraints.y) {
+ transform.position.y += rigidbody.data.linear_velocity.y * dt;
+ }
}
break;
case Rigidbody::BodyType::KINEMATIC:
diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h
index 227ab69..5ed624f 100644
--- a/src/crepe/system/PhysicsSystem.h
+++ b/src/crepe/system/PhysicsSystem.h
@@ -6,7 +6,7 @@ namespace crepe {
/**
* \brief System that controls all physics
- *
+ *
* This class is a physics system that uses a rigidbody and transform to add physics to a game
* object.
*/
@@ -15,10 +15,10 @@ public:
using System::System;
/**
* \brief updates the physics system.
- *
+ *
* It calculates new velocties and changes the postion in the transform.
*/
- void update() override;
+ void fixed_update() override;
};
} // namespace crepe
diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp
index ad510f5..30bb422 100644
--- a/src/crepe/system/RenderSystem.cpp
+++ b/src/crepe/system/RenderSystem.cpp
@@ -2,42 +2,63 @@
#include <cassert>
#include <cmath>
#include <functional>
-#include <iostream>
+#include <optional>
#include <stdexcept>
#include <vector>
-#include "../ComponentManager.h"
+#include "../api/Camera.h"
#include "../api/ParticleEmitter.h"
#include "../api/Sprite.h"
+#include "../api/Text.h"
#include "../api/Transform.h"
-#include "../api/Vector2.h"
+#include "../facade/Font.h"
#include "../facade/SDLContext.h"
+#include "../facade/Texture.h"
+#include "../manager/ComponentManager.h"
+#include "../manager/ResourceManager.h"
+#include "api/Text.h"
+#include "facade/Font.h"
+#include "util/AbsolutePosition.h"
#include "RenderSystem.h"
+#include "types.h"
using namespace crepe;
using namespace std;
-void RenderSystem::clear_screen() { this->context.clear_screen(); }
+void RenderSystem::clear_screen() {
+ SDLContext & ctx = this->mediator.sdl_context;
+ ctx.clear_screen();
+}
-void RenderSystem::present_screen() { this->context.present_screen(); }
-void RenderSystem::update_camera() {
- ComponentManager & mgr = this->component_manager;
+void RenderSystem::present_screen() {
+ SDLContext & ctx = this->mediator.sdl_context;
+ ctx.present_screen();
+}
+void RenderSystem::update_camera() {
+ ComponentManager & mgr = this->mediator.component_manager;
+ SDLContext & ctx = this->mediator.sdl_context;
RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();
if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene");
for (Camera & cam : cameras) {
if (!cam.active) continue;
- this->context.set_camera(cam);
- this->curr_cam_ref = &cam;
+ const Transform & transform
+ = mgr.get_components_by_id<Transform>(cam.game_object_id).front().get();
+ vec2 new_camera_pos = transform.position + cam.data.postion_offset;
+ ctx.update_camera_view(cam, new_camera_pos);
+ return;
}
+ throw std::runtime_error("No active cameras in current scene");
}
bool sorting_comparison(const Sprite & a, const Sprite & b) {
- if (a.sorting_in_layer < b.sorting_in_layer) return true;
- if (a.sorting_in_layer == b.sorting_in_layer) return a.order_in_layer < b.order_in_layer;
+ if (a.data.sorting_in_layer != b.data.sorting_in_layer)
+ return a.data.sorting_in_layer < b.data.sorting_in_layer;
+ if (a.data.order_in_layer != b.data.order_in_layer)
+ return a.data.order_in_layer < b.data.order_in_layer;
return false;
}
@@ -49,16 +70,41 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const {
return sorted_objs;
}
-void RenderSystem::update() {
+void RenderSystem::frame_update() {
this->clear_screen();
- this->update_camera();
this->render();
+ this->render_text();
this->present_screen();
}
-bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) {
+void RenderSystem::render_text() {
+ SDLContext & ctx = this->mediator.sdl_context;
+ ComponentManager & mgr = this->mediator.component_manager;
+ ResourceManager & resource_manager = this->mediator.resource_manager;
+
+ RefVector<Text> texts = mgr.get_components_by_type<Text>();
+
+ for (Text & text : texts) {
+ if (!text.active) continue;
+ if (!text.font.has_value())
+ text.font.emplace(ctx.get_font_from_name(text.font_family));
+
+ const Font & font = resource_manager.get<Font>(text.font.value());
+ const auto & transform
+ = mgr.get_components_by_id<Transform>(text.game_object_id).front().get();
+ ctx.draw_text(SDLContext::RenderText {
+ .text = text,
+ .font = font,
+ .transform = transform,
+ });
+ }
+}
- ComponentManager & mgr = this->component_manager;
+bool RenderSystem::render_particle(const Sprite & sprite, const Transform & transform) {
+ ComponentManager & mgr = this->mediator.component_manager;
+ SDLContext & ctx = this->mediator.sdl_context;
+ ResourceManager & resource_manager = this->mediator.resource_manager;
+ Texture & res = resource_manager.get<Texture>(sprite.source);
vector<reference_wrapper<ParticleEmitter>> emitters
= mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id);
@@ -66,34 +112,54 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale)
bool rendering_particles = false;
for (const ParticleEmitter & em : emitters) {
- if (!(&em.data.sprite == &sprite)) continue;
+ if (&em.sprite != &sprite) continue;
rendering_particles = true;
if (!em.active) continue;
- for (const Particle & p : em.data.particles) {
+ for (const Particle & p : em.particles) {
if (!p.active) continue;
- this->context.draw_particle(sprite, p.position, p.angle, scale,
- *this->curr_cam_ref);
+ if (p.time_in_life < em.data.begin_lifespan) continue;
+
+ ctx.draw(SDLContext::RenderContext {
+ .sprite = sprite,
+ .texture = res,
+ .pos = p.position,
+ .angle = p.angle + transform.rotation,
+ .scale = transform.scale,
+ });
}
}
return rendering_particles;
}
-void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) {
- this->context.draw(sprite, tm, *this->curr_cam_ref);
+void RenderSystem::render_normal(const Sprite & sprite, const Transform & transform) {
+ SDLContext & ctx = this->mediator.sdl_context;
+ ResourceManager & resource_manager = this->mediator.resource_manager;
+ const Texture & res = resource_manager.get<Texture>(sprite.source);
+ vec2 pos = AbsolutePosition::get_position(transform, sprite.data.position_offset);
+ ctx.draw(SDLContext::RenderContext {
+ .sprite = sprite,
+ .texture = res,
+ .pos = pos,
+ .angle = transform.rotation,
+ .scale = transform.scale,
+ });
}
void RenderSystem::render() {
+ ComponentManager & mgr = this->mediator.component_manager;
+ this->update_camera();
- ComponentManager & mgr = this->component_manager;
RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>();
+ ResourceManager & resource_manager = this->mediator.resource_manager;
RefVector<Sprite> sorted_sprites = this->sort(sprites);
+ RefVector<Text> text_components = mgr.get_components_by_type<Text>();
for (const Sprite & sprite : sorted_sprites) {
if (!sprite.active) continue;
const Transform & transform
= mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get();
- bool rendered_particles = this->render_particle(sprite, transform.scale);
+ bool rendered_particles = this->render_particle(sprite, transform);
if (rendered_particles) continue;
diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h
index 30b41cf..627a743 100644
--- a/src/crepe/system/RenderSystem.h
+++ b/src/crepe/system/RenderSystem.h
@@ -1,24 +1,21 @@
#pragma once
-#include <functional>
-#include <vector>
-
-#include "facade/SDLContext.h"
+#include <cmath>
#include "System.h"
-#include <cmath>
+#include "types.h"
namespace crepe {
class Camera;
class Sprite;
-
+class Transform;
+class Text;
/**
- * \class RenderSystem
* \brief Manages rendering operations for all game objects.
*
* RenderSystem is responsible for rendering, clearing and presenting the screen, and
- * managing the active camera.
+ * managing the active camera.
*/
class RenderSystem : public System {
public:
@@ -27,7 +24,7 @@ public:
* \brief Updates the RenderSystem for the current frame.
* This method is called to perform all rendering operations for the current game frame.
*/
- void update() override;
+ void frame_update() override;
private:
//! Clears the screen in preparation for rendering.
@@ -39,49 +36,36 @@ private:
//! Updates the active camera used for rendering.
void update_camera();
- //! Renders the whole screen
+ //! Renders all the sprites and particles
void render();
+ //! Renders all Text components
+ void render_text();
+
+private:
/**
* \brief Renders all the particles on the screen from a given sprite.
*
* \param sprite renders the particles with given texture
- * \param tm the Transform component for scale
+ * \param transform the component that holds the position, rotation, and scale.
* \return true if particles have been rendered
*/
- bool render_particle(const Sprite & sprite, const double & scale);
-
+ bool render_particle(const Sprite & sprite, const Transform & transform);
/**
- * \brief renders a sprite with a Transform component on the screen
+ * \brief renders a sprite with a Transform component on the screen
*
* \param sprite the sprite component that holds all the data
- * \param tm the Transform component that holds the position,rotation and scale
+ * \param transform the Transform component that holds the position,rotation and scale
*/
- void render_normal(const Sprite & sprite, const Transform & tm);
+ void render_normal(const Sprite & sprite, const Transform & transform);
/**
* \brief sort a vector sprite objects with
*
- * \param objs the vector that will do a sorting algorithm on
+ * \param objs the vector that will do a sorting algorithm on
* \return returns a sorted reference vector
*/
RefVector<Sprite> sort(RefVector<Sprite> & objs) const;
-
- /**
- * \todo Include color handling for sprites.
- * \todo Add text rendering using SDL_ttf for text components.
- * \todo Implement a text component and a button component.
- * \todo Ensure each sprite is checked for active status before rendering.
- * \todo Sort all layers by order before rendering.
- * \todo Consider adding text input functionality.
- */
-
-private:
- //! Pointer to the current active camera for rendering
- Camera * curr_cam_ref = nullptr;
- // TODO: needs a better solution
-
- SDLContext & context = SDLContext::get_instance();
};
} // namespace crepe
diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp
new file mode 100644
index 0000000..efc3be4
--- /dev/null
+++ b/src/crepe/system/ReplaySystem.cpp
@@ -0,0 +1,54 @@
+#include "../manager/ReplayManager.h"
+#include "../manager/SystemManager.h"
+
+#include "EventSystem.h"
+#include "RenderSystem.h"
+#include "ReplaySystem.h"
+
+using namespace crepe;
+using namespace std;
+
+void ReplaySystem::fixed_update() {
+ ReplayManager & replay = this->mediator.replay_manager;
+ ReplayManager::State state = replay.get_state();
+ ReplayManager::State last_state = this->last_state;
+ this->last_state = state;
+
+ switch (state) {
+ case ReplayManager::IDLE:
+ break;
+ case ReplayManager::RECORDING: {
+ replay.frame_record();
+ break;
+ }
+ case ReplayManager::PLAYING: {
+ if (last_state != ReplayManager::PLAYING) this->playback_begin();
+ bool last = replay.frame_step();
+ if (last) this->playback_end();
+ break;
+ }
+ }
+}
+
+void ReplaySystem::playback_begin() {
+ SystemManager & systems = this->mediator.system_manager;
+ ComponentManager & components = this->mediator.component_manager;
+
+ this->playback = {
+ .components = components.save(),
+ .systems = systems.save(),
+ };
+
+ systems.disable_all();
+ systems.get_system<RenderSystem>().active = true;
+ systems.get_system<ReplaySystem>().active = true;
+ systems.get_system<EventSystem>().active = true;
+}
+
+void ReplaySystem::playback_end() {
+ SystemManager & systems = this->mediator.system_manager;
+ ComponentManager & components = this->mediator.component_manager;
+
+ components.restore(this->playback.components);
+ systems.restore(this->playback.systems);
+}
diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h
new file mode 100644
index 0000000..bbc8d76
--- /dev/null
+++ b/src/crepe/system/ReplaySystem.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "../manager/ReplayManager.h"
+#include "../manager/SystemManager.h"
+
+#include "System.h"
+
+namespace crepe {
+
+/**
+ * \brief ReplayManager helper system
+ *
+ * This system records and replays recordings using ReplayManager.
+ */
+class ReplaySystem : public System {
+public:
+ using System::System;
+
+ void fixed_update() override;
+
+private:
+ //! Last ReplayManager state
+ ReplayManager::State last_state = ReplayManager::IDLE;
+
+ /**
+ * \brief Playback snapshot
+ *
+ * When starting playback, the component state is saved and most systems are disabled. This
+ * struct stores the engine state before ReplayManager::play is called.
+ */
+ struct Snapshot {
+ ComponentManager::Snapshot components;
+ SystemManager::Snapshot systems;
+ };
+ //! Before playback snapshot
+ Snapshot playback;
+
+ //! Snapshot state and disable systems during playback
+ void playback_begin();
+ //! Restore state from before \c playback_begin()
+ void playback_end();
+};
+
+} // namespace crepe
diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp
index c33309c..ed0c7cc 100644
--- a/src/crepe/system/ScriptSystem.cpp
+++ b/src/crepe/system/ScriptSystem.cpp
@@ -1,41 +1,41 @@
-#include <functional>
-
-#include "../ComponentManager.h"
#include "../api/BehaviorScript.h"
#include "../api/Script.h"
+#include "../manager/ComponentManager.h"
#include "ScriptSystem.h"
using namespace std;
using namespace crepe;
-void ScriptSystem::update() {
- dbg_trace();
-
- RefVector<Script> scripts = this->get_scripts();
+void ScriptSystem::fixed_update() {
+ LoopTimerManager & timer = this->mediator.loop_timer;
+ duration_t delta_time = timer.get_scaled_fixed_delta_time();
+ this->update(&Script::fixed_update, delta_time);
+}
- for (auto & script_ref : scripts) {
- Script & script = script_ref.get();
- if (!script.initialized) {
- script.init();
- script.initialized = true;
- }
- script.update();
- }
+void ScriptSystem::frame_update() {
+ LoopTimerManager & timer = this->mediator.loop_timer;
+ duration_t delta_time = timer.get_delta_time();
+ this->update(&Script::frame_update, delta_time);
}
-RefVector<Script> ScriptSystem::get_scripts() const {
- RefVector<Script> scripts = {};
- ComponentManager & mgr = this->component_manager;
+void ScriptSystem::update(
+ void (Script::*update_function)(duration_t), const duration_t & delta_time
+) {
+ ComponentManager & mgr = this->mediator.component_manager;
RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>();
- for (auto behavior_script_ref : behavior_scripts) {
- BehaviorScript & behavior_script = behavior_script_ref.get();
+ for (BehaviorScript & behavior_script : behavior_scripts) {
if (!behavior_script.active) continue;
+
Script * script = behavior_script.script.get();
if (script == nullptr) continue;
- scripts.push_back(*script);
- }
- return scripts;
+ if (!script->initialized) {
+ script->init();
+ script->initialized = true;
+ }
+
+ (*script.*update_function)(delta_time);
+ }
}
diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h
index 32e1fcd..257b615 100644
--- a/src/crepe/system/ScriptSystem.h
+++ b/src/crepe/system/ScriptSystem.h
@@ -1,8 +1,8 @@
#pragma once
-#include "System.h"
+#include "../manager/LoopTimerManager.h"
-#include "../types.h"
+#include "System.h"
namespace crepe {
@@ -10,30 +10,28 @@ class Script;
/**
* \brief Script system
- *
- * The script system is responsible for all \c BehaviorScript components, and
- * calls the methods on classes derived from \c Script.
+ *
+ * The script system is responsible for all \c BehaviorScript components, and calls the methods
+ * on classes derived from \c Script.
*/
class ScriptSystem : public System {
public:
using System::System;
- /**
- * \brief Call Script::update() on all active \c BehaviorScript instances
- *
- * This routine updates all scripts sequentially using the Script::update()
- * method. It also calls Script::init() if this has not been done before on
- * the \c BehaviorScript instance.
- */
- void update() override;
+
+public:
+ //! Call Script::fixed_update() on all active \c BehaviorScript instances
+ void fixed_update() override;
+ //! Call Script::frame_update() on all active \c BehaviorScript instances
+ void frame_update() override;
private:
/**
- * \brief Aggregate all active \c BehaviorScript components and return a list
- * of references to their \c Script instances (utility)
+ * \brief Call Script `*_update` member function on all active \c BehaviorScript instances
*
- * \returns List of active \c Script instances
+ * \note This routine also calls Script::init() if this has not been done before on the \c
+ * BehaviorScript instance.
*/
- RefVector<Script> get_scripts() const;
+ void update(void (Script::*update_function)(duration_t), const duration_t & delta_time);
};
} // namespace crepe
diff --git a/src/crepe/system/System.cpp b/src/crepe/system/System.cpp
index 937a423..ecc740d 100644
--- a/src/crepe/system/System.cpp
+++ b/src/crepe/system/System.cpp
@@ -1,7 +1,5 @@
-#include "../util/Log.h"
-
#include "System.h"
using namespace crepe;
-System::System(ComponentManager & mgr) : component_manager(mgr) { dbg_trace(); }
+System::System(const Mediator & mediator) : mediator(mediator) {}
diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h
index 28ea20e..e2ce7eb 100644
--- a/src/crepe/system/System.h
+++ b/src/crepe/system/System.h
@@ -1,5 +1,7 @@
#pragma once
+#include "../manager/Mediator.h"
+
namespace crepe {
class ComponentManager;
@@ -7,23 +9,24 @@ class ComponentManager;
/**
* \brief Base ECS system class
*
- * This class is used as the base for all system classes. Classes derived from
- * System must implement the System::update() method and copy Script::Script
- * with the `using`-syntax.
+ * This class is used as the base for all system classes. Classes derived from System must
+ * implement the System::update() method and copy Script::Script with the `using`-syntax.
*/
class System {
public:
- /**
- * \brief Process all components this system is responsible for.
- */
- virtual void update() = 0;
+ //! Code that runs in the fixed loop
+ virtual void fixed_update() {};
+ //! Code that runs in the frame loop
+ virtual void frame_update() {};
+ //! Indicates that the update functions of this system should be run
+ bool active = true;
public:
- System(ComponentManager &);
+ System(const Mediator & m);
virtual ~System() = default;
protected:
- ComponentManager & component_manager;
+ const Mediator & mediator;
};
} // namespace crepe
diff --git a/src/crepe/types.h b/src/crepe/types.h
index 914c76c..69cc526 100644
--- a/src/crepe/types.h
+++ b/src/crepe/types.h
@@ -1,5 +1,7 @@
#pragma once
+#include "api/Vector2.h"
+
#include <cstdint>
#include <functional>
#include <vector>
@@ -13,4 +15,16 @@ typedef uint32_t game_object_id_t;
template <typename T>
using RefVector = std::vector<std::reference_wrapper<T>>;
-} // namespace crepe
+//! Default Vector2<int> type
+typedef Vector2<int> ivec2;
+
+//! Default Vector2<unsigned int> type
+typedef Vector2<unsigned int> uvec2;
+
+//! Default Vector2<float> type
+typedef Vector2<float> vec2;
+
+//! Default Vector2<double> type
+typedef Vector2<double> dvec2;
+
+}; // namespace crepe
diff --git a/src/crepe/util/AbsolutePosition.cpp b/src/crepe/util/AbsolutePosition.cpp
new file mode 100644
index 0000000..29ade23
--- /dev/null
+++ b/src/crepe/util/AbsolutePosition.cpp
@@ -0,0 +1,20 @@
+#include "AbsolutePosition.h"
+
+using namespace crepe;
+
+vec2 AbsolutePosition::get_position(const Transform & transform, const vec2 & offset) {
+ // Get the rotation in radians
+ float radians1 = transform.rotation * (M_PI / 180.0);
+
+ // Calculate total offset with scale
+ vec2 total_offset = offset * transform.scale;
+
+ // Rotate
+ float rotated_total_offset_x1
+ = total_offset.x * cos(radians1) - total_offset.y * sin(radians1);
+ float rotated_total_offset_y1
+ = total_offset.x * sin(radians1) + total_offset.y * cos(radians1);
+
+ // Final positions considering scaling and rotation
+ return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1));
+}
diff --git a/src/crepe/util/AbsolutePosition.h b/src/crepe/util/AbsolutePosition.h
new file mode 100644
index 0000000..857c1ac
--- /dev/null
+++ b/src/crepe/util/AbsolutePosition.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "api/Transform.h"
+
+#include "types.h"
+
+namespace crepe {
+
+/**
+ * \brief A class for calculating the absolute position of an object.
+ *
+ * This class provides a utility function to get the position of an object in the world space,
+ * taking into account the transform and any additional offset.
+ */
+class AbsolutePosition {
+public:
+ /**
+ * \brief Get the absolute position of an object.
+ *
+ * This function calculates the absolute position by combining the transform position with an optional offset.
+ *
+ * \param transform The transform of the object, which contains its position, rotation, and scale.
+ * \param offset The offset to apply to the object's position (in local space).
+ * \return The absolute position of the object as a 2D vector.
+ */
+ static vec2 get_position(const Transform & transform, const vec2 & offset);
+};
+
+} // namespace crepe
diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt
index 94ed906..33160a7 100644
--- a/src/crepe/util/CMakeLists.txt
+++ b/src/crepe/util/CMakeLists.txt
@@ -1,6 +1,7 @@
target_sources(crepe PUBLIC
LogColor.cpp
Log.cpp
+ AbsolutePosition.cpp
)
target_sources(crepe PUBLIC FILE_SET HEADERS FILES
@@ -11,5 +12,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
Proxy.hpp
OptionalRef.h
OptionalRef.hpp
+ AbsolutePosition.h
)
diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp
index 84d80a8..ce25a1d 100644
--- a/src/crepe/util/Log.cpp
+++ b/src/crepe/util/Log.cpp
@@ -4,6 +4,7 @@
#include "../api/Config.h"
#include "Log.h"
+#include "LogColor.h"
using namespace crepe;
using namespace std;
diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h
index d55b11e..b43fe30 100644
--- a/src/crepe/util/Log.h
+++ b/src/crepe/util/Log.h
@@ -2,27 +2,6 @@
#include <format>
-// allow user to disable debug macros
-#ifndef CREPE_DISABLE_MACROS
-
-#include "LogColor.h"
-
-// utility macros
-#define _crepe_logf_here(level, fmt, ...) \
- crepe::Log::logf(level, "{}" fmt, \
- crepe::LogColor().fg_white(false).str(std::format( \
- "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \
- __VA_ARGS__)
-
-// very illegal global function-style macros
-// NOLINTBEGIN
-#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__)
-#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str)
-#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "")
-// NOLINTEND
-
-#endif
-
namespace crepe {
/**
@@ -34,11 +13,11 @@ class Log {
public:
//! Log message severity
enum Level {
- TRACE, //< Include (internal) function calls
- DEBUG, //< Include dbg_logf output
- INFO, //< General-purpose messages
- WARNING, //< Non-fatal errors
- ERROR, //< Fatal errors
+ TRACE, //!< Include (internal) function calls
+ DEBUG, //!< Include dbg_logf output
+ INFO, //!< General-purpose messages
+ WARNING, //!< Non-fatal errors
+ ERROR, //!< Fatal errors
};
/**
diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h
index 253bc07..1b2cb3f 100644
--- a/src/crepe/util/OptionalRef.h
+++ b/src/crepe/util/OptionalRef.h
@@ -25,13 +25,21 @@ public:
*/
OptionalRef<T> & operator=(T & ref);
/**
- * \brief Retrieve this reference
+ * \brief Retrieve this reference (cast)
*
* \returns Internal reference if it is set
*
* \throws std::runtime_error if this function is called while the reference it not set
*/
- operator T & () const;
+ operator T &() const;
+ /**
+ * \brief Retrieve this reference (member access)
+ *
+ * \returns Internal reference if it is set
+ *
+ * \throws std::runtime_error if this function is called while the reference it not set
+ */
+ T * operator->() const;
/**
* \brief Check if this reference is not empty
*
diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp
index ae7c73e..5e36b3a 100644
--- a/src/crepe/util/OptionalRef.hpp
+++ b/src/crepe/util/OptionalRef.hpp
@@ -12,13 +12,20 @@ OptionalRef<T>::OptionalRef(T & ref) {
}
template <typename T>
-OptionalRef<T>::operator T & () const {
+OptionalRef<T>::operator T &() const {
if (this->ref == nullptr)
throw std::runtime_error("OptionalRef: attempt to dereference nullptr");
return *this->ref;
}
template <typename T>
+T * OptionalRef<T>::operator->() const {
+ if (this->ref == nullptr)
+ throw std::runtime_error("OptionalRef: attempt to dereference nullptr");
+ return this->ref;
+}
+
+template <typename T>
OptionalRef<T> & OptionalRef<T>::operator=(T & ref) {
this->ref = &ref;
return *this;
diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h
new file mode 100644
index 0000000..e448070
--- /dev/null
+++ b/src/crepe/util/dbg.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Log.h"
+#include "LogColor.h"
+
+// utility macros
+#define _crepe_logf_here(level, fmt, ...) \
+ crepe::Log::logf( \
+ level, "{}" fmt, \
+ crepe::LogColor().fg_white(false).str( \
+ std::format("{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__) \
+ ), \
+ __VA_ARGS__ \
+ )
+
+// very illegal global function-style macros
+// NOLINTBEGIN
+#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__)
+#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str)
+#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "")
+// NOLINTEND
diff --git a/src/doc/feature/animator_creation.dox b/src/doc/feature/animator_creation.dox
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/doc/feature/animator_creation.dox
diff --git a/src/doc/feature/bgm.dox b/src/doc/feature/bgm.dox
new file mode 100644
index 0000000..968abb8
--- /dev/null
+++ b/src/doc/feature/bgm.dox
@@ -0,0 +1,22 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_bgm Playing background music
+\ingroup feature
+\brief Add background music to a scene using the AudioSource component
+
+This page shows how to implement background music using the AudioSource
+effects.
+
+\see AudioSource
+
+\par Example
+
+\note This example assumes you already have a GameObject. If not, read
+\"\ref feature_gameobject\" first.
+
+\todo Merge #60
+
+*/
+}
diff --git a/src/doc/feature/config.dox b/src/doc/feature/config.dox
new file mode 100644
index 0000000..ae3a054
--- /dev/null
+++ b/src/doc/feature/config.dox
@@ -0,0 +1,61 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_config Engine configuration
+\ingroup feature
+\brief Configure default values and global options
+
+Default values and options that apply to the engine globally are read from a
+singleton struct named Config.
+
+\see Config
+
+\par Example
+
+Configuration options may be set individually or by assigning a [designated
+initializer list][desginit]. All of Config's members have default values and can
+safely be omitted from initializer lists.
+
+[desginit]: https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers
+
+```cpp
+#include <crepe/api/Config.h>
+
+int main() {
+ auto & config = crepe::Config::get_instance();
+
+ // Designated initializer list
+ config = {
+ // specify options here
+ };
+
+ // Reset default options
+ config = {};
+
+ // Set specific option
+ config.log.color = false;
+}
+```
+
+\par Options
+
+\noop Display config properties in monospace font
+\htmlonly
+<style>
+tr td:first-child { font-family: monospace; }
+</style>
+\endhtmlonly
+
+|Option|Description|
+|-|-|
+|\ref Config::asset::root_pattern ".asset.root_pattern"|\copybrief Config::asset::root_pattern|
+|\ref Config::log::color ".log.color"|\copybrief Config::log::color|
+|\ref Config::log::level ".log.level"|\copybrief Config::log::level|
+|\ref Config::physics::gravity ".physics.gravity"|\copybrief Config::physics::gravity|
+|\ref Config::savemgr::location ".savemgr.location"|\copybrief Config::savemgr::location|
+|\ref Config::window_settings::default_size ".window_settings.default_size"|\copybrief Config::window_settings::default_size|
+|\ref Config::window_settings::window_title ".window_settings.window_title"|\copybrief Config::window_settings::window_title|
+
+*/
+}
diff --git a/src/doc/feature/gameobject.dox b/src/doc/feature/gameobject.dox
index c561874..ac3927c 100644
--- a/src/doc/feature/gameobject.dox
+++ b/src/doc/feature/gameobject.dox
@@ -2,9 +2,9 @@
namespace crepe {
/**
-\defgroup feature_gameobject GameObjects
+\defgroup feature_gameobject Entity basics
\ingroup feature
-\brief GameObject to create a Scene
+\brief Building game entities using a GameObject
GameObjects are the fundamental building blocks of a Scene. They represent entities
in the game world and can have various components attached to them to define their
diff --git a/src/doc/feature/proxy.dox b/src/doc/feature/proxy.dox
new file mode 100644
index 0000000..66bbd2f
--- /dev/null
+++ b/src/doc/feature/proxy.dox
@@ -0,0 +1,43 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_proxy Proxy utility
+\ingroup feature
+\brief Use ValueBroker as if it were a regular variable
+
+Proxy provides operators that allow you to use a ValueBroker instance as if it
+were a regular variable. Proxy implements a constructor that allows it to be
+used as a substitute return type for any function that returns ValueBroker.
+
+\see ValueBroker
+\see Proxy
+
+\par Example
+
+```cpp
+#include <crepe/util/Proxy.h>
+#include <crepe/ValueBroker.h>
+
+int calculation(int value) {
+ return 3 * value;
+}
+
+void anywhere() {
+ crepe::ValueBroker<int> foo_handle;
+ crepe::Proxy foo = foo_handle;
+
+ // implicitly calls .set()
+ foo += 10;
+
+ // implicitly calls .get()
+ int out = calculation(foo);
+
+ // explicitly cast (also calls .get())
+ int casted = int(foo);
+}
+
+```
+
+*/
+}
diff --git a/src/doc/feature/savemgr.dox b/src/doc/feature/savemgr.dox
new file mode 100644
index 0000000..6aeab03
--- /dev/null
+++ b/src/doc/feature/savemgr.dox
@@ -0,0 +1,80 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_savemgr Save data
+\ingroup feature
+\brief Functions to persistently store and retrieve arbitrary values
+
+The SaveManager may be used to persistently store game state such as player
+progress, statistics, achievements, etc. It works like a key-value store, where
+the key is a string and the value is an arbitrary type.
+
+SaveManager implements the following:
+
+- Storage and retrieval of primitive types and strings.
+- Automatic initialization of the database using default values.
+- The underlying database format is journaled, which reduces the likelihood of
+ players losing save data when an unexpected crash occurs while the SaveManager
+ is writing to disk.
+
+\see SaveManager
+
+\par Example
+
+The SaveManager instance reference may be retrieved by calling \c
+get_save_manager(). This function is available---
+
+- Within (derivatives of) Script
+
+- \todo Within (derivatives of) Scene
+
+- \todo As a public member function of LoopManager
+
+```cpp
+// Retrieve save manager
+crepe::SaveManager & save_manager = get_save_manager();
+```
+
+SaveManager may be used *explicitly*, using the \ref SaveManager::set "set()",
+\ref SaveManager::get "get()" and \ref SaveManager::has "has()" methods:
+```cpp
+// Check if the key "foo" exists, and initialize it to 3 if it doesn't
+if (!save_manager.has("foo"))
+ save_manager.set<int>("foo", 3);
+// Get value of key "foo"
+int foo = save_manager.get<int>("foo");
+
+// ~~~ arbitrary game code ~~~
+foo += 10;
+// ~~~ arbitrary game code ~~~
+
+// Save "foo" back to database
+save_manager.set<int>("foo", foo);
+```
+
+Alternatively, SaveManager::get may be called with a default value as second
+parameter. This changes its return type to ValueBroker, which acts as a
+read/write handle to the specific key requested, and remembers the key and its
+value type for you:
+```cpp
+// Get a read/write handle to the value stored in key "foo", and initialize it
+// to 3 if it doesn't exist yet
+ValueBroker foo_handle = save_manager.get<int>("foo", 3);
+int foo = foo_handle.get();
+
+// ~~~ arbitrary game code ~~~
+foo += 10;
+// ~~~ arbitrary game code ~~~
+
+// Save back to database
+foo_handle.set(foo);
+```
+
+To further simplify game code, the return value of SaveManager::get may be
+implicitly cast to Proxy instead of ValueBroker. This allows the database value
+to be used as if it were a regular variable. This usage is detailed separately
+in \"\ref feature_proxy\".
+
+*/
+}
diff --git a/src/doc/feature/scene.dox b/src/doc/feature/scene.dox
index 5f34446..b680eec 100644
--- a/src/doc/feature/scene.dox
+++ b/src/doc/feature/scene.dox
@@ -6,10 +6,11 @@ namespace crepe {
\ingroup feature
\brief User-defined scenes
-Scenes can be used to implement game environments, and allow arbitrary game objects to be organized
-as part of the game structure. Scenes are implemented as derivative classes of Scene, which are
-added to the game using the SceneManager. Scenes describe the start of a Scene and cannot modify
-GameObjects during runtime of a Scene (use \ref feature_script "Scripting" for this purpose).
+Scenes can be used to implement game environments, and allow arbitrary game
+objects to be organized as part of the game structure. Scenes are implemented as
+derivative classes of Scene, which are added to the game using the SceneManager.
+Scenes describe the start of a Scene and cannot modify GameObjects during
+runtime of a Scene (use \ref feature_script for this purpose).
\see SceneManager
\see GameObject
@@ -18,48 +19,49 @@ GameObjects during runtime of a Scene (use \ref feature_script "Scripting" for t
\par Example
-This example demonstrates how to define and add scenes to the loop/scene manager in the `crepe` framework.
-Each concrete scene should be derived from Scene. In the example below, the concrete scene is named MyScene.
-A concrete scene should, at least, implement (override) two methods, namely load_scene() and get_name(). The
-scene is build (using GameObjects) in the load_scene() method. GameObjects should be made using the
-component_manager::new_object(). In the example below, two GameObjects (named object1 and object2) are added
-to MyScene. object1 and object2 do not have any non-default Components attached to them, however, if needed,
-this should also be done in load_scene(). Each concrete scene must have a unique name. This unique name is
-used to load a new concrete scene (via a Script). The unique name is set using the get_name() method. In the
-example below, MyScene's unique name is my_scene.
-After setting up one or more concrete scene(s), the concrete scene(s) should be added to the loop/scene manager.
-This is done in your main(). Firstly, the LoopManager should be instantiated. Than, all the concrete scene(s)
-should be added to the loop/scene manger via loop_mgr::add_scene<>(). The templated argument should define the
-concrete scene to be added.
+This example demonstrates how to define and add scenes to the loop/scene manager
+in the `crepe` framework. Each concrete scene should be derived from Scene. In
+the example below, the concrete scene is named MyScene. A concrete scene should,
+at least, implement (override) two methods, namely load_scene() and get_name().
+The scene is build (using GameObjects) in the load_scene() method. GameObjects
+should be made using the component_manager::new_object().
+
+In the example below, two GameObjects (named object1 and object2) are added to
+MyScene. object1 and object2 do not have any non-default Components attached to
+them, however, if needed, this should also be done in load_scene(). Each
+concrete scene must have a unique name. This unique name is used to load a new
+concrete scene (via a Script). The unique name is set using the get_name()
+method. In the example below, MyScene's unique name is my_scene.
+
+After setting up one or more concrete scene(s), the concrete scene(s) should be
+added to the loop/scene manager. This is done in your main(). Firstly, the
+LoopManager should be instantiated. Than, all the concrete scene(s) should be
+added to the loop/scene manger via loop_mgr::add_scene<>(). The templated
+argument should define the concrete scene to be added.
```cpp
-#include <crepe/api/LoopManager.h>
-#include <crepe/api/GameObject.h>
+#include <crepe/api/Engine.h>
#include <crepe/api/Scene.h>
-#include <crepe/api/Vector2.h>
using namespace crepe;
class MyScene : public Scene {
public:
- using Scene::Scene;
-
void load_scene() {
- auto & mgr = this->component_manager;
- GameObject object1 = mgr.new_object("object1", "tag_my_scene", Vector2{0, 0}, 0, 1);
- GameObject object2 = mgr.new_object("object2", "tag_my_scene", Vector2{1, 0}, 0, 1);
+ GameObject object1 = new_object("object1", "tag_my_scene", vec2{0, 0}, 0, 1);
+ GameObject object2 = new_object("object2", "tag_my_scene", vec2{1, 0}, 0, 1);
}
string get_name() const { return "my_scene"; }
};
int main() {
- LoopManager loop_mgr;
+ Engine foo;
// Add the scenes to the loop manager
- loop_mgr.add_scene<MyScene>();
+ foo.add_scene<MyScene>();
- loop_mgr.start();
+ return foo.main();
}
```
diff --git a/src/doc/feature/script.dox b/src/doc/feature/script.dox
index d25a63b..162b0f5 100644
--- a/src/doc/feature/script.dox
+++ b/src/doc/feature/script.dox
@@ -2,19 +2,14 @@
namespace crepe {
/**
-\defgroup feature_script Scripting
+\defgroup feature_script Scripting basics
\ingroup feature
-\brief User-defined scripts for game objects
+\brief Create a concrete Script and attach it to a GameObject
Scripts can be used to implement game behavior, and allow arbitrary code to run
as part of the game loop. Scripts are implemented as derivative classes of
-Script, which are added to game objects using the BehaviorScript \ref Component
-"component".
-
-\todo This section is incomplete:
-- Utility functions to get components/events/etc inside script
-- How to listen for events
-- Extensions of script (keylistener)
+Script, which are added to \ref GameObject "game objects" using the \ref
+BehaviorScript \ref Component "component".
\see Script
\see BehaviorScript
@@ -22,11 +17,14 @@ Script, which are added to game objects using the BehaviorScript \ref Component
\par Example
-First, define a class that inherits from Script. This class acts as an
-interface, and has two functions (\ref Script::init "\c init()" and \ref
-Script::update "\c update()"), which may be implemented (they are empty by
-default). From now on, this derivative class will be referred to as a *concrete
-script*.
+\note This example assumes you already have a GameObject. If not, read
+\"\ref feature_gameobject\" first.
+
+First, define a class (anywhere) that inherits from Script. The Script class
+acts as an interface, and has three functions (\ref Script::init "\c init()",
+\ref Script::fixed_update "\c fixed_update()" and \ref Script::frame_update
+"\c frame_update()"), which *may* be implemented (they are empty by default).
+From now on, this derivative class will be referred to as a *concrete script*.
```cpp
#include <crepe/api/Script.h>
@@ -36,25 +34,33 @@ class MyScript : public crepe::Script {
void init() {
// called once
}
- void update() {
+
+ void fixed_update(crepe::duration_t delta_time) {
// called on fixed update
}
+ void frame_update(crepe::duration_t delta_time) {
+ // called for every rendered frame
+ }
};
```
-Concrete scripts can be instantiated and attached to \ref GameObject
-"game objects" using the BehaviorScript \ref Component "component".
+After defining a concrete script, it can be instantiated and attached to \ref
+feature_gameobject "game objects" during \ref feature_scene
+"scene initialization" using a BehaviorScript component:
```cpp
using namespace crepe;
-GameObject obj = component_manager.new_object("name");
+GameObject obj;
-// create BehaviorScript instance
+// Create a BehaviorScript component to hold MyScript
BehaviorScript & behavior_script = obj.add_component<BehaviorScript>();
-// attach (and instantiate) MyScript to behavior_script
+
+// Instantiate (and attach) MyScript to behavior_script
behavior_script.set_script<MyScript>();
+```
-// the above can also be done in a single call for convenience:
+The above can also be done in a single call for convenience:
+```cpp
obj.add_component<BehaviorScript>().set_script<MyScript>();
```
diff --git a/src/doc/feature/script_ecs.dox b/src/doc/feature/script_ecs.dox
new file mode 100644
index 0000000..8bd3376
--- /dev/null
+++ b/src/doc/feature/script_ecs.dox
@@ -0,0 +1,57 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_script_ecs Using ECS inside Script
+\ingroup feature
+\brief Query the component manager inside a concrete Script
+
+Script provides several methods to request references to components during
+runtime. These methods may be used in cases where it is either not practical or
+impossible to manually pass the references required to implement a certain
+behavior.
+
+\see Script
+\see ComponentManager
+
+\par Example
+
+\note This example assumes you already have a concrete Script. If not, read
+\"\ref feature_script\" first.
+
+The component manager can be queried for components inside Script using the
+following methods:
+
+- For requesting components on the same GameObject as this Script instance:
+ - Script::get_component(): \copybrief Script::get_component
+ - Script::get_components(): \copybrief Script::get_components
+- For requesting components in the current Scene:
+ - Script::get_components_by_id(): \copybrief Script::get_components_by_id
+ - Script::get_components_by_name(): \copybrief Script::get_components_by_name
+ - Script::get_components_by_tag(): \copybrief Script::get_components_by_tag
+
+```cpp
+#include <crepe/util/Log.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Metadata.h>
+
+using namespace crepe;
+
+class MyScript : public Script {
+ void show_self() {
+ Metadata & own_metadata = get_component<Metadata>();
+ logf("My name is {}", own_metadata.name);
+ }
+
+ void list_enemies() {
+ RefVector<Metadata> enemies = get_components_by_tag<Metadata>("enemy");
+ logf("There are {} enemies:", enemies.size());
+ for (const Metadata & enemy : enemies) {
+ logf("- {}", enemy.name);
+ }
+ }
+};
+```
+
+*/
+}
diff --git a/src/doc/feature/sfx.dox b/src/doc/feature/sfx.dox
new file mode 100644
index 0000000..2a5c9cc
--- /dev/null
+++ b/src/doc/feature/sfx.dox
@@ -0,0 +1,24 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup feature_sfx Playing sound effects
+\ingroup feature
+\brief Fire a sound effect using the AudioSource component
+
+This page shows how to implement one-shot sound effects using the AudioSource
+component's 'fire and forget'-style API.
+
+\see AudioSource
+
+\par Example
+
+\note This example assumes you already have a GameObject to attach the
+AudioSource component to, and uses a Script to control the AudioSource instance.
+Separate pages describing these features in more detail can be found at \"\ref
+feature_gameobject\" and \"\ref feature_script\" respectively.
+
+\todo Merge #60
+
+*/
+}
diff --git a/src/doc/features.dox b/src/doc/features.dox
index 4786bed..56d17c7 100644
--- a/src/doc/features.dox
+++ b/src/doc/features.dox
@@ -1,10 +1,69 @@
// vim:ft=doxygen
/**
+\htmlonly
+<style>
+table.memberdecls,
+.groupheader
+{ display: none; }
+ul,
+li
+{ margin: 1ex 0pt; }
+</style>
+\endhtmlonly
+
\defgroup feature Features
\brief Engine components
This page lists engine features and contains usage instructions for each
feature.
+- Basics
+ - \todo Hello world / engine initialization
+
+ - \ref feature_config \n\copybrief feature_config
+
+- Scenes
+ - \ref feature_scene \n\copybrief feature_scene
+ - \todo Navigating between scenes
+
+- Input
+ - \todo Key/Mouse events (w/ Script)
+
+- Actors / game objects
+ - \ref feature_gameobject \n\copybrief feature_gameobject
+
+- \todo HUD
+
+- Animation
+ - \todo Animation using spritesheet
+
+ - \todo Particle effects
+
+- Save data
+ - \ref feature_savemgr \n\copybrief feature_savemgr
+
+- Audio
+ - \ref feature_sfx \n\copybrief feature_sfx
+ - \ref feature_bgm \n\copybrief feature_bgm
+
+- \todo AI
+
+- \todo Physics
+
+- Scripting
+ - \ref feature_script \n\copybrief feature_script
+ - \ref feature_script_ecs \n\copybrief feature_script_ecs
+
+ - \todo Subscribing to *any* event inside Script
+
+ - \todo Creating and dispatching custom events
+
+- \todo Replay
+
+- Utilities
+ - \todo Logging
+
+ - \ref feature_proxy \n\copybrief feature_proxy
+
*/
diff --git a/src/doc/index.dox b/src/doc/index.dox
index 5ec7889..342db98 100644
--- a/src/doc/index.dox
+++ b/src/doc/index.dox
@@ -5,6 +5,20 @@
Welcome to the documentation for the crêpe game engine.
-\see feature
+\see \ref install "Engine installation instructions"
+\see \ref feature "Example code and usage instructions"
+\see [API documentation](annotated.html)
+
+\noop No bold links in "See also" section
+\htmlonly
+<style> .section.see a { font-weight: normal; } </style>
+\endhtmlonly
+
+*/
+
+/**
+
+\namespace crepe
+\brief Engine namespace
*/
diff --git a/src/doc/internal/component.dox b/src/doc/internal/component.dox
new file mode 100644
index 0000000..0dd4cb5
--- /dev/null
+++ b/src/doc/internal/component.dox
@@ -0,0 +1,41 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup internal_component Components
+\ingroup internal
+\brief ECS Components
+
+Components are attached to GameObject instances and are composed by the game
+programmer to create specific entities in the game world. While they are
+implemented as C++ classes, components should be treated as C-style structs,
+meaning all members are public and they do not contain functions.
+
+A basic component has the following structure:
+```cpp
+#include <crepe/Component.h>
+
+class MyComponent : public crepe::Component {
+public:
+ // Add your custom component's ininitializer properties after the `id`
+ // parameter. The first parameter is controlled by GameObject::add_component,
+ // while all other parameters are forwarded using std::forward.
+ MyComponent(game_object_id_t id, ...);
+
+ // Optionally define the `get_instances_max` method to limit the amount of
+ // instances of this component per GameObject. The default implementation for
+ // this function returns -1, which means the instance count does not have an
+ // upper limit:
+ virtual int get_instances_max() const { return -1; }
+
+ // Properties
+ // ...
+};
+```
+
+Generally, components are "handled" by \ref internal_system "systems", which may
+optionally change the components' state. Components' state may also be
+controlled by the game programmer through \ref feature_script "scripts".
+
+*/
+}
diff --git a/src/doc/internal/resource.dox b/src/doc/internal/resource.dox
new file mode 100644
index 0000000..56f1de0
--- /dev/null
+++ b/src/doc/internal/resource.dox
@@ -0,0 +1,12 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup internal_resource Resources
+\ingroup internal
+\brief Concrete resources
+
+\todo This section is incomplete
+
+*/
+}
diff --git a/src/doc/internal/style.dox b/src/doc/internal/style.dox
new file mode 100644
index 0000000..dad2df0
--- /dev/null
+++ b/src/doc/internal/style.dox
@@ -0,0 +1,9 @@
+// vim:ft=doxygen
+/**
+
+\defgroup internal_style Code style
+\ingroup internal
+\brief Coding conventions
+\include{doc} contributing.md
+
+*/
diff --git a/src/doc/internal/system.dox b/src/doc/internal/system.dox
new file mode 100644
index 0000000..17a101e
--- /dev/null
+++ b/src/doc/internal/system.dox
@@ -0,0 +1,26 @@
+// vim:ft=doxygen
+namespace crepe {
+/**
+
+\defgroup internal_system Systems
+\ingroup internal
+\brief ECS Systems
+
+\todo This section is incomplete
+
+A system is responsible for processing the data stored in \ref
+internal_component "components".
+
+A basic system has the following structure:
+```cpp
+#include <crepe/system/System.h>
+
+class MySystem : public System {
+public:
+ using System::System;
+ void update() override;
+};
+```
+
+*/
+}
diff --git a/src/doc/internals.dox b/src/doc/internals.dox
new file mode 100644
index 0000000..2d2ca56
--- /dev/null
+++ b/src/doc/internals.dox
@@ -0,0 +1,10 @@
+// vim:ft=doxygen
+/**
+
+\defgroup internal Internals
+\brief Internal engine structure and other conventions
+
+\todo This page is incomplete
+\todo Anything about Contexts?
+
+*/
diff --git a/src/doc/layout.xml b/src/doc/layout.xml
index 2244fa7..c98c790 100644
--- a/src/doc/layout.xml
+++ b/src/doc/layout.xml
@@ -1,61 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<doxygenlayout version="1.0">
<navindex>
- <tab type="mainpage" visible="yes" title=""/>
+ <tab type="mainpage" visible="yes" title="Intro"/>
+ <tab type="user" url="@ref install" title="Installation"/>
+ <tab type="user" url="@ref feature" title="Features"/>
+ <!-- <tab type="user" url="@ref internal" title="Internals"/> -->
<tab type="pages" visible="no" title="" intro=""/>
- <tab type="topics" visible="yes" title="" intro=""/>
- <tab type="modules" visible="yes" title="" intro="">
+ <tab type="topics" visible="no" title="" intro=""/>
+ <tab type="modules" visible="no" title="" intro="">
<tab type="modulelist" visible="yes" title="" intro=""/>
<tab type="modulemembers" visible="yes" title="" intro=""/>
</tab>
- <tab type="namespaces" visible="no" title="">
+ <tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</tab>
- <tab type="concepts" visible="yes" title="">
+ <tab type="concepts" visible="no" title="">
</tab>
- <tab type="interfaces" visible="yes" title="">
+ <tab type="interfaces" visible="no" title="">
<tab type="interfacelist" visible="yes" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
</tab>
- <tab type="classes" visible="yes" title="">
+ <tab type="classes" visible="yes" title="API">
<tab type="classlist" visible="yes" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="yes" title="" intro=""/>
<tab type="classmembers" visible="yes" title="" intro=""/>
</tab>
- <tab type="structs" visible="yes" title="">
+ <tab type="structs" visible="no" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
- <tab type="exceptions" visible="yes" title="">
+ <tab type="exceptions" visible="no" title="">
<tab type="exceptionlist" visible="yes" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
</tab>
- <tab type="files" visible="yes" title="">
+ <tab type="files" visible="no" title="">
<tab type="filelist" visible="yes" title="" intro=""/>
<tab type="globals" visible="yes" title="" intro=""/>
</tab>
- <tab type="examples" visible="yes" title="" intro=""/>
+ <tab type="examples" visible="no" title="" intro=""/>
</navindex>
<class>
<briefdescription visible="yes"/>
+ <detaileddescription title=""/>
<includes visible="$SHOW_HEADERFILE"/>
<inheritancegraph visible="yes"/>
<collaborationgraph visible="yes"/>
<memberdecl>
+ <publicmethods title=""/>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<interfaces title=""/>
<publicslots title=""/>
<signals title=""/>
- <publicmethods title=""/>
- <publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
+ <publicstaticmethods title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
@@ -79,7 +83,6 @@
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
- <detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
@@ -99,11 +102,12 @@
</class>
<namespace>
<briefdescription visible="yes"/>
+ <detaileddescription title=""/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
- <classes visible="yes" title=""/>
+ <classes visible="no" title=""/>
<concepts visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
@@ -116,7 +120,6 @@
<properties title=""/>
<membergroups visible="yes"/>
</memberdecl>
- <detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
diff --git a/src/doc/style.css b/src/doc/style.css
index 08bc9f5..efc669b 100644
--- a/src/doc/style.css
+++ b/src/doc/style.css
@@ -1,4 +1,33 @@
#titlearea,
-address {
- display: none;
+address,
+a[href="namespaces.html"]
+{ display: none; }
+
+h2.groupheader { margin-top: revert; }
+
+dl {
+ padding: 4px 12px !important;
+ border-radius: 8px !important;
+ border: 0 !important;
+}
+dt {
+ margin-bottom: 0.5ex;
+}
+
+a:hover {
+ text-decoration: revert !important;
+ background: unset !important;
+}
+
+dl.section.see,
+dl.section.user {
+ padding: 0 !important;
+ border-radius: 0 !important;
+ margin-top: 0;
+}
+dl.section.see dt,
+dl.section.user dt {
+ font-size: 130%;
+ margin-bottom: 0.5ex;
+ margin-top: 1.5ex;
}
diff --git a/src/example/AITest.cpp b/src/example/AITest.cpp
new file mode 100644
index 0000000..4c4e25e
--- /dev/null
+++ b/src/example/AITest.cpp
@@ -0,0 +1,96 @@
+#include <crepe/api/AI.h>
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/LoopManager.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/manager/Mediator.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+using namespace std;
+
+class Script1 : public Script {
+ bool shutdown(const ShutDownEvent & event) {
+ // Very dirty way of shutting down the game
+ throw "ShutDownEvent";
+ return true;
+ }
+
+ bool mousemove(const MouseMoveEvent & event) {
+ /*RefVector<AI> aivec = this->get_components<AI>();
+ AI & ai = aivec.front().get();
+ ai.flee_target
+ = vec2{static_cast<float>(event.mouse_x), static_cast<float>(event.mouse_y)};*/
+ return true;
+ }
+
+ void init() {
+ subscribe<ShutDownEvent>([this](const ShutDownEvent & ev) -> bool {
+ return this->shutdown(ev);
+ });
+ subscribe<MouseMoveEvent>([this](const MouseMoveEvent & ev) -> bool {
+ return this->mousemove(ev);
+ });
+ }
+};
+
+class Scene1 : public Scene {
+public:
+ void load_scene() override {
+ Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+
+ GameObject game_object1 = mgr.new_object("", "", vec2 {0, 0}, 0, 1);
+ GameObject game_object2 = mgr.new_object("", "", vec2 {0, 0}, 0, 1);
+
+ Asset img {"asset/texture/test_ap43.png"};
+
+ Sprite & test_sprite = game_object1.add_component<Sprite>(
+ img,
+ Sprite::Data {
+ .color = Color::MAGENTA,
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 2,
+ .order_in_layer = 2,
+ .size = {0, 100},
+ .angle_offset = 0,
+ .position_offset = {0, 0},
+ }
+ );
+
+ AI & ai = game_object1.add_component<AI>(3000);
+ // ai.arrive_on();
+ // ai.flee_on();
+ ai.path_follow_on();
+ ai.make_oval_path(500, 1000, {0, -1000}, 1.5708, true);
+ ai.make_oval_path(1000, 500, {0, 500}, 4.7124, false);
+ game_object1.add_component<Rigidbody>(Rigidbody::Data {
+ .mass = 0.1f,
+ .max_linear_velocity = 40,
+ });
+ game_object1.add_component<BehaviorScript>().set_script<Script1>();
+
+ game_object2.add_component<Camera>(
+ ivec2 {1080, 720}, vec2 {5000, 5000},
+ Camera::Data {
+ .bg_color = Color::WHITE,
+ .zoom = 1,
+ }
+ );
+ }
+
+ string get_name() const override { return "Scene1"; }
+};
+
+int main() {
+ LoopManager engine;
+ engine.add_scene<Scene1>();
+ engine.start();
+
+ return 0;
+}
diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt
index 560e2bc..afe6cb7 100644
--- a/src/example/CMakeLists.txt
+++ b/src/example/CMakeLists.txt
@@ -16,8 +16,8 @@ function(add_example target_name)
add_dependencies(examples ${target_name})
endfunction()
-add_example(asset_manager)
-add_example(savemgr)
add_example(rendering_particle)
-add_example(gameloop)
-
+add_example(button)
+add_example(replay)
+add_example(loadfont)
+add_example(AITest)
diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp
deleted file mode 100644
index 917b547..0000000
--- a/src/example/asset_manager.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <crepe/api/AssetManager.h>
-#include <crepe/api/Texture.h>
-#include <crepe/facade/Sound.h>
-
-using namespace crepe;
-
-int main() {
-
- // this needs to be called before the asset manager otherwise the destructor of sdl is not in
- // the right order
- { Texture test("../asset/texture/img.png"); }
- // FIXME: make it so the issue described by the above comment is not possible (i.e. the order
- // in which internal classes are instantiated should not impact the way the engine works).
-
- auto & mgr = AssetManager::get_instance();
-
- {
- // TODO: [design] the Sound class can't be directly included by the user as it includes
- // SoLoud headers.
- auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
- auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
- auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
- auto img = mgr.cache<Texture>("../asset/texture/img.png");
- auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
- }
-
- {
- auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg");
- auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav");
- auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav");
-
- auto img = mgr.cache<Texture>("../asset/texture/img.png");
- auto img1 = mgr.cache<Texture>("../asset/texture/second.png");
- }
-}
diff --git a/src/example/button.cpp b/src/example/button.cpp
new file mode 100644
index 0000000..ea7f528
--- /dev/null
+++ b/src/example/button.cpp
@@ -0,0 +1,43 @@
+#include <SDL2/SDL_timer.h>
+#include <chrono>
+#include <crepe/Component.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/Button.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Texture.h>
+#include <crepe/api/Transform.h>
+#include <crepe/facade/SDLContext.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/system/AnimatorSystem.h>
+#include <crepe/system/InputSystem.h>
+#include <crepe/system/RenderSystem.h>
+#include <crepe/types.h>
+using namespace crepe;
+using namespace std;
+
+int main(int argc, char * argv[]) {
+ Mediator mediator;
+ ComponentManager mgr {mediator};
+ RenderSystem sys {mediator};
+ EventManager event_mgr {mediator};
+ InputSystem input_sys {mediator};
+ SDLContext sdl_context {mediator};
+ GameObject obj = mgr.new_object("camera", "camera", vec2 {0, 0}, 0, 1);
+ auto & camera = obj.add_component<Camera>(
+ ivec2 {500, 500}, vec2 {500, 500},
+ Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f}
+ );
+ auto start = std::chrono::steady_clock::now();
+ while (true) {
+ const keyboard_state_t & keyboard_state = sdl_context.get_keyboard_state();
+ input_sys.update();
+ sys.update();
+ event_mgr.dispatch_events();
+ SDL_Delay(30);
+ }
+ return 0;
+}
diff --git a/src/example/gameloop.cpp b/src/example/gameloop.cpp
deleted file mode 100644
index a676f20..0000000
--- a/src/example/gameloop.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "crepe/api/LoopManager.h"
-using namespace crepe;
-int main() {
- LoopManager gameloop;
- gameloop.start();
- return 1;
-}
diff --git a/src/example/loadfont.cpp b/src/example/loadfont.cpp
new file mode 100644
index 0000000..dd7caff
--- /dev/null
+++ b/src/example/loadfont.cpp
@@ -0,0 +1,51 @@
+#include <SDL2/SDL_ttf.h>
+#include <crepe/api/Asset.h>
+#include <crepe/api/Text.h>
+#include <crepe/facade/Font.h>
+#include <crepe/facade/FontFacade.h>
+#include <crepe/facade/SDLContext.h>
+#include <crepe/manager/Mediator.h>
+#include <crepe/manager/ResourceManager.h>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <optional>
+using namespace crepe;
+int main() {
+
+ // SDLFontContext font_facade;
+ Mediator mediator;
+ FontFacade font_facade {};
+ SDLContext sdl_context {mediator};
+ // ComponentManager component_manager{mediator};
+ ResourceManager resource_manager {mediator};
+ try {
+ // Correct way to create a unique pointer for Text
+ std::unique_ptr<Text> label = std::make_unique<Text>(
+ 1, vec2(100, 100), vec2(0, 0), "OpenSymbol", Text::Data {}, "test text"
+ );
+ // std::cout << "Path: " << label->font.get_path() << std::endl;
+ Asset asset1 = font_facade.get_font_asset("OpenSymbol");
+ std::cout << asset1.get_path() << std::endl;
+ std::unique_ptr<Text> label2 = std::make_unique<Text>(
+ 1, vec2(100, 100), vec2(0, 0), "fsaafdafsdafsdafsdasfdds", Text::Data {}
+ );
+ Asset asset = Asset("test test");
+ label->font.emplace(asset);
+ std::cout << label->font.value().get_path() << std::endl;
+ // label2->font = std::make_optional(asset);
+ // std::cout << "Path: " << label2->font.get_path() << std::endl;
+ ResourceManager & resource_mgr = mediator.resource_manager;
+ const Font & res = resource_manager.get<Font>(label->font.value());
+ // TTF_Font * test_font = res.get_font();
+ // if (test_font == NULL) {
+ // std::cout << "error with font" << std::endl;
+ // } else {
+ // std::cout << "correct font retrieved" << std::endl;
+ // }
+ } catch (const std::exception & e) {
+ std::cout << "Standard exception thrown: " << e.what() << std::endl;
+ }
+
+ return 0;
+}
diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp
index 4571afb..e6b31a7 100644
--- a/src/example/rendering_particle.cpp
+++ b/src/example/rendering_particle.cpp
@@ -1,71 +1,74 @@
-#include "api/Camera.h"
-#include "system/ParticleSystem.h"
-#include <SDL2/SDL_timer.h>
-#include <crepe/ComponentManager.h>
+
+#include "api/Asset.h"
+#include "api/Text.h"
#include <crepe/Component.h>
+#include <crepe/api/Animator.h>
+#include <crepe/api/Button.h>
+#include <crepe/api/Camera.h>
#include <crepe/api/Color.h>
+#include <crepe/api/Engine.h>
#include <crepe/api/GameObject.h>
#include <crepe/api/ParticleEmitter.h>
#include <crepe/api/Rigidbody.h>
#include <crepe/api/Sprite.h>
-#include <crepe/api/Texture.h>
#include <crepe/api/Transform.h>
-#include <crepe/api/Vector2.h>
-#include <crepe/system/RenderSystem.h>
-
-#include <chrono>
-#include <iostream>
-#include <memory>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/Mediator.h>
+#include <crepe/types.h>
using namespace crepe;
using namespace std;
-int main(int argc, char * argv[]) {
- ComponentManager mgr;
- GameObject game_object = mgr.new_object("", "", Vector2{100, 100}, 0, 0.1);
- RenderSystem sys{mgr};
- ParticleSystem psys{mgr};
+class TestScene : public Scene {
+public:
+ void load_scene() {
+ GameObject game_object = new_object("", "", vec2 {0, 0}, 0, 1);
- Color color(255, 255, 255, 255);
+ Color color(255, 255, 255, 255);
- Sprite & test_sprite = game_object.add_component<Sprite>(
- make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{false, false});
- test_sprite.order_in_layer = 5;
+ Asset img {"asset/spritesheet/pokemon_spritesheet.png"};
- auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{
- .position = {0, 0},
- .max_particles = 10,
- .emission_rate = 0.1,
- .min_speed = 6,
- .max_speed = 20,
- .min_angle = -20,
- .max_angle = 20,
- .begin_lifespan = 0,
- .end_lifespan = 60,
- .force_over_time = Vector2{0, 0},
- .boundary{
- .width = 1000,
- .height = 1000,
- .offset = Vector2{0, 0},
- .reset_on_exit = false,
- },
- .sprite = test_sprite,
- });
- game_object.add_component<Camera>(Color::WHITE);
+ Sprite & test_sprite = game_object.add_component<Sprite>(
+ img,
+ Sprite::Data {
+ .color = color,
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 2,
+ .order_in_layer = 2,
+ .size = {1, 0},
+ .angle_offset = 0,
+ .position_offset = {0, 1},
+ .world_space = false,
+ }
+ );
- game_object
- .add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color,
- FlipSettings{false, false})
- .order_in_layer
- = 6;
+ auto & anim = game_object.add_component<Animator>(
+ test_sprite, ivec2 {56, 56}, uvec2 {4, 4},
+ Animator::Data {
+ .looping = 0,
+ }
+ );
- auto start = std::chrono::steady_clock::now();
- while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) {
- psys.update();
- sys.update();
- SDL_Delay(10);
+ anim.set_anim(1);
+ anim.pause();
+ anim.next_anim();
+
+ auto & cam = game_object.add_component<Camera>(
+ ivec2 {1280, 720}, vec2 {5, 5},
+ Camera::Data {
+ .bg_color = Color::WHITE,
+ .postion_offset = {1000, 1000},
+ }
+ );
}
+ string get_name() const { return "TestScene"; };
+};
+
+int main(int argc, char * argv[]) {
+ Engine engine;
+ engine.add_scene<TestScene>();
+ engine.main();
return 0;
}
diff --git a/src/example/replay.cpp b/src/example/replay.cpp
new file mode 100644
index 0000000..00a6502
--- /dev/null
+++ b/src/example/replay.cpp
@@ -0,0 +1,89 @@
+#include <crepe/api/Config.h>
+#include <crepe/api/Engine.h>
+#include <crepe/api/Script.h>
+
+using namespace crepe;
+using namespace std;
+
+class AnimationScript : public Script {
+ Transform * transform;
+ float t = 0;
+
+ void init() { transform = &get_component<Transform>(); }
+
+ void update() {
+ t += 0.05;
+ transform->position = {sin(t), cos(t)};
+ }
+};
+
+class Timeline : public Script {
+ unsigned i = 0;
+ recording_t recording;
+
+ void update() {
+ switch (i++) {
+ default:
+ break;
+ case 10:
+ logf("record start");
+ replay.record_start();
+ break;
+ case 60:
+ logf("record end, playing recording");
+ this->recording = replay.record_end();
+ replay.play(this->recording);
+ break;
+ case 61:
+ logf("done, releasing recording");
+ replay.release(this->recording);
+ break;
+ case 72:
+ logf("exit");
+ queue_event<ShutDownEvent>();
+ break;
+ };
+ }
+};
+
+class TestScene : public Scene {
+public:
+ using Scene::Scene;
+
+ void load_scene() {
+ Mediator & mediator = this->mediator;
+ ComponentManager & mgr = mediator.component_manager;
+
+ GameObject cam = mgr.new_object("cam");
+ cam.add_component<Camera>(
+ ivec2 {640, 480}, vec2 {3, 3},
+ Camera::Data {
+ .bg_color = Color::WHITE,
+ }
+ );
+
+ GameObject square = mgr.new_object("square");
+ square.add_component<Sprite>(
+ Asset {"asset/texture/square.png"},
+ Sprite::Data {
+ .size = {0.5, 0.5},
+ }
+ );
+ square.add_component<BehaviorScript>().set_script<AnimationScript>();
+
+ GameObject scapegoat = mgr.new_object("");
+ scapegoat.add_component<BehaviorScript>().set_script<Timeline>();
+ }
+
+ string get_name() const { return "scene1"; }
+};
+
+int main(int argc, char * argv[]) {
+ Config & cfg = Config::get_instance();
+ cfg.log.level = Log::Level::DEBUG;
+
+ Engine engine;
+
+ engine.add_scene<TestScene>();
+ return engine.main();
+}
diff --git a/src/example/savemgr.cpp b/src/example/savemgr.cpp
deleted file mode 100644
index 65c4a34..0000000
--- a/src/example/savemgr.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/** \file
- *
- * Standalone example for usage of the save manager
- */
-
-#include <cassert>
-#include <crepe/api/Config.h>
-#include <crepe/api/SaveManager.h>
-#include <crepe/util/Log.h>
-#include <crepe/util/Proxy.h>
-
-using namespace crepe;
-
-// unrelated setup code
-int _ = []() {
- // make sure all log messages get printed
- auto & cfg = Config::get_instance();
- cfg.log.level = Log::Level::TRACE;
-
- return 0; // satisfy compiler
-}();
-
-int main() {
- const char * key = "mygame.test";
-
- SaveManager & mgr = SaveManager::get_instance();
-
- dbg_logf("has key = {}", mgr.has(key));
- ValueBroker<int> prop = mgr.get<int>(key, 0);
- Proxy<int> val = mgr.get<int>(key, 0);
-
- dbg_logf("val = {}", mgr.get<int>(key).get());
- prop.set(1);
- dbg_logf("val = {}", mgr.get<int>(key).get());
- val = 2;
- dbg_logf("val = {}", mgr.get<int>(key).get());
- mgr.set<int>(key, 3);
- dbg_logf("val = {}", mgr.get<int>(key).get());
-
- dbg_logf("has key = {}", mgr.has(key));
- assert(true == mgr.has(key));
-
- return 0;
-}
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp
index 8aa7629..f41e9de 100644
--- a/src/test/AssetTest.cpp
+++ b/src/test/AssetTest.cpp
@@ -7,20 +7,15 @@ using namespace std;
using namespace crepe;
using namespace testing;
-class AssetTest : public Test {
-public:
- Config & cfg = Config::get_instance();
- void SetUp() override { this->cfg.asset.root_pattern = ".crepe-root"; }
-};
-
-TEST_F(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); }
+TEST(AssetTest, Existant) { ASSERT_NO_THROW(Asset {"asset/texture/img.png"}); }
-TEST_F(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); }
+TEST(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset {"asset/nonexistant"}); }
-TEST_F(AssetTest, Rootless) {
+TEST(AssetTest, Rootless) {
+ Config & cfg = Config::get_instance();
cfg.asset.root_pattern.clear();
string arbitrary = "\\/this is / /../passed through as-is";
- Asset asset{arbitrary};
+ Asset asset {arbitrary};
ASSERT_EQ(arbitrary, asset.get_path());
}
diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp
new file mode 100644
index 0000000..e548221
--- /dev/null
+++ b/src/test/AudioTest.cpp
@@ -0,0 +1,170 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <crepe/api/AudioSource.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/system/AudioSystem.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+class AudioTest : public Test {
+private:
+ class TestSoundContext : public SoundContext {
+ public:
+ MOCK_METHOD(SoundHandle, play, (Sound & resource), (override));
+ MOCK_METHOD(void, stop, (const SoundHandle &), (override));
+ MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override));
+ MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override));
+ };
+
+ class TestAudioSystem : public AudioSystem {
+ public:
+ using AudioSystem::AudioSystem;
+ StrictMock<TestSoundContext> context;
+ virtual SoundContext & get_context() { return this->context; }
+ };
+
+private:
+ Mediator mediator;
+ ComponentManager component_manager {mediator};
+ ResourceManager resource_manager {mediator};
+
+public:
+ TestAudioSystem system {mediator};
+ TestSoundContext & context = system.context;
+
+private:
+ GameObject entity = component_manager.new_object("name");
+
+public:
+ AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg");
+};
+
+TEST_F(AudioTest, Default) {
+ EXPECT_CALL(context, play(_)).Times(0);
+ EXPECT_CALL(context, stop(_)).Times(0);
+ EXPECT_CALL(context, set_volume(_, _)).Times(0);
+ EXPECT_CALL(context, set_loop(_, _)).Times(0);
+ system.fixed_update();
+}
+
+TEST_F(AudioTest, Play) {
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(0);
+ EXPECT_CALL(context, set_loop(_, _)).Times(0);
+ EXPECT_CALL(context, set_volume(_, _)).Times(0);
+ component.play();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(1);
+ EXPECT_CALL(context, set_loop(_, _)).Times(1);
+ EXPECT_CALL(context, set_volume(_, _)).Times(1);
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, Stop) {
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(0);
+ component.stop();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(1);
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, Volume) {
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_volume(_, _)).Times(0);
+ component.volume += 0.2;
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_volume(_, component.volume)).Times(1);
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, Looping) {
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_loop(_, _)).Times(0);
+ component.loop = !component.loop;
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, set_loop(_, component.loop)).Times(1);
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, StopOnDeactivate) {
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, stop(_)).Times(1);
+ component.active = false;
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, PlayOnActive) {
+ component.active = false;
+ component.play_on_awake = true;
+ system.fixed_update();
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(context, play(_)).Times(1);
+ EXPECT_CALL(context, set_loop(_, _)).Times(1);
+ EXPECT_CALL(context, set_volume(_, _)).Times(1);
+
+ component.active = true;
+ system.fixed_update();
+ }
+}
+
+TEST_F(AudioTest, PlayImmediately) {
+ component.play_on_awake = false;
+ component.play();
+
+ EXPECT_CALL(context, play(_)).Times(1);
+ EXPECT_CALL(context, set_volume(_, _)).Times(1);
+ EXPECT_CALL(context, set_loop(_, _)).Times(1);
+
+ system.fixed_update();
+}
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 8cb4232..ea92d96 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -1,9 +1,12 @@
target_sources(test_main PUBLIC
main.cpp
+ CollisionTest.cpp
PhysicsTest.cpp
ScriptTest.cpp
ParticleTest.cpp
+ AudioTest.cpp
AssetTest.cpp
+ ResourceManagerTest.cpp
OptionalRefTest.cpp
RenderSystemTest.cpp
EventTest.cpp
@@ -11,5 +14,15 @@ target_sources(test_main PUBLIC
SceneManagerTest.cpp
ValueBrokerTest.cpp
DBTest.cpp
+ Vector2Test.cpp
+ # LoopManagerTest.cpp
+ LoopTimerTest.cpp
+ InputTest.cpp
+ ScriptEventTest.cpp
+ ScriptSceneTest.cpp
+ Profiling.cpp
+ SaveManagerTest.cpp
+ ScriptSaveManagerTest.cpp
+ ScriptECSTest.cpp
+ ReplayManagerTest.cpp
)
-
diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp
new file mode 100644
index 0000000..c571c1a
--- /dev/null
+++ b/src/test/CollisionTest.cpp
@@ -0,0 +1,364 @@
+#include "api/BoxCollider.h"
+#include "manager/Mediator.h"
+#include <cmath>
+#include <cstddef>
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Transform.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/manager/Mediator.h>
+#include <crepe/system/CollisionSystem.h>
+#include <crepe/system/ScriptSystem.h>
+#include <crepe/types.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+class CollisionHandler : public Script {
+public:
+ int box_id;
+ function<void(const CollisionEvent & ev)> test_fn = [](const CollisionEvent & ev) {};
+
+ CollisionHandler(int box_id) { this->box_id = box_id; }
+
+ bool on_collision(const CollisionEvent & ev) {
+ //Log::logf("Box {} script on_collision()", box_id);
+ test_fn(ev);
+ return true;
+ }
+
+ void init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->on_collision(ev);
+ });
+ }
+ void update() {
+ // Retrieve component from the same GameObject this script is on
+ }
+};
+
+class CollisionTest : public Test {
+public:
+ Mediator m;
+ EventManager event_mgr {m};
+ ComponentManager mgr {m};
+ CollisionSystem collision_sys {m};
+ ScriptSystem script_sys {m};
+ LoopTimerManager loop_timer {m};
+
+ GameObject world = mgr.new_object("world", "", {50, 50});
+ GameObject game_object1 = mgr.new_object("object1", "", {50, 50});
+ GameObject game_object2 = mgr.new_object("object2", "", {50, 30});
+
+ CollisionHandler * script_object1_ref = nullptr;
+ CollisionHandler * script_object2_ref = nullptr;
+
+ void SetUp() override {
+ world.add_component<Rigidbody>(Rigidbody::Data {
+ // TODO: remove unrelated properties:
+ .body_type = Rigidbody::BodyType::STATIC,
+ });
+ // Create a box with an inner size of 10x10 units
+ world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {0, -100}); // Top
+ world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {0, 100}); // Bottom
+ world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {-100, 0}); // Left
+ world.add_component<BoxCollider>(vec2 {100, 100}, vec2 {100, 0}); // right
+
+ game_object1.add_component<Rigidbody>(Rigidbody::Data {
+ .mass = 1,
+ .gravity_scale = 0.01,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {0, 0},
+ .constraints = {0, 0, 0},
+ .elasticity_coefficient = 1,
+ .collision_layers = {0},
+ });
+ game_object1.add_component<BoxCollider>(vec2 {10, 10}, vec2 {0, 0});
+ BehaviorScript & script_object1
+ = game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1);
+ script_object1_ref = static_cast<CollisionHandler *>(script_object1.script.get());
+ ASSERT_NE(script_object1_ref, nullptr);
+
+ game_object2.add_component<Rigidbody>(Rigidbody::Data {
+ .mass = 1,
+ .gravity_scale = 0.01,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {0, 0},
+ .constraints = {0, 0, 0},
+ .elasticity_coefficient = 1,
+ .collision_layers = {0},
+ });
+ game_object2.add_component<BoxCollider>(vec2 {10, 10}, vec2 {0, 0});
+ BehaviorScript & script_object2
+ = game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2);
+ script_object2_ref = static_cast<CollisionHandler *>(script_object2.script.get());
+ ASSERT_NE(script_object2_ref, nullptr);
+
+ // Ensure Script::init() is called on all BehaviorScript instances
+ script_sys.fixed_update();
+ }
+};
+
+TEST_F(CollisionTest, collision_example) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ };
+ EXPECT_FALSE(collision_happend);
+ collision_sys.fixed_update();
+ EXPECT_FALSE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, -10);
+ EXPECT_EQ(ev.info.resolution.y, -10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 30};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, 0);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, 0);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION
+ );
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45, 30};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 0);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 0);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION
+ );
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 25};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_both) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, -10);
+ EXPECT_EQ(ev.info.resolution.y, -10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10, 10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10, 10};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION
+ );
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45, 30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10, 10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10, 10};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION
+ );
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 25};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10, 10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10, 10};
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_static_both) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn
+ = [&collision_happend](const CollisionEvent & ev) { collision_happend = true; };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 30};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_static_x_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ // is static should not be called
+ //FAIL();
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45, 30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10, 10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_static_y_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.self.transform.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(
+ ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION
+ );
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ // is static should not be called
+ //FAIL();
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50, 25};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10, 10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.fixed_update();
+ EXPECT_TRUE(collision_happend);
+}
diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp
index e80814c..7f2c339 100644
--- a/src/test/DBTest.cpp
+++ b/src/test/DBTest.cpp
@@ -1,6 +1,7 @@
-#include <crepe/facade/DB.h>
#include <gtest/gtest.h>
+#include <crepe/facade/DB.h>
+
using namespace std;
using namespace crepe;
using namespace testing;
@@ -26,3 +27,11 @@ TEST_F(DBTest, Has) {
db.set("foo", "bar");
EXPECT_EQ(db.has("foo"), true);
}
+
+TEST_F(DBTest, MultipleKeys) {
+ db.set("foo", "foo");
+ db.set("bar", "bar");
+
+ EXPECT_EQ(db.get("foo"), "foo");
+ EXPECT_EQ(db.get("bar"), "bar");
+}
diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp
index d5a5826..92436a9 100644
--- a/src/test/ECSTest.cpp
+++ b/src/test/ECSTest.cpp
@@ -1,23 +1,30 @@
#include <gtest/gtest.h>
#define protected public
+#define private public
-#include <crepe/ComponentManager.h>
#include <crepe/api/GameObject.h>
#include <crepe/api/Metadata.h>
#include <crepe/api/Transform.h>
#include <crepe/api/Vector2.h>
+#include <crepe/manager/ComponentManager.h>
using namespace std;
using namespace crepe;
class ECSTest : public ::testing::Test {
+ Mediator m;
+
public:
- ComponentManager mgr{};
+ ComponentManager mgr {m};
+
+ class TestComponent : public Component {
+ using Component::Component;
+ };
};
TEST_F(ECSTest, createGameObject) {
- GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
+ GameObject obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>();
vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>();
@@ -37,8 +44,8 @@ TEST_F(ECSTest, createGameObject) {
}
TEST_F(ECSTest, deleteAllGameObjects) {
- GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
- GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
+ GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
mgr.delete_all_components();
@@ -48,7 +55,7 @@ TEST_F(ECSTest, deleteAllGameObjects) {
EXPECT_EQ(metadata.size(), 0);
EXPECT_EQ(transform.size(), 0);
- GameObject obj2 = mgr.new_object("body2", "person2", Vector2{1, 0}, 5, 1);
+ GameObject obj2 = mgr.new_object("body2", "person2", vec2 {1, 0}, 5, 1);
metadata = mgr.get_components_by_type<Metadata>();
transform = mgr.get_components_by_type<Transform>();
@@ -70,8 +77,8 @@ TEST_F(ECSTest, deleteAllGameObjects) {
}
TEST_F(ECSTest, deleteGameObject) {
- GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
- GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
+ GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
mgr.delete_all_components_of_id(0);
@@ -96,7 +103,7 @@ TEST_F(ECSTest, deleteGameObject) {
TEST_F(ECSTest, manyGameObjects) {
for (int i = 0; i < 5000; i++) {
- GameObject obj = mgr.new_object("body", "person", Vector2{0, 0}, 0, i);
+ GameObject obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, i);
}
vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>();
@@ -128,7 +135,7 @@ TEST_F(ECSTest, manyGameObjects) {
for (int i = 0; i < 10000 - 5000; i++) {
string tag = "person" + to_string(i);
- GameObject obj = mgr.new_object("body", tag, Vector2{0, 0}, i, 0);
+ GameObject obj = mgr.new_object("body", tag, vec2 {0, 0}, i, 0);
}
metadata = mgr.get_components_by_type<Metadata>();
@@ -136,11 +143,58 @@ TEST_F(ECSTest, manyGameObjects) {
EXPECT_EQ(metadata.size(), 10000 - 5000);
EXPECT_EQ(transform.size(), 10000);
+
+ for (int i = 0; i < 10000 - 5000; i++) {
+ EXPECT_EQ(metadata[i].get().game_object_id, i + 5000);
+ EXPECT_EQ(metadata[i].get().name, "body");
+ EXPECT_EQ(metadata[i].get().tag, "person" + to_string(i));
+ EXPECT_EQ(metadata[i].get().parent, -1);
+ EXPECT_EQ(metadata[i].get().children.size(), 0);
+
+ EXPECT_EQ(transform[i].get().game_object_id, i);
+ EXPECT_EQ(transform[i].get().position.x, 0);
+ EXPECT_EQ(transform[i].get().position.y, 0);
+ EXPECT_EQ(transform[i].get().rotation, 0);
+ EXPECT_EQ(transform[i].get().scale, i);
+ }
+
+ mgr.delete_all_components();
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 0);
+ EXPECT_EQ(transform.size(), 0);
+
+ for (int i = 0; i < 10000; i++) {
+ string name = "body" + to_string(i);
+ GameObject obj = mgr.new_object(name, "person", vec2 {0, 0}, 0, 0);
+ }
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 10000);
+ EXPECT_EQ(transform.size(), 10000);
+
+ for (int i = 0; i < 10000; i++) {
+ EXPECT_EQ(metadata[i].get().game_object_id, i);
+ EXPECT_EQ(metadata[i].get().name, "body" + to_string(i));
+ EXPECT_EQ(metadata[i].get().tag, "person");
+ EXPECT_EQ(metadata[i].get().parent, -1);
+ EXPECT_EQ(metadata[i].get().children.size(), 0);
+
+ EXPECT_EQ(transform[i].get().game_object_id, i);
+ EXPECT_EQ(transform[i].get().position.x, 0);
+ EXPECT_EQ(transform[i].get().position.y, 0);
+ EXPECT_EQ(transform[i].get().rotation, 0);
+ EXPECT_EQ(transform[i].get().scale, 0);
+ }
}
TEST_F(ECSTest, getComponentsByID) {
- GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
- GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
+ GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0);
vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1);
@@ -163,19 +217,21 @@ TEST_F(ECSTest, getComponentsByID) {
TEST_F(ECSTest, tooMuchComponents) {
try {
- GameObject obj0 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
- obj0.add_component<Transform>(Vector2{10, 10}, 0, 1);
+ GameObject obj0 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ obj0.add_component<Transform>(vec2 {10, 10}, 0, 1);
} catch (const exception & e) {
- EXPECT_EQ(e.what(),
- string("Exceeded maximum number of instances for this component type"));
+ EXPECT_EQ(
+ e.what(), string("Exceeded maximum number of instances for this component type")
+ );
}
try {
- GameObject obj1 = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
obj1.add_component<Metadata>("body", "person");
} catch (const exception & e) {
- EXPECT_EQ(e.what(),
- string("Exceeded maximum number of instances for this component type"));
+ EXPECT_EQ(
+ e.what(), string("Exceeded maximum number of instances for this component type")
+ );
}
vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>();
@@ -187,11 +243,11 @@ TEST_F(ECSTest, tooMuchComponents) {
TEST_F(ECSTest, partentChild) {
{
- GameObject body = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1);
- GameObject right_leg = mgr.new_object("rightLeg", "person", Vector2{1, 1}, 0, 1);
- GameObject left_leg = mgr.new_object("leftLeg", "person", Vector2{1, 1}, 0, 1);
- GameObject right_foot = mgr.new_object("rightFoot", "person", Vector2{2, 2}, 0, 1);
- GameObject left_foot = mgr.new_object("leftFoot", "person", Vector2{2, 2}, 0, 1);
+ GameObject body = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ GameObject right_leg = mgr.new_object("rightLeg", "person", vec2 {1, 1}, 0, 1);
+ GameObject left_leg = mgr.new_object("leftLeg", "person", vec2 {1, 1}, 0, 1);
+ GameObject right_foot = mgr.new_object("rightFoot", "person", vec2 {2, 2}, 0, 1);
+ GameObject left_foot = mgr.new_object("leftFoot", "person", vec2 {2, 2}, 0, 1);
// Set the parent of each GameObject
right_foot.set_parent(right_leg);
@@ -234,3 +290,195 @@ TEST_F(ECSTest, partentChild) {
EXPECT_EQ(metadata[1].get().children[0], 3);
EXPECT_EQ(metadata[2].get().children[0], 4);
}
+
+TEST_F(ECSTest, persistent) {
+ GameObject obj0 = mgr.new_object("obj0", "obj0", vec2 {0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("obj1", "obj1", vec2 {0, 0}, 0, 1);
+ obj1.set_persistent();
+ GameObject obj2 = mgr.new_object("obj2", "obj2", vec2 {0, 0}, 0, 1);
+
+ vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>();
+ vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 3);
+ EXPECT_EQ(transform.size(), 3);
+
+ mgr.delete_components_by_id<Metadata>(1);
+ mgr.delete_components<Metadata>();
+ mgr.delete_all_components_of_id(1);
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 1);
+ EXPECT_EQ(transform.size(), 3);
+
+ mgr.delete_all_components();
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 1);
+ EXPECT_EQ(transform.size(), 1);
+
+ EXPECT_EQ(metadata[0].get().game_object_id, 1);
+ EXPECT_EQ(metadata[0].get().name, "obj1");
+ EXPECT_EQ(metadata[0].get().tag, "obj1");
+ EXPECT_EQ(metadata[0].get().parent, -1);
+ EXPECT_EQ(metadata[0].get().children.size(), 0);
+
+ EXPECT_EQ(transform[0].get().game_object_id, 1);
+ EXPECT_EQ(transform[0].get().position.x, 0);
+ EXPECT_EQ(transform[0].get().position.y, 0);
+
+ GameObject obj3 = mgr.new_object("obj3", "obj3", vec2 {0, 0}, 0, 5);
+ GameObject obj4 = mgr.new_object("obj4", "obj4", vec2 {0, 0}, 0, 5);
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 3);
+ EXPECT_EQ(transform.size(), 3);
+
+ EXPECT_EQ(metadata[0].get().game_object_id, 0);
+ EXPECT_EQ(metadata[0].get().name, "obj3");
+
+ EXPECT_EQ(metadata[1].get().game_object_id, 1);
+ EXPECT_EQ(metadata[1].get().name, "obj1");
+
+ EXPECT_EQ(metadata[2].get().game_object_id, 2);
+ EXPECT_EQ(metadata[2].get().name, "obj4");
+
+ EXPECT_EQ(transform[0].get().game_object_id, 0);
+ EXPECT_EQ(transform[0].get().scale, 5);
+
+ EXPECT_EQ(transform[1].get().game_object_id, 1);
+ EXPECT_EQ(transform[1].get().scale, 1);
+
+ EXPECT_EQ(transform[2].get().game_object_id, 2);
+ EXPECT_EQ(transform[2].get().scale, 5);
+}
+
+TEST_F(ECSTest, resetPersistent) {
+ GameObject obj0 = mgr.new_object("obj0", "obj0", vec2 {0, 0}, 0, 1);
+ GameObject obj1 = mgr.new_object("obj1", "obj1", vec2 {0, 0}, 0, 1);
+ obj1.set_persistent();
+ GameObject obj2 = mgr.new_object("obj2", "obj2", vec2 {0, 0}, 0, 1);
+
+ vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>();
+ vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 3);
+ EXPECT_EQ(transform.size(), 3);
+
+ mgr.delete_all_components();
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 1);
+ EXPECT_EQ(transform.size(), 1);
+
+ vector<reference_wrapper<Metadata>> metadata_id = mgr.get_components_by_id<Metadata>(1);
+
+ EXPECT_EQ(metadata_id.size(), 1);
+ EXPECT_EQ(metadata_id[0].get().game_object_id, 1);
+ EXPECT_EQ(metadata_id[0].get().name, "obj1");
+
+ mgr.set_persistent(1, false);
+ mgr.delete_all_components();
+
+ metadata = mgr.get_components_by_type<Metadata>();
+ transform = mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 0);
+ EXPECT_EQ(transform.size(), 0);
+}
+
+TEST_F(ECSTest, IDByName) {
+ GameObject foo = mgr.new_object("foo");
+ GameObject bar = mgr.new_object("bar");
+
+ {
+ auto objects = mgr.get_objects_by_name("");
+ EXPECT_EQ(objects.size(), 0);
+ }
+
+ {
+ auto objects = mgr.get_objects_by_name("foo");
+ EXPECT_EQ(objects.size(), 1);
+ EXPECT_TRUE(objects.contains(foo.id));
+ }
+}
+
+TEST_F(ECSTest, IDByTag) {
+ GameObject foo = mgr.new_object("foo", "common tag");
+ GameObject bar = mgr.new_object("bar", "common tag");
+
+ {
+ auto objects = mgr.get_objects_by_tag("");
+ EXPECT_EQ(objects.size(), 0);
+ }
+
+ {
+ auto objects = mgr.get_objects_by_tag("common tag");
+ EXPECT_EQ(objects.size(), 2);
+ EXPECT_TRUE(objects.contains(foo.id));
+ EXPECT_TRUE(objects.contains(bar.id));
+ }
+}
+
+TEST_F(ECSTest, ComponentsByName) {
+ GameObject foo = mgr.new_object("foo");
+ foo.add_component<TestComponent>();
+ GameObject bar = mgr.new_object("bar");
+ bar.add_component<TestComponent>();
+ bar.add_component<TestComponent>();
+
+ {
+ auto objects = mgr.get_components_by_name<TestComponent>("");
+ EXPECT_EQ(objects.size(), 0);
+ }
+
+ {
+ auto objects = mgr.get_components_by_name<TestComponent>("foo");
+ EXPECT_EQ(objects.size(), 1);
+ }
+
+ {
+ auto objects = mgr.get_components_by_name<TestComponent>("bar");
+ EXPECT_EQ(objects.size(), 2);
+ }
+}
+
+TEST_F(ECSTest, ComponentsByTag) {
+ GameObject foo = mgr.new_object("foo", "common tag");
+ foo.add_component<TestComponent>();
+ GameObject bar = mgr.new_object("bar", "common tag");
+ bar.add_component<TestComponent>();
+ bar.add_component<TestComponent>();
+
+ {
+ auto objects = mgr.get_components_by_tag<TestComponent>("");
+ EXPECT_EQ(objects.size(), 0);
+ }
+
+ {
+ auto objects = mgr.get_components_by_tag<TestComponent>("common tag");
+ EXPECT_EQ(objects.size(), 3);
+ }
+}
+
+TEST_F(ECSTest, Snapshot) {
+ GameObject foo = mgr.new_object("foo");
+
+ foo.transform.position = {1, 1};
+
+ ComponentManager::Snapshot snapshot = mgr.save();
+
+ foo.transform.position = {0, 0};
+
+ mgr.restore(snapshot);
+
+ EXPECT_EQ(foo.transform.position, (vec2 {1, 1}));
+}
diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp
index b0e6c9c..6105679 100644
--- a/src/test/EventTest.cpp
+++ b/src/test/EventTest.cpp
@@ -1,8 +1,6 @@
-
-#include "api/Event.h"
-#include "api/EventManager.h"
-#include "api/IKeyListener.h"
-#include "api/IMouseListener.h"
+#include <crepe/api/Event.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/manager/Mediator.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace std;
@@ -11,72 +9,56 @@ using namespace crepe;
class EventManagerTest : public ::testing::Test {
protected:
+ Mediator mediator;
+ EventManager event_mgr {mediator};
void SetUp() override {
// Clear any existing subscriptions or events before each test
- EventManager::get_instance().clear();
+ event_mgr.clear();
}
void TearDown() override {
// Ensure cleanup after each test
- EventManager::get_instance().clear();
+ event_mgr.clear();
}
};
-class MockKeyListener : public IKeyListener {
-public:
- MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override));
- MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override));
-};
-
-class MockMouseListener : public IMouseListener {
-public:
- MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override));
- MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override));
- MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override));
- MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override));
-};
-
TEST_F(EventManagerTest, EventSubscription) {
- EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) {
- std::cout << "Key Event Triggered" << std::endl;
- return true;
- };
+ EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; };
// Subscribe to KeyPressEvent
- EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1);
+ event_mgr.subscribe<KeyPressEvent>(key_handler, 1);
// Verify subscription (not directly verifiable; test by triggering event)
- EventManager::get_instance().trigger_event<KeyPressEvent>(
- KeyPressEvent{
+ event_mgr.trigger_event<KeyPressEvent>(
+ KeyPressEvent {
.repeat = true,
.key = Keycode::A,
},
- 1);
- EventManager::get_instance().trigger_event<KeyPressEvent>(
- KeyPressEvent{
+ 1
+ );
+ event_mgr.trigger_event<KeyPressEvent>(
+ KeyPressEvent {
.repeat = true,
.key = Keycode::A,
},
- EventManager::CHANNEL_ALL);
+ EventManager::CHANNEL_ALL
+ );
}
TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) {
bool triggered = false;
EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) {
triggered = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false;
};
- EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler,
- EventManager::CHANNEL_ALL);
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler, EventManager::CHANNEL_ALL);
- MouseClickEvent click_event{
- .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE};
- EventManager::get_instance().trigger_event<MouseClickEvent>(click_event,
- EventManager::CHANNEL_ALL);
+ MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE};
+ event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
EXPECT_TRUE(triggered);
}
@@ -85,24 +67,21 @@ TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) {
int test_channel = 1;
EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) {
triggered = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false;
};
- EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel);
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler, test_channel);
- MouseClickEvent click_event{
- .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE};
- EventManager::get_instance().trigger_event<MouseClickEvent>(click_event,
- EventManager::CHANNEL_ALL);
+ MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE};
+ event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
EXPECT_FALSE(triggered);
- EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel);
+ event_mgr.trigger_event<MouseClickEvent>(click_event, test_channel);
}
TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {
- EventManager & event_manager = EventManager::get_instance();
// Flags to track handler calls
bool triggered_true = false;
@@ -111,28 +90,27 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {
// Handlers
EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) {
triggered_true = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return true; // Stops propagation
};
EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) {
triggered_false = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false; // Allows propagation
};
// Test event
- MouseClickEvent click_event{
- .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE};
- event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);
- event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);
+ MouseClickEvent click_event {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE};
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);
// Trigger event
- event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
+ event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
// Check that only the true handler was triggered
EXPECT_TRUE(triggered_true);
@@ -141,12 +119,12 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {
// Reset and clear
triggered_true = false;
triggered_false = false;
- event_manager.clear();
- event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);
- event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);
+ event_mgr.clear();
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL);
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL);
// Trigger event again
- event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
+ event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL);
// Check that both handlers were triggered
EXPECT_TRUE(triggered_true);
@@ -154,39 +132,39 @@ TEST_F(EventManagerTest, EventManagerTest_callback_propagation) {
}
TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) {
- EventManager & event_manager = EventManager::get_instance();
bool triggered1 = false;
bool triggered2 = false;
int test_channel = 1;
EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) {
triggered1 = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false; // Allows propagation
};
EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) {
triggered2 = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false; // Allows propagation
};
- event_manager.subscribe<MouseClickEvent>(mouse_handler1);
- event_manager.subscribe<MouseClickEvent>(mouse_handler2, test_channel);
-
- event_manager.queue_event<MouseClickEvent>(
- MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});
- event_manager.queue_event<MouseClickEvent>(
- MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE},
- test_channel);
- event_manager.dispatch_events();
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler1);
+ event_mgr.subscribe<MouseClickEvent>(mouse_handler2, test_channel);
+
+ event_mgr.queue_event<MouseClickEvent>(
+ MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}
+ );
+ event_mgr.queue_event<MouseClickEvent>(
+ MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE},
+ test_channel
+ );
+ event_mgr.dispatch_events();
EXPECT_TRUE(triggered1);
EXPECT_TRUE(triggered2);
}
TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {
- EventManager & event_manager = EventManager::get_instance();
// Flags to track if handlers are triggered
bool triggered1 = false;
@@ -195,29 +173,30 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {
// Define EventHandlers
EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) {
triggered1 = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false; // Allows propagation
};
EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) {
triggered2 = true;
- EXPECT_EQ(e.mouse_x, 100);
- EXPECT_EQ(e.mouse_y, 200);
+ EXPECT_EQ(e.mouse_pos.x, 100);
+ EXPECT_EQ(e.mouse_pos.y, 200);
EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
return false; // Allows propagation
};
// Subscribe handlers
- subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1);
- subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2);
+ subscription_t handler1_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler1);
+ subscription_t handler2_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler2);
// Queue events
- event_manager.queue_event<MouseClickEvent>(
- MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});
+ event_mgr.queue_event<MouseClickEvent>(
+ MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}
+ );
// Dispatch events - both handlers should be triggered
- event_manager.dispatch_events();
+ event_mgr.dispatch_events();
EXPECT_TRUE(triggered1); // Handler 1 should be triggered
EXPECT_TRUE(triggered2); // Handler 2 should be triggered
@@ -226,14 +205,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {
triggered2 = false;
// Unsubscribe handler1
- event_manager.unsubscribe(handler1_id);
+ event_mgr.unsubscribe(handler1_id);
// Queue the same event again
- event_manager.queue_event<MouseClickEvent>(
- MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});
+ event_mgr.queue_event<MouseClickEvent>(
+ MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}
+ );
// Dispatch events - only handler 2 should be triggered, handler 1 should NOT
- event_manager.dispatch_events();
+ event_mgr.dispatch_events();
EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered
EXPECT_TRUE(triggered2); // Handler 2 should be triggered
@@ -241,14 +221,15 @@ TEST_F(EventManagerTest, EventManagerTest_unsubscribe) {
triggered2 = false;
// Unsubscribe handler2
- event_manager.unsubscribe(handler2_id);
+ event_mgr.unsubscribe(handler2_id);
// Queue the event again
- event_manager.queue_event<MouseClickEvent>(
- MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE});
+ event_mgr.queue_event<MouseClickEvent>(
+ MouseClickEvent {.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}
+ );
// Dispatch events - no handler should be triggered
- event_manager.dispatch_events();
+ event_mgr.dispatch_events();
EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered
EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered
}
diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp
new file mode 100644
index 0000000..a1fe59a
--- /dev/null
+++ b/src/test/InputTest.cpp
@@ -0,0 +1,337 @@
+#include <gtest/gtest.h>
+
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/system/RenderSystem.h>
+#define protected public
+#define private public
+
+#include "api/KeyCodes.h"
+#include "manager/ComponentManager.h"
+#include "manager/EventManager.h"
+#include "manager/Mediator.h"
+#include "system/InputSystem.h"
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_keycode.h>
+#include <crepe/api/Button.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Metadata.h>
+#include <crepe/api/Transform.h>
+#include <crepe/api/Vector2.h>
+#include <crepe/facade/SDLContext.h>
+#include <gmock/gmock.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+
+class InputTest : public ::testing::Test {
+public:
+ Mediator mediator;
+ ComponentManager mgr {mediator};
+ SDLContext sdl_context {mediator};
+
+ InputSystem input_system {mediator};
+ ResourceManager resman {mediator};
+ RenderSystem render {mediator};
+ EventManager event_manager {mediator};
+ //GameObject camera;
+ vec2 offset = {100, 200};
+
+protected:
+ void SetUp() override {
+ GameObject obj = mgr.new_object("camera", "camera", offset, 0, 1);
+ auto & camera = obj.add_component<Camera>(
+ ivec2 {500, 500}, vec2 {500, 500},
+ Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f}
+ );
+ render.frame_update();
+ //mediator.event_manager = event_manager;
+ //mediator.component_manager = mgr;
+ //event_manager.clear();
+ }
+ void TearDown() override {}
+ void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) {
+ SDL_Event event;
+
+ // Simulate Mouse Button Down event
+ SDL_zero(event);
+ event.type = SDL_MOUSEBUTTONDOWN;
+ event.button.x = mouse_x;
+ event.button.y = mouse_y;
+ event.button.button = mouse_button;
+ SDL_PushEvent(&event);
+
+ // Simulate Mouse Button Up event
+ SDL_zero(event);
+ event.type = SDL_MOUSEBUTTONUP;
+ event.button.x = mouse_x;
+ event.button.y = mouse_y;
+ event.button.button = mouse_button;
+ SDL_PushEvent(&event);
+ }
+};
+
+TEST_F(InputTest, MouseDown) {
+ bool mouse_triggered = false;
+ EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) {
+ mouse_triggered = true;
+ //middle of the screen = 0,0
+ EXPECT_EQ(event.mouse_pos, offset);
+ EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE);
+ return false;
+ };
+ event_manager.subscribe<MousePressEvent>(on_mouse_down);
+
+ SDL_Event event;
+ SDL_zero(event);
+ event.type = SDL_MOUSEBUTTONDOWN;
+ // middle of the screen of a 500*500 camera = 250*250
+ event.button.x = 250;
+ event.button.y = 250;
+ event.button.button = SDL_BUTTON_LEFT;
+ SDL_PushEvent(&event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(mouse_triggered);
+}
+
+TEST_F(InputTest, MouseUp) {
+ bool function_triggered = false;
+ EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) {
+ function_triggered = true;
+ EXPECT_EQ(e.mouse_pos, offset);
+ EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE);
+ return false;
+ };
+ event_manager.subscribe<MouseReleaseEvent>(on_mouse_release);
+
+ SDL_Event event;
+ SDL_zero(event);
+ event.type = SDL_MOUSEBUTTONUP;
+ event.button.x = 250;
+ event.button.y = 250;
+ event.button.button = SDL_BUTTON_LEFT;
+ SDL_PushEvent(&event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(function_triggered);
+}
+
+TEST_F(InputTest, MouseMove) {
+ bool function_triggered = false;
+ EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) {
+ function_triggered = true;
+ EXPECT_EQ(e.mouse_pos, offset);
+ EXPECT_EQ(e.mouse_delta.x, 10);
+ EXPECT_EQ(e.mouse_delta.y, 10);
+ return false;
+ };
+ event_manager.subscribe<MouseMoveEvent>(on_mouse_move);
+
+ SDL_Event event;
+ SDL_zero(event);
+ event.type = SDL_MOUSEMOTION;
+ event.motion.x = 250;
+ event.motion.y = 250;
+ event.motion.xrel = 10;
+ event.motion.yrel = 10;
+ SDL_PushEvent(&event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(function_triggered);
+}
+
+TEST_F(InputTest, KeyDown) {
+ bool function_triggered = false;
+
+ // Define event handler for KeyPressEvent
+ EventHandler<KeyPressEvent> on_key_press = [&](const KeyPressEvent & event) {
+ function_triggered = true;
+ EXPECT_EQ(event.key, Keycode::B); // Validate the key is 'B'
+ EXPECT_EQ(event.repeat, true); // Validate repeat flag
+ return false;
+ };
+
+ event_manager.subscribe<KeyPressEvent>(on_key_press);
+
+ // Simulate SDL_KEYDOWN event
+ SDL_Event test_event;
+ SDL_zero(test_event);
+ test_event.type = SDL_KEYDOWN; // Key down event
+ test_event.key.keysym.scancode = SDL_SCANCODE_B; // Set scancode for 'B'
+ test_event.key.repeat = 1; // Set repeat flag
+ SDL_PushEvent(&test_event);
+
+ input_system.fixed_update(); // Process the event
+ event_manager.dispatch_events(); // Dispatch events to handlers
+
+ EXPECT_TRUE(function_triggered); // Check if the handler was triggered
+}
+
+TEST_F(InputTest, KeyUp) {
+ bool function_triggered = false;
+ EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) {
+ function_triggered = true;
+ EXPECT_EQ(event.key, Keycode::B);
+ return false;
+ };
+ event_manager.subscribe<KeyReleaseEvent>(on_key_release);
+
+ SDL_Event event;
+ SDL_zero(event);
+ event.type = SDL_KEYUP;
+ event.key.keysym.scancode = SDL_SCANCODE_B;
+ SDL_PushEvent(&event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(function_triggered);
+}
+
+TEST_F(InputTest, MouseClick) {
+ bool on_click_triggered = false;
+ EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) {
+ on_click_triggered = true;
+ EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE);
+ EXPECT_EQ(event.mouse_pos, offset);
+ return false;
+ };
+ event_manager.subscribe<MouseClickEvent>(on_mouse_click);
+
+ this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(on_click_triggered);
+}
+
+TEST_F(InputTest, testButtonClick) {
+ GameObject button_obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ bool button_clicked = false;
+ event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) {
+ button_clicked = true;
+ EXPECT_EQ(event.metadata.game_object_id, button_obj.id);
+ return false;
+ });
+ auto & button
+ = button_obj.add_component<Button>(vec2 {100, 100}, Button::Data {}, vec2 {0, 0});
+
+ bool hover = false;
+ button.active = true;
+ this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_FALSE(button_clicked);
+
+ this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(button_clicked);
+}
+TEST_F(InputTest, buttonPositionCamera) {
+ GameObject button_obj = mgr.new_object("body", "person", vec2 {50, 50}, 0, 1);
+ bool button_clicked = false;
+ event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) {
+ button_clicked = true;
+ EXPECT_EQ(event.metadata.game_object_id, button_obj.id);
+ return false;
+ });
+ auto & button = button_obj.add_component<Button>(
+ vec2 {10, 10},
+ Button::Data {
+ .world_space = false,
+ },
+ vec2 {0, 0}
+ );
+
+ bool hover = false;
+ button.active = true;
+ this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_FALSE(button_clicked);
+
+ this->simulate_mouse_click(300, 300, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(button_clicked);
+}
+TEST_F(InputTest, buttonPositionWorld) {
+ GameObject button_obj = mgr.new_object("body", "person", vec2 {50, 50}, 0, 1);
+ bool button_clicked = false;
+ event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) {
+ button_clicked = true;
+ EXPECT_EQ(event.metadata.game_object_id, button_obj.id);
+ return false;
+ });
+ auto & button = button_obj.add_component<Button>(
+ vec2 {10, 10},
+ Button::Data {
+ .world_space = true,
+ },
+ vec2 {0, 0}
+ );
+ bool hover = false;
+ button.active = true;
+ this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_FALSE(button_clicked);
+
+ this->simulate_mouse_click(300, 300, SDL_BUTTON_LEFT);
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_FALSE(button_clicked);
+}
+TEST_F(InputTest, testButtonHover) {
+ GameObject button_obj = mgr.new_object("body", "person", vec2 {0, 0}, 0, 1);
+ bool button_hover = false;
+ event_manager.subscribe<ButtonEnterEvent>([&](const ButtonEnterEvent & event) {
+ button_hover = true;
+ EXPECT_EQ(event.metadata.game_object_id, button_obj.id);
+ return false;
+ });
+ event_manager.subscribe<ButtonExitEvent>([&](const ButtonExitEvent & event) {
+ button_hover = false;
+ EXPECT_EQ(event.metadata.game_object_id, button_obj.id);
+ return false;
+ });
+ auto & button = button_obj.add_component<Button>(
+ vec2 {100, 100},
+ Button::Data {
+ .world_space = true,
+ },
+ vec2 {0, 0}
+ );
+ // Mouse on button
+ SDL_Event hover_event;
+ SDL_zero(hover_event);
+ hover_event.type = SDL_MOUSEMOTION;
+ hover_event.motion.x = 250;
+ hover_event.motion.y = 250;
+ hover_event.motion.xrel = 10;
+ hover_event.motion.yrel = 10;
+ SDL_PushEvent(&hover_event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_TRUE(button.hover);
+ EXPECT_TRUE(button_hover);
+ // Mouse not on button
+ SDL_Event event;
+ SDL_zero(event);
+ event.type = SDL_MOUSEMOTION;
+ event.motion.x = 500;
+ event.motion.y = 500;
+ event.motion.xrel = 10;
+ event.motion.yrel = 10;
+ SDL_PushEvent(&event);
+
+ input_system.fixed_update();
+ event_manager.dispatch_events();
+ EXPECT_FALSE(button.hover);
+ EXPECT_FALSE(button_hover);
+}
diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp
new file mode 100644
index 0000000..302d96c
--- /dev/null
+++ b/src/test/LoopManagerTest.cpp
@@ -0,0 +1,78 @@
+#include <chrono>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <thread>
+#define private public
+#define protected public
+#include <crepe/api/Engine.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/manager/LoopTimerManager.h>
+using namespace std::chrono;
+using namespace crepe;
+
+class DISABLED_LoopManagerTest : public ::testing::Test {
+protected:
+ class TestGameLoop : public crepe::Engine {
+ public:
+ MOCK_METHOD(void, fixed_update, (), (override));
+ MOCK_METHOD(void, frame_update, (), (override));
+ };
+
+ TestGameLoop test_loop;
+ void SetUp() override {}
+};
+
+TEST_F(DISABLED_LoopManagerTest, FixedUpdate) {
+ // Arrange
+ test_loop.loop_timer.set_target_framerate(60);
+
+ // Set expectations for the mock calls
+ EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65));
+ EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52));
+
+ // Start the loop in a separate thread
+ std::thread loop_thread([&]() { test_loop.start(); });
+
+ // Let the loop run for exactly 1 second
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ // Stop the game loop
+ test_loop.game_running = false;
+ // Wait for the loop thread to finish
+ loop_thread.join();
+
+ // Test finished
+}
+
+TEST_F(DISABLED_LoopManagerTest, ScaledFixedUpdate) {
+ // Arrange
+ test_loop.loop_timer.set_target_framerate(60);
+
+ // Set expectations for the mock calls
+ EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65));
+ EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52));
+
+ // Start the loop in a separate thread
+ std::thread loop_thread([&]() { test_loop.start(); });
+
+ // Let the loop run for exactly 1 second
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ // Stop the game loop
+ test_loop.game_running = false;
+ // Wait for the loop thread to finish
+ loop_thread.join();
+
+ // Test finished
+}
+
+TEST_F(DISABLED_LoopManagerTest, ShutDown) {
+ // Arrange
+ test_loop.loop_timer.set_target_framerate(60);
+ // Start the loop in a separate thread
+ std::thread loop_thread([&]() { test_loop.start(); });
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ test_loop.event_manager.trigger_event<ShutDownEvent>(ShutDownEvent {});
+ // Wait for the loop thread to finish
+ loop_thread.join();
+}
diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp
new file mode 100644
index 0000000..52e412e
--- /dev/null
+++ b/src/test/LoopTimerTest.cpp
@@ -0,0 +1,97 @@
+#include <chrono>
+#include <gtest/gtest.h>
+#include <thread>
+
+#define private public
+#define protected public
+
+#include <crepe/manager/LoopTimerManager.h>
+#include <crepe/manager/Mediator.h>
+
+using namespace std::chrono;
+using namespace crepe;
+
+class LoopTimerTest : public ::testing::Test {
+protected:
+ Mediator mediator;
+ LoopTimerManager loop_timer {mediator};
+
+ void SetUp() override { loop_timer.start(); }
+};
+
+TEST_F(LoopTimerTest, EnforcesTargetFrameRate) {
+ // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms)
+ loop_timer.set_target_framerate(60);
+
+ auto start_time = steady_clock::now();
+ loop_timer.enforce_frame_rate();
+
+ auto elapsed_time = steady_clock::now() - start_time;
+ auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count();
+
+ // For 60 FPS, the target frame time is around 16.67ms
+ ASSERT_NEAR(elapsed_ms, 16.7, 5);
+}
+
+TEST_F(LoopTimerTest, SetTargetFps) {
+ // Set the target FPS to 120
+ loop_timer.set_target_framerate(120);
+
+ // Calculate the expected frame time (~8.33ms per frame)
+ duration_t expected_frame_time = std::chrono::duration<float>(1.0 / 120.0);
+
+ ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001);
+}
+
+TEST_F(LoopTimerTest, DeltaTimeCalculation) {
+ // Set the target FPS to 60 (16.67 ms per frame)
+ loop_timer.set_target_framerate(60);
+
+ auto start_time = steady_clock::now();
+ loop_timer.update();
+ auto end_time = steady_clock::now();
+
+ // Check the delta time
+ duration_t delta_time = loop_timer.get_delta_time();
+
+ auto elapsed_time = duration_cast<seconds>(end_time - start_time).count();
+
+ // Assert that delta_time is close to the elapsed time
+ ASSERT_NEAR(delta_time.count(), elapsed_time, 1);
+}
+
+TEST_F(LoopTimerTest, DISABLED_getCurrentTime) {
+ // Set the target FPS to 60 (16.67 ms per frame)
+ loop_timer.set_target_framerate(60);
+
+ auto start_time = steady_clock::now();
+
+ // Sleep
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ loop_timer.update();
+
+ auto end_time = steady_clock::now();
+
+ // Get the elapsed time in seconds as a double
+ auto elapsed_time
+ = std::chrono::duration_cast<elapsed_time_t>(end_time - start_time).count();
+
+ ASSERT_NEAR(loop_timer.get_elapsed_time().count(), elapsed_time, 5);
+}
+TEST_F(LoopTimerTest, getFPS) {
+ // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms)
+ loop_timer.set_target_framerate(60);
+
+ auto start_time = steady_clock::now();
+ loop_timer.enforce_frame_rate();
+
+ auto elapsed_time = steady_clock::now() - start_time;
+ loop_timer.update();
+ unsigned int fps = loop_timer.get_fps();
+ auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count();
+
+ // For 60 FPS, the target frame time is around 16.67ms
+ ASSERT_NEAR(elapsed_ms, 16.7, 1);
+ ASSERT_NEAR(fps, 60, 2);
+}
diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp
index 1c69348..83f7b23 100644
--- a/src/test/OptionalRefTest.cpp
+++ b/src/test/OptionalRefTest.cpp
@@ -18,9 +18,7 @@ TEST(OptionalRefTest, Normal) {
ref.clear();
EXPECT_FALSE(ref);
- ASSERT_THROW({
- string & value_ref = ref;
- }, runtime_error);
+ ASSERT_THROW({ string & value_ref = ref; }, runtime_error);
}
TEST(OptionalRefTest, Empty) {
@@ -28,9 +26,7 @@ TEST(OptionalRefTest, Empty) {
OptionalRef<string> ref;
EXPECT_FALSE(ref);
- ASSERT_THROW({
- string & value_ref = ref;
- }, runtime_error);
+ ASSERT_THROW({ string & value_ref = ref; }, runtime_error);
}
TEST(OptionalRefTest, Chain) {
@@ -44,4 +40,3 @@ TEST(OptionalRefTest, Chain) {
value_ref = "bar";
EXPECT_EQ(value_ref, value);
}
-
diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp
index eee022f..eee7a73 100644
--- a/src/test/ParticleTest.cpp
+++ b/src/test/ParticleTest.cpp
@@ -1,56 +1,70 @@
-#include "api/Vector2.h"
-#include <crepe/ComponentManager.h>
-#include <crepe/Particle.h>
+#include "api/Asset.h"
#include <crepe/api/Config.h>
#include <crepe/api/GameObject.h>
-#include <crepe/api/ParticleEmitter.h>
#include <crepe/api/Rigidbody.h>
#include <crepe/api/Sprite.h>
#include <crepe/api/Transform.h>
-#include <crepe/system/ParticleSystem.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/LoopTimerManager.h>
#include <gtest/gtest.h>
#include <math.h>
+#define protected public
+#define private public
+#include <crepe/Particle.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/system/ParticleSystem.h>
using namespace std;
using namespace std::chrono_literals;
using namespace crepe;
class ParticlesTest : public ::testing::Test {
+ Mediator m;
+
public:
- ComponentManager component_manager;
- ParticleSystem particle_system{component_manager};
+ ComponentManager component_manager {m};
+ ParticleSystem particle_system {m};
+ LoopTimerManager loop_timer {m};
void SetUp() override {
ComponentManager & mgr = this->component_manager;
std::vector<std::reference_wrapper<Transform>> transforms
= mgr.get_components_by_id<Transform>(0);
if (transforms.empty()) {
- GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0);
+ GameObject game_object = mgr.new_object("", "", vec2 {0, 0}, 0, 0);
Color color(0, 0, 0, 0);
+ auto s1 = Asset("asset/texture/img.png");
Sprite & test_sprite = game_object.add_component<Sprite>(
- make_shared<Texture>("asset/texture/img.png"), color,
- FlipSettings{true, true});
+ s1,
+ Sprite::Data {
+ .color = color,
+ .flip = Sprite::FlipSettings {true, true},
+ .size = {10, 10},
+ }
+ );
- game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{
- .position = {0, 0},
- .max_particles = 100,
- .emission_rate = 0,
- .min_speed = 0,
- .max_speed = 0,
- .min_angle = 0,
- .max_angle = 0,
- .begin_lifespan = 0,
- .end_lifespan = 0,
- .force_over_time = Vector2{0, 0},
- .boundary{
- .width = 0,
- .height = 0,
- .offset = Vector2{0, 0},
- .reset_on_exit = false,
- },
- .sprite = test_sprite,
- });
+ game_object.add_component<ParticleEmitter>(
+ test_sprite,
+ ParticleEmitter::Data {
+ .offset = {0, 0},
+ .max_particles = 100,
+ .emission_rate = 0,
+ .min_speed = 0,
+ .max_speed = 0,
+ .min_angle = 0,
+ .max_angle = 0,
+ .begin_lifespan = 0,
+ .end_lifespan = 0,
+ .force_over_time = vec2 {0, 0},
+ .boundary {
+ .width = 0,
+ .height = 0,
+ .offset = vec2 {0, 0},
+ .reset_on_exit = false,
+ },
+ }
+ );
}
transforms = mgr.get_components_by_id<Transform>(0);
Transform & transform = transforms.front().get();
@@ -60,7 +74,7 @@ public:
std::vector<std::reference_wrapper<ParticleEmitter>> rigidbodies
= mgr.get_components_by_id<ParticleEmitter>(0);
ParticleEmitter & emitter = rigidbodies.front().get();
- emitter.data.position = {0, 0};
+ emitter.data.offset = {0, 0};
emitter.data.emission_rate = 0;
emitter.data.min_speed = 0;
emitter.data.max_speed = 0;
@@ -68,9 +82,9 @@ public:
emitter.data.max_angle = 0;
emitter.data.begin_lifespan = 0;
emitter.data.end_lifespan = 0;
- emitter.data.force_over_time = Vector2{0, 0};
- emitter.data.boundary = {0, 0, Vector2{0, 0}, false};
- for (auto & particle : emitter.data.particles) {
+ emitter.data.force_over_time = vec2 {0, 0};
+ emitter.data.boundary = {0, 0, vec2 {0, 0}, false};
+ for (auto & particle : emitter.particles) {
particle.active = false;
}
}
@@ -87,21 +101,21 @@ TEST_F(ParticlesTest, spawnParticle) {
emitter.data.max_angle = 0.1;
emitter.data.max_speed = 10;
emitter.data.max_angle = 10;
- particle_system.update();
+ particle_system.fixed_update();
//check if nothing happend
- EXPECT_EQ(emitter.data.particles[0].active, false);
- emitter.data.emission_rate = 1;
+ EXPECT_EQ(emitter.particles[0].active, false);
+ emitter.data.emission_rate = 50;
//check particle spawnes
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[0].active, true);
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[1].active, true);
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[2].active, true);
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[3].active, true);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[0].active, true);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[1].active, true);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[2].active, true);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[3].active, true);
- for (auto & particle : emitter.data.particles) {
+ for (auto & particle : emitter.particles) {
// Check velocity range
EXPECT_GE(particle.velocity.x, emitter.data.min_speed);
// Speed should be greater than or equal to min_speed
@@ -127,13 +141,13 @@ TEST_F(ParticlesTest, moveParticleHorizontal) {
emitter.data.end_lifespan = 100;
emitter.data.boundary.height = 100;
emitter.data.boundary.width = 100;
- emitter.data.min_speed = 1;
- emitter.data.max_speed = 1;
+ emitter.data.min_speed = 50;
+ emitter.data.max_speed = 50;
emitter.data.max_angle = 0;
- emitter.data.emission_rate = 1;
+ emitter.data.emission_rate = 50;
for (int a = 1; a < emitter.data.boundary.width / 2; a++) {
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[0].position.x, a);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[0].position.x, a);
}
}
@@ -144,14 +158,14 @@ TEST_F(ParticlesTest, moveParticleVertical) {
emitter.data.end_lifespan = 100;
emitter.data.boundary.height = 100;
emitter.data.boundary.width = 100;
- emitter.data.min_speed = 1;
- emitter.data.max_speed = 1;
+ emitter.data.min_speed = 50;
+ emitter.data.max_speed = 50;
emitter.data.min_angle = 90;
emitter.data.max_angle = 90;
- emitter.data.emission_rate = 1;
+ emitter.data.emission_rate = 50;
for (int a = 1; a < emitter.data.boundary.width / 2; a++) {
- particle_system.update();
- EXPECT_EQ(emitter.data.particles[0].position.y, a);
+ particle_system.fixed_update();
+ EXPECT_EQ(emitter.particles[0].position.y, a);
}
}
@@ -169,9 +183,9 @@ TEST_F(ParticlesTest, boundaryParticleReset) {
emitter.data.max_angle = 90;
emitter.data.emission_rate = 1;
for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) {
- particle_system.update();
+ particle_system.fixed_update();
}
- EXPECT_EQ(emitter.data.particles[0].active, false);
+ EXPECT_EQ(emitter.particles[0].active, false);
}
TEST_F(ParticlesTest, boundaryParticleStop) {
@@ -188,15 +202,19 @@ TEST_F(ParticlesTest, boundaryParticleStop) {
emitter.data.max_angle = 90;
emitter.data.emission_rate = 1;
for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) {
- particle_system.update();
+ particle_system.fixed_update();
}
const double TOLERANCE = 0.01;
- EXPECT_NEAR(emitter.data.particles[0].velocity.x, 0, TOLERANCE);
- EXPECT_NEAR(emitter.data.particles[0].velocity.y, 0, TOLERANCE);
- if (emitter.data.particles[0].velocity.x != 0)
- EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x),
- emitter.data.boundary.height / 2, TOLERANCE);
- if (emitter.data.particles[0].velocity.y != 0)
- EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y),
- emitter.data.boundary.width / 2, TOLERANCE);
+ EXPECT_NEAR(emitter.particles[0].velocity.x, 0, TOLERANCE);
+ EXPECT_NEAR(emitter.particles[0].velocity.y, 0, TOLERANCE);
+ if (emitter.particles[0].velocity.x != 0)
+ EXPECT_NEAR(
+ std::abs(emitter.particles[0].position.x), emitter.data.boundary.height / 2,
+ TOLERANCE
+ );
+ if (emitter.particles[0].velocity.y != 0)
+ EXPECT_NEAR(
+ std::abs(emitter.particles[0].position.y), emitter.data.boundary.width / 2,
+ TOLERANCE
+ );
}
diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp
index 1e37c26..85eb6d5 100644
--- a/src/test/PhysicsTest.cpp
+++ b/src/test/PhysicsTest.cpp
@@ -1,8 +1,10 @@
-#include <crepe/ComponentManager.h>
#include <crepe/api/Config.h>
#include <crepe/api/GameObject.h>
#include <crepe/api/Rigidbody.h>
#include <crepe/api/Transform.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/LoopTimerManager.h>
+#include <crepe/manager/Mediator.h>
#include <crepe/system/PhysicsSystem.h>
#include <gtest/gtest.h>
@@ -11,25 +13,26 @@ using namespace std::chrono_literals;
using namespace crepe;
class PhysicsTest : public ::testing::Test {
+ Mediator m;
+
public:
- ComponentManager component_manager;
- PhysicsSystem system{component_manager};
+ ComponentManager component_manager {m};
+ PhysicsSystem system {m};
+ LoopTimerManager loop_timer {m};
void SetUp() override {
ComponentManager & mgr = this->component_manager;
vector<reference_wrapper<Transform>> transforms
= mgr.get_components_by_id<Transform>(0);
if (transforms.empty()) {
- auto entity = mgr.new_object("", "", Vector2{0, 0}, 0, 0);
- entity.add_component<Rigidbody>(Rigidbody::Data{
+ auto entity = mgr.new_object("", "", vec2 {0, 0}, 0, 0);
+ entity.add_component<Rigidbody>(Rigidbody::Data {
.mass = 1,
.gravity_scale = 1,
.body_type = Rigidbody::BodyType::DYNAMIC,
- .max_linear_velocity = Vector2{10, 10},
+ .max_linear_velocity = 10,
.max_angular_velocity = 10,
.constraints = {0, 0},
- .use_gravity = true,
- .bounce = false,
});
}
transforms = mgr.get_components_by_id<Transform>(0);
@@ -54,40 +57,41 @@ TEST_F(PhysicsTest, gravity) {
ASSERT_FALSE(transforms.empty());
EXPECT_EQ(transform.position.y, 0);
- system.update();
- EXPECT_EQ(transform.position.y, 1);
+ system.fixed_update();
+ EXPECT_NEAR(transform.position.y, 0.0004, 0.0001);
- system.update();
- EXPECT_EQ(transform.position.y, 3);
+ system.fixed_update();
+ EXPECT_NEAR(transform.position.y, 0.002, 0.001);
}
TEST_F(PhysicsTest, max_velocity) {
ComponentManager & mgr = this->component_manager;
vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);
Rigidbody & rigidbody = rigidbodies.front().get();
+ rigidbody.data.gravity_scale = 0;
ASSERT_FALSE(rigidbodies.empty());
EXPECT_EQ(rigidbody.data.linear_velocity.y, 0);
rigidbody.add_force_linear({100, 100});
rigidbody.add_force_angular(100);
- system.update();
- EXPECT_EQ(rigidbody.data.linear_velocity.y, 10);
- EXPECT_EQ(rigidbody.data.linear_velocity.x, 10);
+ system.fixed_update();
+ EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01);
+ EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01);
EXPECT_EQ(rigidbody.data.angular_velocity, 10);
rigidbody.add_force_linear({-100, -100});
rigidbody.add_force_angular(-100);
- system.update();
- EXPECT_EQ(rigidbody.data.linear_velocity.y, -10);
- EXPECT_EQ(rigidbody.data.linear_velocity.x, -10);
+ system.fixed_update();
+ EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01);
+ EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01);
EXPECT_EQ(rigidbody.data.angular_velocity, -10);
}
TEST_F(PhysicsTest, movement) {
- Config::get_instance().physics.gravity = 0;
ComponentManager & mgr = this->component_manager;
vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0);
Rigidbody & rigidbody = rigidbodies.front().get();
+ rigidbody.data.gravity_scale = 0;
vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0);
const Transform & transform = transforms.front().get();
ASSERT_FALSE(rigidbodies.empty());
@@ -95,32 +99,34 @@ TEST_F(PhysicsTest, movement) {
rigidbody.add_force_linear({1, 1});
rigidbody.add_force_angular(1);
- system.update();
- EXPECT_EQ(transform.position.x, 1);
- EXPECT_EQ(transform.position.y, 1);
- EXPECT_EQ(transform.rotation, 1);
+ system.fixed_update();
+ EXPECT_NEAR(transform.position.x, 0.02, 0.001);
+ EXPECT_NEAR(transform.position.y, 0.02, 0.001);
+ EXPECT_NEAR(transform.rotation, 0.02, 0.001);
rigidbody.data.constraints = {1, 1, 1};
- EXPECT_EQ(transform.position.x, 1);
- EXPECT_EQ(transform.position.y, 1);
- EXPECT_EQ(transform.rotation, 1);
-
- rigidbody.data.linear_damping.x = 0.5;
- rigidbody.data.linear_damping.y = 0.5;
- rigidbody.data.angular_damping = 0.5;
- system.update();
- EXPECT_EQ(rigidbody.data.linear_velocity.x, 0.5);
- EXPECT_EQ(rigidbody.data.linear_velocity.y, 0.5);
- EXPECT_EQ(rigidbody.data.angular_velocity, 0.5);
+ EXPECT_NEAR(transform.position.x, 0.02, 0.001);
+ EXPECT_NEAR(transform.position.y, 0.02, 0.001);
+ EXPECT_NEAR(transform.rotation, 0.02, 0.001);
+ rigidbody.data.constraints = {0, 0, 0};
+ rigidbody.data.linear_velocity_coefficient.x = 0.5;
+ rigidbody.data.linear_velocity_coefficient.y = 0.5;
+ rigidbody.data.angular_velocity_coefficient = 0.5;
+ system.fixed_update();
+ EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01);
+ EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01);
+ EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01);
rigidbody.data.constraints = {1, 1, 0};
- rigidbody.data.angular_damping = 0;
+ rigidbody.data.angular_velocity_coefficient = 0;
rigidbody.data.max_angular_velocity = 1000;
rigidbody.data.angular_velocity = 360;
- system.update();
- EXPECT_EQ(transform.rotation, 1);
+ system.fixed_update();
+ EXPECT_NEAR(transform.rotation, 7.24, 0.01);
rigidbody.data.angular_velocity = -360;
- system.update();
- EXPECT_EQ(transform.rotation, 1);
+ system.fixed_update();
+ EXPECT_NEAR(transform.rotation, 0.04, 0.001);
+ system.fixed_update();
+ EXPECT_NEAR(transform.rotation, 352.84, 0.01);
}
diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp
new file mode 100644
index 0000000..d8bd09d
--- /dev/null
+++ b/src/test/Profiling.cpp
@@ -0,0 +1,262 @@
+#include <chrono>
+#include <cmath>
+#include <crepe/api/Asset.h>
+#include <crepe/manager/Mediator.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/system/ParticleSystem.h>
+#include <crepe/system/PhysicsSystem.h>
+#include <crepe/system/RenderSystem.h>
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Transform.h>
+#include <crepe/facade/SDLContext.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/system/CollisionSystem.h>
+#include <crepe/system/ScriptSystem.h>
+#include <crepe/types.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+class TestScript : public Script {
+ bool oncollision(const CollisionEvent & test) {
+ Log::logf("Box {} script on_collision()", test.info.self.transform.game_object_id);
+ return true;
+ }
+ void init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool {
+ return this->oncollision(ev);
+ });
+ }
+ void fixed_update() {
+ // Retrieve component from the same GameObject this script is on
+ }
+};
+
+class DISABLED_ProfilingTest : public Test {
+public:
+ // Config for test
+ // Minimum amount to let test pass
+ const int min_gameobject_count = 100;
+ // Maximum amount to stop test
+ const int max_gameobject_count = 3000;
+ // Amount of times a test runs to calculate average
+ const int average = 5;
+ // Maximum duration to stop test
+ const std::chrono::microseconds duration = 16000us;
+
+ Mediator m;
+ SDLContext sdl_context {m};
+ ResourceManager resman {m};
+ ComponentManager mgr {m};
+ // Add system used for profling tests
+ EventManager evmgr {m};
+ LoopTimerManager loopmgr {m};
+ CollisionSystem collision_sys {m};
+ PhysicsSystem physics_sys {m};
+ ParticleSystem particle_sys {m};
+ RenderSystem render_sys {m};
+ ScriptSystem script_sys {m};
+
+ // Test data
+ std::map<std::string, std::chrono::microseconds> timings;
+ int game_object_count = 0;
+ std::chrono::microseconds total_time = 0us;
+
+ void SetUp() override {
+
+ GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0});
+ do_not_use.add_component<Camera>(
+ ivec2 {1080, 720}, vec2 {2000, 2000},
+ Camera::Data {
+ .bg_color = Color::WHITE,
+ .zoom = 1.0f,
+ }
+ );
+ // initialize systems here:
+ //calls init
+ script_sys.fixed_update();
+ //creates window
+ render_sys.frame_update();
+ }
+
+ // Helper function to time an update call and store its duration
+ template <typename Func>
+ std::chrono::microseconds time_function(const std::string & name, Func && func) {
+ auto start = std::chrono::steady_clock::now();
+ func();
+ auto end = std::chrono::steady_clock::now();
+ std::chrono::microseconds duration
+ = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+ timings[name] += duration;
+ return duration;
+ }
+
+ // Run and profile all systems, return the total time in milliseconds
+ std::chrono::microseconds run_all_systems() {
+ std::chrono::microseconds total_microseconds = 0us;
+ total_microseconds
+ += time_function("PhysicsSystem", [&]() { physics_sys.fixed_update(); });
+ total_microseconds
+ += time_function("CollisionSystem", [&]() { collision_sys.fixed_update(); });
+ total_microseconds
+ += time_function("ParticleSystem", [&]() { particle_sys.fixed_update(); });
+ total_microseconds
+ += time_function("RenderSystem", [&]() { render_sys.frame_update(); });
+ return total_microseconds;
+ }
+
+ // Print timings of all functions
+ void log_timings() const {
+ std::string result = "\nFunction timings:\n";
+
+ for (const auto & [name, duration] : timings) {
+ result += name + " took " + std::to_string(duration.count() / 1000.0 / average)
+ + " ms (" + std::to_string(duration.count() / average) + " µs).\n";
+ }
+
+ result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average)
+ + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n";
+
+ result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n";
+
+ GTEST_LOG_(INFO) << result;
+ }
+
+ void clear_timings() {
+ for (auto & [key, value] : timings) {
+ value = std::chrono::microseconds(0);
+ }
+ }
+};
+
+TEST_F(DISABLED_ProfilingTest, Profiling_1) {
+ while (this->total_time / this->average < this->duration) {
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object("gameobject", "", {0, 0});
+ }
+
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if (this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}
+
+TEST_F(DISABLED_ProfilingTest, Profiling_2) {
+ while (this->total_time / this->average < this->duration) {
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object(
+ "gameobject", "", {static_cast<float>(game_object_count * 2), 0}
+ );
+ gameobject.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 0.0,
+ .body_type = Rigidbody::BodyType::STATIC,
+ });
+ gameobject.add_component<BoxCollider>(vec2 {0, 0}, vec2 {1, 1});
+
+ gameobject.add_component<BehaviorScript>().set_script<TestScript>();
+ Sprite & test_sprite = gameobject.add_component<Sprite>(
+ Asset {"asset/texture/square.png"},
+ Sprite::Data {
+ .color = {0, 0, 0, 0},
+ .flip = {.flip_x = false, .flip_y = false},
+ .sorting_in_layer = 1,
+ .order_in_layer = 1,
+ .size = {.y = 500},
+ }
+ );
+ }
+
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if (this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}
+
+TEST_F(DISABLED_ProfilingTest, Profiling_3) {
+ while (this->total_time / this->average < this->duration) {
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object(
+ "gameobject", "", {static_cast<float>(game_object_count * 2), 0}
+ );
+ gameobject.add_component<Rigidbody>(Rigidbody::Data {
+ .gravity_scale = 0,
+ .body_type = Rigidbody::BodyType::STATIC,
+ });
+ gameobject.add_component<BoxCollider>(vec2 {0, 0}, vec2 {1, 1});
+ gameobject.add_component<BehaviorScript>().set_script<TestScript>();
+ Sprite & test_sprite = gameobject.add_component<Sprite>(
+ Asset {"asset/texture/square.png"},
+ Sprite::Data {
+ .color = {0, 0, 0, 0},
+ .flip = {.flip_x = false, .flip_y = false},
+ .sorting_in_layer = 1,
+ .order_in_layer = 1,
+ .size = {.y = 500},
+ }
+ );
+ auto & test = gameobject.add_component<ParticleEmitter>(
+ test_sprite,
+ ParticleEmitter::Data {
+ .max_particles = 10,
+ .emission_rate = 100,
+ .end_lifespan = 100000,
+ .boundary {
+ .width = 1000,
+ .height = 1000,
+ .offset = vec2 {0, 0},
+ .reset_on_exit = false,
+ },
+
+ }
+ );
+ }
+ render_sys.frame_update();
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if (this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}
diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp
index ac479d3..bdd87ee 100644
--- a/src/test/RenderSystemTest.cpp
+++ b/src/test/RenderSystemTest.cpp
@@ -1,4 +1,7 @@
-#include "api/Camera.h"
+#include "api/Asset.h"
+#include "facade/SDLContext.h"
+#include "manager/ResourceManager.h"
+#include "types.h"
#include <functional>
#include <gtest/gtest.h>
#include <memory>
@@ -7,11 +10,11 @@
#define private public
#define protected public
-#include <crepe/ComponentManager.h>
+#include <crepe/api/Camera.h>
#include <crepe/api/Color.h>
#include <crepe/api/GameObject.h>
#include <crepe/api/Sprite.h>
-#include <crepe/api/Texture.h>
+#include <crepe/manager/ComponentManager.h>
#include <crepe/system/RenderSystem.h>
@@ -20,65 +23,77 @@ using namespace crepe;
using namespace testing;
class RenderSystemTest : public Test {
+ Mediator m;
+
public:
- ComponentManager mgr{};
- RenderSystem sys{mgr};
+ ComponentManager mgr {m};
+ SDLContext ctx {m};
+ ResourceManager resource_manager {m};
+ RenderSystem sys {m};
GameObject entity1 = this->mgr.new_object("name");
GameObject entity2 = this->mgr.new_object("name");
GameObject entity3 = this->mgr.new_object("name");
GameObject entity4 = this->mgr.new_object("name");
void SetUp() override {
- auto & sprite1
- = entity1.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"),
- Color(0, 0, 0, 0), FlipSettings{false, false});
- ASSERT_NE(sprite1.sprite_image.get(), nullptr);
- sprite1.order_in_layer = 5;
- sprite1.sorting_in_layer = 5;
- EXPECT_EQ(sprite1.order_in_layer, 5);
- EXPECT_EQ(sprite1.sorting_in_layer, 5);
- auto & sprite2
- = entity2.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"),
- Color(0, 0, 0, 0), FlipSettings{false, false});
- ASSERT_NE(sprite2.sprite_image.get(), nullptr);
- sprite2.sorting_in_layer = 2;
- sprite2.order_in_layer = 1;
-
- EXPECT_EQ(sprite2.sorting_in_layer, 2);
- EXPECT_EQ(sprite2.order_in_layer, 1);
-
- auto & sprite3
- = entity3.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"),
- Color(0, 0, 0, 0), FlipSettings{false, false});
- ASSERT_NE(sprite3.sprite_image.get(), nullptr);
- sprite3.sorting_in_layer = 1;
- sprite3.order_in_layer = 2;
-
- EXPECT_EQ(sprite3.sorting_in_layer, 1);
- EXPECT_EQ(sprite3.order_in_layer, 2);
-
- auto & sprite4
- = entity4.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"),
- Color(0, 0, 0, 0), FlipSettings{false, false});
- ASSERT_NE(sprite4.sprite_image.get(), nullptr);
- sprite4.sorting_in_layer = 1;
- sprite4.order_in_layer = 1;
- EXPECT_EQ(sprite4.sorting_in_layer, 1);
- EXPECT_EQ(sprite4.order_in_layer, 1);
+ auto s1 = Asset("asset/texture/img.png");
+ auto s2 = Asset("asset/texture/img.png");
+ auto s3 = Asset("asset/texture/img.png");
+ auto s4 = Asset("asset/texture/img.png");
+ auto & sprite1 = entity1.add_component<Sprite>(
+ s1,
+ Sprite::Data {
+ .color = Color(0, 0, 0, 0),
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 5,
+ .order_in_layer = 5,
+ .size = {10, 10},
+ }
+ );
+
+ EXPECT_EQ(sprite1.data.order_in_layer, 5);
+ EXPECT_EQ(sprite1.data.sorting_in_layer, 5);
+ auto & sprite2 = entity2.add_component<Sprite>(
+ s2,
+ Sprite::Data {
+ .color = Color(0, 0, 0, 0),
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 2,
+ .order_in_layer = 1,
+ }
+ );
+ EXPECT_EQ(sprite2.data.sorting_in_layer, 2);
+ EXPECT_EQ(sprite2.data.order_in_layer, 1);
+
+ auto & sprite3 = entity3.add_component<Sprite>(
+ s3,
+ Sprite::Data {
+ .color = Color(0, 0, 0, 0),
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 1,
+ .order_in_layer = 2,
+ }
+ );
+ EXPECT_EQ(sprite3.data.sorting_in_layer, 1);
+ EXPECT_EQ(sprite3.data.order_in_layer, 2);
+
+ auto & sprite4 = entity4.add_component<Sprite>(
+ s4,
+ Sprite::Data {
+ .color = Color(0, 0, 0, 0),
+ .flip = Sprite::FlipSettings {false, false},
+ .sorting_in_layer = 1,
+ .order_in_layer = 1,
+ }
+ );
+ EXPECT_EQ(sprite4.data.sorting_in_layer, 1);
+ EXPECT_EQ(sprite4.data.order_in_layer, 1);
}
};
-TEST_F(RenderSystemTest, expected_throws) {
- GameObject entity1 = this->mgr.new_object("NAME");
-
- // no texture img
- EXPECT_ANY_THROW({
- entity1.add_component<Sprite>(make_shared<Texture>("NO_IMAGE"), Color(0, 0, 0, 0),
- FlipSettings{false, false});
- });
-
+TEST_F(RenderSystemTest, NoCamera) {
// No camera
- EXPECT_ANY_THROW({ this->sys.update(); });
+ EXPECT_ANY_THROW({ this->sys.frame_update(); });
}
TEST_F(RenderSystemTest, make_sprites) {}
@@ -96,32 +111,35 @@ TEST_F(RenderSystemTest, sorting_sprites) {
// 3. sorting_in_layer: 2, order_in_layer: 1 (entity2)
// 4. sorting_in_layer: 5, order_in_layer: 5 (entity1)
- EXPECT_EQ(sorted_sprites[0].get().sorting_in_layer, 1);
- EXPECT_EQ(sorted_sprites[0].get().order_in_layer, 1);
+ EXPECT_EQ(sorted_sprites[0].get().data.sorting_in_layer, 1);
+ EXPECT_EQ(sorted_sprites[0].get().data.order_in_layer, 1);
- EXPECT_EQ(sorted_sprites[1].get().sorting_in_layer, 1);
- EXPECT_EQ(sorted_sprites[1].get().order_in_layer, 2);
+ EXPECT_EQ(sorted_sprites[1].get().data.sorting_in_layer, 1);
+ EXPECT_EQ(sorted_sprites[1].get().data.order_in_layer, 2);
- EXPECT_EQ(sorted_sprites[2].get().sorting_in_layer, 2);
- EXPECT_EQ(sorted_sprites[2].get().order_in_layer, 1);
+ EXPECT_EQ(sorted_sprites[2].get().data.sorting_in_layer, 2);
+ EXPECT_EQ(sorted_sprites[2].get().data.order_in_layer, 1);
- EXPECT_EQ(sorted_sprites[3].get().sorting_in_layer, 5);
- EXPECT_EQ(sorted_sprites[3].get().order_in_layer, 5);
+ EXPECT_EQ(sorted_sprites[3].get().data.sorting_in_layer, 5);
+ EXPECT_EQ(sorted_sprites[3].get().data.order_in_layer, 5);
for (size_t i = 1; i < sorted_sprites.size(); ++i) {
const Sprite & prev = sorted_sprites[i - 1].get();
const Sprite & curr = sorted_sprites[i].get();
- if (prev.sorting_in_layer == curr.sorting_in_layer) {
- EXPECT_LE(prev.order_in_layer, curr.order_in_layer);
+ if (prev.data.sorting_in_layer == curr.data.sorting_in_layer) {
+ EXPECT_LE(prev.data.order_in_layer, curr.data.order_in_layer);
} else {
- EXPECT_LE(prev.sorting_in_layer, curr.sorting_in_layer);
+ EXPECT_LE(prev.data.sorting_in_layer, curr.data.sorting_in_layer);
}
}
}
TEST_F(RenderSystemTest, Update) {
- entity1.add_component<Camera>(Color::WHITE);
+ entity1.add_component<Camera>(
+ ivec2 {100, 100}, vec2 {100, 100},
+ Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f}
+ );
{
vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>();
ASSERT_EQ(sprites.size(), 4);
@@ -131,7 +149,7 @@ TEST_F(RenderSystemTest, Update) {
EXPECT_EQ(sprites[2].get().game_object_id, 2);
EXPECT_EQ(sprites[3].get().game_object_id, 3);
}
- this->sys.update();
+ this->sys.frame_update();
{
vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>();
ASSERT_EQ(sprites.size(), 4);
@@ -149,7 +167,11 @@ TEST_F(RenderSystemTest, Camera) {
EXPECT_NE(cameras.size(), 1);
}
{
- entity1.add_component<Camera>(Color::WHITE);
+ entity1.add_component<Camera>(
+ ivec2 {100, 100}, vec2 {100, 100},
+ Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f}
+ );
+
auto cameras = this->mgr.get_components_by_type<Camera>();
EXPECT_EQ(cameras.size(), 1);
}
@@ -157,18 +179,22 @@ TEST_F(RenderSystemTest, Camera) {
//TODO improve with newer version
}
TEST_F(RenderSystemTest, Color) {
- entity1.add_component<Camera>(Color::WHITE);
+ entity1.add_component<Camera>(
+ ivec2 {100, 100}, vec2 {100, 100},
+ Camera::Data {.bg_color = Color::WHITE, .zoom = 1.0f}
+ );
+
auto & sprite = this->mgr.get_components_by_id<Sprite>(entity1.id).front().get();
- ASSERT_NE(sprite.sprite_image.get(), nullptr);
-
- sprite.color = Color::GREEN;
- EXPECT_EQ(sprite.color.r, Color::GREEN.r);
- EXPECT_EQ(sprite.color.g, Color::GREEN.g);
- EXPECT_EQ(sprite.color.b, Color::GREEN.b);
- EXPECT_EQ(sprite.color.a, Color::GREEN.a);
- this->sys.update();
- EXPECT_EQ(sprite.color.r, Color::GREEN.r);
- EXPECT_EQ(sprite.color.g, Color::GREEN.g);
- EXPECT_EQ(sprite.color.b, Color::GREEN.b);
- EXPECT_EQ(sprite.color.a, Color::GREEN.a);
+ //ASSERT_NE(sprite.texture.texture.get(), nullptr);
+
+ sprite.data.color = Color::GREEN;
+ EXPECT_EQ(sprite.data.color.r, Color::GREEN.r);
+ EXPECT_EQ(sprite.data.color.g, Color::GREEN.g);
+ EXPECT_EQ(sprite.data.color.b, Color::GREEN.b);
+ EXPECT_EQ(sprite.data.color.a, Color::GREEN.a);
+ this->sys.frame_update();
+ EXPECT_EQ(sprite.data.color.r, Color::GREEN.r);
+ EXPECT_EQ(sprite.data.color.g, Color::GREEN.g);
+ EXPECT_EQ(sprite.data.color.b, Color::GREEN.b);
+ EXPECT_EQ(sprite.data.color.a, Color::GREEN.a);
}
diff --git a/src/test/ReplayManagerTest.cpp b/src/test/ReplayManagerTest.cpp
new file mode 100644
index 0000000..b2619eb
--- /dev/null
+++ b/src/test/ReplayManagerTest.cpp
@@ -0,0 +1,38 @@
+#include <gtest/gtest.h>
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Scene.h>
+#include <crepe/api/Script.h>
+#include <crepe/manager/ReplayManager.h>
+#include <crepe/system/ReplaySystem.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ReplayManagerTest : public Test {
+ Mediator mediator;
+
+public:
+ ComponentManager component_manager {mediator};
+ ReplayManager replay_manager {mediator};
+ ReplaySystem replay_system {mediator};
+
+ GameObject entity = component_manager.new_object("foo");
+ Transform & entity_transform
+ = component_manager.get_components_by_id<Transform>(entity.id).back();
+ Metadata & entity_metadata
+ = component_manager.get_components_by_id<Metadata>(entity.id).back();
+};
+
+TEST_F(ReplayManagerTest, Default) {
+ // replay_manager.record_start();
+
+ // replay_system.fixed_update();
+ // entity_transform.position += {1, 1};
+ // replay_system.fixed_update();
+ // entity_transform.position += {1, 1};
+ // replay_system.fixed_update();
+
+ // recording_t recording = replay_manager.record_end();
+}
diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp
new file mode 100644
index 0000000..e5a7fad
--- /dev/null
+++ b/src/test/ResourceManagerTest.cpp
@@ -0,0 +1,84 @@
+#include "manager/Mediator.h"
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/api/GameObject.h>
+#include <crepe/manager/ResourceManager.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ResourceManagerTest : public Test {
+ Mediator mediator;
+
+public:
+ ResourceManager resource_manager {mediator};
+
+ class Unrelated : public Resource {
+ using Resource::Resource;
+ };
+
+ Asset asset_a {"asset/texture/img.png"};
+ Asset asset_b {"asset/texture/ERROR.png"};
+
+ class TestResource : public Resource {
+ public:
+ static unsigned instances;
+
+ public:
+ const unsigned instance;
+ TestResource(const Asset & src, Mediator & mediator)
+ : Resource(src, mediator),
+ instance(this->instances++) {}
+ ~TestResource() { this->instances--; }
+ bool operator==(const TestResource & other) const {
+ return this->instance == other.instance;
+ }
+ };
+
+private:
+ void SetUp() override { TestResource::instances = 0; }
+};
+unsigned ResourceManagerTest::TestResource::instances = 0;
+
+TEST_F(ResourceManagerTest, Default) {
+ TestResource & res_1 = resource_manager.get<TestResource>(asset_a);
+ TestResource & res_2 = resource_manager.get<TestResource>(asset_a);
+ TestResource & res_3 = resource_manager.get<TestResource>(asset_b);
+ TestResource & res_4 = resource_manager.get<TestResource>(asset_b);
+
+ ASSERT_EQ(res_1, res_2);
+ ASSERT_EQ(res_3, res_4);
+ EXPECT_NE(res_1, res_3);
+
+ EXPECT_EQ(TestResource::instances, 2);
+
+ resource_manager.clear();
+}
+
+TEST_F(ResourceManagerTest, Persistent) {
+ resource_manager.set_persistent(asset_a, true);
+ EXPECT_EQ(TestResource::instances, 0);
+
+ resource_manager.get<TestResource>(asset_a);
+ resource_manager.get<TestResource>(asset_a);
+ resource_manager.get<TestResource>(asset_b);
+ resource_manager.get<TestResource>(asset_b);
+ EXPECT_EQ(TestResource::instances, 2);
+
+ resource_manager.clear();
+ EXPECT_EQ(TestResource::instances, 1);
+
+ resource_manager.clear_all();
+ EXPECT_EQ(TestResource::instances, 0);
+}
+
+TEST_F(ResourceManagerTest, UnmatchedType) {
+ EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); });
+
+ EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error);
+}
diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp
new file mode 100644
index 0000000..fd53200
--- /dev/null
+++ b/src/test/SaveManagerTest.cpp
@@ -0,0 +1,51 @@
+#include <gtest/gtest.h>
+
+#include <crepe/ValueBroker.h>
+#include <crepe/facade/DB.h>
+#include <crepe/manager/SaveManager.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class SaveManagerTest : public Test {
+ Mediator m;
+ class TestSaveManager : public SaveManager {
+ using SaveManager::SaveManager;
+
+ // in-memory database for testing
+ DB db {};
+ virtual DB & get_db() override { return this->db; }
+ };
+
+public:
+ TestSaveManager mgr {m};
+};
+
+TEST_F(SaveManagerTest, ReadWrite) {
+ ASSERT_FALSE(mgr.has("foo"));
+ mgr.set<string>("foo", "bar");
+ ASSERT_TRUE(mgr.has("foo"));
+
+ string value = mgr.get<string>("foo");
+ EXPECT_EQ(value, "bar");
+}
+
+TEST_F(SaveManagerTest, DefaultValue) {
+ ValueBroker value = mgr.get<int>("foo", 3);
+
+ ASSERT_EQ(value.get(), 3);
+ value.set(5);
+ EXPECT_EQ(value.get(), 5);
+}
+
+TEST_F(SaveManagerTest, MultipleKeys) {
+ ValueBroker foo = mgr.get<int>("foo", 1);
+ ValueBroker bar = mgr.get<int>("bar", 2);
+
+ EXPECT_EQ(foo.get(), 1);
+ EXPECT_EQ(bar.get(), 2);
+
+ EXPECT_EQ(mgr.get<int>("foo"), 1);
+ EXPECT_EQ(mgr.get<int>("bar"), 2);
+}
diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp
index dab2ce9..e58ce36 100644
--- a/src/test/SceneManagerTest.cpp
+++ b/src/test/SceneManagerTest.cpp
@@ -1,48 +1,60 @@
-#include <crepe/ComponentManager.h>
+#include <gtest/gtest.h>
+
#include <crepe/api/GameObject.h>
#include <crepe/api/Metadata.h>
#include <crepe/api/Scene.h>
-#include <crepe/api/SceneManager.h>
#include <crepe/api/Transform.h>
#include <crepe/api/Vector2.h>
-#include <gtest/gtest.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/SceneManager.h>
+#include <crepe/types.h>
using namespace std;
using namespace crepe;
class ConcreteScene1 : public Scene {
public:
- using Scene::Scene;
-
void load_scene() {
- auto & mgr = this->component_manager;
- GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", Vector2{0, 0}, 0, 1);
- GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", Vector2{1, 0}, 0, 1);
- GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", Vector2{2, 0}, 0, 1);
+ GameObject object1 = new_object("scene_1", "tag_scene_1", vec2 {0, 0}, 0, 1);
+ GameObject object2 = new_object("scene_1", "tag_scene_1", vec2 {1, 0}, 0, 1);
+ GameObject object3 = new_object("scene_1", "tag_scene_1", vec2 {2, 0}, 0, 1);
}
- string get_name() const { return "scene1";}
+ string get_name() const { return "scene1"; }
};
class ConcreteScene2 : public Scene {
public:
- using Scene::Scene;
+ void load_scene() {
+ GameObject object1 = new_object("scene_2", "tag_scene_2", vec2 {0, 0}, 0, 1);
+ GameObject object2 = new_object("scene_2", "tag_scene_2", vec2 {0, 1}, 0, 1);
+ GameObject object3 = new_object("scene_2", "tag_scene_2", vec2 {0, 2}, 0, 1);
+ GameObject object4 = new_object("scene_2", "tag_scene_2", vec2 {0, 3}, 0, 1);
+ }
+
+ string get_name() const { return "scene2"; }
+};
+
+class ConcreteScene3 : public Scene {
+public:
+ ConcreteScene3(const string & name) : name(name) {}
void load_scene() {
- auto & mgr = this->component_manager;
- GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 0}, 0, 1);
- GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 1}, 0, 1);
- GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 2}, 0, 1);
- GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 3}, 0, 1);
+ GameObject object1 = new_object("scene_3", "tag_scene_3", vec2 {0, 0}, 0, 1);
}
- string get_name() const { return "scene2";}
+ string get_name() const { return name; }
+
+private:
+ const string name;
};
class SceneManagerTest : public ::testing::Test {
+ Mediator m;
+
public:
- ComponentManager component_mgr{};
- SceneManager scene_mgr{component_mgr};
+ ComponentManager component_mgr {m};
+ SceneManager scene_mgr {m};
};
TEST_F(SceneManagerTest, loadScene) {
@@ -124,3 +136,25 @@ TEST_F(SceneManagerTest, loadScene) {
EXPECT_EQ(transform[3].get().position.x, 0);
EXPECT_EQ(transform[3].get().position.y, 3);
}
+
+TEST_F(SceneManagerTest, perfectForwarding) {
+ scene_mgr.add_scene<ConcreteScene3>("scene3");
+
+ scene_mgr.load_next_scene();
+
+ vector<reference_wrapper<Metadata>> metadata
+ = component_mgr.get_components_by_type<Metadata>();
+ vector<reference_wrapper<Transform>> transform
+ = component_mgr.get_components_by_type<Transform>();
+
+ EXPECT_EQ(metadata.size(), 1);
+ EXPECT_EQ(transform.size(), 1);
+
+ EXPECT_EQ(metadata[0].get().game_object_id, 0);
+ EXPECT_EQ(metadata[0].get().name, "scene_3");
+ EXPECT_EQ(metadata[0].get().tag, "tag_scene_3");
+ EXPECT_EQ(metadata[0].get().parent, -1);
+ EXPECT_EQ(metadata[0].get().children.size(), 0);
+ EXPECT_EQ(transform[0].get().position.x, 0);
+ EXPECT_EQ(transform[0].get().position.y, 0);
+}
diff --git a/src/test/ScriptECSTest.cpp b/src/test/ScriptECSTest.cpp
new file mode 100644
index 0000000..1ec33ba
--- /dev/null
+++ b/src/test/ScriptECSTest.cpp
@@ -0,0 +1,41 @@
+#include <gtest/gtest.h>
+
+#define protected public
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Metadata.h>
+#include <crepe/api/Script.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/system/ScriptSystem.h>
+
+#include "ScriptTest.h"
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ScriptECSTest : public ScriptTest {
+public:
+ class TestComponent : public Component {
+ using Component::Component;
+ };
+};
+
+TEST_F(ScriptECSTest, GetOwnComponent) {
+ MyScript & script = this->script;
+ Metadata & metadata = script.get_component<Metadata>();
+
+ EXPECT_EQ(metadata.name, OBJ_NAME);
+}
+
+TEST_F(ScriptECSTest, GetOwnComponents) {
+ const unsigned COUNT = 4;
+
+ for (unsigned i = 0; i < COUNT; i++) entity.add_component<TestComponent>();
+
+ MyScript & script = this->script;
+ RefVector<TestComponent> components = script.get_components<TestComponent>();
+
+ EXPECT_EQ(components.size(), COUNT);
+}
diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp
new file mode 100644
index 0000000..8b4a72d
--- /dev/null
+++ b/src/test/ScriptEventTest.cpp
@@ -0,0 +1,50 @@
+#include <gtest/gtest.h>
+
+// stupid hack to allow access to private/protected members under test
+#define private public
+#define protected public
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Vector2.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/system/ScriptSystem.h>
+
+#include "ScriptTest.h"
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ScriptEventTest : public ScriptTest {
+public:
+ EventManager & event_manager = mediator.event_manager;
+
+ struct MyEvent : public Event {};
+};
+
+TEST_F(ScriptEventTest, Default) {
+ BehaviorScript & behaviorscript = this->behaviorscript;
+ MyScript & script = this->script;
+ EventManager & evmgr = this->event_manager;
+
+ unsigned event_count = 0;
+ script.subscribe<MyEvent>([&](const MyEvent &) {
+ event_count++;
+ return true;
+ });
+
+ system.fixed_update();
+ behaviorscript.active = false;
+ EXPECT_EQ(0, event_count);
+
+ evmgr.trigger_event<MyEvent>();
+ EXPECT_EQ(0, event_count);
+
+ behaviorscript.active = true;
+ evmgr.trigger_event<MyEvent>();
+ EXPECT_EQ(1, event_count);
+}
diff --git a/src/test/ScriptSaveManagerTest.cpp b/src/test/ScriptSaveManagerTest.cpp
new file mode 100644
index 0000000..e2debae
--- /dev/null
+++ b/src/test/ScriptSaveManagerTest.cpp
@@ -0,0 +1,35 @@
+#include <gtest/gtest.h>
+
+// stupid hack to allow access to private/protected members under test
+#define private public
+#define protected public
+
+#include <crepe/facade/DB.h>
+#include <crepe/manager/SaveManager.h>
+
+#include "ScriptTest.h"
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ScriptSaveManagerTest : public ScriptTest {
+public:
+ class TestSaveManager : public SaveManager {
+ using SaveManager::SaveManager;
+
+ // in-memory database for testing
+ DB db {};
+ virtual DB & get_db() override { return this->db; }
+ };
+
+ TestSaveManager save_mgr {mediator};
+};
+
+TEST_F(ScriptSaveManagerTest, GetSaveManager) {
+ MyScript & script = this->script;
+
+ SaveManager & mgr = script.get_save_manager();
+
+ EXPECT_EQ(&mgr, &save_mgr);
+}
diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp
new file mode 100644
index 0000000..7d01f14
--- /dev/null
+++ b/src/test/ScriptSceneTest.cpp
@@ -0,0 +1,30 @@
+#include <gtest/gtest.h>
+
+// stupid hack to allow access to private/protected members under test
+#define private public
+#define protected public
+
+#include "ScriptTest.h"
+#include <crepe/manager/SceneManager.h>
+
+using namespace std;
+using namespace crepe;
+using namespace testing;
+
+class ScriptSceneTest : public ScriptTest {
+public:
+ SceneManager scene_manager {mediator};
+
+ class MyScene : public Scene {};
+};
+
+TEST_F(ScriptSceneTest, Default) {
+ BehaviorScript & behaviorscript = this->behaviorscript;
+ MyScript & script = this->script;
+
+ const char * non_default_value = "foo";
+ ASSERT_NE(non_default_value, scene_manager.next_scene);
+
+ script.set_next_scene(non_default_value);
+ EXPECT_EQ(non_default_value, scene_manager.next_scene);
+}
diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp
index 19fef6d..40aa25c 100644
--- a/src/test/ScriptTest.cpp
+++ b/src/test/ScriptTest.cpp
@@ -1,72 +1,95 @@
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
// stupid hack to allow access to private/protected members under test
#define private public
#define protected public
-#include <crepe/ComponentManager.h>
-#include <crepe/api/BehaviorScript.h>
-#include <crepe/api/GameObject.h>
-#include <crepe/api/Script.h>
-#include <crepe/api/Vector2.h>
-#include <crepe/system/ScriptSystem.h>
+#include "ScriptTest.h"
using namespace std;
using namespace crepe;
using namespace testing;
-class ScriptTest : public Test {
-public:
- ComponentManager component_manager{};
- ScriptSystem system{component_manager};
-
- class MyScript : public Script {
- // NOTE: default (private) visibility of init and update shouldn't cause
- // issues!
- void init() { this->init_count++; }
- void update() { this->update_count++; }
-
- public:
- unsigned init_count = 0;
- unsigned update_count = 0;
- };
-
- BehaviorScript * behaviorscript_ref = nullptr;
- MyScript * script_ref = nullptr;
-
- void SetUp() override {
- auto & mgr = this->component_manager;
- GameObject entity = mgr.new_object("name");
- BehaviorScript & component = entity.add_component<BehaviorScript>();
-
- this->behaviorscript_ref = &component;
- EXPECT_EQ(this->behaviorscript_ref->script.get(), nullptr);
- component.set_script<MyScript>();
- ASSERT_NE(this->behaviorscript_ref->script.get(), nullptr);
-
- this->script_ref = (MyScript *) this->behaviorscript_ref->script.get();
- ASSERT_NE(this->script_ref, nullptr);
- }
-};
+void ScriptTest::SetUp() {
+ auto & mgr = this->component_manager;
+ BehaviorScript & component = entity.add_component<BehaviorScript>();
+
+ this->behaviorscript = component;
+ ASSERT_TRUE(this->behaviorscript);
+ EXPECT_EQ(component.script.get(), nullptr);
+ component.set_script<NiceMock<MyScript>>();
+ ASSERT_NE(component.script.get(), nullptr);
+
+ this->script = *(MyScript *) component.script.get();
+ ASSERT_TRUE(this->script);
+}
TEST_F(ScriptTest, Default) {
- EXPECT_EQ(0, this->script_ref->init_count);
- EXPECT_EQ(0, this->script_ref->update_count);
+ MyScript & script = this->script;
+ EXPECT_CALL(script, init()).Times(0);
+ EXPECT_CALL(script, fixed_update(_)).Times(0);
+ EXPECT_CALL(script, frame_update(_)).Times(0);
}
TEST_F(ScriptTest, UpdateOnce) {
- EXPECT_EQ(0, this->script_ref->init_count);
- EXPECT_EQ(0, this->script_ref->update_count);
+ MyScript & script = this->script;
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(script, init()).Times(1);
+ EXPECT_CALL(script, fixed_update(_)).Times(1);
+ system.fixed_update();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(script, init()).Times(0);
+ EXPECT_CALL(script, fixed_update(_)).Times(1);
+ system.fixed_update();
+ }
+
+ {
+ InSequence seq;
- this->system.update();
- EXPECT_EQ(1, this->script_ref->init_count);
- EXPECT_EQ(1, this->script_ref->update_count);
+ EXPECT_CALL(script, frame_update(_)).Times(1);
+ system.frame_update();
+ }
}
-TEST_F(ScriptTest, ListScripts) {
- size_t script_count = 0;
- for (auto & _ : this->system.get_scripts()) {
- script_count++;
+TEST_F(ScriptTest, UpdateInactive) {
+ BehaviorScript & behaviorscript = this->behaviorscript;
+ MyScript & script = this->script;
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(script, init()).Times(0);
+ EXPECT_CALL(script, fixed_update(_)).Times(0);
+ behaviorscript.active = false;
+ system.fixed_update();
+ }
+
+ {
+ InSequence seq;
+
+ EXPECT_CALL(script, init()).Times(1);
+ EXPECT_CALL(script, fixed_update(_)).Times(1);
+ behaviorscript.active = true;
+ system.fixed_update();
}
- ASSERT_EQ(1, script_count);
+}
+
+TEST_F(ScriptTest, SaveManager) {
+ MyScript & script = this->script;
+
+ EXPECT_EQ(&script.get_save_manager(), &this->save_manager);
+}
+
+TEST_F(ScriptTest, LoopTimerManager) {
+ MyScript & script = this->script;
+
+ EXPECT_EQ(&script.get_loop_timer(), &this->loop_timer);
}
diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h
new file mode 100644
index 0000000..f953aab
--- /dev/null
+++ b/src/test/ScriptTest.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <crepe/api/BehaviorScript.h>
+#include <crepe/api/Script.h>
+#include <crepe/manager/ComponentManager.h>
+#include <crepe/manager/EventManager.h>
+#include <crepe/manager/LoopTimerManager.h>
+#include <crepe/manager/SaveManager.h>
+#include <crepe/system/ScriptSystem.h>
+
+class ScriptTest : public testing::Test {
+protected:
+ crepe::Mediator mediator;
+ static constexpr const char * OBJ_NAME = "foo";
+
+public:
+ crepe::ComponentManager component_manager {mediator};
+ crepe::ScriptSystem system {mediator};
+ crepe::EventManager event_mgr {mediator};
+ crepe::LoopTimerManager loop_timer {mediator};
+ crepe::SaveManager save_manager {mediator};
+ crepe::GameObject entity = component_manager.new_object(OBJ_NAME);
+
+ class MyScript : public crepe::Script {
+ // NOTE: explicitly stating `public:` is not required on actual scripts
+
+ public:
+ MOCK_METHOD(void, init, (), (override));
+ MOCK_METHOD(void, fixed_update, (crepe::duration_t), (override));
+ MOCK_METHOD(void, frame_update, (crepe::duration_t), (override));
+ };
+
+ crepe::OptionalRef<crepe::BehaviorScript> behaviorscript;
+ crepe::OptionalRef<MyScript> script;
+
+ virtual void SetUp();
+};
diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp
index e6bb058..5928c37 100644
--- a/src/test/ValueBrokerTest.cpp
+++ b/src/test/ValueBrokerTest.cpp
@@ -13,7 +13,7 @@ public:
int write_count = 0;
int value = 0;
- ValueBroker<int> broker{
+ ValueBroker<int> broker {
[this](const int & target) -> void {
this->write_count++;
this->value = target;
@@ -23,7 +23,7 @@ public:
return this->value;
},
};
- Proxy<int> proxy{broker};
+ Proxy<int> proxy {broker};
void SetUp() override {
ASSERT_EQ(read_count, 0);
diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp
new file mode 100644
index 0000000..b17f95a
--- /dev/null
+++ b/src/test/Vector2Test.cpp
@@ -0,0 +1,542 @@
+#include <gtest/gtest.h>
+
+#include <crepe/api/Vector2.h>
+#include <crepe/types.h>
+
+using namespace crepe;
+
+class Vector2Test : public ::testing::Test {
+public:
+ Vector2<int> int_vec1;
+ Vector2<int> int_vec2;
+ Vector2<double> double_vec1;
+ Vector2<double> double_vec2;
+ Vector2<long> long_vec1;
+ Vector2<long> long_vec2;
+ Vector2<float> float_vec1;
+ Vector2<float> float_vec2;
+
+ void SetUp() override {
+ int_vec1 = {1, 2};
+ int_vec2 = {3, 4};
+ double_vec1 = {1.0, 2.0};
+ double_vec2 = {3.0, 4.0};
+ long_vec1 = {1, 2};
+ long_vec2 = {3, 4};
+ float_vec1 = {1.0f, 2.0f};
+ float_vec2 = {3.0f, 4.0f};
+ }
+};
+
+TEST_F(Vector2Test, Subtract) {
+ Vector2<int> result = int_vec1 - int_vec2;
+ EXPECT_EQ(result.x, -2);
+ EXPECT_EQ(result.y, -2);
+
+ Vector2<double> result2 = double_vec1 - double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, -2.0);
+ EXPECT_FLOAT_EQ(result2.y, -2.0);
+
+ Vector2<long> result3 = long_vec1 - long_vec2;
+ EXPECT_EQ(result3.x, -2);
+ EXPECT_EQ(result3.y, -2);
+
+ Vector2<float> result4 = float_vec1 - float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, -2.0f);
+ EXPECT_FLOAT_EQ(result4.y, -2.0f);
+}
+
+TEST_F(Vector2Test, SubtractScalar) {
+ Vector2<int> result = int_vec1 - 1;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 1);
+
+ Vector2<double> result2 = double_vec1 - 1.0;
+ EXPECT_FLOAT_EQ(result2.x, 0.0);
+ EXPECT_FLOAT_EQ(result2.y, 1.0);
+
+ Vector2<long> result3 = long_vec1 - 1;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 1);
+
+ Vector2<float> result4 = float_vec1 - 1.0f;
+ EXPECT_FLOAT_EQ(result4.x, 0.0f);
+ EXPECT_FLOAT_EQ(result4.y, 1.0f);
+}
+
+TEST_F(Vector2Test, Add) {
+ Vector2<int> result = int_vec1 + int_vec2;
+ EXPECT_EQ(result.x, 4);
+ EXPECT_EQ(result.y, 6);
+
+ Vector2<double> result2 = double_vec1 + double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 4.0);
+ EXPECT_FLOAT_EQ(result2.y, 6.0);
+
+ Vector2<long> result3 = long_vec1 + long_vec2;
+ EXPECT_EQ(result3.x, 4);
+ EXPECT_EQ(result3.y, 6);
+
+ Vector2<float> result4 = float_vec1 + float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 4.0f);
+ EXPECT_FLOAT_EQ(result4.y, 6.0f);
+}
+
+TEST_F(Vector2Test, AddScalar) {
+ Vector2<int> result = int_vec1 + 1;
+ EXPECT_EQ(result.x, 2);
+ EXPECT_EQ(result.y, 3);
+
+ Vector2<double> result2 = double_vec1 + 1.0;
+ EXPECT_FLOAT_EQ(result2.x, 2.0);
+ EXPECT_FLOAT_EQ(result2.y, 3.0);
+
+ Vector2<long> result3 = long_vec1 + 1;
+ EXPECT_EQ(result3.x, 2);
+ EXPECT_EQ(result3.y, 3);
+
+ Vector2<float> result4 = float_vec1 + 1.0f;
+ EXPECT_FLOAT_EQ(result4.x, 2.0f);
+ EXPECT_FLOAT_EQ(result4.y, 3.0f);
+}
+
+TEST_F(Vector2Test, Multiply) {
+ Vector2<int> result = int_vec1 * int_vec2;
+ EXPECT_EQ(result.x, 3);
+ EXPECT_EQ(result.y, 8);
+
+ Vector2<double> result2 = double_vec1 * double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 3.0);
+ EXPECT_FLOAT_EQ(result2.y, 8.0);
+
+ Vector2<long> result3 = long_vec1 * long_vec2;
+ EXPECT_EQ(result3.x, 3);
+ EXPECT_EQ(result3.y, 8);
+
+ Vector2<float> result4 = float_vec1 * float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 3.0f);
+ EXPECT_FLOAT_EQ(result4.y, 8.0f);
+}
+
+TEST_F(Vector2Test, MultiplyScalar) {
+ Vector2<int> result = int_vec1 * 2;
+ EXPECT_EQ(result.x, 2);
+ EXPECT_EQ(result.y, 4);
+
+ Vector2<double> result2 = double_vec1 * 2.0;
+ EXPECT_FLOAT_EQ(result2.x, 2.0);
+ EXPECT_FLOAT_EQ(result2.y, 4.0);
+
+ Vector2<long> result3 = long_vec1 * 2;
+ EXPECT_EQ(result3.x, 2);
+ EXPECT_EQ(result3.y, 4);
+
+ Vector2<float> result4 = float_vec1 * 2.0f;
+ EXPECT_FLOAT_EQ(result4.x, 2.0f);
+ EXPECT_FLOAT_EQ(result4.y, 4.0f);
+}
+
+TEST_F(Vector2Test, Divide) {
+ Vector2<int> result = int_vec1 / int_vec2;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 0);
+
+ Vector2<double> result2 = double_vec1 / double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331);
+ EXPECT_FLOAT_EQ(result2.y, 0.5);
+
+ Vector2<long> result3 = long_vec1 / long_vec2;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 0);
+
+ Vector2<float> result4 = float_vec1 / float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 0.333333343f);
+ EXPECT_FLOAT_EQ(result4.y, 0.5f);
+}
+
+TEST_F(Vector2Test, DivideScalar) {
+ Vector2<int> result = int_vec1 / 2;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 1);
+
+ Vector2<double> result2 = double_vec1 / 2.0;
+ EXPECT_FLOAT_EQ(result2.x, 0.5);
+ EXPECT_FLOAT_EQ(result2.y, 1.0);
+
+ Vector2<long> result3 = long_vec1 / 2;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 1);
+
+ Vector2<float> result4 = float_vec1 / 2.0f;
+ EXPECT_FLOAT_EQ(result4.x, 0.5f);
+ EXPECT_FLOAT_EQ(result4.y, 1.0f);
+}
+
+TEST_F(Vector2Test, AddChain) {
+ Vector2<int> result = int_vec1;
+ result += int_vec2;
+ EXPECT_EQ(result.x, 4);
+ EXPECT_EQ(result.y, 6);
+
+ Vector2<double> result2 = double_vec1;
+ result2 += double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 4.0);
+ EXPECT_FLOAT_EQ(result2.y, 6.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 += long_vec2;
+ EXPECT_EQ(result3.x, 4);
+ EXPECT_EQ(result3.y, 6);
+
+ Vector2<float> result4 = float_vec1;
+ result4 += float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 4.0f);
+ EXPECT_FLOAT_EQ(result4.y, 6.0f);
+}
+
+TEST_F(Vector2Test, AddScalarChain) {
+ Vector2<int> result = int_vec1;
+ result += 1;
+ EXPECT_EQ(result.x, 2);
+ EXPECT_EQ(result.y, 3);
+
+ Vector2<double> result2 = double_vec1;
+ result2 += 1.0;
+ EXPECT_FLOAT_EQ(result2.x, 2.0);
+ EXPECT_FLOAT_EQ(result2.y, 3.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 += 1;
+ EXPECT_EQ(result3.x, 2);
+ EXPECT_EQ(result3.y, 3);
+
+ Vector2<float> result4 = float_vec1;
+ result4 += 1.0f;
+ EXPECT_FLOAT_EQ(result4.x, 2.0f);
+ EXPECT_FLOAT_EQ(result4.y, 3.0f);
+}
+
+TEST_F(Vector2Test, SubtractChain) {
+ Vector2<int> result = int_vec1;
+ result -= int_vec2;
+ EXPECT_EQ(result.x, -2);
+ EXPECT_EQ(result.y, -2);
+
+ Vector2<double> result2 = double_vec1;
+ result2 -= double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, -2.0);
+ EXPECT_FLOAT_EQ(result2.y, -2.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 -= long_vec2;
+ EXPECT_EQ(result3.x, -2);
+ EXPECT_EQ(result3.y, -2);
+
+ Vector2<float> result4 = float_vec1;
+ result4 -= float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, -2.0f);
+ EXPECT_FLOAT_EQ(result4.y, -2.0f);
+}
+
+TEST_F(Vector2Test, SubtractScalarChain) {
+ Vector2<int> result = int_vec1;
+ result -= 1;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 1);
+
+ Vector2<double> result2 = double_vec1;
+ result2 -= 1.0;
+ EXPECT_FLOAT_EQ(result2.x, 0.0);
+ EXPECT_FLOAT_EQ(result2.y, 1.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 -= 1;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 1);
+
+ Vector2<float> result4 = float_vec1;
+ result4 -= 1.0f;
+ EXPECT_FLOAT_EQ(result4.x, 0.0f);
+ EXPECT_FLOAT_EQ(result4.y, 1.0f);
+}
+
+TEST_F(Vector2Test, MultiplyChain) {
+ Vector2<int> result = int_vec1;
+ result *= int_vec2;
+ EXPECT_EQ(result.x, 3);
+ EXPECT_EQ(result.y, 8);
+
+ Vector2<double> result2 = double_vec1;
+ result2 *= double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 3.0);
+ EXPECT_FLOAT_EQ(result2.y, 8.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 *= long_vec2;
+ EXPECT_EQ(result3.x, 3);
+ EXPECT_EQ(result3.y, 8);
+
+ Vector2<float> result4 = float_vec1;
+ result4 *= float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 3.0f);
+ EXPECT_FLOAT_EQ(result4.y, 8.0f);
+}
+
+TEST_F(Vector2Test, MultiplyScalarChain) {
+ Vector2<int> result = int_vec1;
+ result *= 2;
+ EXPECT_EQ(result.x, 2);
+ EXPECT_EQ(result.y, 4);
+
+ Vector2<double> result2 = double_vec1;
+ result2 *= 2.0;
+ EXPECT_FLOAT_EQ(result2.x, 2.0);
+ EXPECT_FLOAT_EQ(result2.y, 4.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 *= 2;
+ EXPECT_EQ(result3.x, 2);
+ EXPECT_EQ(result3.y, 4);
+
+ Vector2<float> result4 = float_vec1;
+ result4 *= 2.0f;
+ EXPECT_FLOAT_EQ(result4.x, 2.0f);
+ EXPECT_FLOAT_EQ(result4.y, 4.0f);
+}
+
+TEST_F(Vector2Test, DivideChain) {
+ Vector2<int> result = int_vec1;
+ result /= int_vec2;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 0);
+
+ Vector2<double> result2 = double_vec1;
+ result2 /= double_vec2;
+ EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331);
+ EXPECT_FLOAT_EQ(result2.y, 0.5);
+
+ Vector2<long> result3 = long_vec1;
+ result3 /= long_vec2;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 0);
+
+ Vector2<float> result4 = float_vec1;
+ result4 /= float_vec2;
+ EXPECT_FLOAT_EQ(result4.x, 0.333333343f);
+ EXPECT_FLOAT_EQ(result4.y, 0.5f);
+}
+
+TEST_F(Vector2Test, DivideScalarChain) {
+ Vector2<int> result = int_vec1;
+ result /= 2;
+ EXPECT_EQ(result.x, 0);
+ EXPECT_EQ(result.y, 1);
+
+ Vector2<double> result2 = double_vec1;
+ result2 /= 2.0;
+ EXPECT_FLOAT_EQ(result2.x, 0.5);
+ EXPECT_FLOAT_EQ(result2.y, 1.0);
+
+ Vector2<long> result3 = long_vec1;
+ result3 /= 2;
+ EXPECT_EQ(result3.x, 0);
+ EXPECT_EQ(result3.y, 1);
+
+ Vector2<float> result4 = float_vec1;
+ result4 /= 2.0f;
+ EXPECT_FLOAT_EQ(result4.x, 0.5f);
+ EXPECT_FLOAT_EQ(result4.y, 1.0f);
+}
+
+TEST_F(Vector2Test, Negatation) {
+ Vector2<int> result = -int_vec1;
+ EXPECT_EQ(result.x, -1);
+ EXPECT_EQ(result.y, -2);
+
+ Vector2<double> result2 = -double_vec1;
+ EXPECT_FLOAT_EQ(result2.x, -1.0);
+ EXPECT_FLOAT_EQ(result2.y, -2.0);
+
+ Vector2<long> result3 = -long_vec1;
+ EXPECT_EQ(result3.x, -1);
+ EXPECT_EQ(result3.y, -2);
+
+ Vector2<float> result4 = -float_vec1;
+ EXPECT_FLOAT_EQ(result4.x, -1.0f);
+ EXPECT_FLOAT_EQ(result4.y, -2.0f);
+}
+
+TEST_F(Vector2Test, Equals) {
+ EXPECT_TRUE(int_vec1 == int_vec1);
+ EXPECT_FALSE(int_vec1 == int_vec2);
+ EXPECT_TRUE(double_vec1 == double_vec1);
+ EXPECT_FALSE(double_vec1 == double_vec2);
+ EXPECT_TRUE(long_vec1 == long_vec1);
+ EXPECT_FALSE(long_vec1 == long_vec2);
+}
+
+TEST_F(Vector2Test, NotEquals) {
+ EXPECT_FALSE(int_vec1 != int_vec1);
+ EXPECT_TRUE(int_vec1 != int_vec2);
+ EXPECT_FALSE(double_vec1 != double_vec1);
+ EXPECT_TRUE(double_vec1 != double_vec2);
+ EXPECT_FALSE(long_vec1 != long_vec1);
+ EXPECT_TRUE(long_vec1 != long_vec2);
+}
+
+TEST_F(Vector2Test, Truncate) {
+ Vector2<int> vec = {3, 4};
+ vec.truncate(3);
+ EXPECT_EQ(vec.x, 0);
+ EXPECT_EQ(vec.y, 0);
+
+ Vector2<double> vec2 = {3.0, 4.0};
+ vec2.truncate(3.0);
+ EXPECT_FLOAT_EQ(vec2.x, 1.8);
+ EXPECT_FLOAT_EQ(vec2.y, 2.4);
+
+ Vector2<long> vec3 = {3, 4};
+ vec3.truncate(3);
+ EXPECT_EQ(vec3.x, 0);
+ EXPECT_EQ(vec3.y, 0);
+
+ Vector2<float> vec4 = {3.0f, 4.0f};
+ vec4.truncate(3.0f);
+ EXPECT_FLOAT_EQ(vec4.x, 1.8f);
+ EXPECT_FLOAT_EQ(vec4.y, 2.4f);
+}
+
+TEST_F(Vector2Test, Normalize) {
+ Vector2<int> vec = {3, 4};
+ vec.normalize();
+ EXPECT_EQ(vec.x, 0);
+ EXPECT_EQ(vec.y, 0);
+
+ Vector2<double> vec2 = {3.0, 4.0};
+ vec2.normalize();
+ EXPECT_FLOAT_EQ(vec2.x, 0.6);
+ EXPECT_FLOAT_EQ(vec2.y, 0.8);
+
+ Vector2<long> vec3 = {3, 4};
+ vec3.normalize();
+ EXPECT_EQ(vec3.x, 0);
+ EXPECT_EQ(vec3.y, 0);
+
+ Vector2<float> vec4 = {3.0f, 4.0f};
+ vec4.normalize();
+ EXPECT_FLOAT_EQ(vec4.x, 0.6f);
+ EXPECT_FLOAT_EQ(vec4.y, 0.8f);
+}
+
+TEST_F(Vector2Test, Length) {
+ Vector2<int> vec = {3, 4};
+ EXPECT_EQ(vec.length(), 5);
+
+ Vector2<double> vec2 = {3.0, 4.0};
+ EXPECT_FLOAT_EQ(vec2.length(), 5.0);
+
+ Vector2<long> vec3 = {3, 4};
+ EXPECT_EQ(vec3.length(), 5);
+
+ Vector2<float> vec4 = {3.0f, 4.0f};
+ EXPECT_FLOAT_EQ(vec4.length(), 5.0f);
+}
+
+TEST_F(Vector2Test, LengthSquared) {
+ Vector2<int> vec = {3, 4};
+ EXPECT_EQ(vec.length_squared(), 25);
+
+ Vector2<double> vec2 = {3.0, 4.0};
+ EXPECT_FLOAT_EQ(vec2.length_squared(), 25.0);
+
+ Vector2<long> vec3 = {3, 4};
+ EXPECT_EQ(vec3.length_squared(), 25);
+
+ Vector2<float> vec4 = {3.0f, 4.0f};
+ EXPECT_FLOAT_EQ(vec4.length_squared(), 25.0f);
+}
+
+TEST_F(Vector2Test, Dot) {
+ Vector2<int> vec1 = {3, 4};
+ Vector2<int> vec2 = {5, 6};
+ EXPECT_EQ(vec1.dot(vec2), 39);
+
+ Vector2<double> vec3 = {3.0, 4.0};
+ Vector2<double> vec4 = {5.0, 6.0};
+ EXPECT_FLOAT_EQ(vec3.dot(vec4), 39.0);
+
+ Vector2<long> vec5 = {3, 4};
+ Vector2<long> vec6 = {5, 6};
+ EXPECT_EQ(vec5.dot(vec6), 39);
+
+ Vector2<float> vec7 = {3.0f, 4.0f};
+ Vector2<float> vec8 = {5.0f, 6.0f};
+ EXPECT_FLOAT_EQ(vec7.dot(vec8), 39.0f);
+}
+
+TEST_F(Vector2Test, Distance) {
+ Vector2<int> vec1 = {1, 1};
+ Vector2<int> vec2 = {4, 5};
+ EXPECT_EQ(vec1.distance(vec2), 5);
+
+ Vector2<double> vec3 = {1.0, 1.0};
+ Vector2<double> vec4 = {4.0, 5.0};
+ EXPECT_FLOAT_EQ(vec3.distance(vec4), 5.0);
+
+ Vector2<long> vec5 = {1, 1};
+ Vector2<long> vec6 = {4, 5};
+ EXPECT_EQ(vec5.distance(vec6), 5);
+
+ Vector2<float> vec7 = {1.0f, 1.0f};
+ Vector2<float> vec8 = {4.0f, 5.0f};
+ EXPECT_FLOAT_EQ(vec7.distance(vec8), 5.0f);
+}
+
+TEST_F(Vector2Test, DistanceSquared) {
+ Vector2<int> vec1 = {3, 4};
+ Vector2<int> vec2 = {5, 6};
+ EXPECT_EQ(vec1.distance_squared(vec2), 8);
+
+ Vector2<double> vec3 = {3.0, 4.0};
+ Vector2<double> vec4 = {5.0, 6.0};
+ EXPECT_FLOAT_EQ(vec3.distance_squared(vec4), 8.0);
+
+ Vector2<long> vec5 = {3, 4};
+ Vector2<long> vec6 = {5, 6};
+ EXPECT_EQ(vec5.distance_squared(vec6), 8);
+
+ Vector2<float> vec7 = {3.0f, 4.0f};
+ Vector2<float> vec8 = {5.0f, 6.0f};
+ EXPECT_FLOAT_EQ(vec7.distance_squared(vec8), 8.0f);
+}
+
+TEST_F(Vector2Test, Perpendicular) {
+ Vector2<int> vec = {3, 4};
+ Vector2<int> result = vec.perpendicular();
+ EXPECT_EQ(result.x, -4);
+ EXPECT_EQ(result.y, 3);
+
+ Vector2<double> vec2 = {3.0, 4.0};
+ Vector2<double> result2 = vec2.perpendicular();
+ EXPECT_FLOAT_EQ(result2.x, -4.0);
+ EXPECT_FLOAT_EQ(result2.y, 3.0);
+
+ Vector2<long> vec3 = {3, 4};
+ Vector2<long> result3 = vec3.perpendicular();
+ EXPECT_EQ(result3.x, -4);
+ EXPECT_EQ(result3.y, 3);
+
+ Vector2<float> vec4 = {3.0f, 4.0f};
+ Vector2<float> result4 = vec4.perpendicular();
+ EXPECT_FLOAT_EQ(result4.x, -4.0f);
+ EXPECT_FLOAT_EQ(result4.y, 3.0f);
+}
+
+TEST_F(Vector2Test, Rotate) {
+ vec2 foo {0, 1};
+
+ foo = foo.rotate(90);
+ const float GOOD_ENOUGH = 0.001;
+ EXPECT_NEAR(foo.x, 1, GOOD_ENOUGH);
+ EXPECT_NEAR(foo.y, 0, GOOD_ENOUGH);
+}
diff --git a/src/test/main.cpp b/src/test/main.cpp
index 241015d..0e1bc75 100644
--- a/src/test/main.cpp
+++ b/src/test/main.cpp
@@ -1,15 +1,29 @@
-#include <crepe/api/Config.h>
-
#include <gtest/gtest.h>
+#include <crepe/api/Config.h>
+
using namespace crepe;
using namespace testing;
+class GlobalConfigReset : public EmptyTestEventListener {
+public:
+ Config & cfg = Config::get_instance();
+
+ // This function is called before each test
+ void OnTestStart(const TestInfo &) override {
+ cfg = {
+ .log = {
+ .level = Log::Level::WARNING,
+ },
+ };
+ }
+};
+
int main(int argc, char ** argv) {
InitGoogleTest(&argc, argv);
- auto & cfg = Config::get_instance();
- cfg.log.level = Log::Level::ERROR;
+ UnitTest & ut = *UnitTest::GetInstance();
+ ut.listeners().Append(new GlobalConfigReset);
return RUN_ALL_TESTS();
}