Version 1.0.1
(Top)
1 Level Design Playground (LDP)
1.1 Prerequisites
1.2 How Levels Work
1.2.1 Main Scene
1.2.2 Level Scenes
1.2.3 Level Root
1.2.4 “Levels” Asset
1.3 Creating A Level
1.3.1 Editing The Tilemaps
1.4 Gameplay elements
1.4.1 Spawning
1.4.2 Player Movement
1.4.3 Checkpoints
1.4.4 Moving platforms
1.4.5 One-Way Platforms
1.4.6 Collectables
1.4.7 Hazards
1.4.8 Enemies
1.4.9 Goal
1.4.10 Timer
1.4.11 Kill Floor
1.5 Gameplay Options and Tuning
1.5.1 Player Movement Params
1.5.2 Enemy Movement
1.5.3 Enemy AI Params
1.6 Input
1.7 Frame Timing
1.8 Performance
1.9 Building An Executable
1.10 Code Architecture
1.10.1 Structs And Procedures
1.10.2 Control Flow
1.10.3 Use of internal Keyword
1.10.4 Use of ref Keyword
1.11 Credits
1.12 License
LDP is a 2D platforming game template created in Unity to be used as a sandbox for game design students and hobbyists to experiment with level design concepts without needing to program and build their own game. The project includes all of its source code, so it can also be extended and serve as a reference example of a fully working game, but the primary goal is for users to be able to design levels with minimal technical knowledge.
The breadth of gameplay elements available for this version of LDP (1.0.1) is fairly limited, but this may be expanded (more enemies, saw blade obstacles, etc) in future versions.
Building levels will not require any coding, but it will require basic knowledge of the Unity editor and use of some Unity tools like the tilemap editor. LDP will not teach you how to use Unity, but most concepts needed for the LDP can be learned from widely available tutorials online and from Unity’s own documentation.
Unity 2020.3.19f1 is recommended. Newer versions are likely to work, but have not yet been tested and are not officially supported.
The Main scene always needs to be loaded in order to run the game. It contains the entry point for the entire game’s code, and without it, nothing will happen.
Level scenes contain all of the gameplay-related objects for a level, and are loaded additively on top of the main scene. To load both the main scene and a level scene in the editor, first open the main scene, then drag and drop a level scene onto the hierarchy.
Every level scene must contain an object with a LevelRoot_mono component, and all other gameplay elements should be parented to this object. This object/component is used to link objects in the scene to the game code.
You must also assign the level to the Levels asset in the main assets folder. This asset tells the game code which level to load when there is not already one loaded or when the player resets the game from a menu. Changing this asset will automatically update the build settings to include the appropriate scenes.
The simplest way to create a new level is to duplicate the scene called Template Level which is included in the main assets folder, then assign the new scene to the Level field in the Levels asset.
The Template scene contains a tiny level with at least one of each gameplay element which you use as a parts kit.
This game’s assets and demo levels are set up using Unity’s tile map system. Three tile maps are present in the template and demo level:
This tilemap has normal collision and can be used to make most of the normal ground.
This tilemap has been assigned a special physics layer which allows the player to pass through objects both ways (but still walk on it), and Platform Effector component that allows all objects to pass through in one direction.
This tilemap has a Hazard_mono component attached to it, which registers it in the game code as something that will kill the player if touched.
If you have a request for an additional gameplay element or mechanic, let us know on the forums: https://stefdevs.itch.io/ldp/community
All gameplay object prefab is located at leveldesignplayground-unity\Assets\DevDev\content\prefabs\
The level spawn point is represented by the level start prefab. When the level is started the player will be spawned at this object’s position. For development purposes, if a player prefab is already in the scene and active, the player will spawn at that location instead. In order to use the level spawn, ensure that there is not a player prefab in the scene already.

The player can move left and right, jump, and fall-through certain platforms. Controllers and keyboards are supported using standard keymappings that most players are familiar with (see: Input subsection).

Checkpoints are represented by flagpoles. Checkpoints are activated by touching the checkpoint object. Only one checkpoint will be active at a time, and dying will respawn the player at the active checkpoint.

There is a prefab for moving platforms included. Moving platforms can move linearly or in a circle, and there are gizmos drawn in the editor for each movement mode to visualize the platform's path. If the mode PINGPONG is selected, you can use the two waypoint child objects to control where the platform moves between.

cycleTime controls the time it takes for the platform to make a full loop, and loopOffset controls the platform's initial position. You can select the movement mode using the exposed trackType enum, and there are variables exposed for each movement type with prefixes denoting the mode that they are used for (ie: circular_radius).

Any collider objects assigned to the layer oneWayPlatforms will allow the player to pass through the geometry from underneath, but still land on it from above. Players can also drop through one-way platforms by pressing down.

You can place collectables in the map for the player to collect. Collectables will be counted at the top-right of the screen and on the level completion screen. The number of collected collectables is also shown in the final win-screen.

Hazards kill the player instantly if collision is detected with the player. Hazards are registered by the game by looking for any object under the level root object and must have a reference to a collider which is used for collision detection.

There is currently one type of enemy included in the kit - the evil piggy. When calm, piggy will patrol back and forth on whatever platform it's standing on. If the player is visible (in front of piggy, not above or below, and within range), piggy will enter an aggro state, turn red, and charge at the player. The player can kill the piggy by jumping on its head, and piggy can kill the player by touching the player in any other way.

The primary objective of levels is to get to the goal. The goal is represented by a trophy. If collision is detected between the player and a goal object, the level will be completed. Multiple goals in one level is supported, but the player will only need to touch one of them to complete the level.

