Author Topic: Best practice question  (Read 4063 times)

Bill.Smock

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 2
  • Posts: 19
    • View Profile
Best practice question
« on: May 20, 2016, 07:02:47 PM »
I need a small nudge in the right direction.  I'm making a game but don't want to build everything and then find myself thinking "shit, should have done things differently."  Networking summary:  2 player co-op, 4 player multiplayer.  The 2 player co-op will require data saves (50+ attributes to each player object), the 4 player multiplayer will not require any saving.  Also relevant to my questions the co-op can also be done locally, as well as single player.

1.  I have Easy Save 2.  Should I be using TNet instead of ES2, for all of this?  Should user preferences, button maps, all that be saved with TNet even though much play will be local?

2.  To get this on steam - just set up a multiplayer in this way and then deal with steam API when I have access?


Thank you very much.  I will definitely be buying this since your support is amazing.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Best practice question
« Reply #1 on: May 21, 2016, 10:15:00 PM »
1. I would strongly advise using TNet, yes. TNet can handle saving for you automatically. State persistence is just a part of its native functionality. Save them all via TNManager.SetPlayerData("key", value) -- just be aware that you can only set this for your own player, not for other players' data. Each player has to set their data on their own, and it gets automatically sync'd with other players and will auto-save to their player file, provided you used TNManager.SetPlayerSave("path"); This is explained more fully in the tutorial: https://docs.google.com/document/d/1WZlIppbYy4CiPbm24Fa4AfZkjXmtKIZEMas94NxrnDY/edit

2. You don't need to use Steam's API's networking (it's a completely different beast). In Windward I only used it for things like achievements and notifications. By notifications I mean when a player hosts the game, I set the appropriate Steam state that lets other players to right-click the player and Join the game. This merely results in a notification in your game that you handle however you like, for example by telling TNet to establish a connection with the remote IP.

Bill.Smock

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 2
  • Posts: 19
    • View Profile
Re: Best practice question
« Reply #2 on: May 26, 2016, 04:54:57 PM »
Thank you for the clear response and advice.  One thing I don't understand - I'm completely ignorant of this whole thing - is what server am I supposed to connect to?  Your videos say a port and IP like it's some arbitrary number I choose.  Can I use a UNet server?  Or would I have to rent from something like Multiplay?  This is assuming a lot of people (obviously not hundreds of thousands) play the game. 

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Best practice question
« Reply #3 on: May 27, 2016, 09:49:41 PM »
TNet comes with a pre-compiled stand-alone server executable -- TNServer.exe. It's a mono executable that can be launched on all platforms if you need a stand-alone server. If you don't and instead want to keep your game peer-to-peer (or friend-only multiplayer), you can launch a server right from within Unity by using TNServerInstance.Start().

TNet's stand-alone server is extremely lightweight on resources and can be launched anywhere. I've had players host servers on Raspberry Pi devices for the fun of it, for example.

Bill.Smock

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 2
  • Posts: 19
    • View Profile
Re: Best practice question
« Reply #4 on: June 02, 2016, 12:58:51 PM »
After looking through the tutorials and playing around with them (automatic state saving is incredible, btw), I have just a few more questions that I think will help me get enough understanding to start implementation.  I think these mostly touch on fundamentals and "the big picture" so hopefully it's not asking some things I should be figuring out on my own.  Just want to be sure I'm understanding the system correctly. 

1.  If I use the standalone server executable you provided, when you say 'save to the server' (e.g. Target.OthersSaved), where exactly is that going?  And how is it retrieved after a disconnect and reconnect?  What if the server instance is different next time, or is that impossible?

2.  I've decided against any multiplayer battling as it would increase the scope of my game too much, so I'll be doing co-op (peer-to-peer) only.  Is this how the server setup works:  player chooses online or whatever at menu and they end up creating the lobby, at which point I call "TNServerInstance.Start()" and then I'm all set to add other players and the original person who's client called TNServerInstane.Start() is the host? Also, does host PC control all AI and spawning, all those things that we associate as being controlled by the server?

3.  In reference to number 2 - will properties be saved locally to each player that is connected?  If it's using TNServerInstance.Start(), I need the data on their machine instead of the server's host so they can load the data without the same exact server instance. 

4.  I want to do 4 player coop that allows for a mixture of x number of local coop + x number of online players (e.g., 2 local players and 2 single players from online).  When I do this, it will simply be two instances of player on the local coop machine and the RFC is just called to broadcast to the other online players while each of the two players is handled locally, right (i.e. both player/character instances will have isMine != null)?  The player will choose a name for their saves initially.  Also, the input per player distinction is handled by rewired so it's very simple and already set up. 

5.  RFCs.  This game is going to have a lot of things going on at once and the player controls must be really tight.  One problem is that the movement is completely non physics based - horizontal and vertical transform positions are manually altered every frame.  Will I instead need to use Lerp or Slerp or some other way, or is it OK to send input updates for vertical and horizontal axes very frequently (maybe 10x/second or more) with SendQuickly?  And then for syncing - your autosync script looks really complex, couldn't I just send a RFC with exact transform coordinates every second or so?  I am willing to take the occasional teleport over a performance hit and/or jittering. 

