Skip to main content
Version: 1.x

Event Handling

Events in ODIN allow you to quickly customize the implementation of voice in your game or application.

Basic application flow

Have a look at this application flow of a very basic lobby application. Events that you need to implement are highlighted in red.

StepTodoDescription
1Join RoomThe user navigates to the multiplayer lobby. Here, all players currently playing the game are connected to the same ODIN room so that they can figure out what to play. The application uses

JoinRoom

function of the

OdinHandler

instance to join the room. Please note: The server automatically creates the room if it does not exist. There is no need for bookkeeping on your side.
2RoomJoinThe

OnRoomJoin

event is triggered that allows you to handle logic before the user actually joins the room. Please note: All upcoming PeerJoined and MediaAdded events are for users that were already connected to the room.
3PeerJoinedFor each user connected to the same ODIN room you'll receive an

OnPeerJoined

event that allows you to handle logic once a peer joined the room.
4MediaAddedFor each user connected to the same ODIN room which has a microphone stream enabled (some of them might only be spectators just listening) an

OnMediaAdded

event is triggered. This event needs to be handled by your application. In this callback, you basically use the

AddPlaybackComponent

member function of the

OdinHandler

singleton instance to create a

PlaybackComponent

that is attached to a GameObject in your scene, depending on your use case.
5RoomJoinedThe

OnRoomJoined

event is triggered that allows you to handle logic after the user joined the room. All PeerJoined and MediaAdded events that come before RoomJoined event are for users that were already connected to the room. Events after RoomJoined event indicate changes to the room after the user connected.
6MediaRemovedWhenever a user disconnects from the room, or closes his microphone stream, an

OnMediaRemoved

event is triggered. You are responsible for cleaning up the

PlaybackComponent

components that you have created earlier in the MediaAdded callback function.
7PeerLeftWhenever a user disconnects from a room, this event is triggered. For example, you can show a push notification that a player has left the room. More info here:

OnPeerLeft

. Important notice:. Whenever a peer leaves a room, the media gets removed as well. So, aligned with this event, for each media of this peer, a MediaRemoved event will be triggered as well.
8Leave RoomYou can use the member function

LeaveRoom

of the

OdinHandler

singleton instance to leave a room. Important Notice: Due to the asynchronous nature of the leaving room operation, the current recommendation is to avoid invoking this function within OnDestroy if the scene is being unloaded. Instead, the best practice is to call the

LeaveRoom

function and subsequently wait for the

OnRoomLeft

event to be triggered. Once this event has been triggered, it is then safe to perform further actions, such as calling LoadScene or Application.Quit.
9RoomLeaveThe event

OnRoomLeave

is triggered to notify you that the current user started to leave the room. You can use it to clean up your scene. You can either do that in this event or the next.
10RoomLeftThe event

OnRoomLeft

is triggered to notify you that the current user has left the room. You need to listen to this event to do some cleanup work: You might have some

PlaybackComponent

components in your scene that you have created earlier. Some (or all of them) are linked to the room that the user just left. Use the

DestroyPlaybackComponents

member function of the

OdinHandler

singleton instance to remove all

PlaybackComponent

elements linked to the room left.

Handling Notifications to users

Many applications notify users that other users have left or joined the room. If you show these notifications whenever a PeerJoined event is incoming, you'll show a notification for every user that is already connected to the user. If you just want to notify users of changes after they have connected the room you have two options:

Example Implementation

We have created a simple showcase demo in Unity. The idea is that you need to find other players in a foggy environment just by their voice. We leverage ODIN's built-in 3D positional audio that typically attaches the voice to player game objects so that their voice represents their location in 3D space - they are louder if close, and you might not hear them if they are far away. If they don't find each other, they can use a walkie-talkie-like functionality to talk to other players independently of their 3D location.

In the first step, we implement that basic walkie-talkie functionality by implementing these events:

OnMediaAdded

,

OnMediaRemoved

, and

OnRoomLeft

.

Walkie-Talkie

This simple example works like this:

The simplest way to do that is to create a new class in Unity (in our example we name it OdinPeerManager) and to implement those callback functions there. Then, either create an empty GameObject in your scene and attach the OdinPeerManager component to it or attach the OdinPeerManager directly to the ODIN Manager prefab that you already have in your scene and use the inspector of the Odin Manager prefab to link the events of the ODIN SDK to your own implementation.

Our OdinPeerManager added to our scene.

This is the player mesh we used in our ODIN example showcase. It's Elena from the Unity Asset Store, which looks stunning and is very easy to use. Highlighted is the walkie-talkie game object that is used to realistically attach all other players' voices to this object. Therefore, other players will hear walkie-talkie sound coming out of this player's walkie-talkie as you would in real life.

This is the Elena Soldier Model from the Unity Asset Store that we used in our demo

The final implementation of our OdinPeerManager, implementing what we have defined above, looks like this:

OdinPeerManager example
using OdinNative.Odin.Room;
using OdinNative.Unity.Audio;
using UnityEngine;
using UnityEngine.Audio;

public class OdinPeerManager : MonoBehaviour
{
[ToolTip("Set to an audio mixer for radio effects")]
public AudioMixerGroup walkieTalkieAudioMixerGroup;

private void AttachWalkieTalkiePlayback(GameObject gameObject, Room room, ulong peerId, ushort mediaId)
{
// Attach the playback component from the other player to our local walkie talkie game object
PlaybackComponent playback = OdinHandler.Instance.AddPlaybackComponent(gameObject, room.Config.Name, peerId, mediaId);

// Set the spatialBlend to 1 for full 3D audio. Set it to 0 if you want to have a steady volume independent of 3D position
playback.PlaybackSource.spatialBlend = 0.5f; // set AudioSource to half 3D
playback.PlaybackSource.outputAudioMixerGroup = walkieTalkieAudioMixerGroup;
}

public void OnRoomLeft(RoomLeftEventArgs eventArgs)
{
Debug.Log($"Room {eventArgs.RoomName} left, remove all playback components");

// Remove all Playback Components linked to this room
OdinHandler.Instance.DestroyPlaybackComponents(eventArgs.RoomName);
}

public void OnMediaRemoved(object sender, MediaRemovedEventArgs eventArgs)
{
Room room = sender as Room;
Debug.Log($"ODIN MEDIA REMOVED. Room: {room.Config.Name}, MediaId: {eventArgs.Media.Id}, UserData: {eventArgs.Peer.UserData.ToString()}");

// Remove all playback components linked to this media id
OdinHandler.Instance.DestroyPlaybackComponents(eventArgs.Media.Id);
}

public void OnMediaAdded(object sender, MediaAddedEventArgs eventArgs)
{
Room room = sender as Room;
Debug.Log($"ODIN MEDIA ADDED. Room: {room.Config.Name}, PeerId: {eventArgs.PeerId}, MediaId: {eventArgs.Media.Id}, UserData: {eventArgs.Peer.UserData.ToString()}");

// Another player connected the room. Find the local player object and add a PlaybackComponent to it.
// In multiplayer games, player objects are often not available at runtime. The GameManager instance handles
// that for us. You need to replace this code with your own
var localPlayerController = GameManager.Instance.GetLocalPlayerController();
if (localPlayerController && localPlayerController.walkieTalkie)
{
AttachWalkieTalkiePlayback(localPlayerController.walkieTalkie, room, eventArgs.PeerId, eventArgs.Media.Id);
}
}
}

What's left is that we need to join the room once the game starts. We do that in our PlayerController script.

Joining a room
public class PlayerController : MonoBehaviour
{
// Join the room when the script starts (i.e. the player is instantiated)
void Start()
{
OdinHandler.Instance.JoinRoom("WalkieTalkie1");
}

// Leave the room once the player object gets destroyed
void OnDestroy()
{
OdinHandler.Instance.LeaveRoom("WalkieTalkie1");
}
}

Switching channels

Walkie-talkies allow users to choose a channel so not everyone is talking on the same channel. We can add this functionality with a couple of lines of code. The only thing we need to do is to leave the current room representing a channel and join another room. That's it.

ODIN rooms are represented by their name. Nothing more. There is no bookkeeping required. Choose a name that makes sense for your application and join that room.

Switching channels
public class PlayerController : MonoBehaviour
{
// The current walkie-talkie channel
public int channelId = 1;

// Create a room name of a channel
string GetOdinRoomNameForChannel(int channel)
{
return $"WalkieTalkie{channel}";
}

// Join the room when the script starts (i.e. the player is instantiated)
void Start()
{
UpdateOdinChannel(channelId);
}

// Leave the room once the player object gets destroyed
void OnDestroy()
{
OdinHandler.Instance.LeaveRoom(GetOdinRoomNameForChannel(channelId));
}

// Leave and join the corresponding channel
private void UpdateOdinChannel(int newChannel, int oldChannel = 0)
{
if (oldChannel != 0)
{
OdinHandler.Instance.LeaveRoom(GetOdinRoomNameForChannel(oldChannel));
}

OdinHandler.Instance.JoinRoom(GetOdinRoomNameForChannel(newChannel));
}

// Check for key presses and change the channel
void Update()
{
if (Input.GetKeyUp(KeyCode.R))
{
int newChannel = channelId + 1;
if (newChannel > 9) newChannel = 1;
UpdateOdinChannel(newChannel, channelId);
}

if (Input.GetKeyUp(KeyCode.F))
{
int newChannel = channelId - 1;
if (newChannel < 1) newChannel = 9;
UpdateOdinChannel(newChannel, channelId);
}
}
}

Joining the world room

All players running around in our scene join the same room, we simply call it "World." So, we adjust our current Start implementation in PlayerController:

Joining the world room
public class PlayerController : MonoBehaviour
{
// ...

// Join the room when the script starts (i.e. the player is instantiated)
void Start()
{
UpdateOdinChannel(channelId);

// Join the world room for positional audio
OdinHandler.Instance.JoinRoom("World");
}

// Leave the room once the player object gets destroyed
void OnDestroy()
{
OdinHandler.Instance.LeaveRoom(GetOdinRoomNameForChannel(channelId));

// Leave the world room
OdinHandler.Instance.LeaveRoom("World");
}

// ...
}

Adjusting OnMediaAdded

That's it. We now have joined the world room. What happens now is that all players' voices are attached to our walkie-talkie. Which is not what we want. We want to have the other players' walkie-talkies attached to our walkie-talkie, but the other players' "world-voice" we want to attach to the corresponding players in the scene so that their voice position is identical to their position in the scene.

Our current implementation of

OnMediaAdded

looks like this:

Current OnMediaAdded implementation
public class OdinPeerManager : MonoBehaviour
{
// ...

public void OnMediaAdded(object sender, MediaAddedEventArgs eventArgs)
{
Room room = sender as Room;
Debug.Log($"ODIN MEDIA ADDED. Room: {room.Config.Name}, PeerId: {eventArgs.PeerId}, MediaId: {eventArgs.Media.Id}, UserData: {eventArgs.Peer.UserData.ToString()}");

// Another player connected the room. Find the local player object and add a PlaybackComponent to it.
// In multiplayer games, player objects are often not available at runtime. The GameManager instance handles
// that for us. You need to replace this code with your own
var localPlayerController = GameManager.Instance.GetLocalPlayerController();
if (localPlayerController && localPlayerController.walkieTalkie)
{
AttachWalkieTalkiePlayback(localPlayerController.walkieTalkie, room, eventArgs.PeerId, eventArgs.Media.Id);
}
}

// ...
}

Depending on the room where the media is added we need to handle things differently. If it's a walkie-talkie room, we add the

PlaybackComponent

representing the other players' voice to the local player's walkie-talkie. This is what we have implemented today. But if it's the world room, we need to attach the

PlaybackComponent

to the game object representing the player in the scene.

OdinPeerManager with positional audio
public class OdinPeerManager : MonoBehaviour
{
// ...

// Create and add a PlaybackComponent to the other player game object
private void AttachOdinPlaybackToPlayer(PlayerController player, Room room, ulong peerId, ushort mediaId)
{
PlaybackComponent playback = OdinHandler.Instance.AddPlaybackComponent(player.gameObject, room.Config.Name, peerId, mediaId);

// Set the spatialBlend to 1 for full 3D audio. Set it to 0 if you want to have a steady volume independent of 3D position
playback.PlaybackSource.spatialBlend = 1.0f; // set AudioSource to full 3D
}

// Our new OnMediaAdded callback handling rooms differently
public void OnMediaAdded(object sender, MediaAddedEventArgs eventArgs)
{
Room room = sender as Room;
Debug.Log($"ODIN MEDIA ADDED. Room: {room.Config.Name}, PeerId: {eventArgs.PeerId}, MediaId: {eventArgs.Media.Id}, UserData: {eventArgs.Peer.UserData.ToString()}");

// Check if this is 3D sound or Walkie Talkie
if (room.Config.Name.StartsWith("WalkieTalkie"))
{
// A player connected Walkie Talkie. Attach to the local player's Walkie Talkie
var localPlayerController = GameManager.Instance.GetLocalPlayerController();
if (localPlayerController && localPlayerController.walkieTalkie)
{
AttachWalkieTalkiePlayback(localPlayerController, room, eventArgs.PeerId, eventArgs.Media.Id);
}
}
else if (room.Config.Name == "World")
{
// This is 3D sound, find the local player object for this stream and attach the Audio Source to this player
PlayerUserDataJsonFormat userData = PlayerUserDataJsonFormat.FromUserData(eventArgs.Peer.UserData);
PlayerController player = GameManager.Instance.GetPlayerForOdinPeer(userData);
if (player)
{
AttachOdinPlaybackToPlayer(player, room, eventArgs.PeerId, eventArgs.Media.Id);
}
}
}

// ...
}

If the room where media is added is a "WalkieTalkie" room, we use the same implementation used before. However, if it's the world room, we need to find the corresponding player game object in the scene and attach the

PlaybackComponent

to it. We also set

spatialBlend

to 1.0 to activate 3D positional audio, that is, Unity will handle 3D audio for us automatically.

info

This guide should show you how to do the basic and required event handling. We don't go into much detail on how to merge your multiplayer framework with ODIN. Depending on the multiplayer framework there are different solutions on how to do that. We show a typical solution in our Mirror Networking guide and have an open-source example available for Photon.