Showing posts with label Gameplay Programming. Show all posts
Showing posts with label Gameplay Programming. Show all posts

Feb 4, 2025

Mercy for Machines

Team of 13
September 2023 - April 2024
Skills Utilized: C++, Unreal Engine, Tools Programming

Introduction

Our team, consisting of programmers, designers, artists, and sound designers, collaborated to develop a game using Unreal Engine 5.2. Mercy for Machines is a 3rd person, narrative driven sci-fi horror game. My primary role was gameplay programming, and I also developed editor tools for designers and implemented destruction mechanics using Chaos physics.

Showcase

↑ Actor Collector

↑ Chaos Destruction

Technical Highlights

Actor Collector

While observing the map creation process, I noticed that manually maintaining lists of various actors was a frequent and inefficient task. To streamline this, I developed a tool that automatically collects and visualizes actors. Users can intuitively select actors using box and sphere volumes, with additional filtering options based on actor class, interface, or component class. To enhance visibility, I implemented an outline material for collected actors and a material effect that desaturates everything else in the scene. To minimize runtime overhead, the tool only retains the collected actor list in the final build without performing any additional operations.

Chaos Destruction

One gameplay sequence required a staircase to collapse, causing the player to fall. I immediately thought of Unreal Engine 5’s Chaos physics engine and its destruction system. While fracturing the mesh was straightforward, controlling how the pieces scattered proved challenging. I achieved the desired effect by carefully utilizing Anchor Fields and Master Fields to direct the fragments’ movement.

Conclusion

Mercy for Machines was a valuable experience that expanded my understanding of Unreal Engine 5. Developing the Actor Collector tool improved level design efficiency, while implementing Chaos Destruction deepened my knowledge of the engine’s physics system. Through this project, I gained hands-on experience with Unreal’s editor scripting, rendering, and physics features, further refining my ability to develop both gameplay mechanics and engine tools.

Feb 3, 2025

Roulette

Team of 2
April 2024
Skills Utilized: C#, Unity, Nintendo Switch Programming

Introduction

As part of a Nintendo Switch development class, I worked on a final project to create a small game. I developed a roguelike deck-building game where players earn points through various interactions between a ball and the slots of a roulette wheel. By tilting the Joy-Con, players can influence the wheel’s movement, allowing some control over where the ball lands.

Showcase





Technical Highlights

Motion Control

Interpreting the 6-axis sensor data to control the roulette wheel’s tilt was the key challenge. The goal was to isolate rotation along the Y-axis and map it to a circular motion for smooth and intuitive control. Through extensive discussions and calculations with my teammate, we derived the correct formulas to achieve precise and responsive motion handling.

Conclusion

Although this project didn’t require extensive use of it, previous assignments in the course provided hands-on experience with native Nintendo Switch development, debugging, and optimization tools. Additionally, working with motion controls reinforced how effectively utilizing input devices can significantly impact gameplay experience.

Feb 2, 2025

Blinded

Team of 5
March 2023 - June 2023
Skills Utilized: C++, OpenGL, GLSL, JSON5, Graphics Programming

Introduction

Blinded is a boss rush-focused souls-like action game where the player restores red, green, and blue to a colorless world by defeating powerful bosses.

In this project, I developed a fragment shader for dynamic color removal and restoration, eliminating the need for multiple textures. I also implemented data-driven enemy and level design, streamlining iteration and balancing. These technical solutions enhanced both the game's artistic vision and development efficiency.

Showcase












Technical Highlights

Data Driven

I have transitioned various game elements, such as monster AI parameters, collision boxes, and level layouts, to a data-driven system using JSON5. This allowed for easy modification and extension without strict formatting constraints. By declaring a struct, exporting its members, and calling a parsing function, we could seamlessly populate in-game data with minimal effort.

Defining enemy attributes and placements in external files eliminated the need for recompiling or relaunching the game, greatly accelerating our iteration cycle. This significantly improved efficiency when fine-tuning enemy behaviors and level layouts.

↑Example of a level layout

↑Example of an enemy's AI parameters

RGB Fragment Shader for Dynamic Color Removal


To optimize asset management, I replaced pre-drawn grayscale and partial-color textures with a fragment shader that dynamically removes red, green, or blue from a full-color image. This eliminated the need for multiple texture versions, significantly reducing asset workload.

A key challenge was ensuring colors were removed correctly rather than simply setting RGB values to zero, which would turn everything black. Instead, we needed to gray out the removed components proportionally.

