Photon matchmaking - Join or create a room using in Unity with an SQL lobby - unity3d

I am trying to implement skill based matchmaking using Photon in Unity. It seems
I got most of this code from the documentation and it works but not well.
The problem is that you can't use JoinOrCreate() with the sql lobby type so my logic here is try and find a room, if it fails create one.
void init()
{
_client = Game.Context.GetComponent<SocketConnectionManager>().client;
joinRoom();
}
public void joinRoom()
{
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0 BETWEEN 100 AND 200";
_client.OpJoinRandomRoom(null, MatchMaker.MaxPlayers, MatchmakingMode.FillRoom, sqlLobby, sqlLobbyFilter);
}
public void createRoom()
{
RoomOptions o = new RoomOptions();
o.MaxPlayers = MatchMaker.MaxPlayers;
o.CustomRoomProperties = new Hashtable() { { "C0", Game.Me.getInt("trophies") } };
o.CustomRoomPropertiesForLobby = new string[] { "C0" }; // this makes "C0" available in the lobby
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
_client.OpCreateRoom("", o, sqlLobby);
}
private void onEvent(EventData obj)
{
if (_client.CurrentRoom != null)
{
if (_client.CurrentRoom.PlayerCount >= _client.CurrentRoom.MaxPlayers)
{
// sweet I am good to go.
}
}
else
{
createRoom();
}
}
The problem is this is pretty unreliable. Say two players try to find a game at the same time they will both search fail and then both create. Now I have two players sitting in empty rooms instead of playing each other.
Any ideas on a better system?
Thanks all.

Thank you for choosing Photon!
First of all, there are few things that you should understand about Photon:
you can't use JoinOrCreate() with the sql lobby type
This is not correct.
Where did you read such thing?
Did you test this yourself? What did you test exactly?
onEvent (LoadBalancingClient.OnEventAction) callback cannot be used to be notified of a failed join random room operation. Instead, you should make use of the LoadBalancingClient.OnOpResponseAction callback, as follows:
private void OnOpResponse(OperationResponse operationResponse)
{
switch (operationResponse.Code)
{
case OperationCode.JoinRandomGame:
if (operationResponse.ReturnCode == ErrorCode.NoMatchFound)
{
createRoom();
}
break;
}
}
To detect a join event inside a room (local or remote player entered a room):
private void onEvent(EventData eventData)
{
switch (eventData.Code)
{
case EventCode.Join:
int actorNr = (int)eventData[ParameterCode.ActorNr];
PhotonPlayer originatingPlayer = this.GetPlayerWithId(actorNr);
if (originatingPlayer.IsLocal)
{
}
else
{
}
break;
}
}
To answer your question:
Say two players try to find a game at the same time they will both
search fail and then both create.
Any ideas on a better system?
No.
This issue happens only during the development phase where you use a few clients to run some tests. Once you have enough user base you won't notice this issue.

Related

Unity Netcode: Change Ownership doesn't work for me

I have been trying to spawn gameObject(Specifically Player gameObject) in Server using this player.GetComponent().Spawn() and then tried to change ownership. It didn't works for me. So frustrated. Can anyone help on this.
I want to spawn two different player in server and client. So I tried to spawn all the players in server only and tried to change the ownership using ownerclientId, LocalClientId. Nothing worked. What happens is that it spawns two players in both server and client screen but only server has the ownership.
Code snippet:
private void Start()
{
var playerSelected = GameObject.Find("PlayerSelected");
int selectedPlayer = playerSelected.GetComponent<PlayerSelected>().selectPlayer;
if (NetworkManager.Singleton.IsServer)
{
PlayerSpawner(selectedPlayer, OwnerClientId);
}
else
{
PlayerSpawnerOnServerRpc(selectedPlayer, OwnerClientId);
}
}
[ServerRpc(RequireOwnership = false)]
void PlayerSpawnerOnServerRpc(int selectedPlayer, ulong clientId)
{
PlayerSpawner(selectedPlayer, clientId);
}
void PlayerSpawner(int selectedPlayer, ulong clientId)
{
GameObject player;
switch(selectedPlayer)
{
case 0:
player = Instantiate(player1Prefab, spawnPositionPlayer1);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
case 1:
player = Instantiate(player2Prefab, spawnPositionPlayer2);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
}
}
The reason this doesn't work is that the server is sending two messages in quick succession.
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
The first tells the clients to spawn an object. The second gives ownership to the object. You cannot guarantee (even running locally) that the Spawn() has finished when the ChangeOwnership() is called. So when ChangeOwnership() is called, the client may not have finished initialising the object that's spawned.
Unity were aware of this, and provided you with SpawnWithOwnership (docs). Replace your two lines with one:
player.GetComponent<NetworkObject>().SpawnWithOwnership(clientId);

