Photon Pun RPC Question

Hi guys,

I need an advice. Below RPC is doing nothing (not even an error lol), it works for one client but that’s it. I am not sure if I am missing a huge thing or if I just use the RPC on the wrong method, but yes I tried and tried and nothing.

On the Gameobject there is a PhotonView which I am ofc calling in the Script.

public void UpdateTheHealthOfKing()
{
view.RPC("UpdateKingHealth", RpcTarget.All);
}


[PunRPC]
public void UpdateKingHealth()
{
if (isKing == true)
{
kingHealth.text = health.ToString();
}
}

Can’t this work for an obious reason that I just didnt understand?

Cheers

Put Debug.Log in both methods and make sure both get called as planned.
The call of the RPC looks fine otherwise. Is the networked object something you instantiated with PhotonNetwork.Instantiate?

hmm

the object is in my scene from the beginning so I do not really instantiate it via code, it’s just “there”

could that be a problem? I tried to RPC various other things in my scene also with the same effect that nothing really happened

Networked scene objects can run RPCs just like instantiated ones, this is fine.
RPCs only work when the client joined a room.

Have a look at the Basics Tutorial, to get the setup right:

Hi Tobias,

I checked the documentations, I watched videos on YoutTube and I even finished a (short) Udemy course building a small MP game with Photon, so it’s not that I do not try…also, I managed to get some things done already, so I dont think the setup is wrong:

  • I can open a room
  • I can join this room as the second player
  • I do have a resource folder :wink:
  • Objects in the scene do have the View component and if I for example move a character of player 1, he moves for player 2 also
  • It is a turn-based game and I got the turn switch running, so if a player preses space it switches turn for both P1 and P2

So I think the basics are there. I am very, very sure it is a minor detail I am missing (like always), but those are the hardest to find out :face_with_spiral_eyes:

So I will go into detail now on what I did and how it looks, and have the slight hope you actually read it and in the end can say “jo why didnt you just…”. An when this happens, I will rename one of my kids to Hobbes to honor you :roll_eyes:

This is the UNIT object the Script “Unit” is attached to"

This is the text it’s all about and that should update

This is the health text object again but well in the hierarchy
6900839--807773--goalie3.PNG

In the Unit Script, I think interesting are:

public Text kingHealth; - the Text that gets updated depending on damage

In my start method I also call UpdateKingHealth(); and ofc I get the PhotonView component

Then like said there is the actual RPC

 public void UpdateTheHealthOfKing()
    {
        view.RPC("UpdateKingHealth", RpcTarget.All);
    }


    [PunRPC]
    public void UpdateKingHealth()
    {
        if (isKing == true)
        {
            kingHealth.text = health.ToString();
        }
    }

and I do call UpdateKingHealth (and accordingly, enemy.UpdateKingHealth at various places in the Scripts). And of course, there is a

public int health;

to actually set the health in the inspector!

I really hope that somewhere there is just that little bug I didnt understand, otherwise I probably spend the next weeks searching :sunglasses:

Wallo f Text, End!

Did you do add Debug.Log to both methods and confirmed that a) the calling method is executed and b) the RPC is being called (or not)?

You got more than one PhotonView in that hierarchy.
Are you sure you call the RPC method on the correct “level” / object?

Yes, I added

 public void UpdateTheHealthOfKing()
    {
        Debug.Log("Hi");
        view.RPC("UpdateKingHealth", RpcTarget.All);
    }


    [PunRPC]
    public void UpdateKingHealth()
    {
        if (isKing == true)
        {
            Debug.Log("Hi");
            kingHealth.text = health.ToString();
        }
    }

And it gave me this

6901337--807887--debug.PNG

so I think it got called perfectly.

I think that what you mentioned is the right way but I cant get my head around it. In the Udemy course I did, I had one game health object for both players, put the script on THAT object, called RPC, Heureka. However in this game I am facing the problem that it is developed as a local multiplayer game (and works fine like that), but this also means there are a lot of objects and scripts that I somehow smashed together over the last many weeks and that makes it pretty complicated for me too understand how to call it on the correct object.

The “Unit” Script with the Rpc Method above is on all Units as the name suggests. However it is NOT on the actual text that should by synchronized (or on its canvas or whatever) - not like in the tutorials where it is always on the same object that does something.

I hope my babbleing is understandable and thanks that you still bear with me!

So, we established that the RPCs are being called locally and remotely. This is nice.

Yes, it’s tricky to move from local multiplayer to online. I can’t follow enough to help though, sorry. At the moment, I am out of ideas.

