Can PUN run RPCs from a ScriptableObject?

Hi,

I am trying to create an Object Pool Manager system in my game and the way I am doing this is that first I have a PoolManager ScriptableObject script that has the prefabs that need to be spawned + List for each type of prefab that needs to be pooled.

When player joins the game and their object is instantiated I call an initialize method on the ScriptableObject that calls a photonView.RPC() method that starts instantiating the required prefabs on all clients. However I am getting a weird error that I am not sure how to resolve. Here is the code on the PoolManager script (ScriptableObject):

public void InitializePool()
    {
        GameObject player = PhotonNetwork.masterClient.TagObject as GameObject;
        PhotonView view = player.GetComponent<PhotonView>();

        view.RPC("Rpc_SpawnPlayerProjectile", PhotonTargets.All);

    }

    [PunRPC]
    private void Rpc_SpawnPlayerProjectile()
    {
        ProjectileController controller = Instantiate(playerProjectilePrefab, poolParent).GetComponent<ProjectileController>();
        controller.gameObject.SetActive(false);
        playerProjectiles.Add(controller);
    }
Note the above is called AFTER the player is instantiated and on the scene. It is also been tested on a room with only the master client.

The error I am getting is this:


PhotonView with ID 1002 has no method "Rpc_SpawnPlayerProjectile" marked with the [PunRPC](C#) or @PunRPC(JS) property! Args:
UnityEngine.Debug:LogError(Object)
NetworkingPeer:ExecuteRpc(Hashtable, Int32) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:3075)
NetworkingPeer:RPC(PhotonView, String, PhotonTargets, PhotonPlayer, Boolean, Object[]) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:3837)
PhotonNetwork:RPC(PhotonView, String, PhotonTargets, Boolean, Object[]) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonNetwork.cs:2941)
PhotonView:RPC(String, PhotonTargets, Object[]) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonView.cs:597)
PoolManager:InitializePool() (at Assets/Scripts/Scriptable Objects/PoolManager.cs:19)
c__Iterator0:MoveNext() (at Assets/Scripts/Connection/SceneManagement.cs:44)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)


As you can see the RPC call doesn't have any parameters and I am not supplying any. Despite that, I tried putting in null as a parameter but still won't work. Any ideas what is going on here?

Best Answer

