From b5d7c927c579480bfd50a67b73caad1af3db51e6 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 4 Dec 2022 15:36:40 -0800 Subject: [PATCH] Basic animations --- src/dawn/dawnlibs.hpp | 1 + src/dawn/display/CMakeLists.txt | 1 + src/dawn/display/animation/Animation.hpp | 173 ++++++++++++++++++++ src/dawn/display/animation/CMakeLists.txt | 10 ++ src/dawn/display/animation/Easing.hpp | 1 + src/dawn/poker/PokerGame.cpp | 2 +- src/dawn/poker/PokerPlayer.cpp | 8 +- src/dawn/poker/PokerPlayer.hpp | 9 + src/dawn/ui/UIComponent.cpp | 16 ++ src/dawn/ui/UIComponent.hpp | 34 +++- src/dawn/ui/UILabel.cpp | 12 +- src/dawn/ui/UILabel.hpp | 2 + src/dawnpokergame/scenes/TestScene.hpp | 5 +- src/dawnpokergame/ui/PokerPlayerDisplay.cpp | 54 +++--- src/dawnpokergame/ui/PokerPlayerDisplay.hpp | 14 +- 15 files changed, 314 insertions(+), 28 deletions(-) create mode 100644 src/dawn/display/animation/Animation.hpp create mode 100644 src/dawn/display/animation/CMakeLists.txt diff --git a/src/dawn/dawnlibs.hpp b/src/dawn/dawnlibs.hpp index 3524b1f7..8e7d9e42 100644 --- a/src/dawn/dawnlibs.hpp +++ b/src/dawn/dawnlibs.hpp @@ -31,6 +31,7 @@ extern "C" { #include #include #include +#include // #include // #include // #include diff --git a/src/dawn/display/CMakeLists.txt b/src/dawn/display/CMakeLists.txt index e38df2d8..a113819a 100644 --- a/src/dawn/display/CMakeLists.txt +++ b/src/dawn/display/CMakeLists.txt @@ -11,5 +11,6 @@ target_sources(${DAWN_TARGET_NAME} ) # Subdirs +add_subdirectory(animation) add_subdirectory(font) add_subdirectory(mesh) \ No newline at end of file diff --git a/src/dawn/display/animation/Animation.hpp b/src/dawn/display/animation/Animation.hpp new file mode 100644 index 00000000..1f387f26 --- /dev/null +++ b/src/dawn/display/animation/Animation.hpp @@ -0,0 +1,173 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "event/Event.hpp" +#include "Easing.hpp" +#include "util/mathutils.hpp" + +namespace Dawn { + template + struct Keyframe { + float_t time; + T value; + }; + + template + struct TimelineItem { + T *modifies; + std::vector> keyframes; + }; + + template + struct Animation { + public: + bool_t loop = false; + bool_t finished = false; + float_t time = 0; + float_t duration = 0; + easefunction_t *easing = &easeLinear; + std::vector> timelineItems; + Event<> eventAnimationEnd; + + /** + * Get an existing timeline item based on the value that will be modified. + * + * @param modifies Value that is intended to be modified for the timeline. + * @return The existing timeline item OR NULL if not found. + */ + struct TimelineItem * getTimelineItem(T *modifies) { + assertNotNull(modifies); + + auto it = this->timelineItems.begin(); + while(it != this->timelineItems.end()) { + if(it->modifies == modifies) return &(*it); + ++it; + } + + return nullptr; + } + + /** + * Add a timeline item to an animation. + * + * @param modifies Value that will be modified for the timeline item. + * @return The timeline item for that modified value. + */ + struct TimelineItem * addTimelineItem(T *modifies) { + assertNotNull(modifies); + struct TimelineItem item; + item.modifies = modifies; + this->timelineItems.push_back(item); + return &this->timelineItems.back(); + } + + /** + * Add a keyframe to the animation. + * + * @param modifies Pointer to the value that will be modified. + * @param time Time that the animation will occur at (gametime seconds). + * @param value Value for this keyframe + * @return The keyframe that was added. + */ + struct Keyframe * addKeyframe(T *modifies, float_t time, T value) { + auto item = this->getTimelineItem(modifies); + if(item == nullptr) item = this->addTimelineItem(modifies); + + struct Keyframe keyframe; + keyframe.time = time; + keyframe.value = value; + this->duration = mathMax(this->duration, time); + this->finished = false; + + item->keyframes.push_back(keyframe); + return &item->keyframes.back(); + } + + /** + * Tick/Update the animation. + * + * @param delta Delta (in seconds) to update the animation by. + */ + void tick(float_t delta) { + if(this->finished) return; + + float_t newTime = this->time + delta; + + auto it = this->timelineItems.begin(); + while(it != this->timelineItems.end()) { + struct Keyframe *keyframeNext = nullptr; + struct Keyframe *keyframeCurrent = nullptr; + + // For each keyframe + auto itKey = it->keyframes.begin(); + while(itKey != it->keyframes.end()) { + if(itKey->time > newTime) { + keyframeNext = &(*itKey); + break; + } + keyframeCurrent = &(*itKey); + ++itKey; + } + + // Skip when no keyframe + float_t oldTime; + T oldValue; + + if(keyframeCurrent == nullptr) { + if(keyframeNext == nullptr) continue; + oldTime = this->time; + oldValue = *it->modifies; + } else if(keyframeNext == nullptr) { + *it->modifies = keyframeCurrent->value; + ++it; + continue; + } else { + oldValue = keyframeCurrent->value; + oldTime = keyframeCurrent->time; + } + + // Slerp between keyframes + float_t keyframeDelta = this->easing( + (newTime - oldTime) / (keyframeNext->time - oldTime) + ); + *it->modifies = oldValue + ( + (keyframeNext->value - oldValue) * keyframeDelta + ); + ++it; + } + + // Update time. + this->time = newTime; + + // Has the animation finished? + if(newTime < this->duration) return; + + // Do we need to loop? + if(this->loop) { + this->time = 0; + return; + } + + // Animation end. + this->finished = true; + + this->eventAnimationEnd.invoke(); + } + + void restart() { + this->time = 0; + this->finished = false; + } + + /** + * Clears an animaton of all its animation items and keyframes. + */ + void clear() { + this->timelineItems.clear(); + this->duration = 0; + } + }; +} \ No newline at end of file diff --git a/src/dawn/display/animation/CMakeLists.txt b/src/dawn/display/animation/CMakeLists.txt new file mode 100644 index 00000000..a4262361 --- /dev/null +++ b/src/dawn/display/animation/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2022 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +# target_sources(${DAWN_TARGET_NAME} +# PRIVATE +# Animation.cpp +# ) \ No newline at end of file diff --git a/src/dawn/display/animation/Easing.hpp b/src/dawn/display/animation/Easing.hpp index 2046274c..9ab232a7 100644 --- a/src/dawn/display/animation/Easing.hpp +++ b/src/dawn/display/animation/Easing.hpp @@ -7,6 +7,7 @@ #pragma once #include "dawnlibs.hpp" + typedef float_t easefunction_t(float_t t); /** diff --git a/src/dawn/poker/PokerGame.cpp b/src/dawn/poker/PokerGame.cpp index 599bb9b4..522891b1 100644 --- a/src/dawn/poker/PokerGame.cpp +++ b/src/dawn/poker/PokerGame.cpp @@ -25,7 +25,7 @@ void PokerGame::newGame() { auto it = this->players.begin(); while(it != this->players.end()) { auto player = *it; - player->chips = POKER_PLAYER_CHIPS_DEFAULT; + player->setChips(POKER_PLAYER_CHIPS_DEFAULT); player->isOut = false; ++it; } diff --git a/src/dawn/poker/PokerPlayer.cpp b/src/dawn/poker/PokerPlayer.cpp index acb138f8..f884d09e 100644 --- a/src/dawn/poker/PokerPlayer.cpp +++ b/src/dawn/poker/PokerPlayer.cpp @@ -24,6 +24,12 @@ void PokerPlayer::addChips(int32_t chips) { this->chips += chips; if(this->chips > 0) this->isOut = false; + eventChipsChanged.invoke(); +} + +void PokerPlayer::setChips(int32_t chips) { + this->chips = 0; + this->addChips(chips); } bool_t PokerPlayer::needsToBetThisRound() { @@ -39,7 +45,7 @@ void PokerPlayer::bet(struct PokerPot *pot, int32_t chips) { assertTrue(chips >= 0); assertTrue(!this->isFolded); assertTrue(!this->isOut); - this->chips -= chips; + this->setChips(this->chips - chips); this->currentBet += chips; this->hasBetThisRound = true; if(chips > 0) { diff --git a/src/dawn/poker/PokerPlayer.hpp b/src/dawn/poker/PokerPlayer.hpp index a38c9b5c..b7cb2a75 100644 --- a/src/dawn/poker/PokerPlayer.hpp +++ b/src/dawn/poker/PokerPlayer.hpp @@ -37,6 +37,8 @@ namespace Dawn { bool_t isHuman = false; std::vector hand; + Event<> eventChipsChanged; + /** * Creates a PokerPlayer instance. * @@ -54,6 +56,13 @@ namespace Dawn { */ void addChips(int32_t chips); + /** + * Sets the chips a player has. + * + * @param chips Chips to set to the player. + */ + void setChips(int32_t chips); + /** * Returns true if the player still needs to bet this betting round. * diff --git a/src/dawn/ui/UIComponent.cpp b/src/dawn/ui/UIComponent.cpp index 12ac8e26..a5eeea5b 100644 --- a/src/dawn/ui/UIComponent.cpp +++ b/src/dawn/ui/UIComponent.cpp @@ -96,6 +96,14 @@ float_t UIComponent::getHeight() { return this->height; } +float_t UIComponent::getContentWidth() { + return this->width; +} + +float_t UIComponent::getContentHeight() { + return this->height; +} + float_t UIComponent::getRelativeX() { return this->relativeX; } @@ -104,6 +112,14 @@ float_t UIComponent::getRelativeY() { return this->relativeY; } +DawnGame * UIComponent::getGame() { + return this->canvas->getGame(); +} + +Scene * UIComponent::getScene() { + return this->canvas->getScene(); +} + void UIComponent::setTransform( UIComponentAlign xAlign, UIComponentAlign yAlign, diff --git a/src/dawn/ui/UIComponent.hpp b/src/dawn/ui/UIComponent.hpp index f70e7edb..c3dacf83 100644 --- a/src/dawn/ui/UIComponent.hpp +++ b/src/dawn/ui/UIComponent.hpp @@ -68,14 +68,30 @@ namespace Dawn { * * @return Width of the component. */ - float_t getWidth(); + virtual float_t getWidth(); /** * Returns the calculated height, based on the internal alignment values. * * @return Height of the component. */ - float_t getHeight(); + virtual float_t getHeight(); + + /** + * Returns the internal width of the content within this element, e.g. + * the content width. + * + * @return Content width. + */ + virtual float_t getContentWidth(); + + /** + * Returns the internal height of the content within this element, e.g. + * the content height. + * + * @return Content height. + */ + virtual float_t getContentHeight(); /** * Returns the X position, relative to this components' parent. @@ -91,6 +107,20 @@ namespace Dawn { */ float_t getRelativeY(); + /** + * Gets the game that this UI component belongs to. + * + * @return Game instance. + */ + DawnGame * getGame(); + + /** + * Gets the scene that thsi UI Component belongs to. + * + * @return Scene instance. + */ + Scene * getScene(); + /** * Updates the transformation for this component. * diff --git a/src/dawn/ui/UILabel.cpp b/src/dawn/ui/UILabel.cpp index fbe08f71..7e7a9060 100644 --- a/src/dawn/ui/UILabel.cpp +++ b/src/dawn/ui/UILabel.cpp @@ -21,7 +21,7 @@ void UILabel::updateMesh() { if(this->font == nullptr || !this->font->isReady()) return; if(this->text.size() == 0) return; - float_t width = this->getWidth(); + float_t width = this->width; if(width == 0) width = -1; this->font->buffer( @@ -54,6 +54,16 @@ void UILabel::setFontSize(float_t fontSize) { this->needsRebuffering = true; } +float_t UILabel::getContentWidth() { + this->updateMesh(); + return this->measure.getWidth(); +} + +float_t UILabel::getContentHeight() { + this->updateMesh(); + return this->measure.getHeight(); +} + void UILabel::drawSelf(UIShader *shader, glm::mat4 selfTransform) { if(this->font == nullptr || this->text.size() == 0) return; diff --git a/src/dawn/ui/UILabel.hpp b/src/dawn/ui/UILabel.hpp index 678ee346..2aaf5d41 100644 --- a/src/dawn/ui/UILabel.hpp +++ b/src/dawn/ui/UILabel.hpp @@ -29,6 +29,8 @@ namespace Dawn { UILabel(UICanvas *canvas); void drawSelf(UIShader *shader, glm::mat4 selfTransform) override; + virtual float_t getContentWidth() override; + virtual float_t getContentHeight() override; void setTransform( UIComponentAlign xAlign, UIComponentAlign yAlign, diff --git a/src/dawnpokergame/scenes/TestScene.hpp b/src/dawnpokergame/scenes/TestScene.hpp index 667e4f28..f89dd078 100644 --- a/src/dawnpokergame/scenes/TestScene.hpp +++ b/src/dawnpokergame/scenes/TestScene.hpp @@ -29,6 +29,7 @@ namespace Dawn { std::vector assets; vectorAppend(&assets, &PokerGameTextbox::getAssets(assMan)); + vectorAppend(&assets, &PokerPlayerDisplay::getAssets(assMan)); return assets; } @@ -50,12 +51,12 @@ namespace Dawn { auto pokerGameItem = this->createSceneItem(); auto pokerGame = pokerGameItem->addComponent(); - for(int32_t i = 0; i < 4; i++) { + for(int32_t i = 0; i < 5; i++) { auto pokerPlayerInstance = this->createSceneItem(); auto pokerPlayer = pokerPlayerInstance->addComponent(); auto uiPlayer = canvas->addElement(); - uiPlayer->setTransform(UI_COMPONENT_ALIGN_STRETCH, UI_COMPONENT_ALIGN_STRETCH, glm::vec4(0, 0, 0, 0), 0); + uiPlayer->setTransform(UI_COMPONENT_ALIGN_STRETCH, UI_COMPONENT_ALIGN_STRETCH, glm::vec4(i * 220, 0, 0, 0), 0); uiPlayer->setPlayer(pokerPlayer); } diff --git a/src/dawnpokergame/ui/PokerPlayerDisplay.cpp b/src/dawnpokergame/ui/PokerPlayerDisplay.cpp index 3340f192..facd4e8f 100644 --- a/src/dawnpokergame/ui/PokerPlayerDisplay.cpp +++ b/src/dawnpokergame/ui/PokerPlayerDisplay.cpp @@ -8,24 +8,16 @@ using namespace Dawn; -void PokerPlayerDisplay::updatePositions() { - UIEmpty::updatePositions(); - - this->labelChips.setTransform( - UI_COMPONENT_ALIGN_START, UI_COMPONENT_ALIGN_START, - glm::vec4(0, this->labelName.getHeight(), 0, 0), - 0 - ); -} - PokerPlayerDisplay::PokerPlayerDisplay(UICanvas *canvas) : UIEmpty(canvas), labelName(canvas), labelChips(canvas) { - this->font = canvas->getGame()->assetManager.get("truetype_ark"); + this->font = getGame()->assetManager.get("truetype_ark"); + // Player Name this->addChild(&this->labelName); + this->labelName.setText("Player Name"); this->labelName.setFont(&this->font->font); this->labelName.setFontSize(40); this->labelName.setTransform( @@ -34,29 +26,53 @@ PokerPlayerDisplay::PokerPlayerDisplay(UICanvas *canvas) : 0.0f ); + // Chips label this->addChild(&this->labelChips); this->labelChips.setFont(&this->font->font); this->labelChips.setFontSize(40); + this->labelChips.setTransform( + UI_COMPONENT_ALIGN_START, UI_COMPONENT_ALIGN_START, + glm::vec4(0, this->labelName.getContentHeight(), 0, 0), + 0.0f + ); - this->font->eventLoaded.addListener(this, &PokerPlayerDisplay::onFontLoaded); + // Events + getScene()->eventSceneUnpausedUpdate.addListener(this, &PokerPlayerDisplay::onSceneUpdate); } void PokerPlayerDisplay::setPlayer(PokerPlayer *player) { assertNull(this->player); this->player = player; - this->labelName.setText("Player"); - this->labelChips.setText("Chips"); + player->eventChipsChanged.addListener(this, &PokerPlayerDisplay::onPlayerChipsChanged); + + this->labelName.setText("Name"); + this->animChips.clear(); + this->animChips.restart(); + this->animChipsValue = player->chips; + this->updatePositions(); } -void PokerPlayerDisplay::onFontLoaded() { - this->labelName.setFont(&this->font->font); - this->labelChips.setFont(&this->font->font); +void PokerPlayerDisplay::onSceneUpdate() { + this->animChips.tick(getGame()->timeManager.delta); - this->updatePositions(); + std::stringstream stream; + stream.precision(0); + stream << "$"; + stream << this->animChipsValue; + this->labelChips.setText(stream.str()); +} + +void PokerPlayerDisplay::onPlayerChipsChanged() { + std::cout << "Chips" << player->chips << std::endl; + + this->animChips.clear(); + this->animChips.addKeyframe(&this->animChipsValue, 0.0f, this->animChipsValue); + this->animChips.addKeyframe(&this->animChipsValue, 1.0f, this->player->chips); + this->animChips.restart(); } PokerPlayerDisplay::~PokerPlayerDisplay() { - this->font->eventLoaded.removeListener(this, &PokerPlayerDisplay::onFontLoaded); + this->canvas->getScene()->eventSceneUnpausedUpdate.removeListener(this, &PokerPlayerDisplay::onSceneUpdate); } \ No newline at end of file diff --git a/src/dawnpokergame/ui/PokerPlayerDisplay.hpp b/src/dawnpokergame/ui/PokerPlayerDisplay.hpp index 64346f5a..e276d8bd 100644 --- a/src/dawnpokergame/ui/PokerPlayerDisplay.hpp +++ b/src/dawnpokergame/ui/PokerPlayerDisplay.hpp @@ -7,12 +7,15 @@ #include "ui/UILabel.hpp" #include "ui/UIEmpty.hpp" #include "poker/PokerPlayer.hpp" +#include "asset/AssetManager.hpp" #include "asset/assets/TrueTypeAsset.hpp" +#include "display/animation/Animation.hpp" namespace Dawn { class PokerPlayerDisplay : public UIEmpty { private: - void onFontLoaded(); + Animation animChips; + int32_t animChipsValue; protected: PokerPlayer *player = nullptr; @@ -20,9 +23,16 @@ namespace Dawn { UILabel labelChips; TrueTypeAsset *font; - void updatePositions() override; + void onPlayerChipsChanged(); + void onSceneUpdate(); public: + static std::vector getAssets(AssetManager *assMan) { + return std::vector{ + assMan->get("truetype_ark") + }; + } + PokerPlayerDisplay(UICanvas *canvas); void setPlayer(PokerPlayer *player);