To achieve this, we used the HSV color model, which represents color using hue, saturation, and value. The hue determines the color type. By measuring the hue's proximity to the removed color, we calculated how much of it should be grayed out. The closer a color was to the removed hue, the more it was desaturated.


Rather than directly desaturating the color, we shifted the hue outside the removed color range. This ensured that a pure yellow (which contains both red and green) would still retain some green when red was removed, preventing incorrect full desaturation.
The final result was an interpolated blend between the original color and a grayscaled version based on how much of the removed component was present.

↑ Part of the final shader

This approach not only streamlined asset creation but also enabled greater gameplay flexibility, allowing players to dynamically regain colors in a non-linear progression.

Conclusion

Through Blinded, I gained valuable experience in shader programming and data-driven design. The shader development process deepened my understanding of color manipulation in graphics programming, particularly when working with RGB components and HSV models. Additionally, transitioning the game’s enemies and levels to a data-driven approach taught me how to optimize workflows and speed up iteration cycles, greatly improving both development efficiency and gameplay balance. This project enhanced my problem-solving skills, especially when faced with complex technical challenges and the need for creative solutions.

Nov 11, 2020

Q

Team of 5
September 2020 - December 2020
Skills Utilized: C++, OpenGL, Python, Graphics Programming, Scripting System, Template Metaprogramming

Introduction

During this semester's GAM class, my group decided to create a 2D action platformer game. I was responsible for constructing the graphics layer of the custom game engine using OpenGL and establishing a Python scripting system.
Built an object-oriented graphics layer encapsulating OpenGL concepts like meshes, textures, and shaders. To enable fast iteration, implemented a Python scripting system by embedding Python into the engine and exposing engine functions using template metaprogramming.

Showcase

↑ Prototype Demo

↑ Graphics showcase




↑ Scripting system showcase







↑ Scripting system showcase (integrated with the engine)




GitHubFunction Property Inspector (Part of the Python Embedder)

Responsibilites

  • Technical Director
  • Graphics Programming
  • Scripting System
  • Part of the Engine Programming
  • Other Gameplay Programming

Technical Highlights

Graphics layer of the engine

One of the challenges when working with OpenGL in an object-oriented language is that it functions as a giant state machine. While efforts have been made to encapsulate some aspects in version 4.5 and above, the core inconvenience remains. Consequently, during the design phase of the engine's graphics layer, I prioritized an object-oriented approach. I encapsulated each distinct concept, such as mesh, texture, and shader, in its own class to maximize encapsulation.


Python scripting system

Last semester, an assignment required the application of template programming. I decided to develop a Python embedder for personal exploration. And in this semester, for the game project, I decided to upgrade and use this embedder instead of relying on existing solutions, well, just for fun.
It automates the cumbersome process of registering C++ functions for using them in Python. All you have to do is one macro or a function call, depending on your preference. It heavily uses template metaprogramming for that automation.
One biggest challenge was iterating over the variadic template parameters. To expose a function to Python, you need to make another function that takes in the PyObjects, parses them then calls the original function with the parsed objects.
I was making that part of the automation and I needed to parse an arbitrary amount of arguments, so I was taking the list of the parameter types as a std::tuple. I needed to iterate over them to parse them. I couldn't use a for loop since the index of the std::tuple_element_t should be complie-time constant.
What should I do? The only thing I know in extra is the count of the parameters. After many trials and errors, I managed to figure out a solution. Make an index sequence with that count, take them with std::index_sequence<ArgumentIndices...>, and call a function for each of them with { parse<Tuple, Indices>(args)... }.




With this, I was able to register all different kinds of functions with various parameter lists with a single function call.

Conclusion

It was fun to design and build a graphics layer of the engine and implement a scripting system, which allowed us to iterate faster on the gameplay programming. Throughout the project, I gained valuable insights into OpenGL and template metaprogramming, as well as the integration of a scripting language into a game engine. These experiences collectively contributed to a significant enhancement of my skillset.

Nov 9, 2020

U.R.U.K

Team of 4
February 2020 - July 2020
Skills Utilized: C++, Game Engine Programming, Tools Programming, Memory Management

Introduction

In this semester, the goal of the GAM class was to form a group and make a complete game. The main mechanic of this game was a gate that opens when the count of creatures matches a certain number. Given the substantial scope of the game and the restriction on using pre-existing game engines, I took the lead to develop a custom game engine.
Designed a custom game engine with an Actor-Component architecture and a memory manager that stores components contiguously for better cache efficiency. Used CRTP-based static polymorphism to implement deserialization for each component. Developed a state machine editor and map editor, implementing an undo/redo system using a command-based action structure.

