top of page

Pixel Art, RPG, Platformer, Procedural Map

DOPC

Overview

Players play as a pirate character with 4 abilities. Each save, a world will be procedurally generated containing multiple levels each with a procedurally generated map. With the goal to destroy Tumors on each map, whilst surviving the enemies that are scattered or spawned, that use a custom A* Pathfinding Algorithm to traverse the map.

Background

Personal Project

D.O.P.C. is my longest ongoing passion project. The project is primarily a challenge for my game programming skills, as well as to learn game design with first hand experience. Skills I have demonstrated throughout the project is drawing assets (Pixel Art), make custom tools, create game components, game designing, and quality assurance.

Tools & Skills

Tools Used:

  • Unity

  • Visual Studio Editor (C#)

  • Aseprite

  • Commercial free fonts sites

  • References from numerous games

Skills Applied:

  • C#

  • HLSL

  • Advanced Algorithm

  • Unity Engine Proficiency

  • Shader Graphs

  • System Design

  • Pixel art animation

  • UI/UX Design

  • Technical Design

  • Level Design

Goals

Goals:

The goal of this project is to create an RPG game, with procedurally generated maps, where level design is crucial to increase re-playability, A* pathfinding AI, which is necessary for randomly generated levels.

Field of View

Overview

To simulate field of view, I implemented a field of view component, when attached to a player, it draws and updates a circle mesh that is also attached to the player, to reveal/cover objects based on certain game object tags.

​

The component also offers settings that can be modified through the inspector:

​​

- Use Asset Mesh (Bool): If true, it will reveal a "Mesh" field which will be used as the Field Of View without updating.

​

- Layers To Collide (LayerMask): Tells the script which layers the raycast should detect for.

​

Field Of View (Int): The angle (0 degree is positive x) to draw the mesh.

​

View Distance (Float): Controls the radius of the drawn mesh.

​

Ray Count (Int): The number of rays to draw, higher = smoother movement.

​

Extend (Float): The amount to extend into blocks, this is to prevent a harsh cutoff at scene objects.

Reveal/Hide

To hide and reveal certain entities, a masking shader material is used on the target entity, and another shader that reveals any sprite that is on the same stencil is set on the FOV mesh, which reveals entities only within the mesh.

Final Touch - Lighting

The global light is reduced to simulate a dark and gloomy theme. Since the FOV mesh itself does not emit light, a Spot Light is attached to the player to illuminate player's surroundings.

Pathfinding

Overview

Pathfinding is a crucial part of this project. Since, each playable level is planned to be procedurally generated. So enemies should be able to find the player anywhere on any map, as long as it is treated.

Map Treatment

For each level intended for pathfinding, a 2D grid is updated to represent which tiles are walkable, vertical, edge, and wall tiles.

Tile Types:

  • Walkable tiles, so any tile that is above a ground tile.Walkable:-

  • Edge: Edges represents the tile on the edge of a platform.

  • Wall: Wall tiles are tiles that is next to a wall.

  • Vertical: Vertical tiles are chosen by the start of edge tile to the ground tile vertically.

A* Pathfinding

I have chosen to implement A* as my pathfinding algorithm. After the map is treated, given any position, the pathfinding agent will find the optimal path to the target position. Using a traversal rule set, the agent is able to identity when to jump to another platform and how high to jump.

DOPC_GroundTraversalAlgorithm.png

Traversal Rules

After a path has been calculated, the agents need a way to read the "map", so for each pair of nodes the agent is traversing, it is passed into a Traversal Algorithm.

Multi-Threading

Although not necessary, nor is it a best practice, I needed to improve my performance for when multiple agents are trying to path find, and I was looking into multi-threading at the time, so I used multi-threading for a quick performance boost. The path calculation utilizes .NET core's Task system, to spread out the path calculation work to different workers, to prevent long calculation time when multiple agents are trying to path find and potential frame freezes. This is subjected to change, as well as additional optimization tips that needs to be applied to the algorithm.

​

DOPC_Multi-Thread_Code_edited.jpg

Map Generation

Overview

Map generation in DOPC is split into a few steps

  1. First, it generates the high level view of the map.

  2. Generate map nodes that are connected to each other on the map.

  3. Generate map terrain using Perlin Noise.

  4. Weed out any map nodes that appears to be on water.

Landscape Generation

Implementation:

The first pass of the landscape is generated using UnityEngine's Mathf library, by calling PerlinNoise(x, y)

Noise is stored inside a 2D array as heights, which can also be converted into a texture in later steps. Using the heights, and applying a simple algorithm to determine what color each pixel should be based on their height value, we get the result on the bottom.

​

Problem:

This worked, however, this is not how a real terrain looks like. Note that the edges are smooth and not very nature-like yet.

DOPC_LandscapeGenerator_1.png
DOPC_Landscape_FirstPass.png

Octaves

In order to make a more natural looking terrain/map, I introduced 3 additional settings to my World Map Generator component:

DOPC_PerlinNoise_Octaves.png

Octaves: Controls how many passes to put the noise map through.

Persistence: Controls how much influence each octave has.

Lacunarity: Controls how much detail to add with each pass.

Polished Terrain

As a result, this is the newly revised terrain:

DOPC_Landscape_WithOctaves.png

Level Nodes

With the terrain generated, level nodes are added to the map randomly and holds a certain information:
 

- Seed: The seed that will be used to generate that level.

- Neighbors: An array of custom Vector2Int structure that represents the connected neighbors.


Which gives us the final result:

Room Generation

Overview

Each room (or level) is generated procedurally, and its seed is determined by the world maps' nodes (See previous section: Map Generation).

​

To procedurally generate a level from scratch can sometimes make the level less interesting and decreases replayability, so to combat this, the workflow of generating each room is as follows:

  1. Handcrafted levels with different sizes (e.g. 1x1, 2x1, 2x2 etc.)

  2. A start and end point is selected, and the solution path is chosen by connecting different rooms randomly and eventually makes it to the end point. (Each room can be attached if they have openings on the correct side).

  3. After the path is found, randomly select rooms to be attached along the solution path to fill the map.

Note:
The rooms that are colored green represents the Solution Path.

Handcrafted Rooms

Each room consists of:

​

- Tilemaps: The room layout.

- Room Component: Contains information of the room.

- Decorations: A game object parent that act as a folder for all decorations.

- Gameplay: A game object parent that act as a folder for gameplay elements.

DOPC_MatchFunction_1.png

How To Test Compatibility?

Each room has a bitflag that determines whether this room is opened on the top, bottom, right, or left (Bigger rooms follow the same concept).

​

With the MatchOpening functions, I am able to quickly test which rooms are compatible with each other using bitwise operations.​

Player/Combat

Overview

So far, there is only one character, however, components are created with reusability in mind, meaning it is easy to implement a new character by only changing the settings through inspector. For combat, base classes were created to ease the creation of new combat abilities.

Pirate Terry

Pirate Terry is the first character made for this game. Just like other future characters, Pirate Terry has four combat abilities and one movement ability.

​

​

DOPC_PT_AbilityUI.png

Each abilities has their respective cooldowns, key controls, elements (such as, fire element, water element etc.) and stats.

The animation and sprites were drawn in Aseprite.

Ability 1 - Water Projectile

DOPC_PT_AbilityUI.png

Throws water projectile towards cursor, on the third attack, a combo is performed and throws three projectiles instead.

Water projectiles reacts to gravity so players may need to adjust their aim when shooting enemies further away.

Ability 2 - Water Cloud

DOPC_PT_AbilityUI.png

QoL

Cloud placements are improved by testing for collision, e.g. if the cloud will spawn in the ground or ceiling, it will instead spawn the cloud below ceiling or above ground.

The second ability spawns a water cloud wherever desired. The cloud slowly moves towards the direction player is facing, and shoots water projectile downwards in a randomized fashion. The cloud stays for a duration and dissipates after.

Ability 3 - Water Birds

DOPC_PT_AbilityUI.png

Spawns water birds in a radial manner, around the player. The birds have a homing effect, and will seek out nearby enemies, though, the range is limited.

Ability 4 - Pirate Ship

DOPC_PT_AbilityUI.png

Spawns a pirate ship from the ground, and sends it towards face direction. Drags and damages all enemies in path. After a number of collisions with enemies, the ship will break, hitting a solid wall would also cause the ship to break.

Movement Ability - Dash

DOPC_PT_AbilityUI.png

For each characters, I will equip them with one unique movement ability. For Pirate Terry, he simply dashes forward, and gives him an additional jump. During dash, Terry will remain its vertical position.

Michellaneous

Post Processing

One of the easiest polishing you can do is adding post processing effect, though, the effect chosen has to fit the style. I chose to only add in the Bloom effect for both the UI and in game objects. Although this is a small visual change, it dramatically improves the game visual appearance.

Grass-Swaying

While adding in decorations onto the scene, the static sprites seems boring and unappealing. So using the help of shaders, I was able to synchronize all swayable object's movement to emulate wind in a jungle.

Particles

Two layers of particle system is used for each entity and both performs an explosion like particle that launches particles in all directions. The first layer has no physical properties, and goes through all colliders, but the second layer attaches itself to only the ground. This way, particles remanence will remain to convey a history of combat in certain areas.

Drops

Each enemy has a loot table, and there is a chance (customizable) to drop a randomized gear and some gold.

Gold drops have different appearances based on their drop amount. Gold drops are picked up automatically whereas equipment drops are picked up using 'F'.

Dialogue

The dialogue system reads in a simple custom formatted text file and turns it into function calls, dialogues, and dialogue options, which is capable of branching to different responses.

Dialogues can be skipped by left clicking, which reveals the rest of the current dialogue. This is to simulate how most games handle their dialogue system.

This text file is the sample dialogue file for blacksmith

image.png

Inventory / Gears

The player inventory separates gears into different categories; headguard, chestguard, legguard, handguard, footguard, totem, and artifact. The red dot represents a new item that was added. The system also supports hover tooltips and allows comparison with the same gear equipped currently. 

For ease of use, both right clicking and double left clicking equips and removes gears.

bottom of page