1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Added fading animation for fade-in effect for summons

This commit is contained in:
Ivan Savenko 2022-12-14 12:04:37 +02:00
parent b2f5a87a0f
commit fb3a08e0a6
14 changed files with 141 additions and 73 deletions

View File

@ -589,6 +589,42 @@ ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CSta
}
bool FadingAnimation::init()
{
logAnim->info("FadingAnimation::init: stack %s", stack->getName());
//TODO: pause animation?
return true;
}
void FadingAnimation::nextFrame()
{
float elapsed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
float fullTime = AnimationControls::getFadeInDuration();
float delta = elapsed / fullTime;
progress += delta;
if (progress > 1.0f)
progress = 1.0f;
uint8_t blueFactor = stack->cloned ? 0 : 255;
uint8_t blueAdded = stack->cloned ? 255 : 0;
uint8_t alpha = CSDL_Ext::lerp(from, dest, progress);
ColorShifterMultiplyAndAdd shifterFade ({255, 255, blueFactor, alpha}, {0, 0, blueAdded, 0});
stackAnimation(stack)->shiftColor(&shifterFade);
if (progress == 1.0f)
delete this;
}
FadingAnimation::FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest):
BattleStackAnimation(owner, _stack),
from(alphaFrom),
dest(alphaDest)
{
}
RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
: AttackAnimation(owner_, attacker, dest_, defender),
projectileEmitted(false)
@ -707,7 +743,6 @@ void RangedAttackAnimation::nextFrame()
RangedAttackAnimation::~RangedAttackAnimation()
{
//FIXME: this assert triggers under some unclear, rare conditions. Possibly - if game window is inactive and/or in foreground/minimized?
assert(!owner.projectilesController->hasActiveProjectile(attackingStack));
assert(projectileEmitted);
@ -846,7 +881,7 @@ void CastAnimation::createProjectile(const Point & from, const Point & dest) con
uint32_t CastAnimation::getAttackClimaxFrame() const
{
//FIXME: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks
//TODO: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks
uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(group);
if (maxFrames > 2)
@ -1117,7 +1152,7 @@ void HeroCastAnimation::nextFrame()
{
float frame = hero->getFrame();
if (frame < 4.0f)
if (frame < 4.0f) // middle point of animation //TODO: un-hardcode
return;
if (!projectileEmitted)
@ -1130,7 +1165,7 @@ void HeroCastAnimation::nextFrame()
if (!owner.projectilesController->hasActiveProjectile(nullptr))
{
emitAnimationEvent();
//FIXME: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile
//TODO: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile
hero->play();
}
}

View File

@ -205,9 +205,21 @@ public:
ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
};
/// Performs fade-in or fade-out animation on stack
class FadingAnimation : public BattleStackAnimation
{
float progress;
uint8_t from;
uint8_t dest;
public:
bool init() override;
void nextFrame() override;
FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest);
};
class RangedAttackAnimation : public AttackAnimation
{
void setAnimationGroup();
void initializeProjectile();
void emitProjectile();

View File

@ -62,7 +62,7 @@ enum Type // list of creature animations, numbers were taken from def files
HITTED = 3, // base animation for when stack is taking damage
DEFENCE = 4, // alternative animation for defending in melee if stack spent its action on defending
DEATH = 5,
DEATH_RANGED = 6, // alternative animation for when stack is killed by ranged attack
DEATH_RANGED = 6, // Optional, alternative animation for when stack is killed by ranged attack
TURN_L = 7,
TURN_R = 8,
//TURN_L2 = 9, //unused - identical to TURN_L
@ -70,14 +70,14 @@ enum Type // list of creature animations, numbers were taken from def files
ATTACK_UP = 11,
ATTACK_FRONT = 12,
ATTACK_DOWN = 13,
SHOOT_UP = 14,
SHOOT_FRONT = 15,
SHOOT_DOWN = 16,
CAST_UP = 17,
CAST_FRONT = 18,
CAST_DOWN = 19,
MOVE_START = 20, // small animation to be played before MOVING
MOVE_END = 21, // small animation to be played after MOVING
SHOOT_UP = 14, // Shooters only
SHOOT_FRONT = 15, // Shooters only
SHOOT_DOWN = 16, // Shooters only
CAST_UP = 17, // If empty, fallback to CAST_FRONT
CAST_FRONT = 18, // Used for any special moves - dragon breath, spellcasting, (possibly - Pit Lord/Ogre Mage ability)
CAST_DOWN = 19, // If empty, fallback to CAST_FRONT
MOVE_START = 20, // small animation to be played before MOVING
MOVE_END = 21, // small animation to be played after MOVING
DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here

View File

@ -73,7 +73,6 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
//don't show animation when no HP is regenerated
switch(bte.effect)
{
//TODO: move to bonus type handler
case Bonus::HP_REGENERATION:
displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, stack->getPosition());
break;

View File

@ -318,7 +318,7 @@ void BattleInterface::stackReset(const CStack * stack)
void BattleInterface::stackAdded(const CStack * stack)
{
stacksController->stackAdded(stack);
stacksController->stackAdded(stack, false);
}
void BattleInterface::stackRemoved(uint32_t stackID)
@ -328,7 +328,7 @@ void BattleInterface::stackRemoved(uint32_t stackID)
queue->update();
}
void BattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
void BattleInterface::stackActivated(const CStack *stack)
{
stacksController->stackActivated(stack);
}

View File

@ -89,7 +89,7 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
for(const CStack * s : stacks)
{
stackAdded(s);
stackAdded(s, true);
}
}
@ -172,17 +172,15 @@ void BattleStacksController::stackReset(const CStack * stack)
});
}
static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
if (stack->isClone())
{
animation->shiftColor(&shifterClone);
}
//static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
//if (stack->isClone())
//{
// animation->shiftColor(&shifterClone);
//}
//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleStacksController::stackAdded(const CStack * stack)
void BattleStacksController::stackAdded(const CStack * stack, bool instant)
{
// Tower shooters have only their upper half visible
static const int turretCreatureAnimationHeight = 235;
@ -212,6 +210,17 @@ void BattleStacksController::stackAdded(const CStack * stack)
stackAnimation[stack->ID]->pos.y = coords.y;
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING);
if (!instant)
{
ColorShifterMultiplyAndAdd shifterFade ({255, 255, 255, 0}, {0, 0, 0, 0});
stackAnimation[stack->ID]->shiftColor(&shifterFade);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
addNewAnim(new FadingAnimation(owner, stack, 0, 255));
});
}
}
void BattleStacksController::setActiveStack(const CStack *stack)

View File

@ -86,7 +86,7 @@ public:
bool facingRight(const CStack * stack) const;
void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex

View File

@ -19,6 +19,11 @@ static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 };
static SDL_Color genShadow(ui8 alpha)
{
return CSDL_Ext::makeColor(0, 0, 0, alpha);
}
SDL_Color AnimationControls::getBlueBorder()
{
return creatureBlueBorder;
@ -134,6 +139,11 @@ float AnimationControls::getFlightDistance(const CCreature * creature)
return static_cast<float>(creature->animation.flightAnimationDistance * 200);
}
float AnimationControls::getFadeInDuration()
{
return 1.0f / settings["battle"]["animationSpeed"].Float();
}
ECreatureAnimType::Type CreatureAnimation::getType() const
{
return type;
@ -150,6 +160,10 @@ void CreatureAnimation::setType(ECreatureAnimType::Type type)
void CreatureAnimation::shiftColor(const ColorShifter* shifter)
{
SDL_Color shadowTest = shifter->shiftColor(genShadow(128));
shadowAlpha = shadowTest.a;
if(forward)
forward->shiftColor(shifter);
@ -160,6 +174,7 @@ void CreatureAnimation::shiftColor(const ColorShifter* shifter)
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
: name(name_),
speed(0.1f),
shadowAlpha(128),
currentFrame(0),
elapsedTime(0),
type(ECreatureAnimType::HOLDING),
@ -281,11 +296,6 @@ inline int getBorderStrength(float time)
return static_cast<int>(borderStrength * 155 + 100); // scale to 0-255
}
static SDL_Color genShadow(ui8 alpha)
{
return CSDL_Ext::makeColor(0, 0, 0, alpha);
}
static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base)
{
return CSDL_Ext::makeColor(base.r, base.g, base.b, ui8(base.a * alpha / 256));
@ -306,11 +316,16 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
);
}
void CreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
{
target[0] = genBorderColor(getBorderStrength(elapsedTime), border);
target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border));
target[1] = genShadow(shadowAlpha / 2);
target[2] = genShadow(shadowAlpha / 2);
target[3] = genShadow(shadowAlpha);
target[4] = genShadow(shadowAlpha);
target[5] = genBorderColor(getBorderStrength(elapsedTime), border);
target[6] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border));
target[7] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
}
void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
@ -326,12 +341,13 @@ void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
if(image)
{
IImage::BorderPallete borderPallete;
genBorderPalette(borderPallete);
IImage::SpecialPalette SpecialPalette;
genSpecialPalette(SpecialPalette);
image->setBorderPallete(borderPallete);
image->setSpecialPallete(SpecialPalette);
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
}
}

View File

