간단하게 게임잼 할까요?, 주말간 라운드 증강 게임 제작 과정


게임잼에 들어가기 앞서 팀원들에게 언리얼 샘플 프로젝트 사용을 독려했다.
나도 언리얼의 TPS 샘플 프로젝트를 사용했다.

image-1778479764440


작업 목표

이번 발표 빌드에서 보여주고 싶었던 것은 단순한 전투 테스트가 아니라, TPS 시점의 라운드 증강 게임으로 읽히는 구조였다.
그래서 이번 작업은 기능을 넓게 늘리기보다, 전투 흐름을 한 방향으로 정리하는 쪽으로 잡았다.

중심 목표는 다음과 같았다.

  • 적이 플레이어를 추격하고 공격하는 구조
  • 여러 타입이 있는 적
  • 라운드 시작, 진행, 승패가 보이는 게임모드
  • 증강을 통한 스탯 향상

AI 개선

이번 작업에서 가장 먼저 정리한 부분은 적 전투 흐름이었다.
기존 프로젝트에도 StateTree 기반 AI 구조는 있었지만, 발표용 프로토타입으로 보이려면 적의 추격과 공격이 더 분명하게 드러나야 했다.

그래서 이번 작업에서는 CombatAIController를 업데이트해서 StateTree 기반 AI를 실제 전투 루프에 더 직접적으로 연결했다.
소유 시점에 AI가 바로 시작되도록 흐름을 정리했고, 프로토타입 기준에서 적이 추격하고 공격하는 동작이 더 자연스럽게 이어지도록 맞췄다.

image-1778470726346

증강

USTRUCT(BlueprintType)
struct FPXStatAugmentData : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName AugmentID;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EPXAugmentRarity Rarity = EPXAugmentRarity::Common;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EPXPlayerStatType StatType = EPXPlayerStatType::AttackDamage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float AddValue = 0.0f;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float MultiplyValue = 1.0f;
};

RoundManager에서 증강 선택을 관리한다.
라운드 종료 후 증강 선택지를 만들고 고르게 되면 OnAugmentChoiceRequestedBroadcast한다.

void APXRoundManager::RequestAugmentChoice()
{
    RoundState = EPXRoundState::ChoosingAugment;
    GenerateAugmentChoices();
    OnAugmentChoiceRequested.Broadcast(CurrentRoundNumber);
}

void APXRoundManager::GenerateAugmentChoices()
{
    CurrentAugmentChoices.Reset();
    StatAugmentTable->GetAllRows<FPXStatAugmentData>(TEXT("PXRoundManager::GenerateAugmentChoices"), AllRows);

    while (!AllRows.IsEmpty() && CurrentAugmentChoices.Num() < AugmentChoiceCount)
    {
        const int32 RowIndex = FMath::RandRange(0, AllRows.Num() - 1);
        CurrentAugmentChoices.Add(*AllRows[RowIndex]);
        AllRows.RemoveAtSwap(RowIndex);
    }
}

이 후보들을 읽어 카드 위젯 3장에 세팅한다.
각 카드의 이름, 설명을 채우고, 버튼 클릭 시 OnAugmentSelected delegate를 Broadcast. 한다.

void UTXAugmentChoiceWidget::ShowChoicesFromRoundManager(APXRoundManager* InRoundManager)
{
    const TArray<FPXStatAugmentData> Picks = RoundManager->GetCurrentAugmentChoices();

    for (int32 i = 0; i < Cards.Num(); ++i)
    {
        Cards[i]->SetupFromStatData(Picks[i]);
        Cards[i]->OnAugmentSelected.AddDynamic(this, &UTXAugmentChoiceWidget::OnStatCardSelected);
    }
}

void UTXAugmentCardWidget::SetupFromStatData(const FPXStatAugmentData& InData)
{
    CachedAugmentID = InData.AugmentID;
    if (Text_Name) Text_Name->SetText(InData.Name);
    if (Text_Desc) Text_Desc->SetText(InData.Description);
}

플레이어가 카드를 고르면 RoundManager에서 플레이어에 스탯을 적용하고 다음 라운드를 진행한다.

void APXRoundManager::NotifyAugmentSelected(FName AugmentID)
{
    if (RoundState != EPXRoundState::ChoosingAugment)
    {
        return;
    }

    ApplyAugmentToPlayer(AugmentID);
    ContinueAfterAugmentChoice();
}
  1. RoundManager가 후보를 만든다
  2. ChoiceWidget이 보여준다
  3. CardWidget이 ID를 넘긴다
  4. RoundManager가 선택 결과를 적용한다
  5. CombatComponent가 실제 전투 수치를 바꾼다

게임모드

이번 발표 빌드가 기존 프로젝트와 가장 달라 보이게 만든 핵심은 APXWaveSurvivalGameMode 추가다.
이전까지는 전투 시스템과 캐릭터 구조가 중심이었다면, 이번 작업에서는 게임이 실제로 시작되고 끝나는 흐름을 게임모드 레벨에서 보이게 만들었다.

  • 기본적인 시작 처리
  • 플레이 진행 상태 관리
  • 승패 처리
  • RoundManager 바인딩
  • 라운드가 끝나면 증강 카드 표시

플레이 화면

https://youtu.be/jAkYU40FLOg

발표

다들 바쁜시간 쪼개서 와주셨다... 감사합니다!

image-1778470101347