Back

Quad Clash

Preface

This game is a multiplayer 2v2v2v2 arena, similar in structure to the Arena game mode in League of Legends. It includes a fully developed bot system that dynamically replaces missing players with AI. This map alone has attracted over 3,000 unique players on Fortnite Discovery and had a peak of over 250 players active at the same time. You can check it out yourself here! Before we dive into the details, here's a short trailer I made for the map's release.

Gameplay Trailer

Details

System Design

There are two main underlying systems in this game: the economy (earning gold, buying weapons and upgrades) and the matchmaking.

The core gameplay loop is straightforward and similar to many other games in the Fortnite space. What sets it apart is the structure and matchmaking. Four teams of two compete in a free-for-all format, with teams and maps constantly cycling. This creates fast-paced 2v2 rounds with plenty of variety and AI opponents that fill in missing players.

As mentioned earlier, the matchmaking works similarly to League of Legends' Arena mode, where winners face winners and losers face losers. This adds variety by mixing up the opponents you play against.

You earn 25 gold for each elimination. This gold can be used to purchase new weapons or upgrade your existing ones, giving you an edge in future fights.

There are various tiers of weapons, ranging from Tier 1 to Exotic, each with its own price point and power. To purchase one of the Exotic weapons, you'll need at least 40 eliminations, giving players something to strive for.

The workbench lets you equip your weapons with various attachments like scopes, grips, and different magazines - for a price.

The player with the most eliminations is displayed by a hologram at the top center of the hub, mimicking their emotes for everyone to see. This motivates players to claim that "throne", showing everyone they're the best in the lobby.

Code Snippets


    GetBestPlayer() : void = 
        # Retrieve all players currently in the playspace
        AllPlayers := GetPlayspace().GetPlayers()
        
        # Loop through each player to populate the Scores array with their current scores
        for(IDX := 0..Scores.Length-1):
            if (Player := AllPlayers[IDX]):
                # Set the score for each player in the Scores array
                if (set Scores[IDX] = ScoreManager.GetCurrentScore(Player)){}
        
        # Initialize a flag to track whether any scores were swapped during sorting
        var Swapped : logic = false
        
        # Main loop to repeatedly sort scores and update the leaderboard display
        loop:
            Sleep(5.0)  # Runs every 5 seconds to keep it up to date
            set Swapped = false  # Reset Swapped to detect any new swaps in this iteration
            
            # Bubble sort loop to order scores in descending order
            for(IDX := 0..Scores.Length-1):
                # Compare each score with the next; swap if current score is higher
                if(Scores[IDX] > Scores[IDX + 1]):
                    if (NextScore := Scores[IDX + 1], CurrentScore := Scores[IDX]):
                        # Perform the swap of scores in the array
                        if(set Scores[IDX] = NextScore, set Scores[IDX + 1] = CurrentScore):
                            set Swapped = true  # Mark that a swap happened
            
            # Once sorting is complete (no swaps), determine the highest scorer
            if (Swapped = false):
                # Check each player to see who matches the top score (Scores[0])
                for (Player : AllPlayers):
                    if (HighScore := Scores[0], HighScore > 0):
                        # If player's score matches the top score, display them on the DanceMannequin
                        if (ScoreManager.GetCurrentScore(Player) = HighScore):
                            DanceMannequin.ActivateSkinAndEmoteCapture(Player)
                    else:
                        # If no high score is found, set the mannequin to its default appearance
                        DanceMannequin.ActivateDefaultPreset()
                break  # Exit loop after displaying the best player
    

        

This code is a simple function to identify the player with the highest score and display their avatar at the top of the hub. It starts by retrieving all players and assigning each player's score to an index in the Scores[] array. Using a bubble sort, it repeatedly checks and swaps scores until they're sorted in descending order. Once sorted, the top player (Scores[0]) is displayed by the DanceMannequin device. If no high score is found, the default mannequin preset is shown. This loop runs every 5 seconds, making sure it stays up to date.


    TeleportAllToArenas() : void =
        # Get the collection of all teams within the playspace
        TeamCollection := GetPlayspace().GetTeamCollection()
        
        # Loop through two indices representing the two winning and two losing teams
        for (Index := 0..1):
            # Retrieve the team from the WinnerTeams array at the current index
            if (WinnerTeam := WinnerTeams[Index]):
                # Get the agents (members) of the winning team and the designated teleporter
                if (TeamMembers := TeamCollection.GetAgents[WinnerTeam]): 
                    if (WinnerTeleporter := Teleporters[Index + WinnerPlacer + ArenaCycler]):
                        # Teleport each team member to their assigned winner teleporter
                        for (Member : TeamMembers):
                            WinnerTeleporter.Teleport(Member)
        
            # Retrieve the team from the LoserTeams array at the current index
            if (LoserTeam := LoserTeams[Index]): 
                # Get the agents (members) of the losing team and the designated teleporter
                if (TeamMembers := TeamCollection.GetAgents[LoserTeam]): 
                    if (LoserTeleporter := Teleporters[Index + LoserPlacer + ArenaCycler]):
                        # Teleport each team member to their assigned loser teleporter
                        for (Member : TeamMembers):
                            LoserTeleporter.Teleport(Member)
        
        # Increment ArenaCycler by 2 to change the arenas the players are going to play in
        set ArenaCycler += 2
        
        # Reset ArenaCycler and swap positions for winner and loser placers when the limit is reached
        if (ArenaCycler > 6, LoserPlace := LoserPlacer, WinnerPlace := WinnerPlacer):
            set ArenaCycler = 0
            set WinnerPlacer = LoserPlace
            set LoserPlacer = WinnerPlace
            Print("Swapped")

        

This code teleports members of winning and losing teams to designated arena teleporters based on their team status. It cycles through two indices for the teams, retrieves team members, and assigns each to specific teleporters (winner or loser) in sequence. After teleporting, it increments a counter (ArenaCycler) to adjust for the next round (this makes sure the teams play in different arenas every round). Once a threshold is reached, it resets the counter and swaps the positions for the winner and loser teleporters to keep the placements dynamic.

Map Design

I designed five arenas for this game, each with a unique layout to encourage different play styles. Most follow a 3-lane style (except for the white map), making them easy to understand and play while still offering various approaches and angles.

This map is built for fast-paced, close-quarters combat. The pit together with the bipyramid above it, give players plenty of tactical options and encourages creative movement. There's also an underground area that can be used to quickly drop out of sight or to surprise opponents from an unexpected angle.

Inside the underground area.

The blue arena is more chaotic than the orange one but still offers three lanes for players to choose from. You can get a shot at the enemy fairly quickly after spawning on this map, so make sure you move fast.

Angle from left side spawn.

This arena has plenty of ways to flank and surprise opponents. It features indoor spaces on both the top and bottom levels of the central building. You can even climb up to the roof for an overview of the entire map.

Indoor area at the bottom.

This map is structured like a tent with entrances on every side, making camping difficult (pun intended) since you need to cover a lot of angles. You can choose to either rush over the top or find an angle from below for your best chances at winning.

Inside the tent.

The final map is larger and more complex than the others, offering players more possibilities and angles to engage.

It also features a half-open bottom area with windows and doors, as well as a more enclosed top area with entrances on each side.