Reexport map textures and fixes for map marker, also fix for seed bed actor

This commit is contained in:
2026-09-26 17:44:03 +02:00
parent 812dea8382
commit 3a3a04b689
71 changed files with 328 additions and 188 deletions

View File

@@ -187,7 +187,7 @@ REM carries, which for this map are 16384 in eight tiles; a piece is baked at th
REM pixels over a quarter of the width is what a piece is. REM pixels over a quarter of the width is what a piece is.
SET MAP=/Game/Maps/Persistent_GameWorld SET MAP=/Game/Maps/Persistent_GameWorld
SET BUILDER=-run=WorldPartitionBuilderCommandlet -Builder=/Script/ProjectEleriEditor.WorldMapBuilder -AllowCommandletRendering -SCCProvider=None -unattended -Verbose SET BUILDER=-run=WorldPartitionBuilderCommandlet -Builder=/Script/ProjectEleriEditor.WorldMapBuilder -AllowCommandletRendering -SCCProvider=None -unattended -Verbose
SET PIECE_SIZE=8192 SET PIECE_SIZE=4096
SET PIECE_TILES=4 SET PIECE_TILES=4
SET PIECE_GRID=8 SET PIECE_GRID=8
SET PIECE_ROOT=%PROJECT_ROOT%Intermediate\WorldMap SET PIECE_ROOT=%PROJECT_ROOT%Intermediate\WorldMap

View File

@@ -109,6 +109,8 @@ bSkipMovies=False
+MapsToCook=(FilePath="/Game/Maps/W_Island_1") +MapsToCook=(FilePath="/Game/Maps/W_Island_1")
+DirectoriesToAlwaysCook=(Path="/Game/GameData") +DirectoriesToAlwaysCook=(Path="/Game/GameData")
+DirectoriesToAlwaysCook=(Path="/NNEDenoiser") +DirectoriesToAlwaysCook=(Path="/NNEDenoiser")
+DirectoriesToAlwaysCook=(Path="/Game/Textures/Map")
+DirectoriesToAlwaysCook=(Path="/Game/Maps/Generated")
bRetainStagedDirectory=False bRetainStagedDirectory=False
CustomStageCopyHandler= CustomStageCopyHandler=

Binary file not shown.

View File