Showcase




↑ State machine editor showcase

↑ Controls of the state machine editor




↑ Automatically generated state machine codes

↑ The state machine of the main character used in production


↑ A screenshot of the map editor

Controls
- How to Play will teach you the basic controls.
- ` : (Cheat) Press ` while on a Fish Gate to skip to the next floor.

Responsibilities

  • Technical Director
  • Core of the game engine
  • Entity-component based object management
  • Custom memory manager
  • State machine editor
  • Map editor
  • Minimap editor
  • UI-related components
  • Camera
  • Many other gameplay programming

Technical Highlights

Entity-component structure custom engine

In this project, I was in charge of designing and implementing the game engine. We built our own since the use of existing ones were restricted.
A game is a singleton, it has multiple levels in it. Levels contain objects, and objects conceptually contain components. I used the word conceptually because they are actually stored separately.

Engine Reference Document


↑ The technical structure of the game (better resolution)

It's the components where the most of the logic resides in. The objects are there just to bundle them up abstractly.

↑ A portion of the interface of the component class

↑ A part of the function that adds a component to a level

Custom memory manager

I implemented a custom memory manager for this project. I know it would be redundant for a student-size project, but I just wanted to give it a try. We used it just for storing the components.
The structure of the memory manager is as follows: allocate a huge chunk of initial memory, and divide it on demand. If a new type of component requests a memory, assign a large, contiguous part of it for that type of components. Later, when the same type of a component requests a memory, it'll give one from the preassigned part. So after some uses, it'll look like this:


A notable challenge was handling component removal. To optimize reference locality by ensuring contiguous components, I efficiently filled gaps after a component was removed. This was achieved by swapping the last element with the removed one, while considering component's memcpy-ability.


The reason I couldn't use std::swap was the fact that swapping with the "deleted" ones whose destructor was called caused problems. Now I come to think of it, it would have been sufficient to swap the elements beforehand and then to call the destructor of the to-be-removed one. 

Aside from that, moving the components within the memory implies one another problem: we cannot use the normal pointers to access to the components. To address this issue, I chose handles to give access to the components to the outside, which is in this case, an id.


Virtual static functions for deserialization

During the late stages of development, a demand for the serialization support emerged. Implementing the serialization was straightforward – just add a virtual function that gets called when serialization happens. However, deserialization posed a challenge. Many components relied on non-default constructors, so forcing them to have a default constructors was not viable. How could we call a virtual function when the object had not yet been instantiated?
There is a technique called "static polymorphism". In short, you call a static function of the child class in parent class using CRTP. Derived from this idea, I devised an approach: Components with non-default constructors were to declare a static function with a specific signature that could be invoked during deserialization, returning an instance of themselves. These functions were stored in a std::map. The store happens automatically if the component has a static variable of a special type designed for this purpose.
So yes, it's not "virtual static", it's rather a dispatcher. But the users felt like as if it were virtual :)



Text alignment

The graphics library provided by the school didn't have any of the text alignment functionalities. So I took the charge and implmented horizontal and vertical text alignment, in addition to line wrapping.
To achieve this, I analyzed the file format used by the library reading the document. Based on the information, I wrote the code to simulate the library's calculation of the width and the height of a given text. However, the calculation was a bit off even though I strictly followed the documentation. After some serious investigation, I reached out to the professor who wrote the library and got the actual calculation used in the library. It turns out it was actually the library that didn't follow the documentation. To accommodate these differences, I implemented appropriate workarounds and successfully finalized the implementation.



Command-based architecture of the state machine editor

The structure of the state machine editor mirrored that of existing engines, functioning as a node-link editor. To accommodate undo and redo functionality for enhanced user experience, I implemented a command-based architecture. Every operations were encapsulated as distinct commands, each comprising OnExecute and OnUndo functions. The program maintained a series of commands, executed the appropriate functions when necessary.


Conclusion

This project represents a significant investment of effort on my part. With 382 commits, I added 34,682 lines and deleted 13,419 lines, excluding library and resource changes. The experience gained was extensive, highlighting the rigorous process of building an engine. Despite the challenges, the project was enjoyable, I had the chance to apply programming patterns and patterns, realized the importance of patience and determination in implementing complex functionality. It also provided an opportunity to devise solutions for intricate problems.
In summary, this project has been instrumental in my growth as a software engineer, and the skills developed will undoubtedly shape my future work.