Unity Photon - How to sync player stats/info - c#

I'm trying to create a co-operative first person multiplayer game and I think I've misunderstood how Photon and RPC is supposed to work.
I have a working first person multiplayer project where player avatars can see each other and move around as the player avatars have a photon view photon transform view.
As part of the player controller script (handles player movement only for local player), which is attached to the player avatar prefab, I have an attribute:
public PlayerCharacterInfo myCharacter;
which contains all of the player's info and stats including name, level, currentHP, maxHP, etc which is added to the player avatar's PlayerController as it the player enters the room and their avatar is instantiated.
When I join a multiplayer room, each player can only see their own stats. For example, in the editor when running the game, only the values of the local editor player avatar are shown on the Player Controller myCharacter.
I can see that the other player avatars have an instantiated myCharacter on them but no values are shown.
At this point, I figure I just need to have an RPC function like this in my player controller that just reassigns the myCharacter to itself so it can be broadcasted to all:
[PunRPC]
void RPC_AddCharacter(PlayerCharacterInfo paramCharacter)
{
myCharacter = paramCharacter;
}
in void Start():
if(PV.IsMine)
{
PV.RPC("RPC_AddCharacter", RpcTarget.All, myCharacter);
}
But this does not let me see the myCharacter values for non-local player avatars.
My goal is to first show the name of the player avatar that you're looking at via a raycast, but I cannot even get this data to sync.
Have I got the right idea here, but I'm executing it wrong?
Or is this just not how Photon works?
Do I need to instead have each player's PlayerCharacterInfo stored in the room controller for each player or something like that?

You want to use Custom Properties.
Photon's Custom Properties consist of a key-values Hashtable which you
can fill on demand. The values are synced and cached on the clients,
so you don't have to fetch them before use. Changes are pushed to the
others by SetCustomProperties().
How is this useful? Typically, rooms and players have some attributes
that are not related to a GameObject: The current map or the color of
a player's character (think: 2d jump and run). Those can be sent via
Object Synchronization or RPC, but it is often more convenient to use
Custom Properties.

Solved somewhat! I removed my RPC call that was trying to to sync my custom PlayerCharacterInfo object and instead made a call and new string 'myCharacterName' in PlayerController to hold just the player's name.
The new RPC call takes the local player's myCharacter.characterName (from PlayerCharacterInfo) and sets the myCharacterName and syncs to all.
This works and my editor player can see the myCharacterName of other players! However I was no closer to syncing my entire myCharacter class.
Now that I had the right idea about how this works, I was able to research a bit more and found that Photon can't sync custom classes normally, but you could serialize your class to be able to send it:
https://doc.photonengine.com/en-us/realtime/current/reference/serialization-in-photon
However reading this made me realize that I probably don't need to sync my entire player data (it contains not only health and status, but inventory, equipment, quest, etc info) and that I'm probably better off just syncing separate data types in separate calls.
This thread helped too:
https://forum.photonengine.com/discussion/880/custom-class-object-sent-over-rpc

Related

Unity/C#: Resetting a whole script back to it "Original" State

I'm working a game in Unity and I've got a Script where I save a lot of values in multiple variables, which I then use in other scripts. You could say its my GameState. The Script itself is not a GameObject, it purely exists to save values. When I start my game the "GameState" has some basic values like Name, TeamName, Money and tons of more variables which are static and filled with pre-set values.
Now comes my problem. If the player plays through the game and picks some options, functions get triggered which change the values in the GameState, like for example he'll receive more money, so the value for money in the GameState changes. But the player also has the option to completely "restart" the game by going back to the main menu (where I use a LoadScene Function). Problem is that the values in the GameState remain changed when he goes back, so when he starts a new game, he doesn't got the pre-set values, but the ones from his last game.
So my question would be, is there an easy way to reset my GameState completely to its original values? I know I could save the default values somewhere and then make a check to see if the game is reloaded to then use them, but I've already got like 60-70 variables in there and don't really want to create another 60-70 just for the default values (unless there is no other option). So does anyone have an idea how I could do that?
I don't think showing the code of the GameState does much, since its really just looking like:
public class GameState
{
//Team
public int TeamID;
public string TeamName;
public string TeamColor;
etc...
}
GameState is a class to contain data. An easy way to create default is to serialize it : add [System.Serializable] on top of the class declaration.
Now you can have say an object in your main scene called default values which has a public/serialized field of type GameState. You can set those in the editor save the scene and bam. Now to reset all the values to default you just copy the default to the current/active set of data.
If you want to expand a bit on that you can also turn the class into a scriptableObject but I don't think you need that.

How to synchronized a canvas with Photon Engine in Unity

