
Project: Shadow of ferenor
Type of work: Freelance – some specific features
Overview
I’ve made a great a friendship with the project owner so I’m in constant touch with the project, doing consultations about code and chit chat on design wise here and there
OBS: This page is quite outdated, I’ve made some extra features on the project during their lifetime, just didn’t moved it here yet, but aside from textures of what you see on the web, the features that I implemented was used as base to grown his ideas on top of it
Dungeon generator
Requisites:
- The rooms can’t overlap
- The number of rooms must be adjustable
- Our room will always have 2 exits
- Each room must have multiple possible models
Challenge: The system itself isn’t complicated as some requisites are simplifying the process, the challenge here is to make it data-oriented as possible/necessary, debuggable and we need to think about a good approach to avoid the overlap.
TLDR; I’ve created some abstractions to store and use the data about each room and dungeon to generate a valid result, exposing some APIs to fit into the loading step in a real case, those APIs got also exposed as buttons in the inspector to allow the user to test them individually, additionally we had a debug-only operation to follow the whole process step by step through the "Generate dungeon Step by Step" (as you can see on gif bellow).
The creation happens in two steps, one to validate and store a valid sequence of rooms followed by an instantiation and setup of rooms using previously stored data. This process is based on a seed, so we can replicate the process just by providing one as a parameter for those APIs, including the buttons from the inspector.

Generate dungeon

Step by step generation
Result: Designer would work with 3 pieces. The necessary data shown below can be created through the menu context on the right mouse click.
- Dungeon generation: This is our component part, it contains the logic and receives our dungeon data and some additional settings.
- Dungeon data: Container for our possible rooms data, dungeon size, and a set of rules that I’ve made to give the user an extra layer of control, for options like not repeating rooms sequentially, for example.
- Room data: Some extra information about the room itself and also contains some valid models.
About the overlap: Initially my idea to represent the rooms was a tree where each layer contains the possible adjacent rooms, going deeper in success (not overlapping) and going up in case of a failure (overlapping). Each step we randomize a not selected yet room to try to go deeper, repeating this process until we get a complete dungeon. If this proccess didn’t produce a valid dungeon we can assume that no valid dungeon can be created using the current rooms.
The solution above works but the chosen approach about checking for overlapping is more like: we don’t check. This overlap doesn’t happen often and even when it happens, it is not a problem, we just recreate the dungeon again.
Room events
Requisites:
- Each room will have only one event
- As a room can be built in both directions (the character can come from left to right or from right to left), we must be able to change the event properties accordingly to the direction
- Initially we must implement samples for some events: A treasure event, Shop/NPC event, and a Battle event (this one requires additional work and will be a feature by itself)
Challenge: Making it generic is the challenge here. These events can go basically anywhere, so the challenge is to make it as generic and as simpler as I can.
TLDR; The room will feed itself during a setup step, in which it will look for event components that implement a contract that contains some yieldable routines. After this, an event will be randomized according to a seed provided by the dungeon generator. When the event is triggered by the player, it’s controller is taken by the event which will run the yieldable routines that compose the event itself. You can see some samples below, even though they look far from the final result, adding new steps to the events, like a banner that shows the item found in treasure on screen, for example, is a trivial process.

Treasure event
Walk to some position ->
Wait for the opening animation ->
Keep walking through the dungeon

Shop event
Walk to some position ->
Transfer the control to shop ->
The shop will show a message letter by letter ->
Open the shop interface and wait for a button to be pressed ->
Keep walking through the dungeon
Result: A contract with some yieldable methods along with some context to be passed between the dungeon members (dungeon generator -> room -> event) achieves the generality that the system requires and not will be too hard to implement.
public interface IRoomEvent
{
// We can add additional steps to fulfill some custom requirement
public IEnumerator Setup(RoomEventContext context);
public IEnumerator Run (RoomEventContext context);
}
After generating a valid dungeon the dungeon generator will provide each room a context in a setup step to apply things related to each event (randomizing the enemy group or selecting items that will be sold, for example). During the gameplay an action will trigger the start in the event that will execute the run event.
As some of the information to feed the event will need to come from runtime we can fit in a component and get the data directly OR create a second abstraction layer to pass some additional information to an scriptable object that fulfill this contract and will work as injectable behavior. Personally I prefer the second one, but to avoid these additional layer that can end complicating things instead of helping I will keep the first one. To exemplify the idea I’ve made a simple "Stop and wait in two places event". (see below)
We have some additional code to feed the room with the possible events, randomizing and validating it, but this isn’t important to understand the idea.

