Author: Joëlle Ubink

  • Mute

    Mute

    Project Link

    Mute was a group project made as part of two week-long seminar on the subject of Transformative Interactive Narrative Design. My role in the group was as a programmer for the mechanics and shaders showcased in the experience.

  • SFML  Car Game

    SFML Car Game

    Project Link

    As part of a C++ programming project, I made a simple clone of Speed Racer in SFML.

  • C# Behavior Tree

    Project Link

    For this assignment, I developed a Behavior Tree system for AI agents, specifically a guard and a rogue. I created a hierarchical Behavior Tree structure with a BTNode base class, which includes composite (BTCompositeNode), decorator (BTDecoratorNode), and task (BTTaskNode) nodes. Each of these serves a distinct function, such as managing execution flow, modifying behavior, or performing specific agent actions.

    I implemented several task nodes, including BTWalkToPointNode, BTAttackPlayerNode, and BTThrowSmokeBombNode, to handle movement, combat, and interactions. The Guard and Rogue AI agents use this system, with the Guard detecting and reacting to the player. To facilitate communication between agents, I implemented a GlobalBlackboard, allowing the rogue to respond to changes in the guard’s state.

    I structured the tasks into generic and agent-specific categories to maximize reusability while maintaining flexibility. Additionally, I made the BTLogNode capable of updating text above the agent’s head for easier debugging.

  • SFML Boids

    SFML Boids

    Project Link

    Created as part of an assignment in a couple of days.

    The Boids algorithm used implements the basic rules of cohesion, separation, and alignment. The Boids are baised towards staying within the screen and the speed is adjustable.

  • Basketball AI

    Basketball AI

    Project Link

    While studying at Hanze University of Applied Sciences, my teammate and I developed a basketball simulation game featuring AI-controlled teams. My primary role was designing the AI, ensuring it could make strategic decisions and react dynamically to in-game situations. To achieve this, I built a StateMachine to manage different behaviors, an EnvironmentalInfoComponent to process game data and suggest actions, and a ReflexBehavior system to handle instinctive reactions, such as jumping to block a shot.

    One of the biggest challenges was debugging, as the AI’s growing complexity made it difficult to determine whether unexpected behavior was intentional or a bug. Additionally, calculating realistic ball trajectories required a physics-based approach, and I chose to use Unity’s rigid body system rather than manually interpolating the ball’s movement. This ensured that shooting and dribbling felt natural and dynamic.

    The AI continuously evaluates its environment, adapting its actions based on factors like ball possession, opponent positioning, and scoring opportunities. The Examine state acts as the AI’s default mode, helping it transition smoothly between actions like passing, defending, or moving toward the hoop. The ball mechanics were carefully designed to respond to player movement speed, creating a more fluid and realistic experience.

    Overall, I am proud of what we accomplished within the given timeframe. However, if I were to continue development, I would focus on cleaning up the code, implementing a custom pathfinding system for better movement control, replacing the simple player models with more detailed ones, and adding an in-game UI for configuring matches before they start.

  • Procedural City Generator

    Procedural City Generator

    Project Link

    In the first year of my degree program at HKU, I made a procedural city generator for my end-year project over a 5-6 month period.

    I started making this procedural city generator in the Godot game engine but later switched to Unity. I switched to Unity as it had better tools for debugging the generation while I was working on it. The system works through layers and the only-the-fly instantiation of prefab objects. This allowed me to implement a floating origin system allowing the map size to reach millions of units without issue.

    The layer system works by defining the characteristics of each layer with a custom script that tells the generator how to interpret each pixel of the image layer and how to apply it. The roadmap layout uses binary space partitioning to create a generated grid-based roadmap layout. The white space represents an area for buildings to spawn on while the black space represents a piece of road. The script interpreting the roadmap then interprets this information and instantiates it around the player. The height of buildings is determined through a pre-generated height map. The location of where roads, buildings, and water spawn are based on the Land/Ocean layer where a white pixel correlates to being land and a black pixel water.

    The generator spawns objects based on where the player is located in the virtual space. The virtual space is the size of the map in pixels multiplied by the predefined in-game size of a pixel. The player can be placed at any point in the world. The world then gets spawned around the player based on the maximum spawning distance for prefab objects. The world gets regenerated whenever the player travels too far away from their origin point. The origin point is a predefined position in the Unity game world. When the world gets regenerated the position of the player gets reset to the origin point while retaining the distance traveled in virtual space.

    The prefabs contained prebaked lightmap information which enabled me to optimize lighting by making lighting static when far away from the player. The windows in the buildings feature parallax mapping for extra detail.

    The one major issue I faced during development was the time needed to regenerate the world around the player. At render sizes of greater than 20 by 20 pixels from the map the FPS halves and creates stutter. I was partially able to address this by spreading out the instantiation of objects over a larger amount of frames. This approach created a new issue, however, as the world seemingly reappeared in front of the player, which broke the illusion of a contiguous world space. I ultimately ended up compromising by accepting some level of stutter when the world has to be regenerated.

    Due to time constraints, I was not able to look at other potential optimization techniques like object culling, (H)LODs, and object pooling. Nevertheless, I was quite happy with what I was able to make with the allotted time.