I'm trying to synchronize a canvas with photon engine so every player can see it. This canvas will be kind of a tv that any player can turn on and the rest can watch it. I could synchronized a cube adding the PhotonView and the PhotonRigidBody components to the prefab but when I tried the same with the canvas it didn't work at all.
Can anyone tell me what components are required to do this and if it needed it what should I handle with an extra script (i.e transfer ownership).
There is nothing special about the canvas, but it could be locked in place.
There are two solutions I have for you:
Observable Component:
You could write a custom observable component, and add it to the PhotonView:
To make use of this function, the script has to implement the IPunObservable interface.
public class CustomObservable : MonoBehaviourPunCallbacks, IPunObservable
{
[SerializeField] PlayerController playerController;
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
stream.SendNext(playerController.playerNumber);
stream.SendNext(playerController.playerScore);
}
else
{
playerController.playerNumber = (int)stream.ReceiveNext();
playerController.playerScore = (float)stream.ReceiveNext();
}
}
}
Custom Properties:
You could also use custom properties to sync the data across all players.
Photon's Custom Properties consist of a key-values Hashtable which you can fill on demand. The values are synced and cached on the clients, so you don't have to fetch them before use. Changes are pushed to the others by SetCustomProperties().
How is this useful? Typically, rooms and players have some attributes that are not related to a GameObject: The current map or the color of a player's character (think: 2d jump and run). Those can be sent via Object Synchronization or RPC, but it is often more convenient to use Custom Properties.
PhotonNetwork.CurrentRoom.SetCustomProperties(Hashtable propsToSet)
You can write a script that uses Photons callback, and updated the UI elements.
OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)

How to collect gold coin only once in a level

I have some levels in my game. Some of the levels contain a gold coin. I need the player to be able to collect it only once. If the user plays the same level again the coin should not appear again. (I'm using unity and c#)
Here the Script attached to the coin:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Goldcoin : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
SaveManager.Instance.state.goldcoin++;
SaveManager.Instance.Save();
Destroy(gameObject);
}
}
}
What you want is data persistance between scenes.
Data persistence means that even if you close the app, or change the scene, this data will persist, won't be deleted.
If you want persistance only in the same program execution, you can have an object that won't be destroyed on every Scene load, using DontDestroyOnLoad.
But if you want to keep this information between multiple executions of the app you need something else, like a file.
You can store the state of the current scene (or even the whole game) in a multiple ways, the easyiest one is:
Create a Serializable class that will keep the information of the scene/level.
Serialize that class into a file (like a JSON) when you want to save changes.
Deserialize that file when you restart the game, to convert the information on the file to your virtual class, again.
If you are not familiar with JSON's, you can check my JsonManager Repo.

How to save a float between scenes in unity

Hi there I am very new to code and I'm really struggling. I'm just trying to create a simple bet system for a blackjack game, in which you place your bet and the value is saved then it gets carried into my next scene which would be the game scene. So basically I created a button in a scene called BetScene when the button is pressed it adds 25 to the betamount and removes 25 from the playeramount. Once the player is happy with the money they placed they press the second button PlaceBet. When that button gets pressed the game scene loads so what I want to do is save the amount that was placed and display it in the game scene. Hope this makes sense I really need help I'm really trying to learn thanks
Okay, so first: This scenario does not sound like you should use multiple scenes, so I would say "reconsider your architecture and scene flow in this case." But I am also here, to answer your question, so let's do that instead.
A great way of sharing data between scenes is using a ScriptableObject as a data container. You simply create the class of the data you want to use in multiple scenes and then create an instance of your ScriptableObject as an asset in your Assets-folder.
This asset can then be assigned to all components that need to use it for data transfer. It is basically like a texture that is painted on in one scene and read from in another scene.
You can even improve on this by creating a kind of "data binding" using these ScriptableObject assets as variables. There is a great talk about that topic from 2017 on Unitys YouTube channel: Unite Austin 2017 - Game Architecture with Scriptable Objects
Solution 1: Using PlayerPrefs.
PlayerPrefs.SetFloat("HIGHSCORE", 123.45f);
float highscore = PlayerPrefs.GetFloat("HIGHSCORE");
Solution 2: Using Static Instance
public class MyClass: MonoBehavior {
public static MyClass Instance;
public float highscore;
void Awake() {
Instance = this;
DontDestroyOnLoad(this);
}
}
...
public class YourClass: MonoBehavior {
void YourFunction() {
print(MyClass.Instance.highscore);
}
}

Unity Stitching meshes together

