← Back to projects

Arena 44

Vector Shooter with a claustrophobic shrinking arena

Role Prog / Tech Art / GD
Team Solo
Timeline 7 Days (Jam) April 2026 | Bachelor 1st Year
Stack Unity 6 / C#

The Context & Design Philosophy

Arena 44 is a fast-paced, minimalist vector shooter focused on spatial awareness and risk management. Developed in a single week for the Frene Jam, the core foundation was built around two mandatory modifiers: "You are your own enemy" (your projectiles bounce back as lethal hazards) and "Everything shrinks".

While the jam's prompt explicitly requested that everything should shrink over time, early prototyping revealed a critical design flaw. Shrinking the player alongside the arena only created an illusion of a "zoom out", completely neutralizing the mechanical tension.

I deliberately chose to break this rule to serve the game's core loop: the player retains their original size while the walls and UI slowly collapse around them. This architectural decision transformed a visual gimmick into a genuine, claustrophobic gameplay mechanic that actively increases the difficulty as the timer runs.

My Contributions

  • 01 Core Gameplay & Physics Engineered a robust projectile ricochet system and the claustrophobic shrinking arena mechanic to create a tense, risk-reward loop.
  • 02 Tech Art & Game Feel Delivered a visceral combat experience by integrating heavy hit-stops, screen shakes, and dynamic visual effects within a clean vector aesthetic.
  • 03 Event-Driven Architecture Structured the project using a clean, event-based architecture, ensuring the UI and gameplay systems communicate seamlessly without coupling.

Learning & Implementation

01. Custom Physics: The Math Behind the Ricochets

To ensure absolute precision and avoid the unpredictability of native physics engines at high speeds, I engineered a custom, math-driven projectile system. Relying on standard rigidbodies in a confined, high-velocity environment often leads to collision detection failures or "tunneling."

Instead of physics engine ticks, the movement logic is handled inside FixedUpdate using Physics.SphereCast. This continuous collision detection method projects a volume forward based on the projectile's speed and direction, guaranteeing flawless wall impacts.

When a collision is detected, the system calculates the exact ricochet trajectory. For an incoming velocity vector V striking a surface characterized by its perpendicular normal vector N, the new symmetrical exit path relies on this reflection formula:

R = V - 2(V · N)N

In C#, this is elegantly resolved using Unity's Vector3.Reflect, which computes the dot product and vector subtraction in the background.

02. Data-Driven Projectile Architecture

To maintain a modular architecture, the projectile logic is entirely decoupled from its balancing data. Variables such as speed, collision radius, and maximum bounces are injected via a ProjectileStatsSO ScriptableObject. This allows for rapid iteration and game feel adjustments without ever touching the core mathematical loop.

Projectile.cs CUSTOM PHYSICS BEHAVIOR

using Sirenix.OdinInspector;
using System.Collections;
using UnityEngine;

public class Projectile : MonoBehaviour
{
    [InlineEditor, SerializeField] private ProjectileStatsSO Stats;
    [Required] public RSE_OnComboFinished OnComboFinished;
    [InlineEditor, Required] public SoundData BounceSound;

    private Vector3 _currentDirection;
    private int _bounceCount = 0;
    private float _age = 0f;
    public bool CanHurtPlayer { get; private set; } = false;

    public int EnemyKilledCount = 0;

    public void Initialize(Vector3 startDirection)
    {
        _currentDirection = startDirection.normalized;
        StartCoroutine(FirstSecondsKillsCount());
    }

    void Update()
    {
        _age += Time.deltaTime;

        if (!CanHurtPlayer && _age >= Stats.PlayerGracePeriod)
        {
            CanHurtPlayer = true;
            Material objectMat = GetComponent().material;
            objectMat.color = Color.red;
        }
    }

    private void FixedUpdate()
    {
        float moveDistance = Stats.Speed * Time.deltaTime;
        RaycastHit hit;

        if (Physics.SphereCast(transform.position, Stats.CollisionRadius, _currentDirection, out hit, moveDistance, Stats.BounceMask))
        {
            _bounceCount++;

            if (_bounceCount > Stats.MaxBounces)
            {
                Destroy(gameObject);
                return;
            }

            _currentDirection = Vector3.Reflect(_currentDirection, hit.normal);
            AudioManager.Instance.PlayClipAt(BounceSound, transform.position);
            transform.position = hit.point + (hit.normal * Stats.CollisionRadius);
        } 
        else
        {
            transform.Translate(_currentDirection * moveDistance, Space.World);
        }
    }

    private IEnumerator FirstSecondsKillsCount()
    {
        yield return new WaitForSeconds(1.5f);
        OnComboFinished.RaiseEvent(EnemyKilledCount);
    }
}

03. Encapsulation & Logic Protection

A key part of keeping the codebase clean was strictly controlling how external systems interact with the projectiles. In the script above, the state of the bullet is defined by:

public bool CanHurtPlayer { get; private set; } = false;

This auto-implemented property applies a fundamental principle of Object-Oriented Programming: Encapsulation. By setting the setter to private, the entire project can read the state of the projectile (e.g., the player's health script checking if (projectile.CanHurtPlayer)), but absolutely no external system can modify it. Only the projectile itself holds the authority to change its own state after its grace period, preventing accidental overrides from other scripts.

Game Feel & Visual Feedbacks

To compensate for the minimalist vector aesthetic, the game relies heavily on "Juice" to ensure combat feels visceral and rewarding. I focused extensively on visual communication to keep the player informed without relying on complex UI.

Using the Feel asset, I implemented a robust feedback loop: heavy hit-stops (micro-pauses in time upon impact), dynamic chromatic aberration, and intense screen shakes. Every wall bounce and enemy hit has a physical weight to it.

The clean, sharp geometry is rendered using Shapes, guaranteeing absolute readability in the chaos. All of these effects are triggered through my event-driven architecture, ensuring the core physics and the visual juice remain perfectly decoupled.

Hit-Stops & Screen Shake