// The code has some edge cases problems, but it's just to give a sample of the idea
public class IdleInTwoSpotsForSomeTime : MonoBehaviour, IRoomEvent
{
[SerializeField]
private float idleTimeInFirstSpot = 1f;
[SerializeField]
private float idleTimeInSecondSpot = 1f;
[SerializeField]
private Transform firstSpot;
[SerializeField]
private Transform secondSpot;
public IEnumerator Setup(RoomEventContext context)
{
yield return null;
}
// Through the context we can receive the player reference
public IEnumerator Run (RoomEventContext context)
{
yield return new WaitUntil(() => PlayerDistanceToAPosition(context.Player.transform, firstSpot.position) < .5f);
context.Player.CacheCurrentDirection();
context.Player.StopMovement();
yield return new WaitForSeconds(idleTimeInFirstSpot);
context.Player.LoadCachedDirection();
yield return new WaitUntil(() => PlayerDistanceToAPosition(context.Player.transform, secondSpot.position) < .5f);
context.Player.StopMovement();
yield return new WaitForSeconds(idleTimeInSecondSpot);
context.Player.LoadCachedDirection();
}
private static float PlayerDistanceToAPosition(Transform playerTransform, Vector3 targetPosition)
=> (playerTransform.position - targetPosition).sqrMagnitude;
}
Battle system
Requisites:
- Turn-based combat
- Our initial actions will consist of Custom attacks, Skills, Items, and Change character
Challenge: As the feature is quite open, we have some challenges to pass information between members of the battle without coupling it too much.
TLDR; Combat is an event in a given room, so the control will stay inside our IEnumerable Run(). When the event begins, we have a second yieldable layer that runs indefinitely (until the battle isn’t finished) and controls the turn execution (who plays and when), creating a context with this information and passing it to each step of the battle (selecting a skill, selecting targets, using a skill). As each of those steps can be filled in a different way, for example, for a skill, the player will select it through a button while the enemies will use some custom conditions to select one; for targeting, each skill could have their base target (itself, enemy, team-mates, all) and with this information the skill could be executed in any way desired.
With these settings, we could add new steps if things go deeper just passing more information through this context.

Battle event
Result: Each step of the battle will require and could provide some information, a skill/item must have a target to act on, and we will approach it using a turn context parameter for each step of our battle, something similar to
public class TurnContext
{
public BattleActor caster;
public BattleActor target;
public IBattleAction selectedAction;
public int currentTurn;
}
Then we could start to fill it out, our caster and current turn will be decided by a turn-controller, and the selected action will be decided by the caster (from a button press for a player and from a custom rule for an enemy), the target will be based on the kind of skill selected.
It’s important to understand this idea because it is the concept in which the whole system is around, if you need more information in some steps, add it to the context; if you need extra steps add it to the yieldable control.
We have a bunch of additional code to make it work, but this is the core/important idea to show, so with this in mind you could understand the direction that I decided to go, and this will give to a lot of freedom without tangling things too much.
private IEnumerator StartBattle()
{
while (StillBattling())
{
var caster = turnController.CurrentBattleActorToPlay();
CurrentTurnContext = new TurnContext
{
caster = caster
};
yield return caster.SelectSkill(CurrentTurnContext); // Wait for a skill to be injected
targetProvider.ProvideTarget(CurrentTurnContext); // Inject targets on turn context
var selectedAction = CurrentTurnContext.Action;
yield return selectedAction.Run(CurrentTurnContext); // Run the previously injected skill
turnController.IncreaseTurn();
}
}