Simplest way to handle MasterClient-Authoritative AI enemies

I was wondering, does the MasterClient ever switch, besides when the creator of a room leaves the room? I ask because I decided the simplest way for me to handle MasterClient-authoritative AI enemies would be to only have the initial MasterClient PhotonNetwork.Instantiate and control them, but if the MasterClient leaves, rather than have to deal with the yuckiness of trying to re-configure them so the new MasterClient controls them, just make everyone LeaveRoom() in the OnMasterClientSwitched() callback (or whatever its called), effectively booting them and ending the game.

It's a little hacky, but would that work? I wasn't sure if there was any "intelligent" mid-game MasterClient switching, based on user connection speeds or something. That would obviously throw a wrench in my plans.

PS: Nice to see some familiar faces around here! :)

Comments

  • The masterclient might also change when you get into the room, so you would have exclude that case.
    Aside from that, you could make everyone leave the game when the master leaves. It's not the best solution (as games end when a particular player loses interest or connection) but it works.
  • You mean OnMasterClientSwitched() might get called when the first person creates the room (and therefore joins it)? I did a simple test last night by just putting OnMasterClientSwitch(){ LeaveRoom(); } (pseudo, don't remember exact syntax), to test booting players when the room creator leaves. That worked fine, it wasn't booting me as soon as I created rooms, at least.
  • I didn't look it up, so you will be right.
    Keep an eye on that case and for the time being, assume it just works.
  • Thanks for the responses, Tobias.

    If I do decide to allow for MasterClient handover, what would be the best way to handle that?

    If the MasterClient spawns the AI units, he will own them, but if that MasterClient leaves, all his PhotonNetwork.Instantiated AI units will be destroyed. Could you PhotonNetwork.Instantiate objects with their PhotonView owner set to scene? Would those object's ownership change hands to the new MasterClient?
  • All objects that are baked into the scene will transfer just fine: the new MasterClient will take ownership.
    However, PhotonNetwork.Instantiate objects are always linked to the client that instantiated them. So if this (master)client leaves they are useless.

    Photon already does support caching of room based objects, but PUN does not yet allow run-time instantiation of room-owned/scene-owner(/masterclient-owned) objects.

    What you can do right now is place the AI in the scene beforehand. Or have the new masterclient re-allocate the PhotonViewID's after switching (remember that all other clients will have to do so as well). Use an RPC to send the new ViewID and point out which old viewID to replace with what. Also you'd have to disable auto-cleanup and manually destroy/cleanup after a player left.
  • Gotcha, thanks. It would be cool to be able to instantiate scene (MasterClient) owned objects whose ownership was handed over automatically.

    I decided to have the spawners be scene owned, and keep a list of all AI units, their states, and positions/rotations, and just re-instantiating them all by the new MasterClient after handover.
  • Sorry to bring this back from the grave, but I'm trying to do exactly this with my AI. I'm having trouble figuring out what you were talking about up there Leepo. I'm going to try to break the steps down for handing off "player" owned game objects (spawned with PhotonNetwork.Instantiate).

    1) "have the new masterclient re-allocate the PhotonViewID's after switching (remember that all other clients will have to do so as well)"
    function OnMasterClientSwitched(player : PhotonPlayer) {
    	if(PhotonNetwork.player == player) {
    		photonView.RPC("AllocateNewViewID", PhotonTargets.All);
    	}
    }
    
    @RPC
    function AllocateNewViewID() {
    	PhotonNetwork.AllocateViewID();
    }
    

    2) "Use an RPC to send the new ViewID and point out which old viewID to replace with what"

    Are you referring to #1 with this sentence?

    3) "Also you'd have to disable auto-cleanup and manually destroy/cleanup after a player left"

    As a semi side note, what would you recommend is the best way of handling clean up? Something like this? (Untested, trying to get a firm grasp on this before attempting to add something this complicated to my project)
    function OnDisconnectedFromPhoton() {
    	PhotonNetwork.Destroy(gameObject);
    	while(PhotonNetwork.connectionState == ConnectionState.Disconnecting || PhotonNetwork.connectionState == ConnectionState.Connected) {
    		yield;
    	}
    	Application.LoadLevel(0);
    }
    

    ?) If there are any steps that I'm missing please let me know :)
  • I'll start with some notes and we'll see how we can go from there:

    1: You're forgetting to actually send & set the PhotonViewID. You have to use the output of PhotonNetwork.AllocateViewID().
    2: Nope, see my comment for 1 :)

    Maybe the new documentation about manual allocation of viewIDs can help here?

    From the updated docs (1.9.5 and beyond):
    5.2.2	Gain more control: Manually instantiate
    If don’t want to rely on the Resources folders to instantiate objects over the network you’ll have to manually Instantiate objects as shown in the example at the end of this section.
    
    The main reason for wanting to instantiate manually is gaining control over what is downloaded when for streaming webplayers. The details about streaming and the Resources folder in Unity can be found here. 
    
    If you spawn manually, you will have to assign a PhotonViewID yourself, these viewID’s are the key to routing network messages to the correct gameobject/scripts. The player who wants to own and spawn a new object should allocate a new viewID using PhotonNetwork.AllocateViewID();. This PhotonViewID should then be send to all other players using a PhotonView that has already been set up (for example an existing scene PhotonView). You will have to keep in mind that this RPC needs to be buffered so that any clients that connect later will also receive the spawn instructions. Then the RPC message that is used to spawn the object will need a reference to your desired prefab and instantiate this using Unity’s GameObject.Instantiate. Finally you will need to set setup the PhotonViews attached to this prefab by assigning all PhotonViews a PhotonViewID. 
    
    void SpawnMyPlayerEverywhere()
    {        
        //Manually allocate PhotonViewID
        PhotonViewID id1 = PhotonNetwork.AllocateViewID();
    
        photonView.RPC("SpawnOnNetwork", PhotonTargets.AllBuffered, transform.position,
            transform.rotation, id1, PhotonNetwork.player);
    }
    
    public Transform playerPrefab; //set this in the inspector
    
    [RPC]
    void SpawnOnNetwork(Vector3 pos, Quaternion rot, PhotonViewID id1, PhotonPlayer np)
    { 
        Transform newPlayer = Instantiate(playerPrefab, pos, rot) as Transform;
    
        //Set the PhotonView
        PhotonView[] nViews = go.GetComponentsInChildren<PhotonView>();
        nViews[0].viewID = id1;
    }     
    
    If you want to use asset bundles to load your network objects from, all you have to do is add your own assetbundle loading code and replace the “playerPrefab” from the example with the prefab from your asset bundle.
    


    As for item 3: Yes&No, the issue here is usually not to cleanup stuff after leaving a room yourself: you could simply load the mainmenu scene after quitting a game to auto-cleanup ALL scene contents.
    If you want to clean up stuff from a player that has left you can use the following:
    PhotonNetwork.autoCleanUpPlayerObjects=true; //Enable manual cleanup somewhere once.
    PhotonNetwork....
    void Destroy(int viewID)
    void Destroy(PhotonView view)
    void DestroyPlayerObjects(PhotonPlayer player)
    void RemoveAllInstantiatedObjects()
    void RemoveAllInstantiatedObjects(PhotonPlayer player)
    void RemoveRPCs()
    void RemoveRPCs(PhotonView view)
    void RemoveRPCsInGroup(int group)
    
  • Thank you so much for the explanation, Leepo. About the clean up part: are you saying that I should just leave autoCleanUpPlayerObjects on? Is that because this method of spawning my AI will leave them owned by the scene? If so that would be an ideal solution. Thanks again.
  • Alright, I'm off to a great start. I've left autoCleanUpPlayerObjects set to true hoping that OnMasterClientSwitch() would be called before any sort of cleanup had happened, which worked great. Now my only issue is newly connected clients no longer see our AI. Of course this is because the buffered RPC for Instantiating them has been "autoCleaned". Is there a way to protect specific RPC's from being cleaned? Or do I now need to disable autoCleanUpPlayerObjects and handle disconnected players on my own?
  • So far I've got:

    1) Two players are in the game, one is the host, the other a client.

    2) Host leaves, client becomes the new host.

    3) The new host calls the following code that first assigns a new viewID, then restarts the updating logic to move the AI's around. This is called on each of the AI's we have in the scene:
    function OnMasterClientSwitched(player : PhotonPlayer) {
    	if(PhotonNetwork.player == player) {
    		var newID : PhotonViewID = PhotonNetwork.AllocateViewID();
    		photonView.RPC("AllocateNewViewID", PhotonTargets.AllBuffered, newID);
    		Restart();
    	}
    }
    
    @RPC
    function AllocateNewViewID(newID : PhotonViewID) {
    	photonView.viewID = newID;
    }
    

    4) At this point all is well. The new host is now the "owner" of every AI that the original host had spawned with PhotonNetwork.Instantiate() and the AI's logic continues with the new owner. But here's where the problems come in. That buffered RPC call is called on the original viewID (say 1000), then changes the viewID (say to 2000). When a newly connected client joins they receive that RPC on viewID 1000 trying to change the viewID to 2000, which no longer exists. If I could solve that issue I'd just be left with newly connected clients not getting the buffered RPC call to Instantiate the AI, the RPC call created by the initial PhotonNetwork.Instantiate() is being cleaned up when the host leaves in the first place.

    I hope I have explained myself thoroughly, this is a lot for me to wrap my non-networking oriented mind around.
  • About the clean up part: are you saying that I should just leave autoCleanUpPlayerObjects on? Is that because this method of spawning my AI will leave them owned by the scene? If so that would be an ideal solution. Thanks again.
    If possible its easier to leave autocleanup on. However that will only work if your enemy AI are photonviews that are saved in the scene: those will persist even if the masterclient leaves.If you HAVE to spawn these AI via code, then they will be owned by the player who spanwed them and thus removed via autocleanup when that player leaves.
    I've left autoCleanUpPlayerObjects set to true hoping that OnMasterClientSwitch() would be called before any sort of cleanup had happened, which worked great.
    Ahh good idea, didn't think about this. I was thinking about disabling autocleanup so that you can specify what needs to be removed and gain control here.
    Now my only issue is newly connected clients no longer see our AI. Of course this is because the buffered RPC for Instantiating them has been "autoCleaned". Is there a way to protect specific RPC's from being cleaned? Or do I now need to disable autoCleanUpPlayerObjects and handle disconnected players on my own?
    " a way to protect specific RPC's from being cleaned" not really no, only with autocleanup off. I'm not sure if/how we have working buffered room-based RPCs or so..

    About your last post:
    Right, the original Instantiate call is lost and the buffered RPC makes no sense here. The new masterclient would need to tell the newly connected client to spawn the prefab. This is annoying.
    Hmmf, I think we should try to improve this process from PUNs side. E.g. allowing to PN.Instantiate a scene-based object which will not be cleared.
    We already have a "RemoveFromServerInstantiationCache" and there is the option to have the server not-automatically remove instantiation RPCs of players who left ( op[ParameterCode.CleanupCacheOnLeave] = autoCleanUp; ).
    However, ideally the instantiated object should be marked as scene so that the masterclient ownership will automatically be switched. I want to explore this with Tobias but he's gone untill wednesday/thursday.
  • Allowing PN.Instantiate(ed) objects to be owned by the Scene would be an amazing addition. I think I'm just going to have the max amount of AI already in the scene and delete unneeded ones when the game starts with a buffered RPC call. I didn't even think about that before. Thanks for the help Leepo and I hope you guys can work something out in the future :)
  • Alright, this is odd. I have everything working alright with the AI's just in the scene prior to joining (no spawning). But their viewID's are changing upon starting the game. Some are sharing the same viewID which is causing obvious problems. I have no idea why... outside of play mode their viewID's are all different, and they don't share the same ID with any other "scene" owned object. What gives?
  • Oh really, the scene view IDs change at runtime?
    What PUN version are you using? Please try 1.9.5 (see here).
  • This problem persists into 1.9.5. With more than just the AI I have now noticed. Our capture the flag viewID's start out with view IDs 11 and 13 (team 1 and team 2), then upon playing both switch to viewID 10. The same thing happens for our AI. I'm not allocating view IDs manually on these objects, just allowing PUN to handle them.
  • Do you have a repro project which you can share? If not, no problem, I'll have to search for a repro then.
  • It looks as though it only happens when two circumstances are met. 1) I'm playing alone in the editor, and 2) The objects are disabled during "Awake". I'm sorry I can't strip my project down right now. But hopefully that helps :)
  • Thanks, that makes sense and should be an easy repro!
  • Any ideas on a temporary fix for this? I can't allocate my own viewID's on those objects because they're all sharing the same ID to begin with. If I try to update other clients / new clients of the new ID it will just apply to all of them. They're all different in the editor prior to playing, but on play they all shift to the same thing.
  • I could work on a bugfix quickly, but am not yet able to reproduce it. Having the PhotonView gameobjects disabled does not repro the issue for me. They have correct viewIDs in the editor, and when I play the IDs stay the same.

    " then upon playing both switch to viewID 10."
    Are you sure you are not doing something else as well? E.g. using offline mode..or assigning the viewIDs by code?
  • I have not yet found any repros/bug regarding your last issue.

    However, in 1.9.6 I have now added InstantiateSceneObject which is what you'll want to use for your AI. These object persist even if the MC leaves.
    See: http://forum.exitgames.com/viewtopic.php?f=17&t=1473
  • I have no idea what I'm doing wrong. Even with InstantiateSceneObject I'm getting duplicate viewID's. I've stripped my spawning and enable code down for my AI to just a couple functions if you could check it out. I've read through everything and I'm 99.9% positive that I'm not allocating my own id's on them. I use PN.Instantiate extensively in this project with no problems. But having something in the scene prior to runtime seems to mess everything up. So does this new InstantiateSceneObject function.

    Spawning-http://pastebin.com/j9VsFgf9
    Stating/Enable-http://pastebin.com/vhtjzBBN

    P.S. - I'm not sure what the "data" part of InstantiateSceneObject is supposed to contain. I just set up an empty object array to pass it and it seemed okay. But out of curiosity, what's that supposed to contain?
  • It couldn't have anything to do with using UnityScript with the "Plugins" folder at my root could it?
  • They're consistently spawning with viewID 999 too if that could tell you anything at all.
  • Just had another thought, I'm also instantiating these AI's inside of a scene that was loaded via streamed asset bundle.
  • The data parameter can be left null. You could use it to send some instantiation data with your prefab, e.g. the desired HP/color/whatever for this NPC.
    I'm afraid the 999 viewID is a bug:

    PhotonNetwork.cs line 1178 must be "if (!view.isSceneView)" instead of "if (view.isSceneView)" (inside "IsSceneViewIDFree"). I have also updated the download of 1.9.6 in this topic: viewtopic.php?f=17&t=1473


    Is the 999 double viewID the only bug you were having?
  • Excellent! I'm so glad that this was worked out. I hope I could have been of some assistance, this is the most amazing networking solution I've ever got my grubby little hands on. Thank you so much!

    P.S. Your support is out of this world! :D