The question would be, where does kingHealth.text point at? You can pass this to Debug.Log, after the string and in the editor, the log entry will highlight the target object on click (in the console)…

I know it’s hard without actually seeing the project

Well, it is frustrating of course can`t deny that, but in the end, I guess all I can do is try everything out until something good happens eh

Thanks that you tried, appreciated!

So, for the first time in the project, I will have to surrender, step back for a while, train with some other, smaller games and hopefully come back at a later point when I am better, as I feel like I can’t put more time into this without feeling I am not progressing at all. Hopefully I will be able to finish this in the future but for now I guess I just can’t.

For whatever reason, the Debugs only are activated when plaing in the Unity Game View, not in the Standalone, and whatever I do that stupid Text is not updated for both Client and Server, only for one.

As it doesn’t cost me anything, Ill just add the full code here, can’t hurt and mabye in a different dimension it helps someone with something (or someone is super bored) :smile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;

public class Unit : MonoBehaviour
{

    public AudioSource source;
    public AudioClip goal;
    public AudioClip move;

    public GameObject victoryPanel;

    public Animator animator;

    public bool selected;
    GameMaster gm;

    public Text kingHealth;
    public bool isKing;

    public int tileSpeed;
    public bool hasMoved;
    public float moveSpeed;

    public int playerNumber;

    public int attackRange;
    List<Unit> enemiesInRange = new List<Unit>();
    public bool hasAttacked;

    public GameObject weaponIcon;

    public int health;
    public int attackDamage;
    public int defenseDamage;
    public int armor;

    private Animator camAnim;
    public GameObject deathEffect;

    public DamageIcon damageIcon;

    SpriteRenderer sprite;

    GameObject obj;

    PhotonView view;


    private void Start()
    {
        source = GetComponent<AudioSource>();
        animator = GetComponent<Animator>();
        gm = FindObjectOfType<GameMaster>();
        camAnim = Camera.main.GetComponent<Animator>();
        UpdateKingHealth();
        sprite = GetComponent<SpriteRenderer>();
        obj = GameObject.FindGameObjectWithTag("GameMaster");
        view = GetComponent<PhotonView>();
    }


    public void UpdateTheHealthOfKing()
    {
        PhotonView photonView = PhotonView.Get(this);
        photonView.RPC("UpdateKingHealth", RpcTarget.All);
    }


    [PunRPC]
    void UpdateKingHealth()
    {
        if (isKing == true)
        {
            kingHealth.text = health.ToString();
        }
    }

    private void OnMouseOver()
    {
        if (Input.GetMouseButtonDown(1))
        {
            gm.ToggleStatsPanel(this);
        }
    }


    private void OnMouseDown()
    {
        ResetWeaponIcons();
        if (selected == true)
        {
            selected = false;
            gm.selectedUnit = null;
            gm.ResetTiles();
        }
        else
        {
            if (playerNumber == gm.playerTurn)
            {
                if (gm.selectedUnit != null)
                {
                    gm.selectedUnit.selected = false;
                }

                selected = true;
                gm.selectedUnit = this;

                gm.ResetTiles();
                GetEnemies();
                GetWalkableTiles();
            }
        }

        Collider2D col = Physics2D.OverlapCircle(Camera.main.ScreenToWorldPoint(Input.mousePosition), 0.15f);
        Unit unit = col.GetComponent<Unit>();

        if (gm.selectedUnit != null)
        {
            if (gm.selectedUnit.enemiesInRange.Contains(unit) && gm.selectedUnit.hasAttacked == false)
            {
                gm.selectedUnit.Attack(unit);
            }
        }
    }

    public void Attack(Unit enemy)

    {
        animator.SetTrigger("Attack");
        camAnim.SetTrigger("Shake");
        hasAttacked = true;

        if (hasAttacked == true)
        {
            hasMoved = true;
            gm.ResetTiles();
            sprite.color = new Color(1, 0, 0, 1);
        }

        int enemyDamage = attackDamage - enemy.armor;
        int myDamage = enemy.defenseDamage - armor;

        if (enemyDamage >= 1)
        {
            DamageIcon instance = Instantiate(damageIcon, enemy.transform.position, Quaternion.identity);
            instance.Setup(enemyDamage);
            enemy.health -= enemyDamage;
            Debug.Log("oisdjf");
            enemy.UpdateTheHealthOfKing();
        }

        if (transform.tag == "Archer" && enemy.tag != "Archer")
        {
            if (Mathf.Abs(transform.position.x - enemy.transform.position.x) + Mathf.Abs(transform.position.y - enemy.transform.position.y) <= 1)
            {
                if (myDamage >= 1)
                {
                    DamageIcon instance = Instantiate(damageIcon, transform.position, Quaternion.identity);
                    instance.Setup(myDamage);
                    health -= myDamage;
                    Debug.Log("oisdjf");
                    UpdateTheHealthOfKing();
                }
            }
        }
        else
        {
            if (myDamage >= 1)
            {
                DamageIcon instance = Instantiate(damageIcon, transform.position, Quaternion.identity);
                instance.Setup(myDamage);
                health -= myDamage;
                Debug.Log("oisdjf");
                UpdateTheHealthOfKing();
            }
        }
        if (enemy.health <= 0)
        {
            Instantiate(deathEffect, enemy.transform.position, Quaternion.identity);
            Destroy(enemy.gameObject);
            GetWalkableTiles();
            gm.RemoveStatsPanel(enemy);
            if (enemy.isKing == true)
            {
                enemy.victoryPanel.SetActive(true);
                Time.timeScale = 0f;
            }
        }
        if (health <= 0)
        {
            Instantiate(deathEffect, transform.position, Quaternion.identity);
            gm.ResetTiles();
            gm.RemoveStatsPanel(this);
        }
        gm.UpdateStatsPanel();
        obj.GetComponent<GameMaster>().currentTime -= 10;
    }

    void GetWalkableTiles()
    {
        if (hasMoved == true)
        {
            return;
        }

        foreach (Tile tile in FindObjectsOfType<Tile>())
        {
            if (Mathf.Abs(transform.position.x - tile.transform.position.x) + Mathf.Abs(transform.position.y - tile.transform.position.y) <= tileSpeed)
            {
                if (tile.IsClear() == true)
                {
                    tile.Highlight();
                }
            }
        }
    }
    public void GetEnemies()
    {
        enemiesInRange.Clear();
        foreach (Unit unit in FindObjectsOfType<Unit>())
        {
            if (Mathf.Abs(transform.position.x - unit.transform.position.x) + Mathf.Abs(transform.position.y - unit.transform.position.y) <= attackRange)
            {
                if (unit.playerNumber != gm.playerTurn && hasAttacked == false)
                {
                    enemiesInRange.Add(unit);
                    unit.weaponIcon.SetActive(true);
                }
            }
        }
    }

    public void ResetWeaponIcons()
    {
        foreach (Unit unit in FindObjectsOfType<Unit>())
        {
            unit.weaponIcon.SetActive(false);
        }
    }

    public void ResetUnitColors()
    {
        foreach (Unit unit in FindObjectsOfType<Unit>())
        {
            sprite.color = new Color(255, 255, 255, 255);
        }
    }

    public void Move(Vector2 tilePos)
    {
        animator.SetBool("Speed", true);
        gm.ResetTiles();
        StartCoroutine(StartMovement(tilePos));
        obj.GetComponent<GameMaster>().currentTime -= 10;
    }
    IEnumerator StartMovement(Vector2 tilePos)
    {
        source.clip = move;
        source.Play();

        while (transform.position.x != tilePos.x)
        {
            transform.position = Vector2.MoveTowards(transform.position, new Vector2(tilePos.x, transform.position.y), moveSpeed * Time.deltaTime);
            yield return null;
        }

        source.Stop();

        while (transform.position.y != tilePos.y)
        {
            transform.position = Vector2.MoveTowards(transform.position, new Vector2(transform.position.x, tilePos.y), moveSpeed * Time.deltaTime);
            yield return null;
        }
        hasMoved = true;
        sprite.color = new Color(1, 0, 0, 1);
        ResetWeaponIcons();
        GetEnemies();
        gm.MoveStatsPanel(this);
        animator.SetBool("Speed", false);
    }
}

Are the networked objects for both players loaded with the scene? No PhotonNetwork.Instantiate for a prefab?
I wonder, cause the isKing is checked in various places but never set on instantiation (or not here…).
If only one player isKing, then it’s no wonder that the text only updates for one user…

Without knowing the project / scenes, it’s really tricky to help, so I can only agree: Build a minimal test project.
In doubt, you could store the health in the Custom Player Properties, too. Or make it part of the observed components and send the value along with position updates in OnPhotonSerializeView.

Hi TomsTales.
I am stuck with the same error as you are facing. Can you please let me know how did you finally resolve it?