Added a simple Animation system.

Launch/return effect reimplemented.
ViewController's scrolling camera reimplemented as an Animation.
This commit is contained in:
Aloshi 2013-12-08 11:35:43 -06:00
parent 9875a59549
commit a13ed11ead
10 changed files with 256 additions and 34 deletions

View file

@ -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

View file

@ -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<void()> finishedCallback, bool reverse)
{
mAnimationController = std::shared_ptr<AnimationController>(new AnimationController(anim, finishedCallback, reverse));
}
void GuiComponent::stopAnimation()
{
mAnimationController.reset();
}

View file

@ -2,9 +2,12 @@
#define _GUICOMPONENT_H_
#include "InputConfig.h"
#include <memory>
#include <Eigen/Dense>
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<void()> 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<AnimationController> mAnimationController;
};
#endif

View file

@ -0,0 +1,43 @@
#pragma once
#include <Eigen/Dense>
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<typename T>
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);
}

View file

@ -0,0 +1,38 @@
#include "AnimationController.h"
AnimationController::AnimationController(Animation* anim, std::function<void()> 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();
}
}
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <memory>
#include <functional>
#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<void()> finishedCallback = nullptr, bool reverse = false);
virtual ~AnimationController();
void update(int deltaTime);
private:
Animation* mAnimation;
std::function<void()> mFinishedCallback;
bool mReverse;
int mTime;
};

View file

@ -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<float>(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<Eigen::Vector3f>(-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<float>(0.0, 1.0, t*t);
}
private:
Eigen::Affine3f mCameraStart;
Eigen::Vector3f mTarget;
int mDuration;
Eigen::Affine3f& cameraOut;
float& fadeOut;
};

View file

@ -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<Eigen::Vector3f>(-mCameraStart.translation(), mTarget, t*t*t + 1);
}
private:
Eigen::Affine3f mCameraStart;
Eigen::Vector3f mTarget;
Eigen::Affine3f& cameraOut;
};

View file

@ -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");
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<GameListView> ViewController::getSystemView(SystemData* system)
@ -120,7 +136,6 @@ std::shared_ptr<GameListView> ViewController::getSystemView(SystemData* system)
else
view = std::shared_ptr<GameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
//view = std::shared_ptr<GameListView>(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));
}
}

View file

@ -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<GameListView> getSystemView(SystemData* system);
std::shared_ptr<GuiComponent> mCurrentView;
std::map< SystemData*, std::shared_ptr<GameListView> > mSystemViews;
Eigen::Affine3f mCameraPos;
Eigen::Affine3f mCamera;
float mFadeOpacity;
State mState;
};