Answers

  • Bump, still looking for an answer to this problem.
  • Bump, still looking for a solution to the above problem.

    Thanks.
  • Vallar
    Vallar
    edited July 2018

    Hi @Vallar,

    the problem is, that a RPC is usually called on the same object it is invoked on. In your case you are trying to invoke the call from a player object (player.GetComponent();) and call a function on another object (PoolManager.cs). This does not work.

    Instead of using a RPC in this case, I would recommend you using a RaiseEvent call. This one is independent from a PhotonView component and can be invoked from any other script. Means that the necessary OnEvent callback does not have to be implemented in the same script or attached to the same object. To see how RaiseEvent works, you can take a look at the RPCs and RaiseEvent documentation page. It covers an entire section about it.

    Thanks for the suggestion, I got it working using RaiseEvent but I have to say your documentation definitely needs a better example that demonstrates how is this used. The example and explanation made me feel lost the whole time. Luckily things worked.

    EDIT: Your documentation should also include a heads up about subscribing and unsubscribing to events in a SO in OnEnable and OnDisable because for some reason they generate this error:

    Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)

  • The example and explanation made me feel lost the whole time.


    Can you describe why you felt lost? Maybe we can improve this page with some concrete feedback.

    Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)


    When exactly is this error occurring? I have checked the source code and this isn't an error message we are using, so it is either one by Unity or one of yours. I guess it's one of Unity's.
  • The example and explanation made me feel lost the whole time.


    Can you describe why you felt lost? Maybe we can improve this page with some concrete feedback.

    Sure, since you asked, I'll try to explain my thought process step by step (and it is going to be long) so hopefully you can get some insights. When I read the first few lines I understood the general idea of how this works. Then I went to read the example and I was like "What is eventCode? Is this like an ID? OK Why can't I just call the event with something better than an ID, am I supposed to remember the IDs of say 15+ events if I have that much or more? Maybe I am misunderstanding this. What about this "group units"? What does that even mean? OK so this isn't an ID or is it an ID?"

    Then I went to the second line and I read the part with selected unity 1,2,5 and 10 "What is that exactly? What is this? What does that explain? I don't really know what is a unity 1, 2, 5 and 10. Does data need to be in bytes? Am I supposed to turn my GameObject if I need to pass a GameObject into bytes? Isn't that low level stuff?"

    Then I saw this line "PhotonNetwork.RaiseEvent(evCode, content, reliable, null);" and I started thinking "Well if evCode is like an ID, at least I think it is, content is the byte version of my data, reliable is the method, what the hell is null? Why am I using null? What does null do?"

    Since I barely understood what was written I jumped below and looked at the second paragraph. OK, so basically the event gets registered and deregistered like SceneManager.SceneLoaded. OK, that is easy, let's look at the below lines.

    Then I read this: void OnEvent(byte eventcode, object content, int senderid) and I started thinking "OK, so where am I passing the reliable or null part from the previous code I read? Well if I am not passing them, why am I using them? Right well, what about object content. Why in the world would I convert my data to bytes when I can just pass an object if I want? Well if there is a reason, what is that reason? OK, never mind that, I just pass in an object and test and see how it ends up being. But wait... where exactly in my code architecture do I put OnEvent() and where exactly do I call RaiseEvent? Does RaiseEvent trigger OnEvent? Looks so. OK, I'll do that. Oh and look, turns out, I was right, I have to check events with a code. So now I need some kind of documentation to keep up with this mess. OK. Maybe below we'll have more information".

    I look at the rest of the post "Oh, no information and no elaboration and the example is just a "concept kind of thing" not even a proper use case like "You can use events to reduce a player's health so you can set it up like this." It just a holistic example. Right what are these EventOptions? How do I use those? I don't know. OK, let's just do exactly as written here and see how it works.

    Hope this helps.


    When exactly is this error occurring? I have checked the source code and this isn't an error message we are using, so it is either one by Unity or one of yours. I guess it's one of Unity's.
    Yes, it isn't Unity that produces this error not your source code and it produces it exactly when I use the below code in a ScriptableObject to register and deregister the event I need to raise. I spent a good 30min-1 hour trying to debug why in the world this happens because I am not changing scenes. The second I hit play (no scene related code runs) this occurs. It was only resolved when I moved the registration AND the deregistration code from OnEnable and OnDisable and put them on a separate method that I manually call right before raising the event and since I can't use OnDisable on my ScriptableObject I resorted to deregister the event right before I register it so I don't register an event twice.

    When talking about this with a friend of mine (he is a higher level programmer than I) he told me this might be the result of how you (Photon) have implemented the singleton PhotonNetwork. Stress on might, since he didn't look at the source code but he had a similar issue before and it was the singleton implementation that caused it.

    void OnEnable()
    {
    PhotonNetwork.OnEventCall += this.OnEvent;
    }
    void OnDisable()
    {
    PhotonNetwork.OnEventCall -= this.OnEvent;
    }

    Hope this helps.
  • Thank you for the feedback, @Vallar. We can use this, to improve the page, which seems to be really necessary.

    Let me try to quickly explain a few things.

    The EventCode is an identifier for a certain (custom) event. You can use the IDs from 0 to 199 for your own events. A good way to handle multiple custom events is to use fixed names for them. I guess this is best described by a code snippet example: public const byte EVENT_DO_SOMETHING = 1;. This way you can basically add a unique name to a certain event which makes handling them a lot easier than just using numbers.

    About the content and the group units: In this example you have to think of a RTS game and you select certain units, in this case units with the IDs 1, 2, 5 and 10. By writing this I have noticed the typing error in the code snippet on the page, which might have confused you as well. Knowing this I guess you have noticed that this is not about converting something into bytes or similar. In general the content can be anything that Photon can serialize by default. To make this example more useful, you could probably add a position where the units should go to. Then you would have something like "Move Units to Position" event.

    About the other parameters of the RaiseEvent call: reliable simply describes, if the event is being sent reliable. RaiseEventOptions provides a few more options, which can be used to buffer this event or to only send it to a certain user for example. If you use null here, the default RaiseEventOptions will be applied internally. If you don't want to use any special options of the RaiseEventOptions, you are fine with using null here.

    Next you have the OnEvent callback handler: you have to register it, so that custom events are forwarded to it. Therefore you can use OnEnable and OnDisable which is the easiest way - you won't forget to deregister the handler at least. The callback itself is relatively simple: it gets called with the EventCode and Content from above as well as an ID of the sender, which might be needed for game related logic. With this and the information above, I think things are a lot clearer right now.

    You said, that you are missing the reliable and null information here. This is because you don't need them any longer (as a receiver). As said those are just important in terms of (reliable) sending the message to the correct receivers.

    Again, thanks for your feedback, this can be used to improve this page a lot. I hope my explanations are okay so far and help you understanding how RaiseEvent is working better. If you have further questions about this, please feel free to ask.
  • Thank you for the feedback, @Vallar. We can use this, to improve the page, which seems to be really necessary.

    Let me try to quickly explain a few things.

    The EventCode is an identifier for a certain (custom) event. You can use the IDs from 0 to 199 for your own events. A good way to handle multiple custom events is to use fixed names for them. I guess this is best described by a code snippet example: public const byte EVENT_DO_SOMETHING = 1;. This way you can basically add a unique name to a certain event which makes handling them a lot easier than just using numbers.

    Yes, I could do that or possibly an enum and just work from there. But it just feels off having to do that. Specially when I edit the numbers and forgot to update them. But it seems it is kind of a design pattern in PUN. You have a similar issue with RPCs. Strings to call them instead of providing a method directly (like every other networking solution). I could still use a constant, but if I forget to update the method + constant, I will end up with a problem.


    About the content and the group units: In this example you have to think of a RTS game and you select certain units, in this case units with the IDs 1, 2, 5 and 10. By writing this I have noticed the typing error in the code snippet on the page, which might have confused you as well. Knowing this I guess you have noticed that this is not about converting something into bytes or similar. In general the content can be anything that Photon can serialize by default. To make this example more useful, you could probably add a position where the units should go to. Then you would have something like "Move Units to Position" event.

    See if that was mentioned, it would have made that example quite relevant and easy to understand. In fact, I wouldn't probably need to even think about how to use it.


    About the other parameters of the RaiseEvent call: reliable simply describes, if the event is being sent reliable. RaiseEventOptions provides a few more options, which can be used to buffer this event or to only send it to a certain user for example. If you use null here, the default RaiseEventOptions will be applied internally. If you don't want to use any special options of the RaiseEventOptions, you are fine with using null here.

    The thing is, it was never mentioned where to supply the options and since it is WAY after the original mention of RaiseEvent (first paragraph), it was confusing. It is like you are talking about X in first paragraph, move to Y in the second, then we are back to X without saying you are back to X and how it connects.


    With this and the information above, I think things are a lot clearer right now.

    Oh, very much. The example at least makes sense now. Previously it felt more like (sorry to say this) someone just wrote some random code and some other person threw in some random text and we have to jigsaw puzzle it together.


    Again, thanks for your feedback, this can be used to improve this page a lot. I hope my explanations are okay so far and help you understanding how RaiseEvent is working better. If you have further questions about this, please feel free to ask.

    Your explanation was spot on and aligned with many of realizations later. Thank you very much -- not sure if this is your role or not, but I'd say you should be the document writer (and sorry if this throws work on your shoulders you don't like. It is just much of the documentation is like this page -- it is messy and incoherent which makes it difficult to understand what is going on).


    Next you have the OnEvent callback handler: you have to register it, so that custom events are forwarded to it. Therefore you can use OnEnable and OnDisable which is the easiest way - you won't forget to deregister the handler at least.

    I would include a section or some kind of alert that says this won't work with ScriptableObjects due to the error mentioned before (unless I did something stupid I didn't know about). Took me good 30-60 minutes to figure out that this is the case.


    Side note (and quite off topic but since we are on the topic of providing feedback). Support, so far you guys have been excellent (specifically you Christian, you seem to answer every question I have). However, if you can see when this was posted initially and then bumped twice until I got any kind of response. This isn't the first time mind you. It is one of the main things that actually bothers me in Photon. It took almost a week to get a reply and another post of mine took about 3-4 days. Moreover it seems not many developers are answering questions so basically it is all on Photon to resolve issues. Sorry for the seemingly ranty side post but I thought I'd just mention it. Feel free to ignore it if you wish, I wouldn't take it the wrong way (after all it is offtopic :) )