Repo of Multiple projects in Unity C# for Massive Loop.
This is a small fun project made with the Massive Loop SDK for a Massive Loop game world!
Feel free to check it out at : https://home.massiveloop.com/ and our SDK is available for download at : https://massiveloop.com/download/sdk/
With documentation available at : https://docs.massiveloop.com/
We recently have allowed the usage of C# scripting which gives a lot of freedom to our creators. This is one of many fun projects that I've started in order to test out the limits of our new C# compiler. Also I think that creating a fun small Table Tennis game that could be played in VR & Multiplayer sounds pretty fun!
This script powers a fully interactive jukebox in Unity, complete with dynamic song playback, an audio visualizer, and user-friendly controls. Designed for projects that require immersive audio experiences, the Jukebox script integrates seamlessly into Unity's audio system and includes customizable features for enhanced functionality.
Songs array with your desired audio clips.This script is ideal for interactive audio experiences in games, VR/AR environments, or any Unity-based application where music playback and visualization are required.
This script adds dynamic interactivity to VR objects using the MLGrab system, enabling realistic resizing and throwing mechanics. Perfect for VR projects requiring intuitive and immersive object manipulation.
Primary Grab (One-Handed Interaction):
Secondary Grab (Two-Handed Interaction):
Customizable Parameters:
minSize and maxSize: Relative scale limits for resizing.absoluteMinSize and absoluteMaxSize: Absolute size limits to prevent extreme scaling.throwForceMultiplier: Strength of the throwing force.maxThrowForce: Caps the maximum force applied during a throw.Realistic Physics Integration:
Event-Driven System:
Primary Grab:
Secondary Grab:
Physics Integration:
objectToChange field to the target GameObject.Object thrown with impulse force: [force_vector]
The DodgeBallGameManager script manages a team-based dodgeball game in Unity. It controls player interactions, team assignments, game state management, teleportation, and scoring. This script uses MassiveLoop SDK for multiplayer support and TextMeshPro for UI updates.
MLPlayer, MLClickable, and network events).| Type | Name | Description |
|---|---|---|
GameObject |
BlueTeleportObjectLocation | Blue team's teleport location. |
GameObject |
RedTeleportObjectLocation | Red team's teleport location. |
GameObject |
ResetBallPosition | Position to reset game balls. |
GameObject |
GameBall / GameBalls | The game ball(s) in the arena. |
ML.SDK.MLClickable |
taggersClickable / runnersClickable | Clickables for selecting teams. |
ML.SDK.MLClickable |
StartGameClickable | Clickable to start the game. |
TextMeshPro |
timeText | Displays the remaining time. |
TextMeshPro |
GameStatusText | Displays game status text. |
TextMeshPro |
BlueTeamString / RedTeamString | Displays team members. |
TextMeshPro |
BlueTeamScore / RedTeamScore | Displays team scores. |
Stopwatch |
stopwatch | Tracks elapsed game time. |
List<string> |
blueTeam / redTeam | Holds player names for each team. |
int |
blueTeamNumberScore / redTeamNumberScore | Tracks each team's score. |
bool |
isGameActive | Game state flag. |
Collider |
ArenaCollider | Arena collider for teleport checks. |
GameObject |
BlueCelebration / RedCelebration | Celebration effects for winning teams. |
public void OnStartGameEvent(object[] args)
{
GameStatusText.text = "Game Active";
GameStatusText.color = Color.green;
isGameActive = true;
stopwatch.Start();
TeleportPlayersToArena();
Tip.SetActive(true);
BluePortal.SetActive(false);
RedPortal.SetActive(false);
BlueCelebration.SetActive(false);
RedCelebration.SetActive(false);
if (isGameActive == true)
{
taggersClickable.gameObject.SetActive(false);
runnersClickable.gameObject.SetActive(false);
}
}
public void OnResetBallPosition()
{
foreach( GameObject b in GameBalls)
{
b.transform.position = ResetBallPosition.transform.position;
}
}
private void AssignTeam(MLPlayer player, string team)
{
string playerName = player.NickName;
this.InvokeNetwork(EVENT_SELECT_TEAM, EventTarget.All, null, playerName, team);
UpdateTeamLocally(playerName, team);
}
private void TeleportPlayersToArena()
{
MLPlayer localPlayer = MassiveLoopRoom.GetLocalPlayer();
MLPlayer[] playersArray = MassiveLoopRoom.FindPlayersInCollider(ArenaCollider);
// Check if the localPlayer has a valid "team" property
if (localPlayer.GetProperty("team") != null)
{
// If the player is already in the arena, do nothing
if (playersArray.Contains(localPlayer))
{
UnityEngine.Debug.Log("Local player is already in the arena, skipping teleport.");
return;
}
UnityEngine.Debug.Log("Get Property on local player did not return null");
string team = (string)localPlayer.GetProperty("team");
if (team == "Red" && !playersArray.Contains(localPlayer))
{
UnityEngine.Debug.Log("Red team member found");
localPlayer.Teleport(RedTeleportObjectLocation.transform.position);
}
else if (team == "Blue" && !playersArray.Contains(localPlayer))
{
UnityEngine.Debug.Log("Blue team member found");
localPlayer.Teleport(BlueTeleportObjectLocation.transform.position);
}
}
}
private void UpdateTeamText()
{
BlueTeamString.text = $"Blue Team: \n {string.Join(", \n", blueTeam)} \n";
RedTeamString.text = $"Red Team: \n {string.Join(", \n", redTeam)} \n";
}
private void EndGame()
{
int blueTeamCount = blueTeam.Count;
int redTeamCount = redTeam.Count;
isGameActive = false;
BluePortal.SetActive(true);
RedPortal.SetActive(true);
Tip.SetActive(false);
if (blueTeamCount > redTeamCount)
{
GameStatusText.text = "Blue Team Wins!";
GameStatusText.color = Color.cyan;
BlueCelebration.SetActive(true);
RedCelebration.SetActive(false);
blueTeamNumberScore += 1;
BlueTeamScore.text = blueTeamNumberScore.ToString();
}
else if (redTeamCount > blueTeamCount)
{
GameStatusText.text = "Red Team Wins!";
GameStatusText.color = Color.red;
BlueCelebration.SetActive(false);
RedCelebration.SetActive(true);
redTeamNumberScore += 1;
RedTeamScore.text = redTeamNumberScore.ToString();
}
else
{
GameStatusText.text = "It's a Tie!";
GameStatusText.color = new Color(255f / 255f, 97f / 255f, 0f / 255f, 1f); // RGBA
}
this.InvokeNetwork(EVENT_TELEPORT_PLAYERS_BACK_TO_SPAWN, EventTarget.All, null);
ResetGame();
}
private void ResetGame()
{
blueTeam.Clear();
redTeam.Clear();
UpdateTeamText();
stopwatch.Reset();
MLPlayer localPlayer = MassiveLoopRoom.GetLocalPlayer();
localPlayer.SetProperty("team", "none");
if (isGameActive == false)
{
taggersClickable.gameObject.SetActive(true);
runnersClickable.gameObject.SetActive(true);
}
}
The script utilizes MassiveLoop SDK networking events to synchronize actions across all clients.
const string EVENT_SELECT_TEAM = "TeamSelectEvent";
const string EVENT_START_GAME = "EventStartGame";
const string EVENT_TELEPORT_PLAYERS_INTO_ARENA = "TeleportPlayersEvent";
const string EVENT_TELEPORT_PLAYERS_BACK_TO_SPAWN = "TeleportPlayersEvent_BackToSpawn";
These constants represent event names used for invoking and handling networking events.
Event tokens register callbacks to specific network events:
tokenSelectTeam = this.AddEventHandler(EVENT_SELECT_TEAM, OnTeamSelectEvent);
tokenStartGame = this.AddEventHandler(EVENT_START_GAME, OnStartGameEvent);
tokenTeleportPlayers = this.AddEventHandler(EVENT_TELEPORT_PLAYERS_INTO_ARENA, OnTeleportPlayers);
tokenTeleportPlayers_backToSpawn = this.AddEventHandler(EVENT_TELEPORT_PLAYERS_BACK_TO_SPAWN, OnTeleportPlayersBackToSpawn);
Usually, it is best to include these in your start function, that way each network oriented event is available almost right away during run-time. The AddEventHandler registers methods like OnTeamSelectEvent to respond to specific events when they are invoked.
Take this code for example :
private void AssignTeam(MLPlayer player, string team)
{
string playerName = player.NickName;
this.InvokeNetwork(EVENT_SELECT_TEAM, EventTarget.All, null, playerName, team);
UpdateTeamLocally(playerName, team);
}
Updating a player's team is something that we certainly want each client to see. Therefore, we call this.InvokeNetwork(EVENT_SELECT_TEAM, EventTarget.All, null, playerName, team);
Where the general structure of the invokeNetwork call first asks for the event name that we registered the callback to. EVENT_SELECT_TEAM
Then we set our target. EventTarget.All
Note that there are multiple options that are available to you here :
| EventTarget | Description | Example Use Case |
|---|---|---|
| Player | Targets a specific player | Sending player-specific updates |
| OnlyThisClient | Local client only | Local UI or HUD updates |
| Master | Master client (authoritative client) | Spawning, validation, centralized logic |
| All | All clients, including the sender | Global events, game start, animations |
| Others | All clients except the sender | Sync player movement, broadcast events |
InvokeNetworkWhen calling InvokeNetwork, you can pass variables as the third parameter, which will be accessible in the event handler:
this.InvokeNetwork(EVENT_TELEPORT_PLAYERS_INTO_ARENA, EventTarget.All, new object[] { variable1, variable2 });
Third Parameter: An object[] array containing any variables you want to pass. The variables can be accessed through the object[] args parameter.
// Sending the event with variables
this.InvokeNetwork(EVENT_TELEPORT_PLAYERS_INTO_ARENA, EventTarget.All, new object[] { "Arena1", 10 });
// Receiving the event
public void OnStartGameEvent(object[] args)
{
string arenaName = (string)args[0];
int playerCount = (int)args[1];
Debug.Log($"Teleporting {playerCount} players to {arenaName}");
}