Skip to content

Instantly share code, notes, and snippets.

@jchildren
Created December 6, 2015 21:52
Show Gist options
  • Select an option

  • Save jchildren/6ff0c21affea9e07d63d to your computer and use it in GitHub Desktop.

Select an option

Save jchildren/6ff0c21affea9e07d63d to your computer and use it in GitHub Desktop.
Unreal engine instanced static mesh manager for floors and walls
// Fill out your copyright notice in the Description page of Project Settings.
#include "LevelGeneration.h"
#include "LevelManager.h"
// Sets default values
ALevelManager::ALevelManager() :
MaxX(10),
MaxY(10),
MaxZ(1),
TileSize(400),
LevelHeight(400),
FloorThickness(10),
FloorTypes(0),
WallTypes(0),
RandomSeed(0)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Levels.SetNum(1);
ResizeLevels();
RootComponent = CreateDefaultSubobject
<USphereComponent>(TEXT("RootComponent"));
MeshRootComponent = CreateDefaultSubobject
<USphereComponent>(TEXT("MeshRootComponent"));
FloorsRootComponent = CreateDefaultSubobject
<USphereComponent>(TEXT("FloorsRootComponent"));
WallsRootComponent = CreateDefaultSubobject
<USphereComponent>(TEXT("WallsRootComponent"));
MeshRootComponent->AttachTo(RootComponent);
FloorsRootComponent->AttachTo(MeshRootComponent);
WallsRootComponent->AttachTo(MeshRootComponent);
InitAllComponents();
InitInstances();
}
void ALevelManager::BeginDestroy()
{
ClearAllInstances();
ClearAllComponents();
}
// Called when the game starts or when spawned
void ALevelManager::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ALevelManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ALevelManager::ClearAllComponents()
{
ClearComponents(FloorComponents);
ClearComponents(WallComponents);
}
void ALevelManager::ClearComponents(TArray<UInstancedStaticMeshComponent*> &Components)
{
UnregisterComponents(Components);
Components.Empty();
}
void ALevelManager::UnregisterComponents
(TArray<UInstancedStaticMeshComponent*> &Components)
{
for (UInstancedStaticMeshComponent*& Component : Components)
{
Component->DetachFromParent();
Component->UnregisterComponent();
}
}
void ALevelManager::ClearAllInstances()
{
ClearInstances(FloorComponents);
ClearInstances(WallComponents);
}
void ALevelManager::ClearInstances(TArray<UInstancedStaticMeshComponent*> &Components)
{
for (UInstancedStaticMeshComponent*& Component : Components)
{
Component->ClearInstances();
}
}
void ALevelManager::InitAllComponents()
{
InitComponents(FloorComponents, FloorTypes,
TEXT("Floor Component"), FloorsRootComponent);
InitComponents(WallComponents, WallTypes,
TEXT("Wall Component "), WallsRootComponent);
}
void ALevelManager::InitComponents
(TArray<UInstancedStaticMeshComponent*> &Components, int32 Count,
FString BaseComponentName, USphereComponent* ParentComponent)
{
Components.SetNum(Count);
for (auto Component(Components.CreateIterator()); Component; ++Component)
{
FString ComponentName = BaseComponentName;
ComponentName.AppendInt(Component.GetIndex());
*Component = AddInstancedStaticMeshComponent(ComponentName, ParentComponent);
}
}
void ALevelManager::AddComponents
(TArray<UInstancedStaticMeshComponent*> &Components, int32 Count,
FString BaseComponentName, USphereComponent* ParentComponent)
{
for (int32 i = Components.Num(); i < Count; ++i)
{
FString ComponentName = BaseComponentName;
ComponentName.AppendInt(i - 1);
Components.Add(AddInstancedStaticMeshComponent(ComponentName, ParentComponent));
}
}
void ALevelManager::PopComponents
(TArray<UInstancedStaticMeshComponent*> &Components, int32 Count)
{
for (int32 i = Components.Num(); i > Count; --i)
{
Components[i - 1]->DetachFromParent();
Components.Pop();
}
}
UInstancedStaticMeshComponent* ALevelManager::AddInstancedStaticMeshComponent
(FString ComponentName, USphereComponent* ParentComponent)
{
UInstancedStaticMeshComponent* ComponentPtr;
ComponentPtr = NewObject<UInstancedStaticMeshComponent>
(FloorsRootComponent, FName(*ComponentName));
ComponentPtr->AttachTo(ParentComponent);
ComponentPtr->RegisterComponent();
return ComponentPtr;
}
void ALevelManager::InitInstances()
{
// We use iterators rather than ranged based for loops
// here as we need the index of the TArray to determine
// the location of each static mesh intance
for (auto Level(Levels.CreateIterator()); Level; ++Level)
{
FTransform Transform;
Transform.SetRotation(FQuat(FRotator(0.0f, 0.0f, 0.0f)));
int32 z = (LevelHeight + FloorThickness) * Level.GetIndex();
for (auto Floor(Level->Floors.CreateIterator()); Floor; ++Floor)
{
switch (*Floor)
{
case 0:
break;
default:
int32 x = (Floor.GetIndex() / MaxY) * TileSize;
int32 y = (Floor.GetIndex() % MaxY) * TileSize;
Transform.SetLocation(FVector(x, y, z));
// We subtract 1 as Floor == 0 is empty
FloorComponents[*Floor - 1]
->AddInstance(Transform);
break;
}
}
for (auto XWall(Level->XWalls.CreateIterator()); XWall; ++XWall)
{
switch (*XWall)
{
case 0:
break;
default:
int32 x = ((XWall.GetIndex() / 2) / (MaxY + 1)) * TileSize;
int32 y = ((XWall.GetIndex() / 2) % (MaxY + 1)) * TileSize;
Transform.SetLocation(FVector(x, y, z));
// We subtract 1 as Wall == 0 is void
WallComponents[*XWall - 1]
->AddInstance(Transform);
break;
}
}
Transform.SetRotation(FQuat(FRotator(0.0f, 90.0f, 0.0f)));
for (auto YWall(Level->YWalls.CreateIterator()); YWall; ++YWall)
{
switch (*YWall)
{
case 0:
break;
default:
int32 x = ((YWall.GetIndex() / 2) / MaxY) * TileSize;
int32 y = ((YWall.GetIndex() / 2) % MaxY) * TileSize;
Transform.SetLocation(FVector(x, y, z));
// We subtract 1 as Wall == 0 is void
WallComponents[*YWall - 1]
->AddInstance(Transform);
break;
}
}
}
}
void ALevelManager::ResizeLevels()
{
for (FStructLevel& Level : Levels)
{
Level.Resize(MaxX, MaxY);
}
}
#if WITH_EDITOR
void ALevelManager::PostEditChangeProperty(FPropertyChangedEvent& e)
{
FName PropertyName = (e.Property != NULL) ? e.Property->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, MaxX))
{
ResizeLevels();
ClearAllInstances();
InitInstances();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, MaxY))
{
ResizeLevels();
ClearAllInstances();
InitInstances();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, MaxZ))
{
Levels.SetNum(MaxZ);
ResizeLevels();
ClearAllInstances();
InitInstances();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, FloorTypes))
{
if (FloorTypes == 0)
{
ClearComponents(FloorComponents);
}
else if (FloorComponents.Num() == 0)
{
InitComponents(FloorComponents, FloorTypes,
TEXT("Floor Component "), FloorsRootComponent);
}
else if (FloorTypes > FloorComponents.Num())
{
AddComponents(FloorComponents, FloorTypes,
TEXT("Floor Component "), FloorsRootComponent);
}
else if (FloorTypes < FloorComponents.Num())
{
PopComponents(FloorComponents, FloorTypes);
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, WallTypes))
{
if (WallTypes == 0)
{
ClearComponents(WallComponents);
}
else if (WallComponents.Num() == 0)
{
InitComponents(WallComponents, WallTypes,
TEXT("Wall Component "), WallsRootComponent);
}
else if (WallTypes > WallComponents.Num())
{
AddComponents(WallComponents, WallTypes,
TEXT("Wall Component "), WallsRootComponent);
}
else if (WallTypes < WallComponents.Num())
{
PopComponents(WallComponents, WallTypes);
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALevelManager, Levels))
{
ClearAllInstances();
InitInstances();
}
Super::PostEditChangeProperty(e);
}
#endif
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "LevelManager.generated.h"
USTRUCT()
struct FStructLevel
{
GENERATED_USTRUCT_BODY()
/*
*/
UPROPERTY(EditAnywhere, EditFixedSize, BlueprintReadWrite, NoClear)
TArray<int32> Floors;
/*
Wall values are broken up into two seperate arrays
corresponding to their rotation, such that each instance
will be on the line equal to a constant integer * tile size
XWalls and YWalls
x = constant y = constant
Previously this was handled with % 2 and a single array,
but became overly complicated to edit values manually
Values of 0 imply that no corresponding instances should
be created in the location, while and integer less than
the number supported wall types in the object (WallTypes)
will correspond to an instance of a Instanced Static Mesh
at that location, which can be set in the editor
These numbers are agnostic to the wall type, but should
correspond to the type of wall the generator used intends
to create (i.e. plain walls or those with doors and windows)
*/
UPROPERTY(EditAnywhere, EditFixedSize, BlueprintReadWrite, NoClear)
TArray<int32> XWalls;
UPROPERTY(EditAnywhere, EditFixedSize, BlueprintReadWrite, NoClear)
TArray<int32> YWalls;
void Resize(int32 x, int32 y)
{
Floors.SetNumZeroed(x * y);
XWalls.SetNumZeroed(x * y + y);
YWalls.SetNumZeroed(x * y + x);
}
FStructLevel(int32 x, int32 y)
{
Floors.SetNumZeroed(x * y);
XWalls.SetNumZeroed(x * y + y);
YWalls.SetNumZeroed(x * y + x);
}
FStructLevel()
{
Floors.SetNumUninitialized(0);
XWalls.SetNumUninitialized(0);
YWalls.SetNumUninitialized(0);
}
void Destroy()
{
Floors.Empty();
XWalls.Empty();
YWalls.Empty();
}
};
UCLASS()
class LEVELGENERATION_API ALevelManager : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ALevelManager();
// Begin asynchronus clean-up
virtual void BeginDestroy() override;
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick(float DeltaSeconds) override;
UFUNCTION()
void ClearAllComponents();
UFUNCTION()
void ClearAllInstances();
UFUNCTION()
void InitAllComponents();
UFUNCTION()
void InitInstances();
UFUNCTION()
void ResizeLevels();
protected:
UPROPERTY(EditAnywhere, Category = "Map Size")
int32 MaxX;
UPROPERTY(EditAnywhere, Category = "Map Size")
int32 MaxY;
UPROPERTY(EditAnywhere, Category = "Map Size")
int32 MaxZ;
UPROPERTY(EditAnywhere, Category = "Instance Size")
int32 TileSize;
UPROPERTY(EditAnywhere, Category = "Instance Size")
int32 LevelHeight;
UPROPERTY(EditAnywhere, Category = "Instance Size")
int32 FloorThickness;
UPROPERTY(EditAnywhere, Category = "Mesh Types")
int32 FloorTypes;
UPROPERTY(EditAnywhere, Category = "Mesh Types")
int32 WallTypes;
UPROPERTY(EditAnywhere, EditFixedSize, NoClear, Category = "Data")
TArray<FStructLevel> Levels;
UPROPERTY(EditAnywhere, Category = "Level Generation")
int32 RandomSeed;
/*
Component structure is as follows:
RootComponent
FloorsRootComponent
FloorComponent0
FloorComponent1
...
FloorComponent[n]
WallsRootComponent
WallComponent0
WallComponent1
...
WallComponent[n]
*/
UPROPERTY(Instanced, NoClear)
USphereComponent* MeshRootComponent;
UPROPERTY(Instanced, NoClear)
USphereComponent* FloorsRootComponent;
UPROPERTY(Instanced, NoClear)
USphereComponent* WallsRootComponent;
UPROPERTY(EditAnywhere, EditFixedSize, Instanced, NoClear)
TArray<UInstancedStaticMeshComponent*> FloorComponents;
UPROPERTY(EditAnywhere, EditFixedSize, Instanced, NoClear)
TArray<UInstancedStaticMeshComponent*> WallComponents;
private:
// Utility functions
UFUNCTION()
void ClearComponents(TArray<UInstancedStaticMeshComponent*> &Components);
UFUNCTION()
void UnregisterComponents(TArray<UInstancedStaticMeshComponent*> &Components);
UFUNCTION()
void ClearInstances(TArray<UInstancedStaticMeshComponent*> &Components);
UFUNCTION()
void InitComponents(TArray<UInstancedStaticMeshComponent*> &Components,
int32 Count, FString BaseComponentName, USphereComponent* ParentComponent);
UFUNCTION()
void AddComponents(TArray<UInstancedStaticMeshComponent*> &Components,
int32 Count, FString BaseComponentName, USphereComponent* ParentComponent);
UFUNCTION()
void PopComponents(TArray<UInstancedStaticMeshComponent*> &Components,
int32 Count);
UFUNCTION()
UInstancedStaticMeshComponent* AddInstancedStaticMeshComponent
(FString ComponentName, USphereComponent* ParentComponent);
public:
#if WITH_EDITOR
void PostEditChangeProperty(FPropertyChangedEvent& e);
#endif
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment