Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Change Log - Procedural 3D Dungeon Generator Plug-in

## 20250903-1.7.7 (60)
### Changes
* Add DungeonRoomSensorDatabase
* Fixed several bugs
### 変更点
* DungeonRoomSensorDatabaseを追加
* いくつかの不具合を修正

## 20250903-1.7.6 (59)
### Changes
* Removed access to editor functions while in standalone mode.
* Fixed several bugs
### 変更点
* スタンドアローンモード中にエディタ機能へアクセスしていたので削除
* いくつかの不具合を修正

## 20250831-1.7.5 (58)
### Changes
* Enable/disable control of shadow generation in point light derived classes changed from per-partition to per-light.
Expand Down
Binary file modified Content/Maps/Demonstration.umap
Binary file not shown.
4 changes: 2 additions & 2 deletions DungeonGenerator.uplugin
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"FileVersion": 3,
"Version": 58,
"VersionName": "1.7.5",
"Version": 60,
"VersionName": "1.7.7",
"FriendlyName": "Dungeon Generator",
"Description": "Procedural 3d dungeon generator plugin. Easy generation of levels, mini-maps and missions.",
"Category": "Procedural Systems",
Expand Down
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
<div align="center">
<img src="Document/DungeonGenerator-Logo.png" />
<h1>Dungeon generator plugin for Unreal Engine 5</h1>
<p>
<a href="https://github.com/shun126/UE5-DungeonGeneratorDemo/issues">Issues</a>,
<a href="https://github.com/shun126/UE5-DungeonGeneratorDemo/discussions">Discussions</a>,
<a href="https://github.com/shun126/UE5-DungeonGeneratorDemo/wiki">Wiki</a>,
<a href="https://happy-game-dev.undo.jp/_doxygen/dungeon_generator/index.html">Doxygen</a>
</p>
</div>