@ -46,6 +46,9 @@ namespace AnimationControls
/// Returns distance on which flying creatures should during one animation loop
float getFlightDistance(const CCreature * creature);
/// Returns total time for full fade-in effect on newly summoned creatures, in seconds
float getFadeInDuration();
}
/// Class which manages animations of creatures/units inside battles
@ -80,6 +83,9 @@ private:
///type of animation being displayed
ECreatureAnimType::Type type;
/// current value of shadow transparency
uint8_t shadowAlpha;
/// border color, disabled if alpha = 0
SDL_Color border;
@ -90,7 +96,7 @@ private:
void endAnimation();
void genBorderPalette(IImage::BorderPallete & target);
void genSpecialPalette(IImage::SpecialPalette & target);
public:
/// function(s) that will be called when animation ends, after reset to 1st frame

View File

@ -108,7 +108,7 @@ public:
void adjustPalette(const ColorShifter * shifter) override;
void resetPalette() override;
void setBorderPallete(const BorderPallete & borderPallete) override;
void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
friend class SDLImageLoader;
@ -212,32 +212,17 @@ CDefFile::CDefFile(std::string Name):
data(nullptr),
palette(nullptr)
{
#if 0
static SDL_Color H3_ORIG_PALETTE[8] =
{
{ 0, 255, 255, SDL_ALPHA_OPAQUE},
{255, 150, 255, SDL_ALPHA_OPAQUE},
{255, 100, 255, SDL_ALPHA_OPAQUE},
{255, 50, 255, SDL_ALPHA_OPAQUE},
{255, 0, 255, SDL_ALPHA_OPAQUE},
{255, 255, 0, SDL_ALPHA_OPAQUE},
{180, 0, 255, SDL_ALPHA_OPAQUE},
{ 0, 255, 0, SDL_ALPHA_OPAQUE}
};
#endif // 0
//First 8 colors in def palette used for transparency
static SDL_Color H3Palette[8] =
{
{ 0, 0, 0, 0},// 100% - transparency
{ 0, 0, 0, 32},// 75% - shadow border,
{ 0, 0, 0, 64},// TODO: find exact value
{ 0, 0, 0, 128},// TODO: for transparency
{ 0, 0, 0, 128},// 50% - shadow body
{ 0, 0, 0, 0},// 100% - selection highlight
{ 0, 0, 0, 128},// 50% - shadow body below selection
{ 0, 0, 0, 64} // 75% - shadow border below selection
{ 0, 0, 0, 0},// transparency ( used in most images )
{ 0, 0, 0, 64},// shadow border ( used in battle, adventure map def's )
{ 0, 0, 0, 64},// shadow border ( used in fog-of-war def's )
{ 0, 0, 0, 128},// shadow body ( used in fog-of-war def's )
{ 0, 0, 0, 128},// shadow body ( used in battle, adventure map def's )
{ 0, 0, 0, 0},// selection ( used in battle def's )
{ 0, 0, 0, 128},// shadow body below selection ( used in battle def's )
{ 0, 0, 0, 64} // shadow border below selection ( used in battle def's )
};
data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
@ -827,11 +812,11 @@ void SDLImage::resetPalette()
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
}
void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
{
if(surf->format->palette)
{
SDL_SetColors(surf, const_cast<SDL_Color *>(borderPallete.data()), 5, 3);
SDL_SetColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
}
}

View File

@ -37,7 +37,7 @@ class ColorShifter;
class IImage
{
public:
using BorderPallete = std::array<SDL_Color, 3>;
using SpecialPalette = std::array<SDL_Color, 7>;
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0;
@ -65,8 +65,8 @@ public:
virtual void adjustPalette(const ColorShifter * shifter) = 0;
virtual void resetPalette() = 0;
//only indexed bitmaps, colors 5,6,7 must be special
virtual void setBorderPallete(const BorderPallete & borderPallete) = 0;
//only indexed bitmaps with 7 special colors
virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
virtual void horizontalFlip() = 0;
virtual void verticalFlip() = 0;

View File

@ -52,6 +52,13 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect &
image->draw(surface, pos.x, pos.y, &sourceRect);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha)
{
assert(image);
if (image)
image->draw(surface, pos.x, pos.y, &sourceRect, alpha);
}
void Canvas::draw(Canvas & image, const Point & pos)
{
blitAt(image.surface, pos.x, pos.y, surface);

View File

@ -41,6 +41,10 @@ public:
/// renders section of image bounded by sourceRect at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
/// renders section of image bounded by sourceRect at specified position at specific transparency value
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha);
/// renders another canvas onto this canvas
void draw(Canvas & image, const Point & pos);

View File

@ -592,7 +592,6 @@
"index" : 65,
"targetType" : "CREATURE",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "CLONE"
@ -636,7 +635,6 @@
"index" : 66,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -662,7 +660,6 @@
"index" : 67,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -688,7 +685,6 @@
"index" : 68,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -714,7 +710,6 @@
"index" : 69,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"