@@ -604,7 +604,10 @@ FSeedBedSeedSlot& ASeedBedActor::CreateSlot(FIntVector2 SlotCoordinate, bool bRe
DestroySlotComponents(Slot); DestroySlotComponents(Slot);
} }
if (!IsValid(Slot.SeedBedDecal))
{
Slot.SeedBedDecal = CreateSlotDecalComponent(this, GetRootComponent(), GetSlotComponentName(SlotCoordinate, TEXT("Slot"))); Slot.SeedBedDecal = CreateSlotDecalComponent(this, GetRootComponent(), GetSlotComponentName(SlotCoordinate, TEXT("Slot")));
}
if (SeedBedDecalMaterial.IsValid()) if (SeedBedDecalMaterial.IsValid())
{ {
Slot.SeedBedDynamicMat = UKismetMaterialLibrary::CreateDynamicMaterialInstance(this, SeedBedDecalMaterial.Get(), NAME_None, EMIDCreationFlags::Transient); Slot.SeedBedDynamicMat = UKismetMaterialLibrary::CreateDynamicMaterialInstance(this, SeedBedDecalMaterial.Get(), NAME_None, EMIDCreationFlags::Transient);

View File

@@ -121,8 +121,15 @@ UMapWidget::UMapWidget()
bCenterOnPlayerByDefault = true; bCenterOnPlayerByDefault = true;
MarkerSize = 32.f; MarkerSize = 32.f;
// The marker comes down as the map is pulled back, but by less than the map does: readable when the whole
// map is in view, without crowding a map that is zoomed in on a corner of itself.
MarkerZoomScale = -0.5f;
MarkerFacingOffset = 0.f; MarkerFacingOffset = 0.f;
// PlayerMarkerTexture is deliberately left empty: what the marker is a picture of is the design's to say,
// and a map with nothing set there draws no marker rather than failing to open.
MapBackgroundColor = FLinearColor(0.03f, 0.04f, 0.05f, 0.9f); MapBackgroundColor = FLinearColor(0.03f, 0.04f, 0.05f, 0.9f);
Zoom = 1.f; Zoom = 1.f;
@@ -132,13 +139,14 @@ UMapWidget::UMapWidget()
bMapReady = false; bMapReady = false;
bPiecesRequested = false; bPiecesRequested = false;
bMarkerRequested = false;
bMarkerPlaced = false; bMarkerPlaced = false;
// Nothing has been turned yet, and there is no map yet to say how the map lies in the world. // Nothing has been turned yet, and there is no map yet to say how the map lies in the world.
MarkerAngle = 0.f; MarkerAngle = 0.f;
bMarkerAngleApplied = false; bMarkerAngleApplied = false;
// Nothing has been said yet about the design having no marker in it. // Nothing has been said yet about this map having no marker art assigned.
bMissingMarkerReported = false; bMissingMarkerReported = false;
MapUVPerWorldX = FVector2D::ZeroVector; MapUVPerWorldX = FVector2D::ZeroVector;
MapUVPerWorldY = FVector2D::ZeroVector; MapUVPerWorldY = FVector2D::ZeroVector;
@@ -172,9 +180,9 @@ TSharedRef<SWidget> UMapWidget::RebuildWidget()
void UMapWidget::EnsureWidgetTree() void UMapWidget::EnsureWidgetTree()
{ {
// Called from RebuildWidget and again from everything that wants the map on screen, so the work is only // Called from RebuildWidget and again from everything that wants the map on screen, so the work is only
// done when there is something to do: a standing tree is the whole test. The marker is not part of it, // done when there is something to do: a standing tree is the whole test, and the marker built below is as
// because the design's marker is not the map's to make. // much a part of that tree as the pieces are.
if (MapRoot && MapViewport && MapLayer && PieceGrid && Interaction) if (MapRoot && MapViewport && MapLayer && PieceGrid && PlayerMarker && Interaction)
{ {
return; return;
} }
@@ -267,9 +275,25 @@ void UMapWidget::EnsureWidgetTree()
NewPieceImages.Add(PieceImage); NewPieceImages.Add(PieceImage);
} }
// The player's marker is not built here: it is the widget named PlayerMarker in the design, and it is taken // The player's marker is made here rather than taken from the blueprint: the map is what knows where the
// onto this layer by EnsureMarkerOnMap once the layer is standing. A marker built in code could only ever // player is, and the map is what has to size it, place it and turn it. It is one image at one place, so an
// be the one thing this file knew how to draw. // image is the whole of it, and the one thing the design says about it is which art it draws, which
// PlayerMarkerTexture names. It goes on the map's own layer, because that is the thing that moves and zooms
// with the map it marks, and above the pieces, which is what its z order says. It starts hidden and stays
// that way until there is both a place to put it and something to draw there. It is the one widget built
// here that carries a name, because it is the one that gets looked for when the player's place is the thing
// that is wrong, and the name is its own so that nothing the design left in the blueprint answers to it.
UImage* NewMarker = WidgetTree->ConstructWidget<UImage>(UImage::StaticClass(), TEXT("MapPlayerMarker"));
NewMarker->SetVisibility(ESlateVisibility::Hidden);
if (UCanvasPanelSlot* NewMarkerSlot = NewLayer->AddChildToCanvas(NewMarker))
{
NewMarkerSlot->SetAnchors(FAnchors(0.f, 0.f, 0.f, 0.f));
// Alignment centres the widget on the slot's position, so a place on the map is one pair of numbers.
NewMarkerSlot->SetAlignment(FVector2D(0.5f, 0.5f));
NewMarkerSlot->SetAutoSize(false);
NewMarkerSlot->SetZOrder(2);
}
// Everything the map listens for arrives through this last widget: it lies over the whole view, so a drag // Everything the map listens for arrives through this last widget: it lies over the whole view, so a drag
// on the map is a drag on the map and never a click on the level behind it. Nothing is drawn for it. // on the map is a drag on the map and never a click on the level behind it. Nothing is drawn for it.
@@ -311,6 +335,7 @@ void UMapWidget::EnsureWidgetTree()
MapBackground = NewBackground; MapBackground = NewBackground;
PieceGrid = NewPieceGrid; PieceGrid = NewPieceGrid;
PieceImages = MoveTemp(NewPieceImages); PieceImages = MoveTemp(NewPieceImages);
PlayerMarker = NewMarker;
Interaction = NewInteraction; Interaction = NewInteraction;
// The tree is standing but has not been laid out, so the transform is left to the next tick, which is the // The tree is standing but has not been laid out, so the transform is left to the next tick, which is the
@@ -341,6 +366,7 @@ void UMapWidget::EnsureMapReady()
} }
RequestPieceLoads(); RequestPieceLoads();
RequestMarkerLoad();
} }
bool UMapWidget::ResolveMapLayout() bool UMapWidget::ResolveMapLayout()
@@ -462,16 +488,6 @@ void UMapWidget::ResolveMapAxes()
MapUVPerWorldX = (CaptureActor->WorldToBakedMapUV(Centre + FVector(Step, 0.0, 0.0)) - CentreUV) / Step; MapUVPerWorldX = (CaptureActor->WorldToBakedMapUV(Centre + FVector(Step, 0.0, 0.0)) - CentreUV) / Step;
MapUVPerWorldY = (CaptureActor->WorldToBakedMapUV(Centre + FVector(0.0, Step, 0.0)) - CentreUV) / Step; MapUVPerWorldY = (CaptureActor->WorldToBakedMapUV(Centre + FVector(0.0, Step, 0.0)) - CentreUV) / Step;
// A mapping that moves the map nowhere in one of the two directions has nothing to say about which way the
// map lies, and a marker turned by it would be turned by noise.
// if (MapUVPerWorldX.IsNearlyZero() || MapUVPerWorldY.IsNearlyZero())
// {
// MapUVPerWorldX = FVector2D::ZeroVector;
// MapUVPerWorldY = FVector2D::ZeroVector;
//
// return;
// }
bMapAxesResolved = true; bMapAxesResolved = true;
} }
@@ -745,6 +761,74 @@ void UMapWidget::ApplyPieceBrush(UImage* PieceImage, UTexture2D* PieceTexture) c
PieceImage->SetBrush(Brush); PieceImage->SetBrush(Brush);
} }
void UMapWidget::RequestMarkerLoad()
{
if (bMarkerRequested)
{
return;
}
bMarkerRequested = true;
const FSoftObjectPath MarkerPath = PlayerMarkerTexture.ToSoftObjectPath();
if (!MarkerPath.IsValid())
{
// Said once rather than every frame: a map with no marker art is a map somebody has not filled in, and
// it is still a map.
if (!bMissingMarkerReported)
{
bMissingMarkerReported = true;
UE_LOG(LogEleriMapWidget, Warning,
TEXT("This map has no marker art assigned, so the player's place is not drawn. Point Player Marker Texture at a texture to draw one."));
}
return;
}
// Asked for on its own rather than with the pieces, because it is one texture and there is nothing for it
// to wait for: it can arrive while the bake is still being resolved and be waiting on the layer.
MarkerStreamHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
MarkerPath,
FStreamableDelegate::CreateUObject(this, &UMapWidget::ApplyLoadedMarker),
FStreamableManager::AsyncLoadHighPriority);
UE_LOG(LogEleriMapWidget, Verbose, TEXT("Asked for the marker art %s."), *MarkerPath.ToString());
}
void UMapWidget::ApplyLoadedMarker()
{
// Resolved from the path rather than taken from the load's own list of what it brought in, so that art
// something else has already loaded counts as arrived just as much as one this request fetched.
UTexture2D* MarkerTexture = Cast<UTexture2D>(PlayerMarkerTexture.ToSoftObjectPath().ResolveObject());
if (!MarkerTexture || !PlayerMarker)
{
UE_LOG(LogEleriMapWidget, Warning,
TEXT("The marker art %s did not arrive, so the player's place is not drawn."), *PlayerMarkerTexture.ToString());
return;
}
// The brush is replaced whole rather than adjusted, so that the way the marker is drawn is decided in one
// place. Its image size is the art's own, which is also the shape GetMarkerSlotSize reads back out of it,
// so the marker is drawn at the shape it was drawn in rather than squeezed into a square.
const FIntPoint ImportedSize = MarkerTexture->GetImportedSize();
FSlateBrush Brush;
Brush.SetResourceObject(MarkerTexture);
Brush.DrawAs = ESlateBrushDrawType::Image;
Brush.SetImageSize(FVector2D(ImportedSize.X, ImportedSize.Y));
PlayerMarker->SetBrush(Brush);
// The art can arrive after the marker has already been placed, so it is placed and sized again now that it
// has a shape, rather than left square until the next tick happens to notice.
UpdateMarkerView();
UE_LOG(LogEleriMapWidget, Verbose, TEXT("The marker art %s is up."), *PlayerMarkerTexture.ToString());
}
void UMapWidget::ReportMapLayout() void UMapWidget::ReportMapLayout()
{ {
const int32 Grid = FMath::Max(1, GridSize); const int32 Grid = FMath::Max(1, GridSize);
@@ -905,16 +989,22 @@ bool UMapWidget::GetPlayerMapUV(FVector2D& OutUV) const
FVector2D UMapWidget::GetMarkerSlotSize() const FVector2D UMapWidget::GetMarkerSlotSize() const
{ {
// The one thing about the marker's size the map decides: the longer side of it, in the map's own units. // The one thing about the marker's size the map decides: the longer side of it, in the map's own units.
const float Size = FMath::Max(2.f, MarkerSize); // MarkerSize is that size at the map's closest zoom, and MarkerZoomScale is how much of the map being pulled
// back from there the marker's size follows: see the property for what its ends mean. The two ends of the
// zoom range are read in the order they are set in, as they are everywhere else here.
const float ClosestZoom = FMath::Max(FMath::Max(MinZoom, MaxZoom), KINDA_SMALL_NUMBER);
const float ZoomShare = FMath::Clamp(Zoom / ClosestZoom, KINDA_SMALL_NUMBER, 1.f);
const float Size = FMath::Max(2.f, MarkerSize * FMath::Pow(ZoomShare, MarkerZoomScale));
FVector2D MarkerShape = FVector2D::ZeroVector; FVector2D MarkerShape = FVector2D::ZeroVector;
// An icon says what shape it is through the art it draws: the size the art was imported at is the shape it // The marker says what shape it is through the art it draws: the size the art was imported at is the shape
// was drawn with, and the size of what is resident is the second choice for a texture with nothing imported // it was drawn with, and the size of what is resident is the second choice for a texture with nothing
// to give. // imported to give. Before the art has arrived the brush holds no texture at all, so there is no shape.
if (const UImage* MarkerIcon = Cast<UImage>(PlayerMarker)) if (PlayerMarker)
{ {
if (const UTexture2D* IconTexture = Cast<UTexture2D>(MarkerIcon->GetBrush().GetResourceObject())) if (const UTexture2D* IconTexture = Cast<UTexture2D>(PlayerMarker->GetBrush().GetResourceObject()))
{ {
const FIntPoint ImportedSize = IconTexture->GetImportedSize(); const FIntPoint ImportedSize = IconTexture->GetImportedSize();
@@ -926,23 +1016,23 @@ FVector2D UMapWidget::GetMarkerSlotSize() const
} }
} }
// Anything else -- a box, a panel, or an icon with no art pointed at yet -- says what shape it is by the // With no art yet -- or none assigned at all -- the image says what shape it is by the size it asks to be
// size it asks to be drawn at, which is what the art of an image and the contents of a panel are measured // drawn at, which is nothing at all for an empty brush; nothing has a size until it has been laid out, so
// into. Nothing has a size until it has been laid out, so this is asked again whenever the size is written // this is asked again whenever the size is written rather than once.
// rather than once.
if (MarkerShape.X <= 0.0 || MarkerShape.Y <= 0.0) if (MarkerShape.X <= 0.0 || MarkerShape.Y <= 0.0)
{ {
MarkerShape = PlayerMarker ? PlayerMarker->GetDesiredSize() : FVector2D::ZeroVector; MarkerShape = PlayerMarker ? PlayerMarker->GetDesiredSize() : FVector2D::ZeroVector;
} }
// A marker with nothing to say what shape it is is drawn square: a plain box of a marker is a marker. // A marker with no shape of its own -- no art assigned, or none arrived yet -- is drawn square rather than
// not at all: the map still has a size to place it at, and the art may yet turn up.
if (MarkerShape.X <= 0.0 || MarkerShape.Y <= 0.0) if (MarkerShape.X <= 0.0 || MarkerShape.Y <= 0.0)
{ {
return FVector2D(Size, Size); return FVector2D(Size, Size);
} }
// The design's own shape, drawn at the size the map asks for: one setting is the size of the marker whether // The art's own shape, drawn at the size the map asks for: one setting is the size of the marker whatever
// the marker is an icon, a box or a panel of several, and none of them is squeezed out of shape by it. // shape that art is, and none of it is squeezed out of shape by it.
return MarkerShape * (static_cast<double>(Size) / FMath::Max(MarkerShape.X, MarkerShape.Y)); return MarkerShape * (static_cast<double>(Size) / FMath::Max(MarkerShape.X, MarkerShape.Y));
} }
@@ -967,6 +1057,8 @@ bool UMapWidget::ResolveMarkerAngle(float& OutAngle) const
void UMapWidget::UpdateMarkerView() void UMapWidget::UpdateMarkerView()
{ {
// There is nothing to place until the tree is standing, and building the tree is what makes the marker, so
// this is the test for "not built yet" rather than for a design that has no marker in it.
if (!PlayerMarker) if (!PlayerMarker)
{ {
return; return;
@@ -993,14 +1085,18 @@ void UMapWidget::UpdateMarkerView()
} }
// Whether the marker is drawn is the map's to say, because the map is what knows whether there is a place // Whether the marker is drawn is the map's to say, because the map is what knows whether there is a place
// to put it; what the design says is what it looks like when there is one. Only the marker itself is taken // to put it. Only the marker itself is taken out of the way of the pointer, so a press on the map is never
// out of the way of the pointer, so a marker made of several widgets keeps whatever they do themselves. // a press on the marker.
PlayerMarker->SetVisibility(ESlateVisibility::SelfHitTestInvisible); PlayerMarker->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
if (UCanvasPanelSlot* MarkerSlot = Cast<UCanvasPanelSlot>(PlayerMarker->Slot)) if (UCanvasPanelSlot* MarkerSlot = Cast<UCanvasPanelSlot>(PlayerMarker->Slot))
{ {
// Alignment centres the widget on the slot's position, so a place on the map is one pair of numbers. // Alignment centres the widget on the slot's position, so a place on the map is one pair of numbers.
MarkerSlot->SetPosition(MarkerMapUV * static_cast<double>(GetMapDesignSize())); MarkerSlot->SetPosition(MarkerMapUV * static_cast<double>(GetMapDesignSize()));
// The size is written here rather than once at build, because it is the art arriving that gives the
// marker a shape, and the art can arrive after the marker has already been placed.
MarkerSlot->SetSize(GetMarkerSlotSize());
} }
// The turn is the one thing about the marker that changes without its place on the map changing, and it is // The turn is the one thing about the marker that changes without its place on the map changing, and it is
@@ -1033,6 +1129,10 @@ void UMapWidget::SetZoom(float NewZoom)
Zoom = Clamped; Zoom = Clamped;
ApplyViewTransform(); ApplyViewTransform();
// The marker's size is the map's to say, and the map has just changed how large it is drawn at, so the
// marker is sized here rather than at the next tick: a tick later is a frame of it at the old size.
UpdateMarkerView();
} }
void UMapWidget::ZoomBy(float Amount) void UMapWidget::ZoomBy(float Amount)
@@ -1225,16 +1325,23 @@ void UMapWidget::NativeConstruct()
void UMapWidget::NativeDestruct() void UMapWidget::NativeDestruct()
{ {
// The pieces are wanted by this widget and by nothing else, so letting go of the request lets go of whatever // The pieces and the marker art are wanted by this widget and by nothing else, so letting go of the requests
// of them nothing else is holding on to. Asking again on the next show is cheap: what is still in memory // lets go of whatever of them nothing else is holding on to. Asking again on the next show is cheap: what is
// arrives at once. // still in memory arrives at once.
if (PieceStreamHandle.IsValid()) if (PieceStreamHandle.IsValid())
{ {
PieceStreamHandle->ReleaseHandle(); PieceStreamHandle->ReleaseHandle();
PieceStreamHandle.Reset(); PieceStreamHandle.Reset();
} }
if (MarkerStreamHandle.IsValid())
{
MarkerStreamHandle->ReleaseHandle();
MarkerStreamHandle.Reset();
}
bPiecesRequested = false; bPiecesRequested = false;
bMarkerRequested = false;
PanPointerIndex = INDEX_NONE; PanPointerIndex = INDEX_NONE;
bPanning = false; bPanning = false;

View File

@@ -4,6 +4,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UI/EleriBaseWidget.h" #include "UI/EleriBaseWidget.h"
#include "UObject/SoftObjectPtr.h"
#include "MapWidget.generated.h" #include "MapWidget.generated.h"
class AOrthoMapCaptureActor; class AOrthoMapCaptureActor;
@@ -118,9 +119,9 @@ struct FEleriMapPieceCell
* The map itself is built in code, in RebuildWidget, because a user widget's slate widget is made from the root * The map itself is built in code, in RebuildWidget, because a user widget's slate widget is made from the root
* of its widget tree and there is no tree at all until the widget has been initialized. A blueprint subclass of * of its widget tree and there is no tree at all until the widget has been initialized. A blueprint subclass of
* this therefore needs to hold nothing: it may be empty, or its root may be a panel, which is where this * this therefore needs to hold nothing: it may be empty, or its root may be a panel, which is where this
* widget's own tree is added. The one part of the map that is not built here is the player's marker, which is * widget's own tree is added. The player's marker is built here too, because the map is what knows where the
* the widget named PlayerMarker in that blueprint: what a marker looks like is the design's business, and only * player is and is what has to place, size and turn it; what the marker is a picture of is the one thing left
* where it sits on the map is the map's. * to the design, and PlayerMarkerTexture is what says it.
* *
* The pieces are 16384 pixel virtual textures, so loading all 64 of them eagerly would be a stall and a * The pieces are 16384 pixel virtual textures, so loading all 64 of them eagerly would be a stall and a
* virtual texture pool full of a map nobody is looking at. They are asked for lazily, the first time the * virtual texture pool full of a map nobody is looking at. They are asked for lazily, the first time the
@@ -216,22 +217,32 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker")
bool bCenterOnPlayerByDefault; bool bCenterOnPlayerByDefault;
/** The player's place on the map: the widget of this name in the blueprint this map is drawn in, found by /** The art the player's marker is drawn with. The marker itself is made here rather than found in the
* name rather than made here. Nothing in this file draws it, so what the marker looks like -- its brush, * blueprint this map is drawn in: the map is what places it, sizes it and turns it, so the one thing left
* its colours, its corner radii -- belongs to the design. The map takes it onto its own layer, which is * for the design to say is what it is a picture of. Soft, so the art is not loaded until a map is actually
* what a marker has to be on to move and zoom with the map it marks, and then sizes it, places it and * looked at, and optional, because a map with no art is a map with no marker rather than a map that will
* turns it. Any kind of widget will do, from an image to a panel holding several. Optional, because a * not open. */
* design with no marker in it is a map that says so once rather than a map that will not open. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker")
UPROPERTY(BlueprintReadOnly, Category = "Eleri|Map|Marker", meta = (BindWidgetOptional)) TSoftObjectPtr<UTexture2D> PlayerMarkerTexture;
TObjectPtr<UWidget> PlayerMarker;
/** How long the marker's longer side is drawn, in the map's own design units, which is the one thing about /** How long the marker's longer side is drawn, in the map's own design units, at the map's closest zoom: see
* the marker's look the map has to know, because the map is what places it and is measured in these units. * MarkerZoomScale for what it is drawn at further back than that. What shape it is comes from the art
* What shape it is comes from the design: an icon's shape is the shape of its art, and anything else is * PlayerMarkerTexture names, which is the shape that art was imported at, so a wide or a tall marker is not
* drawn at the shape it asks to be, so a wide or a tall marker is not squeezed into a square. */ * squeezed into a square. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker", meta = (ClampMin = "2")) UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker", meta = (ClampMin = "2"))
float MarkerSize; float MarkerSize;
/** How much of the map being pulled back the marker's size follows, as the power the zoom relative to
* MaxZoom is raised to: the marker is drawn at MarkerSize when the map is at its closest, whatever this
* says, and this is what it does below that. At nought the marker is that size in the map's own units at
* every zoom, so it comes down in the view exactly as fast as the map does. At one it comes down in step
* with the zoom as well, which is twice as fast as the map does on screen, and soon leaves a marker too
* small to read. Below nought the marker grows in map units as the map is pulled back instead, so it comes
* down in the view more slowly than the map does: at -1 it holds one size on screen however far out the
* map goes, which stays readable but stops describing the world. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker", meta = (ClampMin = "-1", ClampMax = "1"))
float MarkerZoomScale;
/** Degrees added to the turn the marker is given, for art that does not point up: an arrow drawn pointing /** Degrees added to the turn the marker is given, for art that does not point up: an arrow drawn pointing
* right wants -90 here, and one drawn pointing down wants 180. */ * right wants -90 here, and one drawn pointing down wants 180. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Eleri|Map|Marker")
@@ -381,6 +392,12 @@ protected:
/** Puts every arrived piece on its image, and reports the ones that did not arrive. */ /** Puts every arrived piece on its image, and reports the ones that did not arrive. */
void ApplyLoadedPieces(); void ApplyLoadedPieces();
/** Asks for PlayerMarkerTexture, once. */
void RequestMarkerLoad();
/** Puts the arrived marker texture on the marker, and reports it once if it did not arrive. */
void ApplyLoadedMarker();
/** The brush one piece image is drawn with: the piece's own texture, and the mirroring the bake needs, /** The brush one piece image is drawn with: the piece's own texture, and the mirroring the bake needs,
* which is the one thing about a piece that cannot be asked for and has to be told. */ * which is the one thing about a piece that cannot be asked for and has to be told. */
void ApplyPieceBrush(UImage* PieceImage, UTexture2D* PieceTexture) const; void ApplyPieceBrush(UImage* PieceImage, UTexture2D* PieceTexture) const;
@@ -412,9 +429,9 @@ protected:
* actor to put their position through. */ * actor to put their position through. */
bool GetPlayerMapUV(FVector2D& OutUV) const; bool GetPlayerMapUV(FVector2D& OutUV) const;
/** The size the marker is drawn at: the shape the design gave it, with its longer side at MarkerSize, so /** The size the marker is drawn at: the shape of the art PlayerMarkerTexture names, with its longer side at
* that a wide or a tall marker is not squeezed into a square. A marker with nothing to say what shape it * MarkerSize, so that a wide or a tall marker is not squeezed into a square. Before the art has arrived,
* is -- a box with nothing in it, or a panel with nothing in that -- is drawn square. */ * or with no art assigned at all, the marker has no shape to give and is drawn square. */
FVector2D GetMarkerSlotSize() const; FVector2D GetMarkerSlotSize() const;
/** The angle the marker is turned to on screen, in degrees clockwise, or false when there is nothing to /** The angle the marker is turned to on screen, in degrees clockwise, or false when there is nothing to
@@ -465,6 +482,11 @@ protected:
UPROPERTY(Transient) UPROPERTY(Transient)
TArray<TObjectPtr<UImage>> PieceImages; TArray<TObjectPtr<UImage>> PieceImages;
/** The player's place on the map, made here and put on MapLayer above the pieces. An image, because the
* marker is one texture at one place; everything about how it looks comes from PlayerMarkerTexture. */
UPROPERTY(Transient)
TObjectPtr<UImage> PlayerMarker;
/** Takes every pointer event the map needs, so that a drag is never also a click on the level behind /** Takes every pointer event the map needs, so that a drag is never also a click on the level behind
* the map. */ * the map. */
UPROPERTY(Transient) UPROPERTY(Transient)
@@ -491,6 +513,12 @@ protected:
/** What does the loading, held so that the pieces stay wanted until they have arrived. */ /** What does the loading, held so that the pieces stay wanted until they have arrived. */
TSharedPtr<FStreamableHandle> PieceStreamHandle; TSharedPtr<FStreamableHandle> PieceStreamHandle;
/** Whether PlayerMarkerTexture has been asked for, so that asking happens once rather than once per show. */
bool bMarkerRequested;
/** What does the loading, held so that the marker stays wanted until it has arrived. */
TSharedPtr<FStreamableHandle> MarkerStreamHandle;
/** How much the map is zoomed, and the content point held at the centre of the view. */ /** How much the map is zoomed, and the content point held at the centre of the view. */
FVector2D FocusPoint; FVector2D FocusPoint;
@@ -507,7 +535,7 @@ protected:
bool bMarkerAngleApplied; bool bMarkerAngleApplied;
/** Whether the map has already said that the design has no marker in it, so that a map drawn without one /** Whether the map has already said that no marker art is assigned, so that a map drawn without a marker
* says so once rather than on every update. */ * says so once rather than on every update. */
bool bMissingMarkerReported; bool bMissingMarkerReported;