I'm working on a game in Unity, using Firebase for user and game data.
Everything is fine saving the game to Unity, but in Multiplayer I have the challenge to pair an open game to a unique player 2.
The flow is:
1. You look for a game with a state of "open"
2. If no game is found within X seconds, a new one is created with the state of "open"
I have a query that returns open games (those with state "open")
This query is added a Firebase eventhandler
public void OnButtonLookForOpenMPGames()
{
this.OpenFirebaseGamesAsHost = this.LiveGamereference.OrderByChild("State").EqualTo(WYMSettings.MP_GAME_STATE_OPEN_TO_PLAYER2)
.LimitToFirst(3);
this.OpenFirebaseGamesAsHost.ChildAdded += this.OnOpenMPGameFound;
}
This fires fine in the handler:
private void OnOpenMPGameFound(object sender, ChildChangedEventArgs args)
{
Debug.Log("Looking for open games");
if (args.DatabaseError != null)
{
// handle errors
}
else
{
Debug.Log("Open game found (it may be my own)");
// remove event listener because we got a hit
// it will fire as many times as it's true!
this.OpenFirebaseGamesAsHost.ChildAdded -= this.OnOpenMPGameFound;
if (args.Snapshot.Child("HostId").Value.ToString() != FirebaseAuth.DefaultInstance.CurrentUser.UserId)
{
Debug.Log("Other game than mine found open");
// this.AddDelayedUpdateAction(() =>
// {
// this.MPGameLockInTransaction(args.Snapshot.Reference);
// });
this.MPGameLockInTransaction(args.Snapshot.Reference);
}
}
}
The problem: The handler passes this on to the transaction function, but it fails with an inner exception (Internal Task Faulted) - I've tried many different variations, but can't figure this one out from the official Firebase example.
Problem code:
What I want to achieve is to lock only that game to those two players, the host and the players 2 in this example querying for open games. The State is an int, but here a string for clarity.
private void MPGameLockInTransaction(DatabaseReference mpreference)
{
mpreference.RunTransaction(mutableData =>
{
MultiPlayerGame transactionMPG = mutableData.Value as MultiPlayerGame;
if (transactionMPG == null)
{
return TransactionResult.Abort();
}
if (transactionMPG.State != "open")
{
// game is taken, abort
Debug.Log("transaction aborted");
return TransactionResult.Abort();
}
transactionMPG.State = "game started";
mutableData.Value = transactionMPG;
return TransactionResult.Success(mutableData);
}).ContinueWith(task =>
{
if (task.Exception != null)
{
Debug.Log("Transactionlock to game failed" + task.Exception.ToString());
// Look over again
// not implemented yet
}
else
{
Debug.Log("starting game immediately");
this.AddDelayedUpdateAction(() => this.StartMPGameImmediatelyFromSearch());
}
});
}
This is just a guess but it might be related to threading.
In newer .Net version ContinueWith might end on a thread where most of the Unity API may not be called (including Debug.Log).
Instead you could try and use ContinueWithOnMainThread from the extensions instead which was created exactly for this reason.
With the .NET 4.x runtime, continuations may often execute in the background, whilst this extension method pushes them onto the main thread.
The .NET 3.x runtime didn't have this issue as continuations were contained within the Parse library, which did this implicitly.
Related
Hi Guys I am converting a single player Sudoko game into a multiplayer game (right now 2 players) using Photon in Unity.
The basic logic of the Sudoku game is that there are 300 puzzle data already loaded in it. A random number between 1 to 300 is picked up and the corresponding puzzle is loaded.
But the problem I am facing is even though I have made sure that the same number is getting picked up for both the client and the master server, different puzzles are getting loaded.
So basically I script called MultiManager attached to the MultiManager GameObject in the Sudoku screen.The script looks something like this.
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
puzzleIndex = Random.Range(0, 300);
PV.RPC("RPC_PuzzleIndex", RpcTarget.Others, puzzleIndex);
}
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
}
So in the GameManager script you have these functions:
public void PlayNewGame(string groupId)
{
// Get the PuzzleGroupData for the given groupId
for (int i = 0; i < puzzleGroups.Count; i++)
{
PuzzleGroupData puzzleGroupData = puzzleGroups[i];
if (groupId == puzzleGroupData.groupId)
{
PlayNewGame(puzzleGroupData);
return;
}
}
}
private void PlayNewGame(PuzzleGroupData puzzleGroupData)
{
// Get a puzzle that has not yet been played by the user
PuzzleData puzzleData = puzzleGroupData.GetPuzzle();
// Play the game using the new puzzle data
PlayGame(puzzleData);
}
And in the PuzzleGroupData class you have this function :
public PuzzleData GetPuzzle()
{
return new PuzzleData(puzzleFiles[MultiManager.puzzleIndex], shiftAmount, groupId);
}
I don't quite get as to whats wrong which is happening. I tried to use other variations like keeping that random number outside of the condition inside of PhotonNetwork.isMasterClient and all, but doesn't work.
If anyone can help it would be great. By the way this Sudoku game was purchased and I am trying to convert it to a mutliPlayer game
Since the master is usually the first one in a room so also the first one getting Start called I think what happens is that your other clients are simply not connected yet when the RPC is called.
Further it might also happen (actually pretty likely) that Start is called before the RPC has the chance to be received.
I would rather actually wait until you have the value and do
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
PV.RPC(name of(RPC_PuzzleIndex), RpcTarget.AllBuffered, Random.Range(0, 300));
}
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
This way
the RpcTarget.AllBuffered makes sure that also clients joining later will receive the call
there is no way you start a game without receiving the random value first
I'm using NetworkRoomManager and NetworkDiscovery and when a player exits a room scene I call the NetworkManager.singleton.StopClient() in networkdiscoveryhud then you will found out that its calling the OnClientExitRoom function in networkroomplayerext script twice.
Or I should not use NetworkManager.singleton.StopClient() when client exit a room scene? Below is my script for client or server exiting a room.
public void StopHost()
{
if (NetworkServer.active && NetworkClient.isConnected)
{
NetworkManager.singleton.StopHost();
}
else if (NetworkClient.isConnected)
{
NetworkManager.singleton.StopClient();
}
else if (NetworkServer.active)
{
NetworkManager.singleton.StopServer();
}
networkDiscovery.StopDiscovery();
}
I recommend Lobby and Worlds. It's much more flexible than NetworkRoomManager and has a ton of built in features.
More info on it here. https://trello.com/c/0jT4kZ6O
I'm trying to use a cloud TTS within my Unity game.
With the newer versions (I am using 2019.1), they have deprecated WWW in favour of UnityWebRequest(s) within the API.
I have tried the Unity Documentation, but that didn't work for me.
I have also tried other threads and they use WWW which is deprecated for my Unity version.
void Start()
{
StartCoroutine(PlayTTS());
}
IEnumerator PlayTTS()
{
using (UnityWebRequestMultimedia wr = new UnityWebRequestMultimedia.GetAudioClip(
"https://example.com/tts?text=Sample%20Text&voice=Male",
AudioType.OGGVORBIS)
)
{
yield return wr.Send();
if (wr.isNetworkError)
{
Debug.LogWarning(wr.error);
}
else
{
//AudioClip ttsClip = DownloadHandlerAudioClip.GetContent(wr);
}
}
}
The URL in a browser (I used Firefox) successfully loaded the audio clip allowing me to play it.
What I want it to do is play the TTS when something happens in the game, it has been done within the "void Start" for testing purposes.
Where am I going wrong?
Thanks in advance
Josh
UnityWebRequestMultimedia.GetAudioClip automatically adds a default DownloadHandlerAudioClip which has a property streamAudio.
Set this to true and add a check for UnityWebRequest.downloadedBytes in order to delay the playback before starting.
Something like
public AudioSource source;
IEnumerator PlayTTS()
{
using (var wr = new UnityWebRequestMultimedia.GetAudioClip(
"https://example.com/tts?text=Sample%20Text&voice=Male",
AudioType.OGGVORBIS)
)
{
((DownloadHandlerAudioClip)wr.downloadHandler).streamAudio = true;
wr.Send();
while(!wr.isNetworkError && wr.downloadedBytes <= someThreshold)
{
yield return null;
}
if (wr.isNetworkError)
{
Debug.LogWarning(wr.error);
}
else
{
// Here I'm not sure if you would use
source.PlayOneShot(DownloadHandlerAudioClip.GetContent(wr));
// or rather
source.PlayOneShot((DownloadHandlerAudioClip)wr.downloadHandler).audioClip);
}
}
}
Typed on smartphone so no warranty but I hope you get the idea
I find it very difficult to find a simple reliable library to use, in Unity, for a simple websocket client. WebSocketSharp (ie, https://github.com/sta/websocket-sharp ) seems fine but many items are unexplained.
When incoming messages arrive,
wssharp_connection.OnMessage += (sender, e) =>
{
if (!e.IsText) { return; }
Debug.Log(">> OnMessage, " + e.Data);
Handle(e.Data);
// but wait, not the main Unity thread??
};
void Handle(string msg)
{
.. your conventional Unity call, in a monobehavior
}
Testing shows it seems to arrive on (always? sometimes?) not the main Unity thread.
Does anyone know
In fact is it always not the main thread, or can it vary?
If always another thread, in fact is it always the same one thread, or does it vary/many?
Since every single Unity app that uses a websocket client has to do this, I'm surprised that there are not 100s of discussion about how to handle incoming messages, including on the WebSocketSharp page,
but here's what I would do, just in the usual way you handle "threading" in a frame-based engine like Unity:
wssharp_connection.OnMessage += (sender, e) =>
{
if (!e.IsText) { return; }
incoming_messages.Enqueue(e.Data);
};
// and ...
ConcurrentQueue<string> incoming_messages = new ConcurrentQueue<string>();
void Update()
{
if (incoming_messages.TryDequeue(out var message))
{
Handle(message);
}
}
So then
Is that indeed the usual way to handle the threading problem in WebSocketSharp, when using inside Unity?
Given how many billion Unity apps there are with websockets clients, this must be a pretty well-trodden path.
This question has been asked before but to date there is no answer. I have just struck the same problem. I traced into the mediaplayer source and all is well until I get to gstSetVolume in GSTMediaPlayer for which I don't have the source.
At that point the volume value is still correct.
After setting the volume System.out.println(player.getVolume()); reports the correct value (0.0 - 1.0) BUT the volume has not changed. If the slider is set to 0, the audio is muted. Any other value is full volume.
Platform is Netbeans 8.1 on Windows 7 with JavaFX-8 and JDK-8 and the media source is a standard mp4 which works on all tested players (Windows, VLC etc).
Here is the code attached to a MediaView (which works well):
Runnable videoLoop;
PlayListViewController playListViewController;
// Videos are looped here
public void startVideoLoop() {
playerIndex = 0;
videoLoop = () -> {
// Previous player MUST be stopped otherwise it is left
// in the PLAYING state and the next video will not PLAY
if (player != null) {
player.stop();
}
// Point to the next player
if (++playerIndex == videoPlayersList.size()) {
playerIndex = 0;
}
playVideoSequence();
};
playVideoSequence();
}
public void playVideoSequence() {
// If the video options are shown we can update the listview
if (playListViewController != null) {
playListViewController.setListViewItem(playerIndex);
}
player = videoPlayersList.get(playerIndex);
mediaView.setMediaPlayer(player);
player.setOnEndOfMedia(videoLoop);
player.play();
}
A right mouse button press then invokes a 'settings' option. This is a View Controller which is attached to an FXML view. The controller receives a reference to the videoField when it is invoked. Here is the relevant snippet:
private void setHandlers() {
idVolumeSlider.valueProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue arg0, Object arg1, Object arg2) {
// videoField is a separate class
videoField.setVolume(idVolumeSlider.getValue());
}
});
I'm sorry I cannot supply a 'cut and paste' example, because that would mean a lot of pruning and simplifying of classes to achieve that with the risk of introducing additional problems.