Mortal Rite is a solo or multi-player, co-op game that needs to offer a challenge to players. How do we challenge players when they are solo or in groups of various sizes without having the experience be too hard for a solo player or too easy as soon as you group with someone?
Well, I’m not sure anyone can give an exact answer for this problem. So, here’s how I hope to achieve the possibly unachievable.
But first, some information and a history of decision making about enemies and placing enemies in levels:
In the beginning, we needed a way to put enemies in levels and have them do things. We needed to define what enemies would be doing in general and what tools a level designer would want to have to make levels work.
What does an enemy do when it’s not attacking the player?
Enemies obviously need to attack players. That part is clear. But when the enemy isn’t attacking the player, it needs to do something else. The list that I came up with initially was based on the games that I had played before:
- Idle: Stand around and do nothing until a target is acquired.
- Wander: Move around in a radius until a target is acquired.
- Patrol: Path from waypoint to waypoint
- Guard: Stand in a specific place facing a specific direction and having a specific guard idle
How does a level designer place an enemy in a level?
Poof! Placers exist.
Placers can be put in any level that tells the proper systems what type of enemy to spawn and where to spawn it. Placers allow systems that preload enemies to determine how many enemies a level needs and decouples the enemy count from how many enemies are in a level at any time – specifically: Not all placers need to have an enemy spawned, but it has the information to spawn an enemy if needed. Placers decouple needing to know how each enemy works and how it should spawn from the enemy itself. The placers just handle everything and only present the level designer with the information and settings that they should care about.
Placers also provide feedback about where an enemy can spawn. In order to have enemies move around the level, the level has a Navigation Mesh that the Ai controlling the enemy needs to move from place to place. Placers will turn red when a level designer tries to put an enemy in a location that fails validation, and placers will turn green when the location passes validation.
How does a level designer configure an enemy to do something specific?
Placers have settings to determine what enemy spawns, what mode they should spawn as (e.g.: Idle, Wander, Patrol, etc.), and any other information that is needed. As we add new enemies or spawn modes, the placers are updated with the new enemy types and spawn modes as necessary.
How does a level designer determine when an enemy will spawn?
Placers can be configured to spawn an enemy instantly, after a delay, when another placer’s enemy dies, or when a trigger happens (e.g.: when a player walks into an area, or pulls a lever, etc.). Placers can also interface with Placer Proxies. Placer Proxies can be linked with one or more Placers or one or more Proxies. Placer Proxies can trigger off several events: Spawn, Death, etc.
By using a combination of Placers and Placer Proxies, complex spawn logic can be achieved.
Placers can also be configured to spawn based on how many players are in the level using simple less than, greater than, and the like logic. Example of this is for a single player playthrough you only want one Heavy Archer on the bridge shooting at the player, but when you have 3 players in the level you want two Heavy Archers on the bridge shooting at the players. This adds more complexity, because the Placers and the Proxies also need to be smart enough to trigger off each other if the player counts didn’t meet the linked Placer or Proxy spawn criteria. Kind of difficult to articulate this exact scenario.
What does the Nanite Lumen Test Level look like with all the placers all over it then?
Observe!
That sounds complicated.
It is. Well, it’s more complicated than it needs to be. One of the things that we keep running into is making things overly complicated until we know exactly what we need to do. Once we know exactly what something needs to do, we can simplify it.
Summary
Because of Placers and Placer Proxies, Level designers can place enemies:
- in specific locations
- in specific modes
- with specific spawn criteria
- for specific player counts
Because of Placers we can also preload pools of enemies that can end up saving resources because we don’t need to have all 169 enemies used in the Nanite Lumen Test level spawned.
Hey! I saw on TikTok that you had to spawn all the enemies needed ahead of time!
Right. Originally, we thought we could just spawn in enemies when and where needed. This seemed to work for most of the enemies but there was a point in development where spawning in an enemy at runtime would cause a huge hitch. That’s not good.
We didn’t know at the time that this huge hitch was caused by an issue with how we setup cloth on specific enemies, and that there was an issue with how cloth initialization was being handled by the engine. Knowing is half the battle.
Once we had the hitches, we thought that we needed to pool the enemies so that we could just move them into place and turn them on. We went through a few different versions of pooling:
Pooling Versions:
- Small pools with the hope that we don’t run out of pooled enemies (e.g.: Placers for 30 ghouls in the level, but a pool of 20 ghouls because we didn’t think it was possible to fight through 20 ghouls before recycling ghouls happened).
- Pools for all enemies in the level. Never run out.
Pooling version 1 had the issue of having the pools run out and needing to fall back to runtime spawning. Not good.
Pooling version 2 had the issue of needing to have a ton of pooled enemies hidden somewhere. Even though the enemies were hidden, and they were not running any logic, they still ate more resources than we wanted. Knowing is all of the battle?
All versions of Mortal Rite that have been played by the public have had some form of pooling of enemies, because, based on how the development of Mortal Rite has gone, we needed pooling in order to solve problems caused elsewhere.
“I told you all that to tell you this…”
Tater Salad
We are always looking for ways to do less as developers. There’s so much to do that any place that a developer can save time is helpful, but we never want to compromise on specific things. Example is cloth. We could have sacrificed cloth in order to solve the spawn hitches, but we decided that we cannot give up the cloth. The cloth is too important for our characters.
Alex would often ask, “Isn’t there a simpler way?”
And yes, basically there is.
Now that we’ve been through all of these different ways of not really handling spawning good enough, I can reveal the new master plan for Mortal Rite Version Next and answer more of the original question:
“How do we challenge players when they are solo or in groups of various sizes without having the experience be too hard for a solo player or too easy as soon as you group with someone?“
Me, earlier.
The new master plan for levels is as follows:
Technology:
- Make use of World Partition and Data Layers to manage levels.
- World Partition
- Allows the loading of sections of levels so that clients don’t need to have everything loaded
- Will load enemies that are within range of the client if the enemies are in the level itself. This handles the pooling issues.
- Data Layers
- Allow levels to be split up into sections that can be loaded and unloaded at level load time or at runtime on demand.
- World Partition
- Make use of Placers because the placer workflow is known to the whole team and makes working with enemies easier.
Levels will consist of a base level that is always loaded no matter what, at least one extra level data layer that contains interesting parts of a level, and at least one enemy data layer that contains the enemies for the level.
At level load time, we can choose which combination of data layers to load. Loading different combinations of level data layers and enemy data layers allows us to change the layout of the level, and the enemy count and enemy composition.
Having everything be defined in a Data Layer means that it’s easier for a level designer to just do their thing and not have to worry so much about Placer configurations for the correct number of enemies for the correct number of players. It means that a level designer can just open the Nanite Lumen Test level, hide all data layers except for the 3 player version of the level, and then get to work. The systems will do the rest of the work.
Levels can and will be made with several different level layouts and several different enemy layouts that can be cycled through either at load time or after the level has loaded. This means that each time a player enters a level there is a chance that they will see a different combination of level layout and enemy layout, which increases the challenge that a single level will present, and will keep players on their toes.