Okay that turned out to be more than I intended sorry about that.  This should be all I need to jump right in, pretty excited to get this running and make a lobby with NGUI.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Best practice question
« Reply #5 on: June 04, 2016, 01:50:37 PM »
1. Save file you specified when launching TNServer.exe. It's one of the parameters. By default it's "server.dat".

2. TNServerInstance.Start() just basically instantiates a virtual TNServer.exe. You need to specify a port for it to be accessible by the outside world. Other clients will then be able to connect to your server, provided you're not behind a firewall that doesn't support UPnP, dual firewalls or other reasons why your PC would not be reachable.

3. All data is saved only on the server, unless you choose to save something locally. In Windward I save player files locally, for example. This allows players to keep their "characters" from one server to the next.

4. You're overthinking it. Local or online, it doesn't matter with TNet. It's the same code even when you are playing completely offline. The only difference is in how you start TNServerInstance on the person hosting the game. Broadcasts are rarely needed. TNet 3 supports multiple channels, so to chat join the channel dedicated for chatting while remaining in the game channel at the same time. Broadcasts were needed in TNet 2 days, not so much anymore. Broadcasts behave like regular RFCs in TNet 3, except that they have a built-in spam checker. In other words, use them for chat-related packets.

5. There is no point in sending data that hasn't changed. Input frequency updates are shown in the car example, alterable with a slider. You can see what effect each has on the simulation of a multiplayer game. If the axes don't change, there is no need to send the data -- and there is pretty much never any reason to send more than 10 packets of the same type per second.

AutoSync script is there for convenience. I always recommend creating your own RFCs instead -- this way you will control exactly when the updates are sent out rather than having some dummy script send them out every so often. And again, the car example shows you how to sync input frequently and positions infrequently. The two sliders control the frequency of both, so I strongly advise starting with that example.

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: Best practice question
« Reply #6 on: June 04, 2016, 02:02:26 PM »
I'll try my best to answer your questions:
1. Players are part of a channel. Channels contain a list of SavedRFCs (and SavedObjects). So when you send an RFC with XXXSaved this RFC is simply added to that player's channel's list of SavedRFCs (as seen in TNGameServer::ProcessForwardPacket(...)). When the server is shut down it calls TNGameServer::SaveTo(...) which iterates over all channels on the server, and, if the channel is marked as persistent, saves all RFCs and Objects to a file. Note that this save routine is also called every 2 minutes. When the server is started back up again, it simply reads and loads the file.

2. Yes, and you'd have the TNServerInstance hooked up to a remote lobby server somewhere, so other players can see it. This lobby server would be hosted by you, but the actual game server would be hosted by your players (in your scenario). If you want the host to control AI and spawning, just check TNManager.isHosting in your AI / spawn functions.

3. You're saying you want each player to store their statistics locally (such as their level, rank, inventory, whatever)? You'd have to add some custom logic for this, as by default the data is stored and loaded by the server. Perhaps you can have each player save their stats to a file locally, then whenever they join a channel have them forward the contents of this file to every player in the channel (Target.OthersSaved). There's lots of ways to deal with this, entirely dependent on your design / needs.

4. I don't have any experience with local split-screen. tno.isMine will always return true or false. Uhh, hmm, I might set up a custom object creation function that determines if it's a local player, and, if so, which local player, then save this to a variable in your Player script. Then check that where ever you'd check for tno.isMine (in addition to, not instead of). Another approach could be having a static LocalToNetManager that's passed inputs from each local player (again, with an ID) and forwards these via RFC. This approach might be easier, I think you could use tno.uid (which is handled by TNet when instantiating) instead of having to set a custom ID for local players.

5. I think there's some confusion here. If you're using the AutoSync script, there's no need to sync inputs, or vice versa. To answer your question though, you can send input updates very frequently, but be sure to test it thoroughly before release (there is such a thing as too much). Also, if you're syncing inputs, I wouldn't be syncing the transform very often at all. Maybe once every 5 seconds or so, just to correct any drift from missed packets.

edit: Yeah, 30 seconds was a silly suggestion. Changed to 5.
« Last Edit: June 04, 2016, 02:13:02 PM by cmifwdll »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Best practice question
« Reply #7 on: June 04, 2016, 02:05:58 PM »
Maybe once every 30 seconds or so, just to correct any drift from missed packets.
That is highly dependent on the frequency of input updates and lag spikes, but 30 seconds is a bit much I'd say. Every couple of seconds is fine. In Windward that was 3 seconds. In the Car example it's configurable with a slider.
4. I don't have any experience with local split-screen. tno.isMine will always return true or false. Uhh, hmm, I might set up a custom object creation function that determines if it's a local player, and, if so, which local player, then save this to a variable in your Player script. Then check that where ever you'd check for tno.isMine (in addition to, not instead of). Another approach could be having a static LocalToNetManager that's passed inputs from each local player (again, with an ID) and forwards these via RFC. This approach might be easier, I think you could use tno.uid (which is handled by TNet when instantiating) instead of having to set a custom ID for local players.
If OP's question was about split screen, then my response should have been different, but it's not that difficult. Essentially "owner" is the game instance, not the player. Split screen has 2 game players, but it's still just 1 game instance. No one says you can't have 2 game units in the world at the same time, controlled by different joysticks. Both will have "tno.isMine" as 'true', but so what? Your GamePlayer class should know more, such as what player it is, and what joystick they're controlled by.