Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

War Pigs

Chaotic diner card battles where pigs gamble to avoid becoming bacon. · By WeShipEventually

Jeremy Cahill Blog#2 - Fixing the Hover/Collision Bug and Card Round Flow

A topic by Aluminati Studios created 93 days ago Views: 49
Viewing posts 1 to 1
(1 edit)

 War Pigs Devlog: Fixing the Hover/Collision Bug and Card Round Flow

Author: Jeremy Cahill


The Problem:

The card actors were using a hover feature (scaling the card up and lifting it slightly on mouse-over) to give visual feedback before a click. The issue was that every card actor kept collision enabled all the time, regardless of whether it was actually the top card of the deck or buried underneath other cards.

That meant hover and click events could fire on cards that were stacked below the real top card, since collision never got turned off for them. This eventually snowballed into a full stalemate bug: after enough rounds, the game's internal tracking of "which card is on top" could point to the wrong actor, so a valid click on the real top card got silently rejected.

 The Fix:


Instead of leaving collision always on and just changing visuals on hover, `SetSelectable()` now fully toggles collision on and off based on whether a card is actually the current top card of its deck.

void ACardActor::SetSelectable(bool bNewSelectable)
{
    bIsSelectable = bNewSelectable;
    if (CardSpriteComponent)
    {
        if (bIsSelectable)
        {
            CardSpriteComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
            CardSpriteComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
            CardSpriteComponent->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
        }
        else
        {
            CardSpriteComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        }
    }
    if (!bIsSelectable && bIsHovered)
    {
        OnCardHoverEnd(this);
    }
}


On the deck side, `UpdateTopCardInteraction()` makes sure only index 0 of the deck (always the true top card) is ever selectable:


void ADeckActor::UpdateTopCardInteraction(bool bAllowInteraction)
{
    for (int32 i = 0; i < CardActors.Num(); ++i)
    {
        ACardActor* Card = CardActors[i];
        if (!Card)
        {
            continue;
        }
        const bool bIsCurrentTopCard = bAllowInteraction && i == 0;
        Card->SetSelectable(bIsCurrentTopCard);
    }
}


I also removed a separate `TopIndex` counter that used to track the top card independently of the `DeckArray`/`CardActors` arrays. That counter could drift out of sync over time, which was the deeper cause of the stalemate. Now `DeckArray` and `CardActors` stay index-aligned, so index 0 is always guaranteed to be the actual top card, no separate tracking needed.

 Other Fixes That Went In Alongside This:

- Card shuffle: our shuffle was sampling from the whole deck array on every swap instead of just the remaining unshuffled portion, which is a statistically biased shuffle, not a true Fisher-Yates. Fixed to shrink the sampling range properly, which also cut down on abnormally frequent Wars.

- Card flip timing: cards were being revealed and then hidden again in the same frame, so players only ever saw card backs. Added a short delay before hiding/moving cards so both flips are actually visible.

- Deck sinking off-screen: after enough rounds, both card piles slowly drifted below the table and out of camera view. This was caused by an unbounded stack-height counter. Replaced it with a recalculation that's always bounded by the deck's current size.

 What's Next:

With the click/collision/stalemate issues resolved, next up is implementing War animations itself.

Thanks for following along!