Unet, how to sync a counter to check if all players are ready - unity3d

I'm making a multiplayer bomberman game using Unet. I'm trying to create a ready check to start the game when everyone else is ready.
I tried using a sync var,Command and Rpc calls, but nothing seems to work properly.
When using a Cmd call this way (called when a player pushes the ready button)
[Command]
private void CmdIncreaseReady()
{
IncreaseReady();
RpcIncreaseReady();
}
[ClientRpc]
private void RpcIncreaseReady()
{
IncreaseReady();
}
private void IncreaseReady() {
playersReady++;
//ChechAllReady();
}
When pressed on the client, everyone's counter is 0 (it should be one), and when pressed on the server, the server's counter is 2 and the client 1.
I've tried to call Cmd when !isServer and Rpc when it is, and the result is that stills doesn't update when pressed on the client, but it is updated correctly (the counter is 1 on both), when pressed on the server.
if (!isServer)
{
CmdIncreaseReady();
}
else
{
RpcIncreaseReady();
}
If I delete the IncreaseReady() call on the Cmd, the result is the same than above.
I've tried too to use a [SyncVar] to the counter, with and without a hook, passing 1 on the hook (to increment the counter that amount) and passing the already counter incremented and set variable to that number, nothing seems to work.
I really don't know how to make it work anymore. Could someone help me? I'm really desperate with this. I've searched everywhere.
The script is the game manager, when every client has his version, it has a Netework Identity and has Local player Authority.
I've tried another approach, passing the increased number on the Cmd and Rpc, and inside the functions, just set the playersReady = i. Even that doesn't work.
[UPDATE]:
So, I've found the specific problem that I want to solve, and it's to Sync non-player objects properties.
I've tried to spawn the Game Manager with Client Authority, and now it seems to sync correctly only on the server, the client stills doesn't call the Cmd propety.
Looking at the Debug mode in the inspector, I've been able to see that on the server, hasAutority is true, but is false on the client.
This is a fragment of the code on the player script where I spawn the game manager:
void Update()
{
if (!sceneLoaded)
{
if(isServer & SceneManager.GetActiveScene().name == "Main Scene")
//if (SceneManager.GetActiveScene().name == "Main Scene")
{
if (connectionToClient.isReady)
{
var go = (GameObject)Instantiate(gameManager);
NetworkServer.SpawnWithClientAuthority(go, connectionToClient);
go.transform.SetParent(null);
//go.GetComponent<NetworkIdentity>().AssignClientAuthority(connectionToClient);
globalManager = go.GetComponent<FSM>();
//SetFSM();
sceneLoaded = true;
}
}
}

UNET has a Lobby system which does exactly what I think you want. It has a per-player ready state, so that the game starts when all players are ready.
I would recommend using that, as it saves you having to reimplement things like dropping out, rejoining etc...

Related

Missing something important with ClientRpc (Unity Mirror)

I'm having an issue with ClientRpc never being called any client objects. I've trolled the internet for hours, but I can't find any clues as to why my implementation isn't working.
I'm following an order of events like so:
Add players in a lobby
Switch scene to the gameplay scene
Generate random tiles on the server
Send tile data to the clients using Rpc for rendering
However, the rendering function never gets called.
NetworkManager.cs
public override void OnServerReady(NetworkConnection conn)
{
base.OnServerReady(conn);
//Make sure they're all ready
for (int i = RoomPlayers.Count - 1; i >= 0; i--)
{
if (!RoomPlayers[i].IsReady)
{
return;
}
}
//Previously add SpawnTiles to OnServerReadied
OnServerReadied?.Invoke(conn);
}
GameManager.cs
private void SpawnTiles(NetworkConnection conn)
{
//Generate rawTiles beforehand
Debug.Log(conn.isReady);
Debug.Log("Entered spawn tiles");
RpcSpawnTiles(rawTiles);
}
[ClientRpc]
public void RpcSpawnTiles(short[] rawTiles)
{
Debug.Log("Client spawning tiles");
}
And this is my output when run on a host:
True
Entered spawn tiles
It appears to never enter the Rpc function :(
Is there something super obvious that I'm missing? My GameManager does have a NetworkID attached to it
There seemed to be a combination of things that I had to do to get it to work- I'm not entirely sure which one worked, but if you are having these problems, these are some of the things I had to check-
Just basic code. Make sure that the appropriate things are being created on the client, host, or server
Make sure that there aren't any race conditions, especially when working with changing scenes. You can call stuff from OnClientSceneChanged and OnServerSceneChanged
As derHugo pointed out, I was not checking to see if my package could even be sent. It turns out that the maximum size that can be sent is 1200 bytes, while I was trying to send 100x100x4 bytes. This error didn't occur when I was testing on the host, only when there was an external client

Can Unity Editor enter PlayMode without reloading a script

I am writing an Unity editor script that creates TCP server accepts requests from other application, but I found out that "clicking" play mode reloads my editor scripts which will interrupts the connection by re-initializing my server. Is there any way Unity Editor can stop reloading this particular script and makes it up running at all time?
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
if (m_Server == null)
{
EditorApplication.update += Update;
Debug.Log("starting");
IPAddress ip = IPAddress.Parse(ipAdress);
m_Server = new TcpListener(ip, port);
m_Server.Start();
Debug.Log("Sever started");
Debug.Log(ip);
Debug.Log(port);
//Wait for async client connection
m_Server.BeginAcceptTcpClient(ClientConnected, null);
OnServerStarted?.Invoke();
}
In other words, is there a way to keep all my static variables and my editor coroutines after PlayMode is invoked? I did some search and I think domain reloading has caused it
Haven't tried this and don't have Unity on this PC to test, but can you gate it by using the isPlaying check:
if (!Application.isPlaying)
{
// Do your server stuff here
}
https://docs.unity3d.com/ScriptReference/Application-isPlaying.html
In other words, don't run the code while the game is playing, which is true when you hit the Play button?

Determine which controller is sending me Actions in SteamVR

I'm starting to use the SteamVR action-driven Input system Version: 2.3.2 (sdk 1.4.18). I attached a laser pointer script similar to the Steam version to each controller. Now, when I squeeze the trigger, BOTH scripts receive the squeeze action. Well, of course they do.
So how can I determine if the squeeze is from MY controller to ensure that I only respond to that?
I already looked at the SteamVR_Input_Sources parameter. It always reads 'any', so that doesn't help.
Maybe there is an option somewhere to filter which controller messages you wish to receive or a way to determine who invoked the action...?
You can either subscribe to the action like
public void SubscribeToPlayerAction(SteamVR_Action_Boolean action, SteamVR_Action_Boolean.ChangeHandler onAction)
{
action.AddOnChangeListener(onAction, SteamInputSource); //SteamInputSource can be Left Right or Any
}
Or you can poll
public bool CheckForPlayerAction(SteamVR_Action_Boolean action, ButtonAction buttonState = ButtonAction.PressDown)
{
if (buttonState == ButtonAction.PressDown) return action.GetStateDown(SteamInputSource);
if(buttonState == ButtonAction.IsPressed) return action.GetState(SteamInputSource);
return action.GetStateUp(SteamInputSource);
}
If you go to windows and open the SteamVR input live view, you can get this menu
This menu will show you which controller is receiving an action

How do I deal with app backgrounding to restore the Exoplayer, which I inject with Dagger2?

I have a Fragment which contains an instance of Exoplayer which I provide with Dagger 2 like this:
#Inject SimpleExoPlayer mPlayer;
For the most part, this works well, I can play stuff on my player, the state is correctly restored on lifecycle changes, etc.
The issue I have is that when I click on the Overview button, navigate to another app, and then back, the player is in a black screen state where the seek bar has been restored to its saved position, but it will not play anything, nor respond to play commands on the UI, and just stays black until the Fragment gets recreated.
I am now trying to modify things to get my code to deal with the player just like in this code lab:
private void releasePlayer() {
if (player != null) {
playbackPosition = player.getCurrentPosition();
currentWindow = player.getCurrentindowIndex();
playWhenReady = player.getPlayWhenReady();
player.release();
player = null;
}
}
which should be called in onStop() above API 23.
The initializePlayer() is called in onStart() and it is supposed to look like this. In my case the only difference is that I don't create the player, Dagger does it for me on creation.
private void initializePlayer() {
player = ExoPlayerFactory.newSimpleInstance(
new DefaultRenderersFactory(this),
new DefaultTrackSelector(), new DefaultLoadControl());
playerView.setPlayer(player);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
[...]
}
Note that both code snippets are from the linked code lab.
This is where I am struggling with Dagger. When the Fragment is created or recreated, everything is fine, as Dagger will provide me with a player, but if I have set the player to null in onStop(), and the Fragment is not destroyed and it is restored and only onStart() and onResume() are called, Dagger will not create a new instance, unless I am missing a way on how to this.
Can anyone point to some code examples of Dagger2 and ExoPlayer set up, preferably with a Fragment?
Also shed some light into this, please :)
EDIT: Solved thanks to #David Medenjak
After the hint on using a Provider<> I solved the issue by having a:
#Inject Provider<SimpleExoPlayer> mSimpleExoPlayerProvider
And:
SimpleExoPlayer mPlayer
So when I initialize the player inside onStart I will get a new instance of the Player from the Provider if there is no instance attached to mPlayer, and hold to that player instance util I release the player in onStop where I set mPlayer to null.
if (mPlayer == null) {
// Get new instance of player if mPlayer is null
mPlayer = mSimpleExoPlayerProvider.get();
}
Dagger will not create a new instance, unless I am missing a way on how to this.
If all you want to do is get a new instance from Dagger every time, then you should inject Provider<SimpleExoPlayer> and call provider.get() when you need a new object.
For this to work the player needs to be unscoped, or you will get the same object every time.

WMI and Win32_DeviceChangeEvent - Wrong event type returned?

I am trying to register to a "Device added/ Device removed" event using WMI. When I say device - I mean something in the lines of a Disk-On-Key or any other device that has files on it which I can access...
I am registering to the event, and the event is raised, but the EventType propery is different from the one I am expecting to see.
The documentation (MSDN) states : 1- config change, 2- Device added, 3-Device removed 4- Docking. For some reason I always get a value of 1.
Any ideas ?
Here's sample code :
public class WMIReceiveEvent
{
public WMIReceiveEvent()
{
try
{
WqlEventQuery query = new WqlEventQuery(
"SELECT * FROM Win32_DeviceChangeEvent");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
watcher.EventArrived +=
new EventArrivedEventHandler(
HandleEvent);
// Start listening for events
watcher.Start();
// Do something while waiting for events
System.Threading.Thread.Sleep(10000);
// Stop listening for events
watcher.Stop();
return;
}
catch(ManagementException err)
{
MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
}
}
private void HandleEvent(object sender,
EventArrivedEventArgs e)
{
Console.WriteLine(e.NewEvent.GetPropertyValue["EventType"]);
}
public static void Main()
{
WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
return;
}
}
Well, I couldn't find the code. Tried on my old RAC account, nothing. Nothing in my old backups. Go figure. But I tried to work out how I did it, and I think this is the correct sequence (I based a lot of it on this article):
Get all drive letters and cache
them.
Wait for the WM_DEVICECHANGE
message, and start a timer with a
timeout of 1 second (this is done to
avoid a lot of spurious
WM_DEVICECHANGE messages that start
as start as soon as you insert the
USB key/other device and only end
when the drive is "settled").
Compare the drive letters with the
old cache and detect the new ones.
Get device information for those.
I know there are other methods, but that proved to be the only one that would work consistently in different versions of windows, and we needed that as my client used the ActiveX control on a webpage that uploaded images from any kind of device you inserted (I think they produced some kind of printing kiosk).
Oh! Yup, I've been through that, but using the raw Windows API calls some time ago, while developing an ActiveX control that detected the insertion of any kind of media. I'll try to unearth the code from my backups and see if I can tell you how I solved it. I'll subscribe to the RSS just in case somebody gets there first.
Well,
u can try win32_logical disk class and bind it to the __Instancecreationevent.
You can easily get the required info
I tried this on my system and I eventually get the right code. It just takes a while. I get a dozen or so events, and one of them is the device connect code.