Reel or be Gilled

During Project Vrij II, we worked on a local multiplayer game called Reel or be Gilled. The game is a fast paced collection game in which players compete against each other to catch and deliver as many fish as possible as quickly as they can to score points.

Unlike traditional, calm fishing games, the fish in Reel or be Gilled are anything but harmless. They do not just bite the hook, they can also bite back, stun players, or even explode. These chaotic elements create a competitive and humorous gameplay experience.

Players can actively sabotage each other by using various power up fish, which can be strategically deployed to slow down or blow up opponents. The goal is not only to fish efficiently yourself, but also to make sure your friends have it at least as hard as the fish you pull out of the water.


Project Info:

Teammembers: 10 teammembers see itch.io projectpage
Project duration: HKU Year 2, Period 4 (14-04-2025 until 12-06-2025)
Engine: Unity
Code Languages: C#
Design Patterns: Observer, Singleton & FlyWeight


Below, I share some of the relevant systems I selected (this is only a fraction of the work I have done).

I worked on the complete local multiplayer system, including the character selection system. Players can join at any time by providing a specific input. Once a player selects a character, it is locked and no longer available to other players. When all participating players have chosen a character, the game automatically starts a three second countdown, after which the match begins. If a player cancels their selection or leaves the lobby, the countdown is immediately stopped.

 public CharacterSelection GetNextSelectionPoint(CharacterSelection currentCharacter, int direction, out float yOffset)
    {
        List<CharacterSelection> selections = characterPoints.Where(i => !i.IsSelected).ToList();

        int currentIndex = 0;

        if (selections.Contains(currentCharacter)) currentIndex = selections.IndexOf(currentCharacter);
        else currentIndex = characterPoints.IndexOf(currentCharacter);

        currentIndex += direction;
        if (currentIndex < 0) currentIndex = selections.Count - 1;
        else if (currentIndex > selections.Count - 1) currentIndex = 0;

        yOffset = GetSelectionOffset(selections[currentIndex], null);

        return selections[currentIndex];
    }

    public float GetSelectionOffset(CharacterSelection characterSelection, CharacterSelectPlayer player, float offsetStart = 3.7f)
    {
        float yOffset = offsetStart;
        List<CharacterSelectPlayer> selections = playerSelectors.Where(p => p != null && p.GetCurrentSelection() == characterSelection).ToList();

        int offsetIndex = 0;

        if (player != null)
        {
            offsetIndex = selections.IndexOf(player);

            if (offsetIndex < 0) offsetIndex = 0;
        }
        else offsetIndex = selections.Count;

        yOffset += offsetIndex * .6f;

        return yOffset;
    }

The functions used to determine the height of the player cursor and the associated logic. With this approach, we can add new characters without making any code changes.

Players can cast their fishing rod to catch fish. Because the game is fast paced, the fishing mechanic could not take too long. For this reason, a compact fishing minigame was chosen in which the player moves the stick toward a target point within a radius and confirms this with a button.

If the player misses three times, the fishing minigame fails and the fish escapes. The system technically supports multiple input buttons for this minigame, making the mechanic flexible and easy to adapt. This option was ultimately not used in the game, as it would increase complexity and could potentially be confusing for new players.

    public void UsePressed()
    {
        //Blocks first input (Preventing an unfair strike).
        if (initTime == Time.time || initTime == 0 || playerInput == Vector2.zero) return;

        FishingPoint fishPoint = CheckLocations();

        if (fishPoint == null)
        {
            strikeCount++;

            if (strikeCount >= maxStrikes) CancelFishing();

            return;
        }

        fishingPoints.Remove(fishPoint);
        Destroy(fishPoint.gameObject);

        if (fishingPoints.Count == 0)
        {
            OnCompleteCallback?.Invoke(owner, transform.position - Vector3.one * 2);
            OnFishingComplete?.Invoke();
            CancelFishing();
        }
    }

    private FishingPoint CheckLocations()
    {
        for (int i = 0; i < fishingPoints.Count; i++)
        {
            float pointAngle = fishingPoints[i].FishingAngle;
            float wrappedPlayerAngle = (playerAngle + 360f) % 360f;
            float wrappedPointAngle = (pointAngle + 360f) % 360f;

            float angleDifference = Mathf.Abs(wrappedPlayerAngle - wrappedPointAngle);
            angleDifference = Mathf.Min(angleDifference, 360f - angleDifference);

            if (angleDifference <= collectionRadiusDistance)
            {
                fishingPoints[i].ShowHighlight(true);
                return fishingPoints[i];
            }
        }

        return null;
    }

Fishing Point location checking