Kelingan
Kelingan is a third-person jump 'n run game developed by eight students in the Unity Engine. The development of the user interface with its contents and the general menu logic, as well as several interactive shaders and their impact on the world and the player, are displayed here.
Content
2. Shaders
4. Conclusion
Project Summary
Kelingan is a 3D puzzle-adventure game developed by eight students as a university project in the Unity Engine. The game revolves around warrior Lethe, who has to regain her memories by traveling worlds. The first world that was part of the prototype we developed is set in an icy environment. Hereby, the level is divided into a number of sectors. In order to traverse the world and find secrets, the player has to move out of bounds into the void. Only activated sectors can be accessed from here.
During this project, I participated in the game and level design department during pre-production but switched to a full-time programmer as soon as production started. As a graphics programmer, I developed multiple shaders and effects that enhanced the player's experience and improved the game world. Furthermore, I was responsible for the menu structure and the user interface.
A breakdown of the shaders, as well as the user interface, will be explained in this section. All shaders in Kelingan were written in Shadergraph with the addition of C# scripts if needed.
Shaders
Boundaries Shader
In Kelingan, the player can access the void by going out of bounds. The boundaries shader displays the border between the normal world and the outside should the respective key be pressed.
At a certain distance between the border and the specifically marked object - here, the player- the boundaries start to dissolve.
To achieve this effect, the object must have the dissolve material and the corresponding dissolve script. Properties that a script can access receive a reference name.
Kelingans border has a cloudy color effect that is achieved by a texture flowing in two directions. In contrast, different channels are sampled to prevent it from being the same texture.
The values are then used to lerp over the negative UV coordinates of the mesh. A fresnel effect determines color brightness. The user can choose the colors themselves. Furthermore, depth is created with a normal map.
Whether a spot on the border is transparent or opaque is determined via the alpha threshold that is controlled in a script. The distance between the material and the tagged object is calculated and added to a noise map. The player character is tagged as m_objectToTrack. Their position is the indicator of when the material starts to dissolve.
In order to attain the desired result, the shader was written in multiple steps, with each focusing on something different that was brought together in the end.
Water and Waterfall Shader
In Kelingans icy environment, a waterfall and a lake are present. Both are relatively simple and do not have any physical properties, such as waves. The waterfall consists of a Voronoi noise moving over time. The scale and color can be set by the user. To achieve a gradient look of the color of the waterfall, a simple one minus calculation is used.
Since the waterfall is placed in front of the entrance to the mountain, it can be passed by the player. Therefore, the same dissolve effect of the border is applied here.
The visual style of the lake water in Kelingan was very simplistic, and only the illusion of flowing water was relevant. This was achieved by lake using a black and white texture that is distorted and moved with a simple noise texture manipulated by the time. With this, the former black part of the water texture also receives a gradient effect.
The material color was then added. It can be chosen by the user. Furthermore, simple edge detection is added, so objects in the water receive a foam edge.
Water covers part of a section on the level and was parameterized accordingly.
Portal
The portal enables the player to leave the current world and progress in the game. But the player must complete a world and activate all sectors to open the portal. To achieve this engineering wise different things had to be implemented.
It is a plane with a material. The setup is fairly simple:
A Voronoi noise is twirled with time and masked to achieve the shape.
The portal has two main states: active or inactive.
For the portal to be triggered, all sectors must be active. This is tracked
in the sector registry, which was developed by another programmer. When implementing the portal mechanic, a delegate OnAllSectorsActived was added to that script. This is invoked as soon as the active sector count matches the registered entry count.
In the portal script, the delegate is added to the function HandleAllSectorsActiveted, so it is called when the delegate is invoked.
Here, the portal material is switched to the activated portal material, and a boolean is set to true.
If the player steps into the trigger, they are now transported into another scene.
User Interface
The user interface in Kelingan was developed separated from the game logic, meaning that it does not write game-relevant information like the current player health by itself and rather receives it from other scripts.
This was to ensure a clean project and code structure and easily comprehensible locations of values.
Menu Structure
Kelingan has a pause and a main menu with subcategories each. The complete interface had to be accessible via mouse and keyboard and a controller.
The main menu is a scene in Unity, that consists of different items. Upon start, a title screen is displayed, and the player is asked to press any key to continue. Should a key be pressed, an event is invoked, and the main menu is made visible.
The look of the menus was adjusted to the wishes of the game design lead and the art lead.
In general, the main and pause menu are structured the same way. The difference is that the music must be stopped in the pause menu. The script ButtonMenu holds the menu structure.
To be able to operate the menus with both input possibilities, the selected button had to be tracked. Should the player open another subcategory, the selected button is always the first clickable object. Upon closing, the respective button is selected, so the player must not navigate through the whole menu again. This is achieved by tracking all buttons as serialize fields and adding them in the editor.
The selected gameobject is then set in functions that manage the opening and closing of the submenus.
Furthermore, a mouse timer was implemented. Should the curser not move for a certain time, it is set invisible and vise versa.
This feature was also requested because it was not possible for controller players to navigate the menu if the mouse was moved, since no button was selected.
Options Menu
The options that are changeable in Kelingan are mouse sensitivity, invert camera, and music volume. Each is done in different scripts.
In the SettingsMenu script, the function of the volume sliders is implemented. Should the player already have player preferences set, these are read here.
Should the volume be changed, the function SetVolume is called.
The new volume is calculated with a logarithm to the base ten, multiplied by twenty, and the player preferences are updated.
To change the mouse sensitivity, a delegate OnValueChanged is invoked with the selected sensitivity. This calls a function in the camera manager. Here, the new max speed and acceleration time for the cameras are set.
The inversion of the axis is also handled by the camera manager. A boolean is set to the respective value. Since the Unity camera system was used, the functions called refer to the very same thing.
A problem with the options was encountered when switching scenes since values were not displayed correctly in the user interface or applied in game. Therefore, the values had to be read from the player preferences upon start in the affected classes.
Player Health
The player healthbar is represented by a heart made out of five pieces. Should the player lose health, the pieces are cut and now grey.
Health can be lost by being hit by an enemy or standing in the icy water for too long. Multiple classes are responsible for this logic.
The abstract class HealthComponent serves as a base class for important functions such as Heal or TakeDamage.
PlayerHealthComponent manages the health of the player character and inherits from HealthComponent.
In order for the player to take damage from the floor, the function TakeFloorDamage is overridden, and depending on the current health, the player will either take damage or die.
This function is not called by itself and rather added as a listener to a delegate from the PlayerFloorDamage script, which holds the logic for the damaging floor.
Surfaces that are damaging for the player must be tagged by the ice tag. This is checked in the function IsDamageTag via a raycast.
As soon as the boolean is true, an event that regulates the damage visualization is invoked, and a timer is started. The listening function is located in the DamageVisualisation script, and a vignette effect is displayed on the camera. The strength is dependent on how long the player stayed on the damaging surface.
As soon as the timer reaches zero the player is frozen.
Furthermore, in the DamageVisualisation script, DealFloorDamage is invoked.
It is essential to state that two timers are used here, and it is distinguished between frozen and damaged. This is due to the visual effects and to give the game designers more possibilities. The player receives damage as soon as the damage timer reaches zero.
To prevent the player from jumping to avoid taking damage, the timer is only reset under two conditions: The raycast must not be colliding with a damage tagged object, and the player must be grounded.
Therefore, the timer is paused for the timer the player is jumping while on the ice water.
In the user interface, these changes are applied every time the event OnHealthChanged is invoked since the function UpdateHealthDamage is added as a listener. The fill amount on the healthbar is hereby determined by the forwarded health in percent.
Animations are handled by an animator. It should also be mentioned that the health bar is only visible to the player if they have been taken damage.
Collectibles
In Kelingan there are two types of collectibles: stars and coins. Both are visualized in the user interface and are implemented there in the same way. Apart from being collectibles, they replenish the player's health. Hereby, stars refill the complete health, and coins only heal the player for one hit point. The prefabs each receive the respective tag, and on collecting, a delegate is invoked.
A listener function is added in the PlayerHealthComponent script, enabling the player to be healed.
Similar to the display of the healthbar in the user interface, the amount of collectibles is only shown for a short period of time when the player collects something.
To prevent the animation from starting multiple times, if more items are collected within a short period of time, a timer is used. Should it reach zero, the timer is reset.
Furthermore, the count of the collected item is always incremented on pickup. The function is added as a listener to a delegate event that is invoked on collection.
Conclusion
During the production of Kelingan I was able to learn a lot of good practices in code architecture. Furthermore, it was my first project as a dedicated programmer with no responsibilities in the project management department. Therefore, I was able to really concentrate on programming, especially graphics programming, at the beginning of the project. Since the project was developed in the Unity Engine, I was also able to deepen my existing knowledge of C# and expand it during the course.
What went well
The programming workflow was easy to follow
The learning experience for shader programming in Unity Engine
What went wrong
Lack of communication between the departments caused problems with the workflow
Too late consideration for requirements to bake the light