Sequence startup story before character selection

This commit is contained in:
2026-05-21 22:55:52 +00:00
parent 5cd0c9c6d5
commit e7bd783309
7 changed files with 354 additions and 26 deletions
@@ -2,7 +2,10 @@
#include "AgrarianDemoNoticeWidget.h"
#include "AgrarianGamePlayerController.h"
#include "Engine/World.h"
#include "Input/Reply.h"
#include "InputCoreTypes.h"
#include "Rendering/DrawElements.h"
#include "Styling/CoreStyle.h"
@@ -46,12 +49,76 @@ float GetCreditsSequenceDurationSeconds()
}
return Duration;
}
struct FAgrarianStoryBeat
{
const TCHAR* Title;
const TCHAR* Body;
};
const FAgrarianStoryBeat StoryBeats[] = {
{ TEXT("The lights did not go out at once."), TEXT("They failed by region, by habit, by trust. Cities still glowed in places, but the systems beneath them no longer answered together.") },
{ TEXT("People carried what they could."), TEXT("A few tools. A little seed. Names, debts, grief, and the stubborn memory of how life used to work.") },
{ TEXT("The old world became material."), TEXT("Roads became paths. Machines became shelter. Stores became ruins. Every useful thing had to be understood again before it could be used.") },
{ TEXT("The land kept its own calendar."), TEXT("Rain returned when it returned. Trees grew in years, not minutes. Hunger did not wait for plans, and winter did not care who was ready.") },
{ TEXT("Knowledge became inheritance."), TEXT("A fire tended well could save a night. A field understood well could save a season. A lesson taught well could save a generation.") },
{ TEXT("Agrarian begins at Ground Zero."), TEXT("One person steps forward. What they build, waste, protect, plant, teach, and remember becomes the first line of a new history.") },
};
}
void UAgrarianDemoNoticeWidget::NativeConstruct()
{
Super::NativeConstruct();
CreditsStartTimeSeconds = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0f;
SetIsFocusable(true);
SegmentStartTimeSeconds = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0f;
SetKeyboardFocus();
}
FReply UAgrarianDemoNoticeWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
if (PresentationSegment == EAgrarianStartupPresentationSegment::Splash
|| PresentationSegment == EAgrarianStartupPresentationSegment::Story
|| PresentationSegment == EAgrarianStartupPresentationSegment::Credits)
{
RequestSkipSegment();
return FReply::Handled();
}
return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
}
FReply UAgrarianDemoNoticeWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
if (PresentationSegment == EAgrarianStartupPresentationSegment::Splash
|| PresentationSegment == EAgrarianStartupPresentationSegment::Story
|| PresentationSegment == EAgrarianStartupPresentationSegment::Credits)
{
RequestSkipSegment();
return FReply::Handled();
}
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
void UAgrarianDemoNoticeWidget::SetPresentationSegment(EAgrarianStartupPresentationSegment NewSegment)
{
PresentationSegment = NewSegment;
SegmentStartTimeSeconds = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0f;
SetKeyboardFocus();
}
float UAgrarianDemoNoticeWidget::GetSegmentElapsedSeconds() const
{
const UWorld* World = GetWorld();
return World ? World->GetTimeSeconds() - SegmentStartTimeSeconds : 0.0f;
}
void UAgrarianDemoNoticeWidget::RequestSkipSegment() const
{
if (AAgrarianGamePlayerController* AgrarianController = Cast<AAgrarianGamePlayerController>(GetOwningPlayer()))
{
AgrarianController->AgrarianSkipStartupPresentation();
}
}
int32 UAgrarianDemoNoticeWidget::NativePaint(
@@ -66,9 +133,19 @@ int32 UAgrarianDemoNoticeWidget::NativePaint(
LayerId = Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
const FVector2D Size = AllottedGeometry.GetLocalSize();
const UWorld* World = GetWorld();
const float Elapsed = World ? World->GetTimeSeconds() - CreditsStartTimeSeconds : 0.0f;
if (Elapsed <= GetCreditsSequenceDurationSeconds() + 1.0f)
if (PresentationSegment == EAgrarianStartupPresentationSegment::Splash)
{
DrawSplash(OutDrawElements, LayerId, AllottedGeometry);
return LayerId;
}
if (PresentationSegment == EAgrarianStartupPresentationSegment::Story)
{
DrawStory(OutDrawElements, LayerId, AllottedGeometry);
return LayerId;
}
if (PresentationSegment == EAgrarianStartupPresentationSegment::Credits)
{
FSlateDrawElement::MakeBox(
OutDrawElements,
@@ -120,6 +197,104 @@ int32 UAgrarianDemoNoticeWidget::NativePaint(
return LayerId;
}
void UAgrarianDemoNoticeWidget::DrawSplash(
FSlateWindowElementList& OutDrawElements,
int32& LayerId,
const FGeometry& AllottedGeometry) const
{
const FVector2D Size = AllottedGeometry.GetLocalSize();
const float Elapsed = GetSegmentElapsedSeconds();
const float Pulse = 0.5f + (0.5f * FMath::Sin(Elapsed * 2.1f));
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(Size), FSlateLayoutTransform(FVector2f::ZeroVector)),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.005f, 0.007f, 0.006f, 1.0f));
const FVector2D MarkSize(148.0f, 148.0f);
const FVector2D MarkPosition((Size.X - MarkSize.X) * 0.5f, (Size.Y * 0.5f) - 185.0f);
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(MarkSize), FSlateLayoutTransform(FVector2f(MarkPosition))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.10f, 0.16f, 0.095f, 0.78f));
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(92.0f, 12.0f), FSlateLayoutTransform(FVector2f(MarkPosition.X + 28.0f, MarkPosition.Y + 96.0f))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.68f, 0.86f, 0.44f, 0.85f + (0.15f * Pulse)));
const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 72);
const FSlateFontInfo StudioFont = FCoreStyle::GetDefaultFontStyle("Regular", 24);
const FSlateFontInfo NoticeFont = FCoreStyle::GetDefaultFontStyle("Regular", 16);
DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("AGRARIAN")), Size.Y * 0.5f - 12.0f, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f));
DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Agrarian Studio")), Size.Y * 0.5f + 78.0f, StudioFont, FLinearColor(0.70f, 0.82f, 0.60f, 1.0f));
DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, CopyrightNotice, Size.Y - 86.0f, NoticeFont, FLinearColor(0.58f, 0.64f, 0.54f, 1.0f));
DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Press any key to continue")), Size.Y - 56.0f, NoticeFont, FLinearColor(0.72f, 0.80f, 0.66f, 0.65f + (0.35f * Pulse)));
}
void UAgrarianDemoNoticeWidget::DrawStory(
FSlateWindowElementList& OutDrawElements,
int32& LayerId,
const FGeometry& AllottedGeometry) const
{
const FVector2D Size = AllottedGeometry.GetLocalSize();
const float Elapsed = FMath::Clamp(GetSegmentElapsedSeconds(), 0.0f, 59.99f);
const int32 BeatIndex = FMath::Clamp(FMath::FloorToInt(Elapsed / 10.0f), 0, UE_ARRAY_COUNT(StoryBeats) - 1);
const float BeatTime = FMath::Fmod(Elapsed, 10.0f);
const float FadeIn = SmoothStep(BeatTime / 1.35f);
const float FadeOut = 1.0f - SmoothStep((BeatTime - 8.25f) / 1.25f);
const float Alpha = FMath::Clamp(FadeIn * FadeOut, 0.0f, 1.0f);
const float Drift = (BeatTime - 5.0f) * 8.0f;
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(Size), FSlateLayoutTransform(FVector2f::ZeroVector)),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.008f, 0.010f, 0.008f, 1.0f));
for (int32 BandIndex = 0; BandIndex < 5; ++BandIndex)
{
const float BandY = Size.Y * (0.18f + (0.15f * BandIndex)) + (FMath::Sin(Elapsed * 0.22f + BandIndex) * 16.0f);
const float BandWidth = Size.X * (0.42f + (0.08f * BandIndex));
const float BandX = FMath::Fmod((Elapsed * (18.0f + BandIndex * 7.0f)) + BandIndex * 220.0f, Size.X + BandWidth) - BandWidth;
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(BandWidth, 3.0f + BandIndex), FSlateLayoutTransform(FVector2f(BandX, BandY))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.28f, 0.44f, 0.26f, 0.14f));
}
const FVector2D PanelSize(FMath::Min(980.0f, Size.X - 140.0f), 360.0f);
const FVector2D PanelPosition((Size.X - PanelSize.X) * 0.5f + Drift, (Size.Y - PanelSize.Y) * 0.5f);
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(PanelSize), FSlateLayoutTransform(FVector2f(PanelPosition))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.016f, 0.020f, 0.015f, 0.82f * Alpha));
const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 40);
const FSlateFontInfo BodyFont = FCoreStyle::GetDefaultFontStyle("Regular", 24);
const FSlateFontInfo LabelFont = FCoreStyle::GetDefaultFontStyle("Regular", 15);
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(StoryBeats[BeatIndex].Title), PanelPosition + FVector2D(46.0f, 58.0f), PanelSize.X - 92.0f, TitleFont, FLinearColor(0.90f, 0.96f, 0.82f, Alpha));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(StoryBeats[BeatIndex].Body), PanelPosition + FVector2D(50.0f, 142.0f), PanelSize.X - 100.0f, BodyFont, FLinearColor(0.74f, 0.82f, 0.68f, Alpha));
DrawCenteredText(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Press any key to skip to credits")), Size.Y - 54.0f, LabelFont, FLinearColor(0.68f, 0.76f, 0.62f, 0.78f));
}
void UAgrarianDemoNoticeWidget::DrawCenteredText(
FSlateWindowElementList& OutDrawElements,
int32& LayerId,
@@ -176,7 +351,7 @@ void UAgrarianDemoNoticeWidget::DrawCinematicCredits(
}
const FVector2D Size = AllottedGeometry.GetLocalSize();
const float Elapsed = World->GetTimeSeconds() - CreditsStartTimeSeconds;
const float Elapsed = GetSegmentElapsedSeconds();
const float IntroDelay = 0.55f;
const float SlamSeconds = 0.28f;
const float ExitSeconds = 0.48f;