I've been thinking on this for a few days and have tried a few different things and have googled quite a bit. Iv specifically looked at this thread alot
http://forum.unity3d.com/threads/stitch-multiple-body-parts-into-one-character.16485/
But im confused, and not sure if this 100% what i wanted.
Im just trying to add a new mesh to an already existing gameobject with its own mesh. That runs off the same bone structure or animator, the object being added has the required bones of the position its being pasted at.
https://gyazo.com/19778b3c73ef9a749c8cc338f7e49d79
Thats the object im trying to add onto my player. I tried taking the mesh directly off of it and then linking it to the same bone structure as the player being animated
When the object is created and imported with fuse/mixamo it adds the object as separate mesh objects on a parent player
https://gyazo.com/55ce7442dc186756da4ff149ac3543e5
So if i was to disable the armor mesh id be left with this
https://gyazo.com/09ffd7c7721f46e6980f895a1a873749
But i tried importing my character without the armor on it. Then i opened the character with the player and armor in blender and deleted the player and left the bone structure and the armor and then saved that as a separate fbx and imported that into unity and am pasting the new armor fbx on the original player in an attempt for it too animate the same was it originally was from the mixmo object. But it doesnt, cant even get it to import in Tpose. And when i configure it myself it wont save the configuration and just keeps going back to where it was.
I suppose i should.... only have the bones required on the armor. Then do some type of game logic to figure out what bones are the same name on the armor object as is being added to the player and replacing the player bones with the bone of the armor? But i feel like if i wanted to do it this way, then i need the player split into pieces so that it knows what parts to replace of the player. Or is there a way i can keep the player together and just make the armor follow the bones of the player, i guess would be the easiest thing to do
Im pretty sure the link i posted is pretty much waht i want. I guess i just dont understand it fully and wanted someone to help me better understand it
Ok so, this post is not going to be explaining how the code works but how to get the code TOOOOO work. Masterprompt explains the code well enough here. If you want to understand how the code works follow that link.
Anyways, onto what to do to go about merging 2 objects together to make one object that runs off the animation of the base object.
Example uses could be
Creating a person limb from limb.
Add armor too a base player
The meat and bones of how i get this too work without having a graphic artist to create my assets for me is using fuse and mixamo. I highly suggest these programs. You can still easily get this too work without those programs, but i will be explaining how to do it as if you are using it.
Ok so, download Adobe fuse and create your base player. (preferably naked)
Now save that player as whatever you would like (I used Main_Player_Naked)
Upload this player to mixamo for auto rigging, after downloading the model from mixamo import him into unity. (At this point im assuming you know how to set the model up as a humanoid in unity)
Now that your player is created and imported into unity go ahead and animate him or do whatever you want with him. Whatever you do to him will work fine with the new armor/hair/beard whatever you are adding to your player.
Ok, now time to create the armor we are going to add. Go ahead and reopen your naked player in fuse and recreate him with the new asset you want him to have (Dont change any body shape sizes, just add the new object). (Don't worry we're not switching the whole model out, this is just so we don't have to rig the new asset by ourselves) Now that you have the new asset looking nice, go ahead and follow the first steps of creating the naked played. So save your newly created character and import to mixamo for auto-rigging. Now download and import the new player into unity. Delete all the objects off the player except the new asset (Which is on a separate mesh that the naked player that's under the object)
Now we are going to use the code from Masterprompts post
I've rewritten it, so here's my new code
using UnityEngine;
using System.Collections;
public class CreatePlayer : MonoBehaviour
{
public GameObject objPlayer;
public GameObject objLimb;
public GameObject objAdded;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.H))
AddLimb(objLimb, objPlayer);
if (Input.GetKeyDown(KeyCode.J))
Destroy(objAdded);
}
//
void AddLimb(GameObject BonedObj, GameObject RootObj)
{
var BonedObjects = BonedObj.gameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (SkinnedMeshRenderer SkinnedRenderer in BonedObjects)
{
ProcessBonedObject(SkinnedRenderer, RootObj);
}
}
private void ProcessBonedObject(SkinnedMeshRenderer ThisRenderer, GameObject RootObj)
{
/* Create the SubObject */
var NewObj = new GameObject(ThisRenderer.gameObject.name);
NewObj.transform.parent = RootObj.transform;
/* Add the renderer */
NewObj.AddComponent<SkinnedMeshRenderer>();
var NewRenderer = NewObj.GetComponent<SkinnedMeshRenderer>();
/* Assemble Bone Structure */
var MyBones = new Transform[ThisRenderer.bones.Length];
for (var i = 0; i < ThisRenderer.bones.Length; i++)
MyBones[i] = FindChildByName(ThisRenderer.bones[i].name, RootObj.transform);
/* Assemble Renderer */
NewRenderer.bones = MyBones;
NewRenderer.sharedMesh = ThisRenderer.sharedMesh;
NewRenderer.materials = ThisRenderer.materials;
objAdded = NewObj;
}
private Transform FindChildByName(string ThisName,Transform ThisGObj)
{
Transform ReturnObj;
if( ThisGObj.name==ThisName )
return ThisGObj.transform;
foreach (Transform child in ThisGObj)
{
ReturnObj = FindChildByName( ThisName, child );
if( ReturnObj )
return ReturnObj;
}
return null;
}
}
This was very quickly thrown together. I suggest making it nicer.
But just add the naked player too objPlayer and the asset to add to objLimb.
Run your game and pushing 'H' creates the asset and 'J' deletes it.

Categories