Search Unity

Third Party [Photon] RPC with yield functions + Arguments + bonus question!

Discussion in 'Multiplayer' started by Sbizz, Oct 29, 2014.

  1. Sbizz

    Sbizz

    Joined:
    Oct 2, 2014
    Posts:
    250
    Hey!

    So I'm working on the network of my game and I encountered a problem (not a big one because I have a solution, but I'm asking for something more.. clean, you know!) :

    I have a function like this:

    Code (CSharp):
    1. public IEnumerator FunctionA() {
    2.    // Some code here
    3.    yield return StartCoroutine(FunctionB());
    4.    // Some code there
    5. }
    Something really easy as you can see. The problem is that PhotonView.RPC returns void so I can't call it using StartCoroutine. The only solution I found it's to do that:

    Code (CSharp):
    1. public IEnumerator FunctionA() {
    2.    // Send to the other clients that I called the FunctionB
    3.    PhotonView.RPC("FunctionB", PhotonTargets.Others);
    4.    // Then do it for myself
    5.    yield return StartCoroutine(FunctionB());
    6. }
    And I'm not sure it's the right way.. but it works! Do you have any other solutions to solve this? I mean, I don't want to create a variable and check, by using "while", if it is true or false, blabla. I don't want this:

    Code (CSharp):
    1. public IEnumerator FunctionA() {
    2.    // At the beginning of the function, set _wait to TRUE
    3.    PhotonView.RPC("FunctionB", PhotonTargets.All);
    4.  
    5.     // Wait until FunctionB set _wait to FALSE
    6.     while (_wait) {
    7.         yield return null;
    8.     }
    9. }
    I don't even know if it's gonna work, because I have to go through the server. So, if I want this code works, I have to set the variable _wait to true inside the FunctionA and not FunctionB. Great!

    ****************************

    My second issue (and more important) is I can't send my own classes (which inherits from MonoBehaviour). I have a class named "AObject" which represents every object that the player can interact with (other players, buildings, etc.).

    Code (CSharp):
    1. public IEnumerator FunctionA() {
    2.    AObject obj = getTarget(...);
    3.  
    4.    PhotonView.RPC("FunctionB", PhotonTargets.All, obj);
    5. }
    Unity tells me : "'AObject' can not be serialized". So how I can do that ?

    ****************************

    Bonus question: before I started using Unity3D to create my game, I was working on my own server (from scratch) with RakNet (C++). It works and I just need to create all RPCs (some are already created, like the connection).

    Is it possible to use this server with Photon in Unity3D ? I don't know what I'm gonna receive on my server if I send something through Photon/Unity3D.

    Thank you!!

    PS: Sorry for my english hopefully it's OK.

    Sbizz.
     
  2. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,066
    The method that "is" the RPC could be a coroutine. If you use PhotonView.RPC("coroutinename", PhotonTargets.All), it should be run as coroutine locally and on the other clients.
    At least, that's how it should work. Let me know if it doesn't.
    Don't call PhotonView.RPC() in a coroutine! If you know something has to run each frame, just trigger the start/end of that routine by RPCs. In best case: Only start the coroutine as RPC and let it end with a fixed timing or trigger.

    You can't send classes. If you have to send some values from your own classes, you have to register them as Custom Type first and write de/serialization methods for the class. Check out CustomTypes.cs to see how we do that for Vector3.
    If you simply want to target a specific object, the PhotonView will do that for you. RPCs (and OnPhotonSerializeView) will always target a specific networked object anyways.

    Bonus answer: You can use the Photon Server SDK if you need server-side logic but you can't combine PUN with other servers. The protocol and logic is not built to be re-implemented on top of another networking.

    And as sugar coating on top: Your English is just fine! :)
     
    crowffery likes this.
  3. Sbizz

    Sbizz

    Joined:
    Oct 2, 2014
    Posts:
    250
    Hi tobiass. Thank for your answer.

    Hmm. If I understand well : currently, FunctionA and FunctionB are both coroutines. FunctionA calls FunctionB (RPC) and FunctionA waits until FunctionB ends to continue what it has to do. What you're saying is FunctionA shouldn't be a coroutine because it executes a RPC ? I'm gonna check how I can do that.
    It's a spell cast system, so it's a bit hard to get what I want without using coroutines.. everywhere. Maybe I should see with delegates or events. Here is the "real" code:

    Code (CSharp):
    1. void OnClick() {
    2.     if (/*some conditions*/) {
    3.         StartCoroutine(playerCastSpell(spell));
    4.     }
    5. }
    6.  
    7. IEnumerator playerCastSpell(Spell spell) {
    8.     AObject target = getTarget(...);
    9.  
    10.     if (/*check if target is NOT ok*/) {
    11.         yield break;
    12.     }
    13.  
    14.     if (/*check spell conditions are NOT ok*/) {
    15.         yield break;
    16.     }
    17.  
    18.     // character.getPhotonView().RPC("channeling", PhotonTargets.Other, spell.spellName);
    19.     // Channel the spell for himself
    20.     StartCoroutine(character.channeling());
    21.  
    22.     if (/*everything went well*/) {
    23.         // character.getPhotonView().RPC("cast", PhotonTargets.All, target);
    24.     } else {
    25.         // Otherwise..
    26.     }
    27. }
    But I'm gonna check if I can do something good with delegates, I think it should be OK and more clean.

    And for the other answers, I'm gonna check this evening, I'm at the office right now. The only thing about the classes is: how I can get the good target ? I mean, AObject is a component so.. Do I have to send the object ID and get the good object using a list ? Hmm.

    Thanks again!
    Sbizz.
     
  4. Sbizz

    Sbizz

    Joined:
    Oct 2, 2014
    Posts:
    250
    So I managed to do what you said: I created my own Coroutine class. It's the same as StartCoroutine, but I can know the state of the coroutine (running, paused and finished) AND I use the delegate keyword to call a function when the coroutine ends. Here is the code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class MyCoroutine  {
    5.     public enum eState {
    6.         NONE,
    7.         RUNNING,
    8.         PAUSED,
    9.         FINISHED
    10.     }
    11.  
    12.     // callback function type
    13.     public delegate void callbackType();
    14.  
    15.     // Function which will be called when the coroutine ends
    16.     public callbackType    callbackEnd;
    17.  
    18.     // State of the coroutine
    19.     private eState            _state;
    20.  
    21.     // Coroutine currently running
    22.     private IEnumerator        _coroutine;
    23.  
    24.     public MyCoroutine(IEnumerator coroutine) {
    25.         _coroutine = coroutine;
    26.         _state = eState.NONE;
    27.     }
    28.  
    29.     // start() will launch the coroutine the first time
    30.     // it is called. The other calls will unpause the
    31.     // coroutine if it was paused
    32.     public Coroutine start(MonoBehaviour mono) {
    33.         if (_state == eState.NONE) {
    34.             return mono.StartCoroutine(launch());
    35.         } else if (_state == eState.PAUSED) {
    36.             _state = eState.RUNNING;
    37.         }
    38.  
    39.         return null;
    40.     }
    41.  
    42.     public void stop() {
    43.         _state = eState.FINISHED;
    44.     }
    45.  
    46.     public void pause() {
    47.         _state = eState.PAUSED;
    48.     }
    49.  
    50.     private IEnumerator launch() {
    51.         _state = eState.RUNNING;
    52.  
    53.         while (_coroutine.MoveNext()) {
    54.             yield return _coroutine.Current;
    55.  
    56.             while (_state == eState.PAUSED) {
    57.                 yield return null;
    58.             }
    59.          
    60.             if (_state == eState.FINISHED) {
    61.                 yield break;
    62.             }
    63.         }
    64.  
    65.         // Call the callback function if it was set
    66.         if (callbackEnd != null) {
    67.             callbackEnd();
    68.         }
    69.  
    70.         _state = eState.FINISHED;
    71.     }
    72. }
    Code (CSharp):
    1.     void Start() {
    2.         MyCoroutine co = new MyCoroutine(coroutineFunction());
    3.  
    4.         co.callbackEnd = functionCallback;
    5.         co.start(this);
    6.     }
    7.  
    It works well.

    I still need to find a solution if I wanna send my own classes through the RPC functions.

    Sbizz.
     
    Last edited: Oct 29, 2014
  5. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,066
    What do you want to send? As said, if you want to send values of some instance of your classes, use Custom Types as we do in CustomTypes.cs in the package. Just register your type according to the blueprint there and you should be able to send your data.
     
  6. Sbizz

    Sbizz

    Joined:
    Oct 2, 2014
    Posts:
    250
    I found a solution (I worked late yesterday, I forgot to tell you about that ;P Sorry!)

    Okay, all my "selectable objects" have a component called "AObject". They also have a component PhotonView. Conclusion: they all have a photonView variable and so a viewID.

    My problem was the following: when a player cast a spell, the function must take 2 parameters: the spell (ASpell) and the target (AObject). I was able to replace ASpell by the spellName (string) and find the ASpell within a list. But I didn't want to replace the AObject by a Vector3, because if the targeted player moves, the spell has to follow him. So I created a private static dictionary variable inside AObject and in the "Start" function I added the current object with his viewID and in the OnDestroy function I removed it. Now, I just have to send the viewID instead of the AObject.

    It works, but I'm gonna look after CustomTypes, it will be more clean to do that, I guess.

    Sbizz.
     
  7. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,066
    If you got it running this way, don't change to CustomTypes. They might be "more clean" technically but are some overhead for the de/serialization.
    If you know the target anyways, you could call the RPC directly on it's PhotonView. The RPC will fire on said target object.
    Of course, the usual concurrency issues make this more tricky: The networked object may become destroyed and the destroy message may arrive on the server before the RPC. Then, the RPC will not properly fire.
    If the target is not a networked object, this will not work apparently.

    So maybe it's not worth the effort to refactor.

    Glad you found it. Thanks for the update.