[![license](https://img.shields.io/github/license/shun126/DungeonGenerator)](https://github.com/shun126/DungeonGenerator/blob/main/LICENSE)
Expand All @@ -16,7 +10,7 @@
[![stars](https://img.shields.io/github/stars/shun126/DungeonGenerator?style=social)](https://github.com/shun126/DungeonGenerator/stargazers)
[![youtube](https://img.shields.io/youtube/views/1igd4pls5x8?style=social)](https://youtu.be/1igd4pls5x8)

Please visit our website for full feature list: [https://happy-game-dev.undo.jp/](https://happy-game-dev.undo.jp/plugins/DungeonGenerator/)
Please visit our website for full feature list: [https://happy-game-dev.undo.jp/](https://happy-game-dev.undo.jp/plugins/DungeonGenerator/index.html)

![Screenshot](Document/Screenshot.gif)

Expand Down Expand Up @@ -66,7 +60,7 @@ The foundational generation algorithm you shared was a major source of inspirati
* Open Unreal Engine Editor and create a project using the First Person template or Third Person template.
* Install the Dungeon Generator plugin via Epic Games Launcher, or copy it to the `Plugins` directory of your project.
* Enable the plugin content.
* Open `Plugins/Dungeon Generator/Contents/Demonstration`.
* Open `Plugins/Dungeon Generator/Contents/Maps/Demonstration`.

![](Document/ContentBrowser.gif)

Expand All @@ -80,11 +74,11 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
Or, [Fab](https://fab.com/s/f5587c55bad0) is releasing it under Epic license. If you need a license other than the GPL, please consider it. Proceeds will be used to fund the development of our game.

# 👀 See also
* [Issues](https://github.com/shun126/UE5-DungeonGeneratorDemo/issues)
* [Discussions](https://github.com/shun126/UE5-DungeonGeneratorDemo/discussions)
* [Wiki](https://github.com/shun126/UE5-DungeonGeneratorDemo/wiki)
* [DeepWiki](https://deepwiki.com/shun126/DungeonGenerator)
* [Doxygen](https://happy-game-dev.undo.jp/_doxygen/dungeon_generator/index.html)
* [Issues](https://github.com/shun126/UE5-DungeonGeneratorDemo/issues)
* [Discussions](https://github.com/shun126/UE5-DungeonGeneratorDemo/discussions)

The [Fab](https://fab.com/s/f5587c55bad0) version includes the following enhancements.
* Sub-levels can be applied as dungeon rooms
Expand All @@ -97,6 +91,8 @@ The [Fab](https://fab.com/s/f5587c55bad0) version includes the following enhance

This is an easy to use. Simply drop the DungeonGenerateActor into your level, set the grid scale and number of rooms and start generating out your structures. Please read the [Wiki](https://github.com/shun126/UE5-DungeonGeneratorDemo/wiki) for more information.

Please visit our website for full feature list: [https://happy-game-dev.undo.jp/](https://happy-game-dev.undo.jp/plugins/DungeonGenerator/index.html)

# 😀 Authors
* Nonbiri ([X.com](https://x.com/happy_game_dev) / [YouTube](https://www.youtube.com/channel/UCkLXe57GpUyaOoj2ycREU1Q))
* Shun Moriya ([X.com](https://x.com/monjiro1972))
Expand Down
2 changes: 1 addition & 1 deletion Source/DungeonGenerator/Private/Core/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,7 @@ namespace dungeon
uint8_t depthRatioFromStart = 0;
if (GetDeepestDepthFromStart() > 0)
{
// TODO: ダンジョンの深さを0~255に正規化する関数を検討してください
float depthFromStart = static_cast<float>(room->GetDepthFromStart());
depthFromStart /= static_cast<float>(GetDeepestDepthFromStart());
depthRatioFromStart = static_cast<uint8_t>(depthFromStart * 255.f);
Expand Down Expand Up @@ -1536,7 +1537,6 @@ namespace dungeon
int32 count = static_cast<int32>(std::sqrt(static_cast<float>(room->GetRect().Area())));
if (count >= 5)
{
count /= 2;
for (int32 i = 0; i < count; ++i)
{
const int32 x = mGenerateParameter.GetRandom()->Get(room->GetLeft(), room->GetRight());
Expand Down
2 changes: 1 addition & 1 deletion Source/DungeonGenerator/Private/Core/RoomGeneration/Room.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ namespace dungeon
識別子を取得
@return 識別子
*/
const Identifier& GetIdentifier() const noexcept;
const Identifier GetIdentifier() const noexcept;

/**
X座標を取得
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ All Rights Reserved.

namespace dungeon
{
inline const Identifier& Room::GetIdentifier() const noexcept
inline const Identifier Room::GetIdentifier() const noexcept
{
return mIdentifier;
}
Expand Down
29 changes: 19 additions & 10 deletions Source/DungeonGenerator/Private/DungeonGenerateActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ void ADungeonGenerateActor::PostInitializeComponents()
// Calling the parent class
Super::PostInitializeComponents();

if (AutoGenerateAtStart == true)
{
PostGenerateImplementation();
}

//if (GetNetMode() != NM_Standalone)
if (GetLocalRole() == ROLE_Authority)
{
Expand Down Expand Up @@ -201,16 +206,6 @@ void ADungeonGenerateActor::PreGenerateImplementation()
return;
}

if (GetActorRotation().Equals(FRotator::ZeroRotator) == false)
{
DUNGEON_GENERATOR_ERROR(TEXT("The actor's rotation is not applied in the generated dungeon."));
}

if (GetActorScale().Equals(FVector::OneVector) == false)
{
DUNGEON_GENERATOR_ERROR(TEXT("The actor's scale is not applied in the generated dungeon."));
}

Dispose(true);

// インスタンスメッシュを登録
Expand Down Expand Up @@ -313,6 +308,19 @@ void ADungeonGenerateActor::PreGenerateImplementation()
EndDungeonGeneration();
}

void ADungeonGenerateActor::PostGenerateImplementation() const
{
if (GetActorRotation().Equals(FRotator::ZeroRotator) == false)
{
DUNGEON_GENERATOR_ERROR(TEXT("The actor's rotation is not applied in the generated dungeon."));
}

if (GetActorScale().Equals(FVector::OneVector) == false)
{
DUNGEON_GENERATOR_ERROR(TEXT("The actor's scale is not applied in the generated dungeon."));
}
}

void ADungeonGenerateActor::Dispose(const bool flushStreamLevels)
{
if (IsGenerated())
Expand Down Expand Up @@ -349,6 +357,7 @@ void ADungeonGenerateActor::MulticastOnGenerateDungeon_Implementation()
DUNGEON_GENERATOR_LOG(TEXT("MulticastOnGenerateDungeon: %s"), HasAuthority() ? TEXT("Server") : TEXT("Client"));
#endif
PreGenerateImplementation();
PostGenerateImplementation();
}

void ADungeonGenerateActor::GenerateDungeonWithParameter(UDungeonGenerateParameter* dungeonGenerateParameter)
Expand Down
112 changes: 73 additions & 39 deletions Source/DungeonGenerator/Private/DungeonGenerateBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ ADungeonGenerateActorは配置可能(Placeable)、ADungeonGeneratedActorは配
#include <numeric>
#include <unordered_map>

#include "SubActor/DungeonRoomSensorDatabase.h"

#if WITH_EDITOR
// UnrealEd
#include <EditorActorFolders.h>
Expand All @@ -77,10 +79,21 @@ namespace
const FString LevelsFolderPath = TEXT("/Levels/");
const FString InteriorsFolderPath = TEXT("Interiors");

bool operator==(const EDungeonRoomItem left, const dungeon::Room::Item right)
constexpr bool operator==(const EDungeonRoomItem left, const dungeon::Room::Item right)
{
return static_cast<uint8_t>(left) == static_cast<uint8_t>(right);
}

constexpr EDungeonRoomItem Cast(const dungeon::Room::Item item)
{
return static_cast<EDungeonRoomItem>(item);
}

constexpr EDungeonRoomLocatorParts Cast(const dungeon::Room::Parts parts)
{
return static_cast<EDungeonRoomLocatorParts>(parts);
}

}

ADungeonGenerateBase::ADungeonGenerateBase(const FObjectInitializer& initializer)
Expand Down Expand Up @@ -278,41 +291,45 @@ void ADungeonGenerateBase::DestroySpawnedActors(UWorld* world)
UGameplayStatics::GetAllActorsWithTag(world, GetDungeonGeneratorTag(), actors);

#if WITH_EDITOR
TArray<FFolder> deleteFolders;

// TagにDungeonGeneratorTagがついているアクターのフォルダを回収
for (const AActor* actor : actors)
// Standaloneモードによる起動ではGEditorやGEngineが無効になる
if (IsValid(GEditor))
{
if (IsValid(actor))
{
const FFolder& folder = actor->GetFolder();

if (!folder.IsValid())
continue;
if (folder.GetPath() == folder.GetEmptyPath())
continue;
TArray<FFolder> deleteFolders;

if (const auto* actorFolder = folder.GetActorFolder())
// TagにDungeonGeneratorTagがついているアクターのフォルダを回収
for (const AActor* actor : actors)
{
if (IsValid(actor))
{
if (!actorFolder->IsValid())
const FFolder& folder = actor->GetFolder();

if (!folder.IsValid())
continue;
}
if (folder.GetPath() == folder.GetEmptyPath())
continue;

if (const auto* actorFolder = folder.GetActorFolder())
{
if (!actorFolder->IsValid())
continue;
}

deleteFolders.AddUnique(folder);
deleteFolders.AddUnique(folder);
}
}
}

// パスが長い順に並べ替え
deleteFolders.Sort([](const FFolder& l, const FFolder& r)
// パスが長い順に並べ替え
deleteFolders.Sort([](const FFolder& l, const FFolder& r)
{
return l.GetPath().GetStringLength() > r.GetPath().GetStringLength();
}
);

// フォルダを削除
for (FFolder& folder : deleteFolders)
{
return l.GetPath().GetStringLength() > r.GetPath().GetStringLength();
FActorFolders::Get().DeleteFolder(*world, folder);
}
);

// フォルダを削除
for (FFolder& folder : deleteFolders)
{
FActorFolders::Get().DeleteFolder(*world, folder);
}
#endif

Expand Down Expand Up @@ -1297,18 +1314,35 @@ void ADungeonGenerateBase::CreateImplement_PrepareSpawnRoomSensor(RoomAndRoomSen
// RoomSensorActorを生成
mGenerator->ForEach([this, &roomSensorCache](const std::shared_ptr<const dungeon::Room>& room)
{
ADungeonRoomSensorBase* roomSensorActor = SpawnRoomSensorActorDeferred(
mParameter->GetRoomSensorClass(),
room->GetIdentifier(),
room->GetCenter() * mParameter->GetGridSize().To3D() + GetActorLocation(),
room->GetExtent() * mParameter->GetGridSize().To3D(),
static_cast<EDungeonRoomParts>(room->GetParts()),
static_cast<EDungeonRoomItem>(room->GetItem()),
room->GetBranchId(),
room->GetDepthFromStart(),
mGenerator->GetDeepestDepthFromStart()
);
roomSensorCache[room.get()] = roomSensorActor;
auto* roomSensorClass = mParameter->GetRoomSensorClass();
if (const auto* roomSensorDatabase = mParameter->GetRoomSensorDatabase())
{
// TODO: ダンジョンの深さを0~255に正規化する関数を検討してください
auto depthFromStart = static_cast<float>(room->GetDepthFromStart());
depthFromStart /= static_cast<float>(mGenerator->GetDeepestDepthFromStart());
const auto depthRatioFromStart = static_cast<uint8_t>(depthFromStart * 255.f);

roomSensorClass = roomSensorDatabase->Select(
room->GetIdentifier(),
depthRatioFromStart,
GetSynchronizedRandom()
);
}
if (roomSensorClass)
{
auto* roomSensorActor = SpawnRoomSensorActorDeferred(
roomSensorClass,
room->GetIdentifier(),
room->GetCenter() * mParameter->GetGridSize().To3D() + GetActorLocation(),
room->GetExtent() * mParameter->GetGridSize().To3D(),
static_cast<EDungeonRoomParts>(room->GetParts()),
static_cast<EDungeonRoomItem>(room->GetItem()),
room->GetBranchId(),
room->GetDepthFromStart(),
mGenerator->GetDeepestDepthFromStart()
);
roomSensorCache[room.get()] = roomSensorActor;
}
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ UDungeonGenerateParameter* UDungeonGenerateParameter::GenerateRandomParameter(co
parameter->DoorPartsSelectionMethod = sourceParameter->DoorPartsSelectionMethod;
parameter->DoorParts = sourceParameter->DoorParts;
parameter->DungeonRoomSensorClass = sourceParameter->DungeonRoomSensorClass;
parameter->DungeonRoomSensorDatabase = sourceParameter->DungeonRoomSensorDatabase;
}

return parameter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
@author Shun Moriya
@copyright 2023- Shun Moriya
All Rights Reserved.
*/

#include "SubActor/DungeonRoomSensorDatabase.h"
#include "Core/Math/Random.h"
#include "Core/Debug/Debug.h"
#include "Parameter/DungeonMeshSetDatabase.h"
#include <cmath>

UDungeonRoomSensorDatabase::UDungeonRoomSensorDatabase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}

UClass* UDungeonRoomSensorDatabase::Select(const uint16_t identifier, const uint8_t depthRatioFromStart, const std::shared_ptr<dungeon::Random>& random) const
{
TArray<UClass*> roomSensors;
roomSensors.Reserve(DungeonRoomSensorClass.Num());
for (const auto& dungeonRoomSensorClass : DungeonRoomSensorClass)
{
if (IsValid(dungeonRoomSensorClass))
roomSensors.Add(dungeonRoomSensorClass);
}
if (roomSensors.Num() <= 0)
return nullptr;

// 抽選
switch (SelectionMethod)
{
case EDungeonMeshSetSelectionMethod::Random:
{
const auto index = random->Get(roomSensors.Num());
return roomSensors[index];
}
case EDungeonMeshSetSelectionMethod::Identifier:
{
const auto index = identifier % roomSensors.Num();
return roomSensors[index];
}
case EDungeonMeshSetSelectionMethod::DepthFromStart:
{
const float ratio = static_cast<float>(depthRatioFromStart) / 255.f;
const float index = static_cast<float>(roomSensors.Num() - 1) * ratio;
return roomSensors[static_cast<size_t>(std::round(index))];
}
default:
DUNGEON_GENERATOR_ERROR(TEXT("Set the correct SelectionMethod"));
return nullptr;
}
}
1 change: 1 addition & 0 deletions Source/DungeonGenerator/Public/DungeonGenerateActor.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class DUNGEONGENERATOR_API ADungeonGenerateActor : public ADungeonGenerateBase
void ApplyInstancedMeshCullDistance();

void PreGenerateImplementation();
void PostGenerateImplementation() const;

#if WITH_EDITOR
void DrawDebugInformation() const;
Expand Down
Loading