mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 08:52:19 -07:00
Animate life changes with a counter flash and battlefield shimmer
- Life loss/gain pulses the player life counter with a brief flash - The battlefield table zone shimmers crimson on damage - Respects the per-effect animation toggles Both effects drive their decay from GameScene's shared animation timer through the IAnimatedItem interface (QElapsedTimer based), instead of owning per-item QTimers. Took 8 minutes
This commit is contained in:
parent
39371f9185
commit
68e1ca1616
7 changed files with 136 additions and 8 deletions
|
|
@ -29,8 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state,
|
||||||
{
|
{
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
|
||||||
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
|
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
|
||||||
value = newValue;
|
value = newValue;
|
||||||
|
onValueChanged(oldValue, newValue);
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -228,3 +229,9 @@ void AbstractCounterDialog::changeValue(int diff)
|
||||||
curValue += diff;
|
curValue += diff;
|
||||||
setTextValue(QString::number(curValue));
|
setTextValue(QString::number(curValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/)
|
||||||
|
{
|
||||||
|
// Default: no feedback. Subclasses such as PlayerCounter override this to
|
||||||
|
// flash the counter on meaningful changes (life gain/loss).
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@ protected:
|
||||||
bool hovered = false;
|
bool hovered = false;
|
||||||
bool useNameForShortcut;
|
bool useNameForShortcut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash).
|
||||||
|
*
|
||||||
|
* Called whenever the counter's value changes, before the item repaints.
|
||||||
|
*/
|
||||||
|
virtual void onValueChanged(int oldValue, int newValue);
|
||||||
|
|
||||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
|
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,11 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
|
||||||
AbstractCounter *widget;
|
AbstractCounter *widget;
|
||||||
if (state->getName() == "life") {
|
if (state->getName() == "life") {
|
||||||
widget = playerTarget->addCounter(state);
|
widget = playerTarget->addCounter(state);
|
||||||
|
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
|
||||||
|
if (newValue < oldValue) {
|
||||||
|
tableZoneGraphicsItem->triggerDamageShimmer();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
widget = new GeneralCounter(state, player, true, this);
|
widget = new GeneralCounter(state, player, true, this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#include "player_target.h"
|
#include "player_target.h"
|
||||||
|
|
||||||
|
#include "../../client/settings/cache_settings.h"
|
||||||
#include "../../game/player/player_logic.h"
|
#include "../../game/player/player_logic.h"
|
||||||
#include "../../interface/pixel_map_generator.h"
|
#include "../../interface/pixel_map_generator.h"
|
||||||
|
#include "../game_scene.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
|
|
@ -21,8 +24,8 @@ QRectF PlayerCounter::boundingRect() const
|
||||||
|
|
||||||
void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
const int radius = 8;
|
const int radius = 15;
|
||||||
const qreal border = 1;
|
const qreal border = 1.5;
|
||||||
QPainterPath path(QPointF(50 - border / 2, border / 2));
|
QPainterPath path(QPointF(50 - border / 2, border / 2));
|
||||||
path.lineTo(radius, border / 2);
|
path.lineTo(radius, border / 2);
|
||||||
path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90);
|
path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90);
|
||||||
|
|
@ -30,10 +33,10 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
|
||||||
path.lineTo(50 - border / 2, 30 - border / 2);
|
path.lineTo(50 - border / 2, 30 - border / 2);
|
||||||
path.closeSubpath();
|
path.closeSubpath();
|
||||||
|
|
||||||
QPen pen(QColor(100, 100, 100));
|
QPen pen(qApp->palette().highlight().color());
|
||||||
pen.setWidth(border);
|
pen.setWidthF(border);
|
||||||
painter->setPen(pen);
|
painter->setPen(pen);
|
||||||
painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160));
|
painter->setBrush(hovered ? QColor(18, 24, 38, 200) : QColor(11, 14, 20, 184));
|
||||||
|
|
||||||
painter->drawPath(path);
|
painter->drawPath(path);
|
||||||
|
|
||||||
|
|
@ -45,6 +48,48 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
|
||||||
painter->setFont(font);
|
painter->setFont(font);
|
||||||
painter->setPen(Qt::white);
|
painter->setPen(Qt::white);
|
||||||
painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value));
|
painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value));
|
||||||
|
|
||||||
|
// Life-change flash: emerald on gain, red on loss, decaying over a few ticks.
|
||||||
|
if (flashAlpha > 0) {
|
||||||
|
painter->save();
|
||||||
|
QColor flashColor = flashDelta > 0 ? QColor(52, 224, 122) : QColor(239, 68, 68);
|
||||||
|
flashColor.setAlphaF(0.45 * flashAlpha);
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->setBrush(flashColor);
|
||||||
|
painter->setOpacity(0.85);
|
||||||
|
painter->drawPath(path);
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerCounter::onValueChanged(int oldValue, int newValue)
|
||||||
|
{
|
||||||
|
flashDelta = newValue - oldValue;
|
||||||
|
if (flashDelta == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SettingsCache::instance().userInterface().getLifeCounterAnimationsEnabled()) {
|
||||||
|
flashAlpha = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
flashAlpha = 1.0;
|
||||||
|
flashClock.start();
|
||||||
|
if (scene()) {
|
||||||
|
static_cast<GameScene *>(scene())->registerAnimationItem(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerCounter::animationEvent()
|
||||||
|
{
|
||||||
|
flashAlpha = 1.0 - flashClock.elapsed() / flashDurationMs;
|
||||||
|
if (flashAlpha <= 0.0) {
|
||||||
|
flashAlpha = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem)
|
PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem)
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,34 @@
|
||||||
#ifndef PLAYERTARGET_H
|
#ifndef PLAYERTARGET_H
|
||||||
#define PLAYERTARGET_H
|
#define PLAYERTARGET_H
|
||||||
|
|
||||||
|
#include "../animated_item.h"
|
||||||
#include "../board/abstract_counter.h"
|
#include "../board/abstract_counter.h"
|
||||||
#include "../board/arrow_target.h"
|
#include "../board/arrow_target.h"
|
||||||
#include "../board/graphics_item_type.h"
|
#include "../board/graphics_item_type.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
class PlayerLogic;
|
class PlayerLogic;
|
||||||
|
|
||||||
class PlayerCounter : public AbstractCounter
|
class PlayerCounter : public AbstractCounter, public IAnimatedItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
void onValueChanged(int oldValue, int newValue) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr qreal flashDurationMs = 450.0;
|
||||||
|
|
||||||
|
QElapsedTimer flashClock;
|
||||||
|
qreal flashAlpha = 0.0;
|
||||||
|
int flashDelta = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent);
|
PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent);
|
||||||
QRectF boundingRect() const override;
|
QRectF boundingRect() const override;
|
||||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
|
bool animationEvent() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlayerTarget : public ArrowTarget
|
class PlayerTarget : public ArrowTarget
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "../board/arrow_item.h"
|
#include "../board/arrow_item.h"
|
||||||
#include "../board/card_drag_item.h"
|
#include "../board/card_drag_item.h"
|
||||||
#include "../board/card_item.h"
|
#include "../board/card_item.h"
|
||||||
|
#include "../game_scene.h"
|
||||||
#include "../z_values.h"
|
#include "../z_values.h"
|
||||||
|
|
||||||
#include <QGraphicsScene>
|
#include <QGraphicsScene>
|
||||||
|
|
@ -47,6 +48,31 @@ void TableZone::updateBg()
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TableZone::triggerDamageShimmer()
|
||||||
|
{
|
||||||
|
if (!SettingsCache::instance().userInterface().getBattlefieldFlashEnabled()) {
|
||||||
|
damageShimmerAlpha = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageShimmerAlpha = 1.0;
|
||||||
|
shimmerClock.start();
|
||||||
|
if (scene()) {
|
||||||
|
static_cast<GameScene *>(scene())->registerAnimationItem(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TableZone::animationEvent()
|
||||||
|
{
|
||||||
|
damageShimmerAlpha = 1.0 - shimmerClock.elapsed() / shimmerDurationMs;
|
||||||
|
if (damageShimmerAlpha <= 0.0) {
|
||||||
|
damageShimmerAlpha = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QRectF TableZone::boundingRect() const
|
QRectF TableZone::boundingRect() const
|
||||||
{
|
{
|
||||||
return QRectF(0, 0, width, height);
|
return QRectF(0, 0, width, height);
|
||||||
|
|
@ -77,6 +103,13 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
||||||
painter->fillRect(boundingRect(), FADE_MASK);
|
painter->fillRect(boundingRect(), FADE_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decaying crimson wash from taking damage.
|
||||||
|
if (damageShimmerAlpha > 0.0) {
|
||||||
|
QColor shimmerColor(239, 68, 68);
|
||||||
|
shimmerColor.setAlphaF(0.22 * damageShimmerAlpha);
|
||||||
|
painter->fillRect(boundingRect(), shimmerColor);
|
||||||
|
}
|
||||||
|
|
||||||
paintLandDivider(painter);
|
paintLandDivider(painter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,19 @@
|
||||||
#define TABLEZONE_H
|
#define TABLEZONE_H
|
||||||
|
|
||||||
#include "../../game/zones/table_zone_logic.h"
|
#include "../../game/zones/table_zone_logic.h"
|
||||||
|
#include "../animated_item.h"
|
||||||
#include "../board/abstract_card_item.h"
|
#include "../board/abstract_card_item.h"
|
||||||
#include "select_zone.h"
|
#include "select_zone.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief TableZone is the grid based rect where CardItems may be placed.
|
* @brief TableZone is the grid based rect where CardItems may be placed.
|
||||||
*
|
*
|
||||||
* It is the main play zone and can be customized with background images.
|
* It is the main play zone and can be customized with background images.
|
||||||
*/
|
*/
|
||||||
//! \todo Refactor methods to make more readable, extract logic to private methods (especially reorganizeCards()).
|
//! \todo Refactor methods to make more readable, extract logic to private methods (especially reorganizeCards()).
|
||||||
class TableZone : public SelectZone
|
class TableZone : public SelectZone, public IAnimatedItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
@ -121,6 +124,16 @@ public:
|
||||||
*/
|
*/
|
||||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Flashes the table surface after a player loses life.
|
||||||
|
|
||||||
|
Wired up through the life counter so the battlefield glows when life drops.
|
||||||
|
*/
|
||||||
|
void triggerDamageShimmer();
|
||||||
|
|
||||||
|
/** @brief Decays the damage shimmer by one timer tick. */
|
||||||
|
bool animationEvent() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Toggles the selected items as tapped.
|
Toggles the selected items as tapped.
|
||||||
*/
|
*/
|
||||||
|
|
@ -185,6 +198,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr qreal shimmerDurationMs = 450.0;
|
||||||
|
|
||||||
|
QElapsedTimer shimmerClock;
|
||||||
|
qreal damageShimmerAlpha = 0.0;
|
||||||
|
|
||||||
void paintZoneOutline(QPainter *painter);
|
void paintZoneOutline(QPainter *painter);
|
||||||
void paintLandDivider(QPainter *painter);
|
void paintLandDivider(QPainter *painter);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue