How can I play player-specific sounds in online multiplayer?

I know this sounds like a “just use Play Sound 2D since it’s (supposedly) supposed to play on the clients only” but that is unreliable. I have a handful of player-specific sound effects that shouldn’t be replicated to other clients, but they totally are and I am at a loss after having tried almost everything.

They aren’t using any replicated events at all.
I’ve put the 2D and Play at Location Play Sound events in the character itself, or cast to the Player Controller, or played directly from the controller since it should only belong to the owning player but no luck. When I play a specific sound that’s meant ONLY for the owning client, it plays on every networked player, every time.

What is the trick? Is this a bug? How can I have specific sounds play ONLY on my networked player’s client? (They are sounds like getting hit, “danger” for low health, “you have 10 seconds to do x”, etc.

The only way to do it is to branch on whether or not the character is locally controlled by the player, is a local player controller, or if the pawn is player controlled. There are a number of different types of objects a sound can play on, and you’ll have to check for locality for each of them.

API functions to look at are the following:

/** Returns whether this Controller is a locally controlled PlayerController.  */
UFUNCTION(BlueprintCallable, Category="Pawn")
bool IsLocalPlayerController() const;

/** @return true if controlled by a local (not network) Controller.	 */
UFUNCTION(BlueprintPure, Category="Pawn")
virtual bool IsLocallyControlled() const;

bool FGameplayAbilityActorInfo::IsLocallyControlledPlayer() const;

Then, once you’ve determined if the actor/character/controller/pawn is local, you’ll need to play the appropriate sound. You’ll either want to create a BP struct or some other way to hold the local-sound vs non-local sound for a given event. It’s probably best to wrap the call to PlaySound* (or SpawnSound*) type calls in your own project’s BP library to do the appropriate checks before playing the sound.

This is roughly the paradigm we use for our own internal games. We rarely call PlaySound or SpawnSound calls directly from BP and instead call our own project-level wrappers that do a bunch of state checks, etc. We make use of SoundCue parameter and distance based crossfades. The data for that is also setup in C++ project-level wrappers to those calls.

Thanks for the info Minus_Kelvin. Could you show an example of how you have done those checks through a specific audio Blueprint Struct/Sound Cue?

I have 8 players in my multiplayer game. Everything works as intended (gameplay, variables, VFX, server travel, player possession) by only using Blueprints and audio is my only remaining hurdle.

I have been so close to getting everything to work as intended by calling sounds straight from the character/Player controller blueprint by getting the controller in my character blueprint and casting to my player controller blueprint where I then call a custom event to connect to the correct sound.

This works on all 7 clients - meaning the player-only sounds play correctly on their respective machines - but NOT on the host/listen server player. When I try to cast to my player controller blueprint, the cast fails if the player is the listen server.

I feel like if I can find that ONE right check to do for that host/listen server player and then play sounds it should work. But even dragging off “failed” on my cast to play a sound plays on all players.

So before I dig into making my own BP struct for each sound effect (which I’m still a little fuzzy on how exactly to do that in BP), do I sound close enough to getting it in my character blueprint or should I scrap all that jazz and use what you’re referring to?

Last item of note: when I play sound off of an input action on my character Blueprint, it works perfectly on all players. No casting needed. No work-arounds. It just…works. (Like when it’s in a chain initialized by the fire button input event or pressing any key/gamepad button event.) All of my issues here are resulting from being initialized by Event Begin Play or Event Hit.

Unfortunately, all the code I was looking at is in non-Engine-level gameplay code so I can’t post it to show you specific examples. And, to be honest, it’s also complex and I don’t think I fully understand it. It was written by our gameplay programmers.

I don’t think I can give you much more advice than I already have, unfortunately since I’m usually working at a more lower level (deeper audio engine) and don’t fully grok the interplay between BP messages/events/client-server stuff. All I know is there’s a bunch of code which check’s player locality before playing sounds in a gameplay layer which wraps our normal BP calls for audio.

I appreciate your insight regardless. Thanks for taking the time to answer!

And after more advice and searching, I found the way to basically launder the audio to get Play Sound to execute ONLY on the owning client in a networked multiplayer game. Posting it here in case anyone runs into the same problems!

As I hoped, I wouldn’t need to even cast to the playercontroller to get this to work. This is all now contained within the character blueprint. This double custom event approach first as a server call leading to an owning client call works PERFECTLY to launder out whatever native engine code is hijacking the play sound for other clients.

Here’s a pic of what’s working in my character blueprint.

1 Like

And also, I just posted a BP solution that works perfectly! Just so you know.