I want to make a P2P networked (thinking about using mirror) game with unity, with no dedicated server, similar to Terraria and Valheim

I have managed to basically connect with my friend over internet, by forwarding my IP address from my router settings... this is not viable because there are few people willing to do what I did to play games with their friends. So how to actually do UDP hole punching (basically what I did manually to my router) in unity using the mirror networking solution...
A common solution to this problem is WebRTC, which takes care of the hole punching under the hood. Unity maintains this package which implements WebRTC. They also provide a great tutorial on how to use it. The nuts and bolts of it are:
using UnityEngine;
using Unity.WebRTC;
public class MyPlayerScript : MonoBehaviour
{
RTCPeerConnection localConnection, remoteConnection;
RTCDataChannel sendChannel, receiveChannel;
private void Awake()
{
// Initialize WebRTC
WebRTC.Initialize();
// Create local peer
localConnection = new RTCPeerConnection();
sendChannel = localConnection.CreateDataChannel("sendChannel");
channel.OnOpen = handleSendChannelStatusChange;
channel.OnClose = handleSendChannelStatusChange;
// Create remote peer
remoteConnection = new RTCPeerConnection();
remoteConnection.OnDataChannel = ReceiveChannelCallback;
// register comms paths
localConnection.OnIceCandidate = e => {
!string.IsNullOrEmpty(e.candidate)
|| remoteConnection.AddIceCandidate(ref e);
}
remoteConnection.OnIceCandidate = e => {
!string.IsNullOrEmpty(e.candidate)
|| localConnection.AddIceCandidate(ref e);
}
localConnection.OnIceConnectionChange = state => {
Debug.Log(state);
}
}
//handle begin
IEnumerator Call(){
var op1 = localConnection.CreateOffer();
yield return op1;
var op2 = localConnection.SetLocalDescription(ref op1.desc);
yield return op2;
var op3 = remoteConnection.SetRemoteDescription(ref op1.desc);
yield return op3;
var op4 = remoteConnection.CreateAnswer();
yield return op4;
var op5 = remoteConnection.setLocalDescription(op4.desc);
yield return op5;
var op6 = localConnection.setRemoteDescription(op4.desc);
yield return op6;
}
//handle send messages
void SendMessage(string message)
{
sendChannel.Send(message);
}
void SendBinary(byte[] bytes)
{
sendChannel.Send(bytes);
}
//handle receive messages
void ReceiveChannelCallback(RTCDataChannel channel)
{
receiveChannel = channel;
receiveChannel.OnMessage = HandleReceiveMessage;
}
void HandleReceiveMessage(byte[] bytes)
{
var message = System.Text.Encoding.UTF8.GetString(bytes);
Debug.Log(message);
}
//handle end
private void OnDestroy()
{
sendChannel.Close();
receiveChannel.Close();
localConnection.Close();
remoteConnection.Close();
WebRTC.Finalize();
}
}
I have also found a useful way to do this using mirror and the epic free relay for this. Thanks so much for the other answers, it really helped understand better what I needed to search and use!
You can use Photon Bolt or Photon Fusion to let players host a game on their local machine like minecraft, etc. Photon provides relay as well as tries to use STUN to establish direct peer to peer connection to the host via UDP. PUN2 is also a good choice, although I like Photon Bolt/Fusion better - it's less of a simple RPC framework and more programmer oriented. Also, PUN does not do any STUN direct peer connection, it will always be relayed. Photon Bolt and Fusion will first attempt a STUN direct peer connection and then fallback to relay if necessary. It's been around for years and is the best choice.
Sure you can develop a Unity game with Mirror (although without a relay built in) but it's not nearly as easy to setup and use as Photon Bolt/Fusion and they don't provide a relay. As someone mentioned, you might be able to hack something together in some way but yeah - not recommended.
Ugh, yeah don't use WebRTC for a Unity game (or probably anything other than streaming music/video like it was made for to be honest).
Unity's MLAPI is "under development" and their last API was suddenly dropped "deprecated", so I wouldn't use that.

Getting warning Trying to send command for object without authority

