How to despawn if Object is manually instantiated?

Hello,

I am following the Manual Instantiation from the doc here:

And it works. Now, how do I destroy the gameobject? Do i need to call Photon.Destroy? Or manual destroy, i.e remove PhotonView component, then call Unity Destroy function?

Answers

  • More information:

    I use the manual instantiation because I use PoolManager game assets:


    So for example, I want to spawn a vfx:

    var obj = PoolManager.Pools[pool].Spawn("VFX", pos, rot);


    So, when I want to despawn, by right using the PoolManager would be:

    PoolManager.Pools[pool].Despawn(gameObject);

    However, the gameobject still have photonView component attached to it. How should I do?

    Pardon my english, as its not my first language.

  • JohnTube
    JohnTube ✭✭✭✭✭
    edited December 2021

    Hi @vinibeg,

    Thank you for choosing Photon!

    You need a manual destruction event like manual instantiation:

    • call raise event with custom event code for manual network destruction, payload should be network object's ViewId.
    • remove manual network instantiation event from cache.
    • on receiving clients find network object using ViewId and Unity destroy it locally.

    As a side note, did you consider implementing support for PoolManager as custom prefab pool? Read more here.

  • Hello JohnTube,

    Thanks for your answer. Following your steps:

    call raise event with custom event code for manual network destruction, payload should be network object's ViewId. [ done ]

    remove manual network instantiation event from cache.

    [How do I manually remove the instantiation event from cache? Do i need to remove the photonView component?]

    on receiving clients find network object using ViewId and Unity destroy it locally.

    [ sure, i will use my current PoolManager to despawn it]


    Regarding the PrefabPool, do you mean, I should extend my current PoolManager to implement "IPunPrefabPool"?

    Because my project has 2 modes, single player and multiplayer mode. Do you think i should:

    1. Add photonView component to all single player prefabs?
    2. Add and remove photonView component on spawn and despawn method?


  • Sorry, how do i manual remove instantiate from cache?

    RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache };

    PhotonNetwork.RaiseEventInternal(MPEvent.Remove, photonView.id, options, SendOptions.SendReliable);

    Then on the clients side, i do:

    GameObject g = PhotonView.Find(photonViewID).gameObject;

    Destroy(g)?

  • JohnTube
    JohnTube ✭✭✭✭✭

    Hi @vinibeg,

    The network destroy is three steps, I think you already got two working.

    1. Remove the cached manual instantiation event from the events cache of the room, so no new actor or rejoining actor will receive it, caching option is correct yes (EventCaching.RemoveFromRoomCache) (event code == same manual instantiation event code, payload or data should be a filter for event cache removal if what you sent initially is a Hashtable or same structure which hopefully the server accepts as filter so you will remove only the exact event occurrence related to a specific ViewId and not all instantiations of ALL ViewIDs, raiseEventOptions.TargetActors should be set to the actor number of the instantiator). Read more here about Cached Events here. If you're stuck, share code snippet for both manual instantiation and destruction you used/tried/tested.
    2. Send manual network destroy event (custom event code + ViewId as payload).
    3. When receiving manual network destroy event, destroy or despawn object locally.

    About IPunPrefabPool, it was just a suggestion and a totally alternative way of using manual network instantiation and destruction which allows you to keep using default built-in methods PhotonNetwork.Instantiate & PhotonNetwork.Destroy BUT use your own custom object pooling.

  • vinibeg
    vinibeg
    edited December 2021

    Hello @JohnTube ,

    Thanks for your reply. Here is my codes.

    In my NetworkSpawner.cs, send manual Instantiate / Remove instantiate event codes:

    public void SpawnBallObject(Transform obj, string prefabName, string pool, E_SpawnAction action, int ownerID)
    {
        if(action == E_SpawnAction .Spawn)
        {
            PhotonView photonView = obj.GetComponent<PhotonView>();
            if(photonView)
            {
                // remove it first, and reattach
                Destroy(photonView);
            }
              
            photonView = obj.GetComponent<PhotonView>();
            photonView.OwnershipTransfer = OwnershipOption.Takeover;
            photonView.ObservedComponents = new List<Component>();
    
            if (!obj.GetComponent<PhotonTransformView>())
            {
                obj.gameObject.AddComponent<PhotonTransformView>();
            }
    
            photonView.ObservedComponents.Add(obj.GetComponent<PhotonTransformView>());
    
            if (PhotonNetwork.AllocateViewID(photonView))
            {
                Hashtable ht = new Hashtable();
                ht.Add("prefabName", prefabName);
                ht.Add("pos", obj.transform.position);
                ht.Add("rot", obj.transform.rotation);
                ht.Add("viewID", photonView.ViewID);
                ht.Add("pool", pool);
                ht.Add("ownerID", ownerID);
                ht.Add("action", action);
    
                RaiseEventOptions raiseEventOptions = new RaiseEventOptions
                {
                    Receivers = ReceiverGroup.Others,
                    CachingOption = EventCaching.AddToRoomCache
                };
    
                SendOptions sendOptions = new SendOptions
                {
                    Reliability = true
                };
    
                    PhotonNetwork.RaiseEvent(MP_EventCode.SpawnNetworkObject, ht, raiseEventOptions, sendOptions);
                }
                else
                {
                    Debug.LogError("Failed to allocate a ViewId.");
                    Destroy(obj);
                }
            }
            // Remove instantiate cache from room
            else
            {
                Hashtable ht = new Hashtable();
                ht.Add("prefabName", prefabName); // should i still send this if not in used?
                ht.Add("pos", obj.transform.position); // should i still send this if not in used?
                ht.Add("rot", obj.transform.rotation); // should i still send this if not in used?
                ht.Add("viewID", obj.GetComponent<PhotonView>().ViewID);
                ht.Add("pool", pool);
                ht.Add("ownerID", ownerID); // should i still send this if not in used?
                ht.Add("action", action);
    
                int[] actors = { ownerID };
                RaiseEventOptions raiseEventOptions = new RaiseEventOptions
                {
                    TargetActors = actors,
                    CachingOption = EventCaching.RemoveFromRoomCache
                };
    
                SendOptions sendOptions = new SendOptions
                {
                    Reliability = true
                };
    
                PhotonNetwork.RaiseEvent(MP_EventCode.SpawnNetworkObject, ht, raiseEventOptions, sendOptions);
            }
        }
    


    Then in my EventManager.cs that handles the event:

    ...
    case MP_EventCode.SpawnNetworkObject:
    {
        Hashtable evTable = content as Hashtable;
        E_SpawnAction action = (E_SpawnAction)evTable["action"];
    
        if(action == E_SpawnAction.Spawn)
        {
            // Dont spawn multiple
            if (senderId == PhotonNetwork.LocalPlayer.ActorNumber) break;
    
            string prefabName = (string)evTable["prefabName"];
            Vector3 pos = (Vector3)evTable["pos"];
            string pool = (string)evTable["pool"];
            int ownerID = (int)evTable["ownerID"];
    
            Transform prefab = PoolManager.Pools[pool].Spawn(prefabName);
    
            PhotonView photonView = null;
            if (!prefab.GetComponent<PhotonView>())
               photonView = prefab.gameObject.AddComponent<PhotonView>();
    
            photonView.ViewID = (int)evTable["viewID"];
            photonView.OwnershipTransfer = OwnershipOption.Takeover;
            photonView.ObservedComponents = new List<Component>();
        
            if (!prefab.GetComponent<PhotonTransformView>())
            {
               prefab.gameObject.AddComponent<PhotonTransformView>();
            }
            photonView.ObservedComponents.Add(prefab.GetComponent<PhotonTransformView>());
                 
          }
          else
          {
            // This is for RemoveCacheFromRoom
            // Currently do nothing...
          }     
    }
    break;
    ....
    


    In my Ball.cs script, where I want to destroy the ball:

    public void DespawnObject()
    {
        if(NetworkManager.IsSinglePlayer)
        {
            PoolSpawner.Despawn(gameObject, PoolId.Prefabs);
        }
        else
        {
          // First event: remove cache from room
          NetwoskSpawner.Instance.SpawnBallObject(transform, transform.name, PoolId.Prefabs, E_SpawnAction.Despawn, ownerID);
    
          // Second event: destroy game object
          NetworkSpawner.Instance.DespawnBaseObject(pv.ViewID, PoolId.Prefabs);
        }
      }
    


    Are above codes correct?

    When I try to spawn the ball for second time, I got this:

    AllocateViewID() can't be used for PhotonViews that already have a viewID. This view is: View 1001 on MP_Ball(Clone)001 Spawned By Ball Controller 

  • JohnTube
    JohnTube ✭✭✭✭✭

    Hi @vinibeg,

    Code looks fine but it's missing DespawnBaseObject method and OnEvent handling.

    This part is a bit suspicious from a Unity point of view unless I'm missing something.

    "remove", "reattach"?

            PhotonView photonView = obj.GetComponent<PhotonView>();
            if(photonView)
            {
                // remove it first, and reattach
                Destroy(photonView);
            }
              
            photonView = obj.GetComponent<PhotonView>();
    

    I would suggest minor optimizations or changes (NOT DIRECTLY RELATED TO THE ISSUE IN QUESTION AND ARE OPTIONAL):

    • I understand why you're doing this from the comment, you're spawning locally and sending event to Others to spawn. In this case you won't get the live event but you may get this event if you leave the room and rejoin it (PlayerTTL ! = 0 & RejoinRoom) or join it again (AutoCleanUp / CleanUpCacheOnLeave == false).
            if (senderId == PhotonNetwork.LocalPlayer.ActorNumber) break;
    
    • No need for "action" key/value as you should not receive RemoveFromRoomCache events, those are only meant for the server.
    • No need for "ownerID" if by that you mean ActorNumber (or UserID). ActorNumber can be guessed from ViewID, UserId is exchanged if PublishUserId is enabled.