This project was my first attempt to procedurally generate, well, anything really. I didn't know anything about this topic and, to keep things simple, I ended up creating an old-school text-based adventure game world where the player can travel between locations, interact with non-player characters, complete quests, and engage in combat.
While this is theoretically a playable game, this is a learning project and is mostly about basic procedural generation. It does not have a real game loop or enough content to be fun. For an actual game, check out this project.
- Procedurally generated world with over 12,500 unique settlements of various sizes
- Each containing between 1 and 34 visitable points of interest (shops, quest locations, and dungeons), as well as interactable non-player characters (NPCs)
- Thousands of (reasonably bad) names are generated at runtime for the above objects using various strategies such as
constructing names from syllables for locations, or reading from specific files for points of interest (
e.g.
{class}-{type}-{size}
) - A handful of quest and dialogue templates are randomly assigned to NPCs and then tailored to the generated locations/characters
- By completing quests, the player gains experience, levels up, and gains gold which can be used to buy items in shops
- The further away the player travels from the centre of the world, enemies change and become stronger (up to tier 6 for which the player requires level 60+ to stand a chance)
- Even after visiting thousands of locations, the game's memory utilisation should remain well below 400 MB at all times
- All objects below are generated procedurally, using a seeded
Random
object and handled by aWorldHandler
instance - The
seed
for each object is based on its global coordinates which allows (re-)generating objects when they are required and disposing of them when they are not - The core object is
World
which holdsChunk[][]
, with the player starting in the centre chunk - Each
Chunk
holds aLocation[][]
:- Based on
int density
, a number ofLocation
s are placed in theChunk
Location
s are connected using configurable strategies which result in the world'sGraph
- Based on
- The
Graph
contains the network of connections between locations and their distances- Each
Vertex
in theGraph
holds a set ofEdge
s and aLocationDto
- The
LocationDto
stores theCoordinates
of theLocation
it represents, its neighbours, ID, name, and class name - This is why a
Location
itself can be unloaded and garbage collected and re-generated when required
- Each
- A
Location
(interface) contains reference to neighbouring locations, points of interest inside it, and its location within the chunk and world- The only
Location
available at this stage is aSettlement
- For generation, the most important feature of a
Location
is itsSize
which determines thePointOfInterest
count and, for aSettlement
, also theInhabitant
count - Each
Location
holds aList<PointOfInterest>
which the player can visit
- The only
- The key object a player interacts with is a
PointOfInterest
(POI):- A POIs features are determined by its
Type
- Currently implemented are the following
Type
s:ENTRANCE
,MAIN_SQUARE
,SHOP
,QUEST_LOCATION
andDUNGEON
- At a POI, the player can engage in dialogues with NPCs other events (e.g. "delivery" or "kill" quests), engage in combat, or take other actions
- A POIs features are determined by its
- Each object of each layer (i.e.
World
->Chunk
->Location
->PointOfInterest
) can be located usingCoordinates
- The web of connections and the distance between each (both of which stored in the
Graph
) play an important role e.g. where a player can travel to and how long it takes
- When playing the game via a CLI, the
Player
'sState
determines the player loop that is being executed - Each state (e.g.
PoiLoop
orCombatLoop
) inherits fromAbstractLoop
- A loop follows a simple sequence such as this example from the
DialogueLoop
:
public class DialogueLoop extends AbstractLoop {
@Override
public void execute(List<Action> actions) {
printInteraction(); // Shows the current `Interaction` from the NPCs `Dialogue`
prepareActions(actions); // Reads the available `Action`s for current `Dialogue` from `Event`
promptPlayer(actions); // Shows the above as a `List<Action>` & executes the selection `Action`
postProcess(); // Handles side effects of the outcome e.g. updating the `Event`
}
}
- Almost every loop will
prepareActions()
andpromptPlayer()
- The
Action
interface is the way through which thePlayer
affects the world - examples:- Move to a different
Location
usingLocationAction
- Move to a different
PointOfInterest
usingPoiAction
- Start with an
EventAction
- Changing the
Player
sState
usingStateAction
- Move to a different
- The entire game can also be played via a web API - see How to use the API for details
- You can also find a web interface here: Procedural Generation 1 Frontend
- Folder scanning (
FolderReader
) to read available event file names by category (e.g. events) which works inside a JAR and when running via an IDE - Processing of Yaml files (
YamlReader
) which allows customising eventParticipant
s based on their role ( e.g.eventGiver
oreventTarget
),List<Dialogue>
for each based on event status,List<Reward>
, etc. - Processing of Txt files (
TxtReader
) which is used to generateLocation
,PointOfInterest
andNpc
names - Processing of
String
placeholders in Yaml or Txt files (PlaceholderProcessor
) e.g.&L
for location name or&TOF
for the target owner's first name through which events are tailored to the current places and characters involved
- Core: Java 19, Spring Boot 3 + Lombok, Gradle
guava
for String manipulationsnakeyaml
for Yaml processingconsoleui
by Andreas Wegmann for CLI-based user interface (only in JAR): https://github.com/awegmann/consoleui
This project uses google-java-format
. See https://github.com/google/google-java-format for more details on how to set
it up and use it.
This project's primary mode of interaction is the CLI. It also exposes an API. However, due to the lack of a web interface, it is rather tedious to use.
Clone and build project. Then run JAR with cli-prod
profile:
cd build\libs
java -jar -D"spring.profiles.active"=cli-prod procedural_generation_1-0.4.jar
Clone project and run Application.main
with -Dspring.profiles.active=cli-prod
. During development, use
-Dspring.profiles.active=cli-dev
to see logs.
See How to use the API. A Postman collection is available in /docs
. A web interface can be found
in this repository.
Much of the game can be configured in the application-world.yml
file. This includes, but is not limited to, the
following:
- World properties
- World size (determining the number of chunks)
- Retention zone (i.e. distance from the current chunk beyond which chunks are automatically unloaded)
- Chunk properties
- Chunk size (in km)
- Density range (i.e. number of locations per chunk)
- Minimum distance between chunks
- Maximum distance within which chunks are connected by default
- Distance to chunk border at which the next chunk is loaded (if not already loaded)
- Settlement properties
- Sizes and their respective characteristics (e.g. range of inhabitants, range of points of interest)
- Dungeon properties
- Min vs max possible encounters per dungeon
- Min vs max possible enemies per encounter
- Types of enemies (per tier) and property ranges for each (health, damage, etc.)
- Before developing major new features:
- Refactor the code related to how events work as it's too fiddly to build on it right now - considering:
- What kind of events should be shown (multiple vs only one at a time)?
- Should locations be visible if there's no event (or other activity) there?
- What should determine the kind of event generated for an NPC?
- Add chance to be attacked while travelling
- Refactor the code related to how events work as it's too fiddly to build on it right now - considering:
- User interface:
- Add visual mini-map for both CLI (using ASCII art) and web
- Use an LLM to auto-generate background images for each view in the web interface
- Procedural generation:
- Implement biomes which:
- Determine difficulty of events, attitude towards player, etc. based on environmental factors
- Determine object characteristics such as names and types of events
- Auto-generate items for shops by tier
- Turn dungeons into generated mazes where the player can go from room to room
- Implement biomes which:
- Code:
- Make all key classes Spring beans (e.g.
Settlement
,Amenity
but alsoCliComponent
) - Add user management and authentication
- Allow saving and loading of games
- Allow for an infinite world and store
Graph
in database so that all objects can be placed in the same world which is required for multiplayer - Polish a number of little things like using either only
appProperties.getEnvironment()
orEnvironmentResolver
instead of both and creatingIoHandler
interface to handle all input/output (instead ofCliComponent
) and have implementations for web and CLI, etc.
- Make all key classes Spring beans (e.g.
- Game design:
- Come up with a key objective for the player and an actual (i.e. fun) game loop
- Introduce the possibility for the player to be attacked while travelling
- Add more content esp. more quests
- Implement player equipment, inventory, and item drops
- Multiplayer:
- Allow multiple players to play in the same world and interact with each other