• Home
  • About
  • Projects
GitHub

Dungeonball

GitHub - Dungeonball GitHub
itch.io - Dungeonball Itch.io

> Summary_

Dungeonball: The Slimekeep is a physics-based FPS where you fight your way through a horde of slimes. Pick up dodgeballs in both hands and launch them to defeat enemies.

As part of a Master's assignment, I had to recreate one of Sokpop's games. The task involved analysing the original game's mechanics and re-implementing them from scratch, with the restriction of using only primitive shapes for visuals.

I focused on maximising game feel by making every action satisfying. A key part of this was making the act of picking up and throwing dodgeballs enjoyable. Balls launch at high speed but retain a floaty feel, allowing players to catch them again midair.

GIF of player throwing ball against wall and catching it midair

> Pickup Mechanic_

The game's fast pace meant picking up dodgeballs needed to be both quick and accurate. Players can collect balls within a given range in front of them without needing to look directly at the object. However, to maintain accuracy, any balls the player is directly looking at are prioritised over others nearby.

To achieve this, I used two physics casts: every frame, a SphereCast is performed in front of the player and the first object hit is stored.

public void ScanForGrabbables(Vector3 aimOrigin, Vector3 aimDirection)
{
    _scannedGrabbable = null;
    if (Physics.SphereCast(aimOrigin, _grabSphereRadius, aimDirection, 
        out RaycastHit hit, _grabRange, _grabbableLayer))
    {
        _scannedGrabbable = 
        hit.collider.gameObject.GetComponent<Grabbable>();
    }
}

Debug visuals showing the SphereCast detecting grabbable objects.

GIF of player scanning for dodgeballs

The second cast is made only when the player presses the fire button. A RayCast looking directly in front of the player is performed. If a ball is found, it is picked up. If not, the previously stored object from the SphereCast is used instead.

public bool TryGrabGrabbable(Vector3 aimOrigin, Vector3 aimDirection, 
                             out Grabbable grabbable)
{
    grabbable = null;

    // get object directly in front of player if available
    if (TryGetLineOfSightObject(aimOrigin, aimDirection, ref grabbable))
        return true;

    // if not, use the object stored from the last scan
    if (_scannedGrabbable != null)
    {
        grabbable = _scannedGrabbable;
        return true;
    }

    // if no object in range, return false
    return false;
}

private bool TryGetLineOfSightObject(Vector3 aimOrigin, 
                                     Vector3 aimDirection, 
                                     ref Grabbable grabbable)
{
    if (Physics.Raycast(aimOrigin, aimDirection, out RaycastHit hit, 
                        _grabRange, _grabbableLayer))
    {
        grabbable = hit.collider.gameObject.GetComponent<Grabbable>();
    }
    return grabbable != null;
}

Note the red line turning green when the player looks directly at a ball.

GIF of player scanning for dodgeballs with raycast

It's important to note why the SphereCast is run every frame rather than only when the player presses the fire button. The crosshair UI needs to update each frame to indicate to the player whether they are within range of a dodgeball. This real-time feedback is essential for accuracy in gameplay, which is why the cast is performed continuously.

/// <summary>
/// Checks if player has changed from "hovering over target" to 
/// "not hovering over target" or vice versa.
/// Invokes event if change is detected.
/// </summary>
private void CheckScannedGrabbablesHasChanged()
{
    // Is player hovering over a target this frame?
    bool foundGrabbableThisFrame = _grabber.ScannedGrabbable != null;

    // If the state has changed since the last scan, invoke event
    // (Crosshair UI will subscribe to this event and update accordingly)
    if (_lastScanFoundGrabbable != foundGrabbableThisFrame) 
        OnScannedForGrabbablesChange?.Invoke(foundGrabbableThisFrame);

    // Update state for next frame
    _lastScanFoundGrabbable = foundGrabbableThisFrame;
}

Note how the player picks up a different ball depending on where they're looking.

GIF of player picking up dodgeballs
GitHub itch.io Linkedin YouTube

fynngm@hotmail.com
Built by Fynn Gallagher-Mundy