A Swift package providing reusable core logic for creating, managing, and running tabletop RPG dungeon maps. It is a work in progress; capabilities are being added incrementally.
The short-term goal is to represent a dungeon module the way a GM encounters it in print: multi-level maps made of rooms, hallways, and doors, each keyed to the module's text with read-aloud passages, GM notes, encounters, treasure, point features (levers, idols, pools, traps), and occupancy checks. On top of that, a derived connectivity graph answers reachability questions—which areas are freely reachable, which are gated behind a key or a secret door, and which are unreachable even once every gate is open—to help a GM or map author reason about key progression and pacing.
The library is a generic Swift Package. The included DungeonMapBuilder example app demonstrates iOS/macOS usage with a SwiftUI map viewer and room-content editor.
Add DungeonCore as a Swift package dependency, pointing at a released version:
dependencies: [
.package(url: "https://github.com/mrlegowatch/DungeonCore.git", from: "0.1.0"),
],Then add "DungeonCore" to your target's dependencies.
DungeonCore depends on RolePlayingCore and SwiftDice, both resolved as versioned Swift packages from GitHub (not local paths). RolePlayingCore provides Treasure's currency types (Money, Currencies), CharacterAlignment, CreatureType, CreatureSize, and StatLine, and the CodableWithConfiguration pattern used throughout DungeonCore's JSON coding. SwiftDice is used directly, via RolePlayingCore, for AnyRollable—the dice-or-constant type behind encounter counts, treasure amounts, and occupancy checks.
The source code is grouped into the following areas under Sources/DungeonCore:
| Group | What's inside |
|---|---|
| Dungeons & Maps | Dungeon, DungeonMap, DungeonSection, DungeonCodingContext, UntitledCounter |
| Map Elements | DungeonRoom, DungeonHallway, DungeonDoor; MapPoint, MapSize; WallRuns |
| Room Content | RoomFeature; RoomEncounter, Treasure, TreasureItem (in RoomContent); Occupancy, Chance, EncounterTable |
| Connectivity | DungeonConnectivity, Traversal, DungeonReachability |
Examples/DungeonMapBuilder is a SwiftUI iOS/macOS (Mac Catalyst) app that demonstrates the library. It loads dungeons from JSON via Dungeon(fileName:) and provides:
- A map viewer—a zoomable, pannable canvas rendering rooms, hallways, doors, and labels, with per-overlay show/hide toggles (room/hallway/door labels, feature markers, door-side debug tinting)
- A room detail panel—shown as a trailing inspector on Mac Catalyst/iPad and a bottom sheet on iPhone, displaying a room's read-aloud text, GM notes, occupancy roll, encounters, treasure, point features, and content tags
- A room content editor—edits a room's text, encounters, treasure, and occupancy in place
- A flow mode—highlights reachable, key-gated, and unreachable areas from a chosen starting room via
DungeonConnectivity, with a key-progression summary
Geometry editing (adding and moving rooms, hallways, and doors) exists in the app but is unfinished, so it stays behind a -enableMapEditing launch argument rather than shipping in the default build.
Dungeons round-trip to JSON via Dungeon.init(contentsOf:context:) / Dungeon.write(to:context:). A DungeonCodingContext threads a Currencies registry through the decode/encode chain so Treasure's coin amounts resolve against the host app's currency symbols—see CodableWithConfiguration in RolePlayingCore. Rooms serialize compactly: an axis-aligned rectangular room writes location/size, an ellipse writes location/size/shape, and an arbitrary polygon falls back to its raw edges array.
Dungeon—the top-level container: anid, aname, an optionalurl, and an array ofDungeonMaplevels. Create a new empty dungeon, or load/save one as JSON (init(contentsOf:),init(fileName:from:),write(to:),save()).DungeonMap—a single floor:rooms,hallways,doors, and namedsections. Provides mutation (addRoom/removeRoom/insertRoom, same for hallways/doors/sections), lookup (room(withID:),feature(withID:),section(for:),rooms(in:)), consistency checks (duplicateFeatureIDs,featuresOutsideTheirRooms,unknownSectionIDs,unknownOccupancyTableRoomIDs), and occupancy rolls (rollOccupancy(for:),rollOccupancyDetail(for:),encounterTable(for:)).DungeonSection—a named grouping of rooms under a module heading (e.g. "Lair of the Bracken-folk"), referenced byDungeonRoom.sectionID.DungeonCodingContext—the single configuration type threaded through theCodableWithConfigurationchain for dungeon JSON (currently just aCurrenciesregistry), so new registries can be added without changing every type's coding signature.UntitledCounter—thread-safe counter (viaSynchronization.Atomic) that names new, unsaved dungeons "Untitled", "Untitled 2", and so on.
DungeonRoom—a room defined bylocation/size(rectangle), anedgespolygon, or an ellipse bounding box. Carries a modulekeyNumber,name,readAloudtext,gmNotes,contentTags(encounter/treasure/trap/hazard/empty/special),encounters,treasure, pointfeatures,occupancy, interior decorativewalls, and an optional manuallabelLocation.contains(_:)does ray-casting point-in-polygon hit testing;translate(by:)moves the room and everything attached to it.DungeonHallway—aDungeonRoomsubclass for corridors, built either from centerlinesegmentsplus awidth(edges are computed bycomputeEdges(from:width:)inDungeonSegment.swift, with mitered corners) or from explicitedges.DungeonDoor—aDungeonRoomsubclass covering ten door/passage kinds (opening,door,portcullis,slidingDoor,ladder,trapDoor,elevator,staircase,passage,magicPortal,caveEntrance), each with its own geometry convention (two edge points, a rectangular area, or a path). Attributes (isLocked,hasTrap,isSecret,isOneWay,isStuck,isBarred,hasMagicSeal) and an optionalkeyIDdescribe gating;fromArea/toAreacompute the polygons on either side for connectivity probing;connectionsupports a remote link to another map.MapPoint/MapSize—2D point and size types with vector operations (+,-, unary-,*,length(),normalized(),perpendicular(),dot(_:)) used throughout the map geometry.WallRuns—DungeonMap.wallRuns(for:doorGapRatio:)returns a room's outline as wall polylines with the spans its doors occupy cut out, for rendering walls that visibly open at each door.
RoomFeature—a single keyed point feature inside a room (lever, idol, pool, pit, trap, hazard, hearth, floor/ceiling hatch, container, furnishing, decoration, stairs, other), with its ownlocation, optional footprintsize, read-aloud text, GM notes, andtreasure.RoomEncounter/Treasure/TreasureItem(RoomContent.swift)—RoomEncounterdescribes a creature group (name,AnyRollablecount, alignment, notes, creature type/size, a compactStatLine, and treasure carried by the group or by each member).Treasurepairs coin amounts (aMoneyRoll, so amounts can be dice expressions) with a list ofTreasureItems, any of which may double as a door key viakeyID.Occupancy/Chance/EncounterTable—Chanceis a threshold check ("1-3 on d10 per turn");EncounterTablepicks a row by rolling itsdieand looking up the matching range.Occupancycombines aChancewith either its own table or a reference to another room's table (tableRoomID), rolled viaDungeonMap.rollOccupancyDetail(for:).
DungeonConnectivity—a derived adjacency graph built from a map's doors. Resolves each door's two sides against room/hallway geometry, exposingconnections,entranceDoors(one side resolved—exterior openings), andportalDoors(remote map links). Queries:connections(from:),adjacentRooms(to:)(collapsing hallways transparently),reachable(from:traversal:), andclassify(from:), which three-way classifies every node as.reachable,.keyGated, or.unreachablerelative to a start room.Traversalconfigures whether locks, secrets, and one-way doors are passable, and which keys are held.
Dungeon connectivity and occupancy are new; expect refinement as the DungeonMapBuilder example's geometry editor and flow-mode tooling further develop.
The "Swamp King Lair Level 1.json" sample dungeon (in the example app and in the test resources) is not covered by this repository's MIT License — see CONTENT-NOTICE.md.