From a13ed11ead656cdc19fe4a8dcae0a7c55f47e735 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Sun, 8 Dec 2013 11:35:43 -0600 Subject: [PATCH] Added a simple Animation system. Launch/return effect reimplemented. ViewController's scrolling camera reimplemented as an Animation. --- CMakeLists.txt | 7 +++ src/GuiComponent.cpp | 14 ++++++ src/GuiComponent.h | 8 ++++ src/animations/Animation.h | 43 +++++++++++++++++ src/animations/AnimationController.cpp | 38 +++++++++++++++ src/animations/AnimationController.h | 22 +++++++++ src/animations/LaunchAnimation.h | 64 ++++++++++++++++++++++++++ src/animations/MoveCameraAnimation.h | 24 ++++++++++ src/views/ViewController.cpp | 64 +++++++++++++------------- src/views/ViewController.h | 6 ++- 10 files changed, 256 insertions(+), 34 deletions(-) create mode 100644 src/animations/Animation.h create mode 100644 src/animations/AnimationController.cpp create mode 100644 src/animations/AnimationController.h create mode 100644 src/animations/LaunchAnimation.h create mode 100644 src/animations/MoveCameraAnimation.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 58678f012..0442f7277 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,6 +199,11 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GridGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/Animation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/LaunchAnimation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/MoveCameraAnimation.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h @@ -269,6 +274,8 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/views/GridGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/data/ResourceUtil.cpp ${CMAKE_CURRENT_SOURCE_DIR}/data/converted/ES_logo_16_png.cpp ${CMAKE_CURRENT_SOURCE_DIR}/data/converted/ES_logo_32_png.cpp diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index c4a1fde41..6bd70a989 100644 --- a/src/GuiComponent.cpp +++ b/src/GuiComponent.cpp @@ -2,6 +2,7 @@ #include "Window.h" #include "Log.h" #include "Renderer.h" +#include "animations/AnimationController.h" GuiComponent::GuiComponent(Window* window) : mWindow(window), mParent(NULL), mOpacity(255), mPosition(Eigen::Vector3f::Zero()), mSize(Eigen::Vector2f::Zero()), mTransform(Eigen::Affine3f::Identity()) @@ -32,6 +33,9 @@ bool GuiComponent::input(InputConfig* config, Input input) void GuiComponent::update(int deltaTime) { + if(mAnimationController) + mAnimationController->update(deltaTime); + for(unsigned int i = 0; i < getChildCount(); i++) { getChild(i)->update(deltaTime); @@ -181,3 +185,13 @@ void GuiComponent::textInput(const char* text) (*iter)->textInput(text); } } + +void GuiComponent::setAnimation(Animation* anim, std::function finishedCallback, bool reverse) +{ + mAnimationController = std::shared_ptr(new AnimationController(anim, finishedCallback, reverse)); +} + +void GuiComponent::stopAnimation() +{ + mAnimationController.reset(); +} diff --git a/src/GuiComponent.h b/src/GuiComponent.h index 928d6f2b7..5118b199e 100644 --- a/src/GuiComponent.h +++ b/src/GuiComponent.h @@ -2,9 +2,12 @@ #define _GUICOMPONENT_H_ #include "InputConfig.h" +#include #include class Window; +class Animation; +class AnimationController; class GuiComponent { @@ -48,6 +51,10 @@ public: unsigned int getChildCount() const; GuiComponent* getChild(unsigned int i) const; + // animation will be automatically deleted when it completes or is stopped. + void setAnimation(Animation* animation, std::function finishedCallback = nullptr, bool reverse = false); + void stopAnimation(); + virtual unsigned char getOpacity() const; virtual void setOpacity(unsigned char opacity); @@ -73,6 +80,7 @@ protected: private: Eigen::Affine3f mTransform; //Don't access this directly! Use getTransform()! + std::shared_ptr mAnimationController; }; #endif diff --git a/src/animations/Animation.h b/src/animations/Animation.h new file mode 100644 index 000000000..e34dfb438 --- /dev/null +++ b/src/animations/Animation.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +class Animation +{ +public: + virtual int getDuration() const = 0; + virtual void apply(float t) = 0; +}; + + +// useful helper/interpolation functions +inline float clamp(float min, float max, float val) +{ + if(val < min) + val = min; + else if(val > max) + val = max; + + return val; +} + +//http://en.wikipedia.org/wiki/Smoothstep +inline float smoothStep(float edge0, float edge1, float x) +{ + // Scale, and clamp x to 0..1 range + x = clamp(0, 1, (x - edge0)/(edge1 - edge0)); + + // Evaluate polynomial + return x*x*x*(x*(x*6 - 15) + 10); +} + +template +T lerp(const T& start, const T& end, float t) +{ + if(t <= 0.0f) + return start; + if(t >= 1.0f) + return end; + + return (start * (1 - t) + end * t); +} diff --git a/src/animations/AnimationController.cpp b/src/animations/AnimationController.cpp new file mode 100644 index 000000000..03b75cccd --- /dev/null +++ b/src/animations/AnimationController.cpp @@ -0,0 +1,38 @@ +#include "AnimationController.h" + +AnimationController::AnimationController(Animation* anim, std::function finishedCallback, bool reverse) + : mAnimation(anim), mFinishedCallback(finishedCallback), mReverse(reverse), mTime(0) +{ +} + +AnimationController::~AnimationController() +{ + if(mFinishedCallback) + mFinishedCallback(); + + delete mAnimation; +} + +void AnimationController::update(int deltaTime) +{ + mTime += deltaTime; + float t = (float)mTime / mAnimation->getDuration(); + + if(t > 1.0f) + t = 1.0f; + else if(t < 0.0f) + t = 0.0f; + + mAnimation->apply(mReverse ? 1.0f - t : t); + + if(t == 1.0f) + { + if(mFinishedCallback) + { + // in case mFinishedCallback causes us to be deleted, use a copy + auto copy = mFinishedCallback; + mFinishedCallback = nullptr; + copy(); + } + } +} diff --git a/src/animations/AnimationController.h b/src/animations/AnimationController.h new file mode 100644 index 000000000..5ea62b209 --- /dev/null +++ b/src/animations/AnimationController.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include "Animation.h" + +class AnimationController +{ +public: + // FinishedCallback is guaranteed to be called exactly once, even if the animation does not finish normally. + // Takes ownership of anim (will delete in destructor). + AnimationController(Animation* anim, std::function finishedCallback = nullptr, bool reverse = false); + virtual ~AnimationController(); + + void update(int deltaTime); + +private: + Animation* mAnimation; + std::function mFinishedCallback; + bool mReverse; + int mTime; +}; diff --git a/src/animations/LaunchAnimation.h b/src/animations/LaunchAnimation.h new file mode 100644 index 000000000..3072a5f37 --- /dev/null +++ b/src/animations/LaunchAnimation.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Animation.h" +#include "../Log.h" + +// let's look at the game launch effect: +// -move camera to center on point P (interpolation method: linear) +// -zoom camera to factor Z via matrix scale (interpolation method: exponential) +// -fade screen to black at rate F (interpolation method: exponential) + +// How the animation gets constructed from the example of implementing the game launch effect: +// 1. Current parameters are retrieved through a get() call +// ugliness: +// -have to have a way to get a reference to the animation +// -requires additional implementation in some parent object, potentially AnimationController +// 2. ViewController is passed in LaunchAnimation constructor, applies state through setters +// ugliness: +// -effect only works for ViewController +// 3. Pass references to ViewController variables - LaunchAnimation(mCameraPos, mFadePerc, target) +// ugliness: +// -what if ViewController is deleted? --> AnimationController class handles that +// -no callbacks for changes...but that works well with this style of update, so I think it's okay +// 4. Use callbacks to set variables +// ugliness: +// -boilerplate as hell every time + +// #3 wins +// can't wait to see how this one bites me in the ass + +class LaunchAnimation : public Animation +{ +public: + //Target is a centerpoint + LaunchAnimation(Eigen::Affine3f& camera, float& fade, const Eigen::Vector3f& target, int duration) : + mCameraStart(camera), mTarget(target), mDuration(duration), cameraOut(camera), fadeOut(fade) {} + + int getDuration() const override { return mDuration; } + + void apply(float t) override + { + cameraOut = Eigen::Affine3f::Identity(); + + float zoom = lerp(1.0, 3.0, t*t); + cameraOut.scale(Eigen::Vector3f(zoom, zoom, 1)); + + const float sw = (float)Renderer::getScreenWidth() / zoom; + const float sh = (float)Renderer::getScreenHeight() / zoom; + + Eigen::Vector3f centerPoint = lerp(-mCameraStart.translation() + Eigen::Vector3f(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0), + mTarget, smoothStep(0.0, 1.0, t)); + + cameraOut.translate(Eigen::Vector3f((sw / 2) - centerPoint.x(), (sh / 2) - centerPoint.y(), 0)); + + fadeOut = lerp(0.0, 1.0, t*t); + } + +private: + Eigen::Affine3f mCameraStart; + Eigen::Vector3f mTarget; + int mDuration; + + Eigen::Affine3f& cameraOut; + float& fadeOut; +}; diff --git a/src/animations/MoveCameraAnimation.h b/src/animations/MoveCameraAnimation.h new file mode 100644 index 000000000..9f3675980 --- /dev/null +++ b/src/animations/MoveCameraAnimation.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Animation.h" + +class MoveCameraAnimation : public Animation +{ +public: + MoveCameraAnimation(Eigen::Affine3f& camera, const Eigen::Vector3f& target) : mCameraStart(camera), mTarget(target), cameraOut(camera) {} + + int getDuration() const override { return 400; } + + void apply(float t) override + { + // cubic ease out + t -= 1; + cameraOut.translation() = -lerp(-mCameraStart.translation(), mTarget, t*t*t + 1); + } + +private: + Eigen::Affine3f mCameraStart; + Eigen::Vector3f mTarget; + + Eigen::Affine3f& cameraOut; +}; diff --git a/src/views/ViewController.cpp b/src/views/ViewController.cpp index 94164bc34..526a6e018 100644 --- a/src/views/ViewController.cpp +++ b/src/views/ViewController.cpp @@ -5,9 +5,11 @@ #include "BasicGameListView.h" #include "DetailedGameListView.h" #include "GridGameListView.h" +#include "../animations/LaunchAnimation.h" +#include "../animations/MoveCameraAnimation.h" ViewController::ViewController(Window* window) - : GuiComponent(window), mCurrentView(nullptr), mCameraPos(Eigen::Affine3f::Identity()) + : GuiComponent(window), mCurrentView(nullptr), mCamera(Eigen::Affine3f::Identity()), mFadeOpacity(0) { mState.viewing = START_SCREEN; } @@ -16,6 +18,7 @@ void ViewController::goToSystemSelect() { mState.viewing = SYSTEM_SELECT; goToSystem(SystemData::sSystemVector.at(0)); + //playViewTransition(); } SystemData* getSystemCyclic(SystemData* from, bool reverse) @@ -68,6 +71,12 @@ void ViewController::goToSystem(SystemData* system) mState.data.system = system; mCurrentView = getSystemView(system); + playViewTransition(); +} + +void ViewController::playViewTransition() +{ + setAnimation(new MoveCameraAnimation(mCamera, mCurrentView->getPosition())); } void ViewController::onFileChanged(FileData* file, FileChangeType change) @@ -78,7 +87,7 @@ void ViewController::onFileChanged(FileData* file, FileChangeType change) } } -void ViewController::launch(FileData* game) +void ViewController::launch(FileData* game, const Eigen::Vector3f& center) { if(game->getType() != GAME) { @@ -86,9 +95,16 @@ void ViewController::launch(FileData* game) return; } - // Effect TODO + game->getSystem()->getTheme()->playSound("gameSelectSound"); - game->getSystem()->launchGame(mWindow, game); + + Eigen::Affine3f origCamera = mCamera; + setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), [this, origCamera, center, game] + { + game->getSystem()->launchGame(mWindow, game); + mCamera = origCamera; + setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), nullptr, true); + }); } std::shared_ptr ViewController::getSystemView(SystemData* system) @@ -120,7 +136,6 @@ std::shared_ptr ViewController::getSystemView(SystemData* system) else view = std::shared_ptr(new BasicGameListView(mWindow, system->getRootFolder())); - //view = std::shared_ptr(new GridGameListView(mWindow, system->getRootFolder())); view->setTheme(system->getTheme()); @@ -146,46 +161,24 @@ bool ViewController::input(InputConfig* config, Input input) return false; } - -float clamp(float min, float max, float val) -{ - if(val < min) - val = min; - else if(val > max) - val = max; - - return val; -} - -//http://en.wikipedia.org/wiki/Smoothstep -float smoothStep(float edge0, float edge1, float x) -{ - // Scale, and clamp x to 0..1 range - x = clamp(0, 1, (x - edge0)/(edge1 - edge0)); - - // Evaluate polynomial - return x*x*x*(x*(x*6 - 15) + 10); -} - void ViewController::update(int deltaTime) { if(mCurrentView) { mCurrentView->update(deltaTime); - - // move camera towards current view (should use smoothstep) - Eigen::Vector3f diff = (mCurrentView->getPosition() + mCameraPos.translation()) * 0.0075f * (float)deltaTime; - mCameraPos.translate(-diff); } + + GuiComponent::update(deltaTime); } void ViewController::render(const Eigen::Affine3f& parentTrans) { - Eigen::Affine3f trans = parentTrans * mCameraPos; + Eigen::Affine3f trans = mCamera * parentTrans; - //should really do some clipping here + // draw systems for(auto it = mSystemViews.begin(); it != mSystemViews.end(); it++) { + // clipping Eigen::Vector3f pos = it->second->getPosition(); Eigen::Vector2f size = it->second->getSize(); @@ -196,4 +189,11 @@ void ViewController::render(const Eigen::Affine3f& parentTrans) pos.x() <= camPos.x() + camSize.x() && pos.y() <= camPos.y() + camSize.y()) it->second->render(trans); } + + // fade out + if(mFadeOpacity) + { + Renderer::setMatrix(Eigen::Affine3f::Identity()); + Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x00000000 | (unsigned char)(mFadeOpacity * 255)); + } } diff --git a/src/views/ViewController.h b/src/views/ViewController.h index faf17bc56..33b109a56 100644 --- a/src/views/ViewController.h +++ b/src/views/ViewController.h @@ -19,7 +19,7 @@ public: // Plays a nice launch effect and launches the game at the end of it. // Once the game terminates, plays a return effect. - void launch(FileData* game); + void launch(FileData* game, const Eigen::Vector3f& centerCameraOn = Eigen::Vector3f(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0)); bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; @@ -49,12 +49,14 @@ public: inline const State& getState() const { return mState; } private: + void playViewTransition(); std::shared_ptr getSystemView(SystemData* system); std::shared_ptr mCurrentView; std::map< SystemData*, std::shared_ptr > mSystemViews; - Eigen::Affine3f mCameraPos; + Eigen::Affine3f mCamera; + float mFadeOpacity; State mState; };