I'm new to unity multiplayer so just learning it's concepts. I have this script on my player
private void Start()
{
if (isServer)
{
//run when if it's server
} else
{
//run if its client
this.InstantiateBullet();
}
}
[Command]
void InstantiateBullet()
{
Debug.Log("Test Command");
}
I'm getting a warning "Trying to send command for object without authority. ShootHooks.InstantiateBullet" My question is do I have to assign my host player prefab to client an authority so it can call InstantiateBullet() without getting warning? Or can I just ignore the warning
As the error/warning tells you you may only call commands from an object you have the authority over.
Currently it is running on all instances of your player prefab, also the ones that belong to the other connected players and you don't have the authority over those but they do!
So simply make an additional check
private void Start()
{
if(hasAuthority)
{
if(isServer)
{
...
}
else
{
InstantiateBullet();
}
}
}

Monotouch data sync - why does my code sometimes cause sqlite errors?

I have the following calls (actually a few more than this - it's the overall method that's in question here):
ThreadPool.QueueUserWorkItem(Database.Instance.RefreshEventData);
ThreadPool.QueueUserWorkItem(Database.Instance.RefreshLocationData);
ThreadPool.QueueUserWorkItem(Database.Instance.RefreshActData);
1st point is - is it OK to call methods that call WCF services like this? I tried daisy chaining them and it was a mess.
An example of one of the refresh methods being called above is (they all follow the same pattern, just call different services and populate different tables):
public void RefreshEventData (object state)
{
Console.WriteLine ("in RefreshEventData");
var eservices = new AppServicesClient (new BasicHttpBinding (), new EndpointAddress (this.ServciceUrl));
//default the delta to an old date so that if this is first run we get everything
var eventsLastUpdated = DateTime.Now.AddDays (-100);
try {
eventsLastUpdated = (from s in GuideStar.Data.Database.Main.Table<GuideStar.Data.Event> ()
orderby s.DateUpdated descending
select s).ToList ().FirstOrDefault ().DateUpdated;
} catch (Exception ex1) {
Console.WriteLine (ex1.Message);
}
try {
eservices.GetAuthorisedEventsWithExtendedDataAsync (this.User.Id, this.User.Password, eventsLastUpdated);
} catch (Exception ex) {
Console.WriteLine ("error updating events: " + ex.Message);
}
eservices.GetAuthorisedEventsWithExtendedDataCompleted += delegate(object sender, GetAuthorisedEventsWithExtendedDataCompletedEventArgs e) {
try {
List<Event> newEvents = e.Result.ToList ();
GuideStar.Data.Database.Main.EventsAdded = e.Result.Count ();
lock (GuideStar.Data.Database.Main) {
GuideStar.Data.Database.Main.Execute ("BEGIN");
foreach (var s in newEvents) {
GuideStar.Data.Database.Main.InsertOrUpdateEvent (new GuideStar.Data.Event {
Name = s.Name,
DateAdded = s.DateAdded,
DateUpdated = s.DateUpdated,
Deleted = s.Deleted,
StartDate = s.StartDate,
Id = s.Id,
Lat = s.Lat,
Long = s.Long
});
}
GuideStar.Data.Database.Main.Execute ("COMMIT");
LocationsCount = 0;
}
} catch (Exception ex) {
Console.WriteLine("error InsertOrUpdateEvent " + ex.Message);
} finally {
OnDatabaseUpdateStepCompleted (EventArgs.Empty);
}
};
}
OnDatabaseUpdateStepCompleted - just iterates an updateComplete counter when it's called and when it knows that all of the services have come back ok it removes the waiting spinner and the app carries on.
This works OK 1st time 'round - but then sometimes it doesn't with one of these: http://monobin.com/__m6c83107d
I think the 1st question is - is all this OK? I'm not used to using threading and locks so I am wandering into new ground for me. Is using QueueUserWorkItem like this ok? Should I even be using lock before doing the bulk insert/update? An example of which:
public void InsertOrUpdateEvent(Event festival){
try {
if (!festival.Deleted) {
Main.Insert(festival, "OR REPLACE");
}else{
Main.Delete<Event>(festival);
}
} catch (Exception ex) {
Console.WriteLine("InsertOrUpdateEvent failed: " + ex.Message);
}
}
Then the next question is - what am I doing wrong that is causing these sqlite issues?
w://
Sqlite is not thread safe.
If you want to access Sqlite from more than one thread, you must take a lock before you access any SQLite related structures.
Like this:
lock (db){
// Do your query or insert here
}
Sorry, no specific answers, but some thoughts:
Is SqlLite even threadsafe? I'm not sure - it may be that it's not (to the wrapper isn't). Can you lock on a more global object, so no two threads are inserting at the same time?
It's possible that the MT GC is getting a little overenthusiastic, and releasing your string before it's been used. Maybe keep a local reference to it around during the insert? I've had this happen with view controllers, where I had them in an array (tabcontrollers, specificially), but if I didn't keep an member variable around with the reference, they got GC'ed.
Could you get the data in a threaded manner, then queue everything up and insert them in a single thread? Atleast as a test anyway.

ADO.NET - Bad Practice?

I was reading an article in MSDN several months ago and have recently started using the following snippet to execute ADO.NET code, but I get the feeling it could be bad. Am I over reacting or is it perfectly acceptable?
private void Execute(Action<SqlConnection> action)
{
SqlConnection conn = null;
try {
conn = new SqlConnection(ConnectionString);
conn.Open();
action.Invoke(conn);
} finally {
if (conn != null && conn.State == ConnectionState.Open) {
try {
conn.Close();
} catch {
}
}
}
}
public bool GetSomethingById() {
SomeThing aSomething = null
bool valid = false;
Execute(conn =>
{
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandText = ....
...
SqlDataReader reader = cmd.ExecuteReader();
...
aSomething = new SomeThing(Convert.ToString(reader["aDbField"]));
}
});
return aSomething;
}
What is the point of doing that when you can do this?
public SomeThing GetSomethingById(int id)
{
using (var con = new SqlConnection(ConnectionString))
{
con.Open();
using (var cmd = con.CreateCommand())
{
// prepare command
using (var rdr = cmd.ExecuteReader())
{
// read fields
return new SomeThing(data);
}
}
}
}
You can promote code reuse by doing something like this.
public static void ExecuteToReader(string connectionString, string commandText, IEnumerable<KeyValuePair<string, object>> parameters, Action<IDataReader> action)
{
using (var con = new SqlConnection(connectionString))
{
con.Open();
using (var cmd = con.CreateCommand())
{
cmd.CommandText = commandText;
foreach (var pair in parameters)
{
var parameter = cmd.CreateParameter();
parameter.ParameterName = pair.Key;
parameter.Value = pair.Value;
cmd.Parameters.Add(parameter);
}
using (var rdr = cmd.ExecuteReader())
{
action(rdr);
}
}
}
}
You could use it like this:
//At the top create an alias
using DbParams = Dictionary<string, object>;
ExecuteToReader(
connectionString,
commandText,
new DbParams() { { "key1", 1 }, { "key2", 2 } }),
reader =>
{
// ...
// No need to dispose
}
)
IMHO it is indeed a bad practice, since you're creating and opening a new database-connection for every statement that you execute.
Why is it bad:
performance wise (although connection pooling helps decrease the performance hit): you should open your connection, execute the statements that have to be executed, and close the connection when you don't know when the next statement will be executed.
but certainly context-wise. I mean: how will you handle transactions ? Where are your transaction boundaries ? Your application-layer knows when a transaction has to be started and committed, but you're unable to span multiple statements into the same sql-transaction with this way of working.
This is a very reasonable approach to use.
By wrapping your connection logic into a method which takes an Action<SqlConnection>, you're helping prevent duplicated code and the potential for introduced error. Since we can now use lambdas, this becomes an easy, safe way to handle this situation.
That's acceptable. I've created a SqlUtilities class two years ago that had a similar method. You can take it one step further if you like.
EDIT: Couldn't find the code, but I typed a small example (probably with many syntax errors ;))
SQLUtilities
public delegate T CreateMethod<T> (SqlDataReader reader);
public static T CreateEntity<T>(string query, CreateMethod<T> createMethod, params SqlParameter[] parameters) {
// Open the Sql connection
// Create a Sql command with the query/sp and parameters
SqlDataReader reader = cmd.ExecuteReader();
return createMethod(reader);
// Probably some finally statements or using-closures etc. etc.
}
Calling code
private SomeThing Create(SqlDataReader reader) {
SomeThing something = new SomeThing();
something.ID = Convert.ToIn32(reader["ID"]);
...
return something;
}
public SomeThing GetSomeThingByID (int id) {
return SqlUtilities.CreateEntity<SomeThing> ("something_getbyid", Create, ....);
}
Of course you could use a lambda expression instead of the Create method, and you could easily make a CreateCollection method and reuse the existing Create method.
However if this is a new project. Check out LINQ to entities. Is far easier and flexible than ADO.Net.
Well, In my opinion check what you do before going through it.Something that is working doesn't mean it is best and good programming practice.Check out and find a concrete example and benefit of using it.But if you are considering using for big projects it would be nice using frameworks like NHibernate.Because there are a lot projects even frameworks developed based on it,like http://www.cuyahoga-project.org/.