Elapsed time is tracked on the HUD and at level completion. Time will not reset when the player dies/respawns, but will reset if the game is reset. The level completion time is also shown in the final win-screen.

The kill floor is an empty gameobject referenced by the level environment root that defines the minimum y-position that the player can fall to before being killed. This makes it impossible for the player to fall infinitely.

You can tune and adjust certain parameters like the player movement and enemy behaviour using the assets located in the main Assets folder.

Peak height of the pawn's jump
How high the pawn will bounce when landing on an enemy
How far the pawn can search when looking for a valid foothold to stand on or move toward
Additional leg reach in the direction of the pawn's movement
Time in seconds that the player can press jump before being grounded and still successfully jump.
Time in seconds that the player can press jump after being grounded and still successfully jump. (ie: jumping after passing over a ledge)
Number of rays cast, per meter, when searching for valid footholds to stand on
Maximum steepness, in degrees, that the pawn can stand/run on
Maximum speed the pawn can accelerate to when maximum move input is given and pawn is on the ground
Move acceleration when maximum move input is given and pawn is on the ground
Maximum speed the pawn can accelerate to when maximum move input is given, regardless of if the pawn is on the ground
Move acceleration when maximum move input is given, regardless of if the pawn is on the ground
Drag coefficient which is always applied
Drag coefficient applied when pawn is on the ground and target speed is less than the current speed
Drag coefficient applied when pawn is on the ground and move input direction is opposed to the pawn's current velocity
Same as Player Movement Params subsection

Magnitude of movement input when not attacking the player
Magnitude of movement input when attacking the player
Max distance the enemy can detect the player from when not aggroed
Max distance the enemy can detect the player from when aggroed
Time in seconds that it takes for the enemy to go back to a calm state after losing sight of the player
This project uses Unity's newer input system. You can view and edit the input keybinds by editing the actions asset located at Assets\DevDev\content\Params

Frame timing options can be set in the Game Params asset found in the main Assets folder.

The game simulation will always run at a fixed rate, controlled by the Fixed Tick Rate parameter. This is just like Unity's Fixed Timestep, but is a custom implementation of simulation timing.
You can limit the framerate by setting Frame Rate Max. This can help if you want to lower power consumption, free cpu resources for other processes, or help resolve screen tearing issues.
The fixed tick rate should ideally be much higher than the frame rate, and currently values lower than the frame rate max are not supported.
Frame Rate Min guards against simulation instability in cases of poor performance. If the game fails to hit the minimum frame rate it will stutter instead of accumulating large time deltas or trying to simulate an increasing number of fixed timesteps.
This game is fairly light weight so most computers that can run Unity should have no problem hitting a minimum frame rate of 30fps in the editor play mode. If you are having performance issues, try the following:
Profiler tab is not open and recording.
Scene tab so that it's not drawing.
Fixed Timestep value.
Building the game should be as simple as going to the build settings dialogue (File > Build Settings), configuring for your platform, and pressing build. The included scenes will be automatically populated by the Levels asset in the main Assets folder (see: How Levels Work section)
Extensibility is not the primary consideration for this project, but in case you feel the need to dive into the code, this section will give you an overview of how things are set up. LDP code in some areas may not be as “clean” as it possibly can be, but this will be improved as the project matures.
All source code can be found in the Assets/LDP/code directory.
The two most important files are Structs.cs and Procs.cs. Structs contains all of the data structures, and Procs contains all of the procedures (aka functions). Almost all procedures in the LDP are static functions and only operate on data explicitly passed in via the function parameters. There may be additional files with data or procedures (ie: Procs_UI), but this is only for organizational purposes, and the concept remains the same: the data is seperate from the procedures.
Most of the data structures in LDP are simple C# structs or classes; they do not inherit from Unity's classes (ie: monobehaviour) and thus don't interface directly with the Unity Editor or Runtime. In some situations where we want to expose data to the editor or represent an entity in the scene as a gameobject, the LDP struct is wrapped in a monobehaviour or scriptable object. These wrapper classes are denoted with suffixes like _mono or _so.

The main entry point into the LDP gameplay code is in Main_mono.cs. In Main_mono, a new instance of Game is created on awake, then Procs.Main_Update() in the Update event. This is the only code that runs in Monobehaviour scripts, except for some editor functions and UI hooks. The Game class is the root of all game state, which means that all LDP runtime data can be accessed through Game.
If you set a breakpoint at the Procs.Main_Update() call in Main_mono, you can step through the entire control flow of the program step by step. This is the best way to learn how LDP works.
internal KeywordC#'s
internal keyword is an access modifier like public or private. It's intended function is to manage accessibility between assemblies, but for the purposes of LDP, we use internal for any publicly accessible variable that we want to hide in editor inspectors. The [HideInInspector] attribute is not used to achieve because internal allows you to view the data if the inspector is in debug mode, and internal is much less cumbersome to write.
ref Keyword
LDP code use ref to indicate that a function parameter will be modified in the function. This is necessary for struct parameters to be modified instead of copied, but ref is also used for classes just to be extra clear about what the function is doing.
Thanks to Rob Wilson for all included music and sound effects. If you're looking for someone to create audio for your project, you can contact him here.
World and character textures were created and made available for free by @_PixelFrog and are available here and here.
UI Textures were created and made available for free by Kenney Vleugels and are available here.
Copyright 2021 DevDev Inc.
Redistribution of code, software, or software built using code included in the folder or any subfolders of leveldesignplayground-unity\Assets\LDP\code is not permitted, except for educational use. All rights to audio assets included in this package are owned by CATS COVE MEDIA and redistribution of these audio assets is not permitted, except for educational use.