Players do combat on a grid, using cards to move and deal damage to their opponent
Movement cards can be played at an increased cost in response to a card on your opponent's turn
Resources to play cards are gained by discarding cards
Goals
Explore Systems design and experiment with design choices that I have not seen implemented
Implement a system that builds card rules text from instructions that are stored in data assets
Use Unreal Engine's event system to control the flow of the game
Highlights
Cards can just be made in the editor as data assets
Card objects can be updated at runtime by changing which data asset it is pointing to, saving memory
UCLASS()
class GCG_API AGCG_PlayerController : public APlayerController
{
GENERATED_BODY()
public:
AGCG_PlayerController();
UPROPERTY(EditAnywhere)
TSubclassOf<AActor> PawnClass;
AGCG_GridPawn* GridPawn;
protected:
AGCG_Deck* PlayerDeck;
UPROPERTY(EditAnywhere)
UGCG_DeckDataAsset* DeckDataAsset;
AGCG_GameModeBase* GameMode;
AGCG_Hand* Hand;
UPROPERTY(VisibleAnywhere)
TArray<UGCG_CardDataAsset*> DiscardPile;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UFUNCTION()
void DrawXCards(int32 player_id, uint8 num);
UFUNCTION()
void MoveToDiscard(UGCG_CardDataAsset* da);
public:
virtual void SetupInputComponent() override;
UFUNCTION(BlueprintCallable)
void DrawTest();
UFUNCTION(BlueprintCallable)
void DiscardCardTest();
};
AGCG_PlayerController::AGCG_PlayerController()
{
bShowMouseCursor = true;
bEnableClickEvents = true;
bEnableMouseOverEvents = true;
}
void AGCG_PlayerController::BeginPlay()
{
Super::BeginPlay();
FTransform transform = FTransform(FVector(0.0f, 0.0f, 0.0f));
PlayerDeck = (AGCG_Deck*)UGameplayStatics::BeginDeferredActorSpawnFromClass(
GetWorld(),
AGCG_Deck::StaticClass(),
transform,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn,
(AActor*)this
);
PlayerDeck->SetDeckList(TArray<UGCG_CardDataAsset*>(DeckDataAsset->GetDeckList()));
UGameplayStatics::FinishSpawningActor(PlayerDeck, transform);
Hand = (AGCG_Hand*)UGameplayStatics::BeginDeferredActorSpawnFromClass(
GetWorld(),
AGCG_Hand::StaticClass(),
GetTransformComponent()->GetComponentTransform(),
ESpawnActorCollisionHandlingMethod::AlwaysSpawn,
(AActor*)this
);
UGameplayStatics::FinishSpawningActor(PlayerDeck, GetTransformComponent()->GetComponentTransform());
Hand->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
Hand->AddActorLocalRotation(FRotator(0.0f, 180.0f, 0.0f), false);
Hand->AddActorLocalOffset(FVector(-50.0f, 0.0f, -20.0f), false);
Hand->CardDiscarded.AddDynamic(this, &AGCG_PlayerController::MoveToDiscard);
GameMode = Cast<AGCG_GameModeBase>(UGameplayStatics::GetGameMode(GetWorld()));
GameMode->DrawCards.AddDynamic(this, &AGCG_PlayerController::DrawXCards);
}
void AGCG_PlayerController::DrawXCards(int32 player_id, uint8 num)
{
for (int i=0; i < num; i++)
{
Hand->AddCard(
PlayerDeck->Draw(),
NetPlayerIndex
);
}
}
void AGCG_PlayerController::MoveToDiscard(UGCG_CardDataAsset* da)
{
DiscardPile.Push(da);
}
The player controller is responsible for communicating and executing everything the player does to the state of the game. It also receives events from the game state, and performs the action to the objects the player controls. Having the assets controlled like this makes it easier to control which assets will be replicated for netplay, preventing player's from accessing information they shouldn't have by using tools to read asset data.