Unity c# Firing another GameObject's methods. Better way? - c#

I have TCP client (Unity c#) and server (WinForms app c#). I need my server sending some JSON commands, like this:
{ ""ObjName"": ""Cube_2"", ""Method"":""MoveLeft"", ""Delay"":0}
This certain command says to find GameObject "Cube_2" and fire method "MoveLeft".
When i recieve this from server, i convert it into my AOSCommand class:
public class AOSCommand
{
public string ObjName;
public string Method;
public int delay;
}
And then i do the following (which i think is not the best solution, so here is a question):
private void ProcessCommand(AOSCommand command)
{
GameObject cube = GameObject.Find(command.ObjName);
MonoBehaviour script = cube.GetComponent(command.ObjName.Split(new string[] {"_"}, StringSplitOptions.None)[0]) as MonoBehaviour;
script.Invoke(command.Method, command.delay);
}
How can i fire some method from AOSCommand.Method string in a better way?
The script attached to Cube_2 (and Cube_1 and may be attached to unknown count of other objects):
using UnityEngine;
public class Cube : MonoBehaviour {
private GameObject thisObj;
private void Start()
{
thisObj = this.gameObject;
}
public void MoveLeft()
{
thisObj.transform.Translate(new Vector3(1,0,0));
}
public void MoveRight()
{
thisObj.transform.Translate(new Vector3(-1, 0, 0));
}
}

It depends what you consider wrong.
You should have a single script that takes care of the parsing of the incoming data, this would remove the need to search for a component, it would always be the same.
Then you can have a dictionary of to replace the invoke call.
So your snippet turns into:
private void ProcessCommand(AOSCommand command)
{
GameObject cube = GameObject.Find(command.ObjName);
AOSDispatch dispatch = cube.GetComponent<AOSDispatch>()
if(dispatch == null){ return; } // or debug or exception
dispatch.Call(command);
}
this is on the main receiver. Then comes the script on the cubes:
public class AOSDispatch : MonoBehaviour
{
Dictionary<string, Action> dict;
void Start()
{
dict.Add("MoveLeft", MoveLeft);
dict.Add("MoveRight", MoveRight);
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
private void MoveLeft(){}
private void MoveRight(){}
}
This is not necessarily better, just my two cents on it.
EDIT: there was comment mentioning the json could contain the script type to know what script to use. I would not go this way. AOSDispatch will take care of the dispatching of the message.
Message says MoveLeft, AOSDispatch can either treat the info or forward to a movement controller:
public class AOSDispatch : MonoBehaviour
{
[SerializeField] private MoveController moveCtrl = null;
Dictionary<string, Action> dict;
void Start()
{
dict.Add("MoveLeft", this.moveCtrl.MoveLeft);
dict.Add("MoveRight", this.moveCtrl.MoveRight);
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
}
public class MoveController: MonoBehaviour
{
private void MoveLeft(){}
private void MoveRight(){}
}
there you go, message is forward and cleanly, the AOSDispatch does only the job it is meant to do, dispatch the AOS.
SECONDARY EDIT:
On second thought, here is an improved version.
Create a DispatchManager game object and add the following script:
public class AOSDispatch:MonoBehaviour
{
private IDictionary<string, AOSController> dict;
void Awake(){
this.dict = new Dictionary<string, AOSController>();
AOSController.RaiseCreation += ProcessCreation;
AOSController.RaiseDestruction += ProcessDestruction;
}
void OnDestroy()
{
AOSController.RaiseCreation -= ProcessCreation;
AOSController.RaiseDestruction -= ProcessDestruction;
}
private void ProcessCreation(AOSController controller){
this.dict.Add(controller.name, controller);
}
private void ProcessDestruction(AOSController controller){
AOSController temp= null;
if(this.dict.TryGetValue(controller.name, out temp) == true){
this.dict.Remove(name);
}
}
private void ProcessCommand(AOSCommand command)
{
AOSController controller = null;
if(this.dict.TryGetValue(command.ObjName, out controller) == true){
controller.Call(command);
return;
}
}
}
and then on the objects you have the AOSController that forwards the info as before (just renaming):
public class AOSController: MonoBehaviour
{
public static event Action<AOSController> RaiseCreation;
public static event Action<AOSController> RaiseDestruction;
[SerializeField] private MoveController moveCtrl = null;
Dictionary<string, Action> dict;
void Start()
{
if(RaiseCreation != null) { RaiseCreation(this); }
dict.Add("MoveLeft", this.moveCtrl.MoveLeft);
dict.Add("MoveRight", this.moveCtrl.MoveRight);
}
void OnDestroy()
{
if(RaiseDestruction != null) { RaiseDestruction(this); }
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
}
public class MoveController: MonoBehaviour
{
private void MoveLeft(){}
private void MoveRight(){}
}
On Awake, the Dispatch registers to the static event from the AOSController. In the AOSController.Start, the object triggers the event and passes itself to the AOSDispatch. That one adds it to the dictionary. On destruction, the AOSDispatch gets the event and removes the AOSController.
Now you have a collection that at any given time contains all the AOSController in the scene.
As a result, you don't need to perform a GameObject.Find since you can get the object from the dictionary (real fast process).

Related

PUN2: Specify an instanced player gameObject through RPC call

I'm aware you can't pass gameObject references through RPC calls since every client uses it's own unique references for every instanciation. Is there a way to refer to a specific gameObject on ALL clients via an RPC call?
Here's a sample of what i'm trying to accomplish
if(Input.GetKeyDown("r")){
PhotonView.RPC("ChangeReady", RPCTarget.AllBuffered, gameObject)
}
[PunRPC]
void ChangeReady(GameObject PLAYER) {
PlayerScript PSCRIPT = PLAYER.GetComponent<PlayerScript>();
if (PSCRIPT.READY)
{
PSCRIPT.GetComponent<PlayerScript>().READY = false;
}
else {
PSCRIPT.READY = true;
}
}
I am trying to make a ready check system for players so they can toggle ready on and off and the information is transmitted to the other clients. Not sure how to refer to the specific gameObject since as previously mentioned it's impossible to do in RPC.
How would I go about doing this?
There are a few ways of doing this, if all you want to do is set a ready state:
a) Use CustomProperties, which allows you to store player specific data in a key-value pairs. and then, you can locally check for these values on your player(s) GameObjects and change their behaviours with your RPC. here is an Example:
bool ready = (bool)PhotonNetwork.player.customProperties["Ready"];
Hashtable hash = new Hashtable();
hash.Add("Ready", true);
PhotonNetwork.player.SetCustomProperties(hash);
Follow this link for info on CustomProperties
b) Use OnPhotonInstantiate by implementing IPunMagicInstantiateCallback On your Monobehaviour
void OnPhotonInstantiate(PhotonMessageInfo info)
{
// e.g. store this gameobject as this player's charater in Player.TagObject
info.sender.TagObject = this.GameObject;
}
the TagObject helps you associate a GameObject with a particular player. So once they are instantiated, This is called automatically, and Your GameObject can be tied to the player and retrieved using Player.TagObject and Execute your ready state logic (Via RPC or any other way)
Follow this link for info about OnPhotonInstantiate
See Photon RPC and RaiseEvent => "You can add multiple parameters provided PUN can serialize them" -> See Photon - Supported Types for Serialization
=> No it is not directly possible.
Solution
You will always need a way to tell other devices which object you are referring to since they don't have the same refernences as you.
If you are referring to objetcs of your Photon Players, though you can instead send over the
PhotonNetwork.LocalPlayer.ActorNumber
and on the other side get its according GameObject via the ActorNumber.
There are multiple ways to do so.
I once did this by having a central class like
public class PhotonUserManager : IInRoomCallbacks, IMatchmakingCallbacks
{
private static PhotonUserManager _instance;
private readonly Dictionary<int, GameObject> _playerObjects = new Dictionary<int, GameObject>();
public static bool TryGetPlayerObject(int actorNumber, out GameObject gameObject)
{
return _instance._playerObjects.TryGetValue(actorNumber, out gameObject);
}
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
_instance = new PhotonUserManager();
PhotonNetwork.AddCallbackTarget(_instance);
}
public void OnCreateRoomFailed(short returnCode, string message){ }
public void OnJoinRoomFailed(short returnCode, string message){ }
public void OnCreatedRoom() { }
public void OnJoinedRoom()
{
Refresh();
}
public void OnLeftRoom()
{
_playerObjects.Clear();
}
public void OnJoinRandomFailed(short returnCode, string message){ }
public void OnFriendListUpdate(List<FriendInfo> friendList) { }
public void OnPlayerEnteredRoom(Player newPlayer)
{
Refresh();
}
public void OnPlayerLeftRoom(Player otherPlayer)
{
if(_playerObjects.ContainsKey(otherPlayer.ActorNumber))
{
_playerObjects.Remove(otherPlayer.ActorNumber);
}
}
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged){ }
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps){ }
public void OnMasterClientSwitched(Player newMasterClient){ }
private void Refresh()
{
var photonViews = UnityEngine.Object.FindObjectsOfType<PhotonView>();
foreach (var view in photonViews)
{
var player = view.owner;
//Objects in the scene don't have an owner, its means view.owner will be null => skip
if(player == null) continue;
// we already know this player's object -> nothing to do
if(_playerObjects.ContainsKey(player.ActorNumber)) continue;
var playerObject = view.gameObject;
_playerObjects[player.ActorNumber] = playerObject;
}
}
}
This automatically collects the GameObject owned by the joined players. There is of course one limitation: If the players spawn anything themselves and own it .. it might fail since then there are multiple PhotonViews per player => in that case use the one with the lowest NetworkIdendity. In Photon the viewIDs are composed of first digit = ActorNumber + 3 digits ids ;)
and later do
if(!PhotonUserManager.TryGetPlayerObject(out var obj))
{
Debug.LogError($"Couldn't get object for actor number {receivedActorNumber}");
}
As another option you can use OnPhotonInstantiate
public class SomeComponentOnYourPlayerPrefab : PunBehaviour
{
public override void OnPhotonInstantiate(PhotonMessageInfo info)
{
// only set if not already set in order to not
// overwrite the tag for a player that spawns something later
if(info.sender.TagObject == null)
{
info.sender.TagObject = gameObject;
}
}
}
and then later do
var obj = PhotonNetwork.LocalPlayer.Get(receivedActorNumber).TagObject;

Event to detect ActiveSelf

Is there a listener event to detect if an object is set to inactive from active state or to active from inactive state? I do not want to add it in Update as there will be multiple calls and it would affect my game's performance. So is there an alternative for this?
public GameObject Go_1;
public GameObject Go_2;
void Update () {
if (Go_1.activeSelf) {
} else if (Go_2.activeSelf) {
}
}
You could implement something using the Update method like e.g.
public class ActiveSelfWatcher : MonoBehaviour
{
private Dictionary<ActiveSelfProvider, bool> _lastActiveSelfState = new Dictionary<ActiveSelfProvider, bool>();
private void OnObjectBecameActive(GameObject obj)
{
Debug.Log($"{obj.name} became active!", this);
}
private void OnObjectBecameInactive(GameObject obj)
{
Debug.Log($"{obj.name} became inactive!", this);
}
private void Update()
{
// Iterate through all registered instances of ActiveSelfProvider
foreach(var provider in ActiveSelfProvider.Instances)
{
// pre-cache the GameObject reference
var obj = provider.gameObject;
// pre-cache the current activeSelf state
var currentActive = obj.activeSelf;
if(!_lastActiveSelfState.TryGetValue(provider))
{
// we don't know this provider until now
// TODO here you have to decide whether you want to call the events now once for this provider or not
// TODO otherwise it is only called for providers you already know and changed their state
}
if(currentActive != _lastActiveSelfState[provider])
{
// the state is not the one we stored for this instance
// => it changed its states since the last frame
// Call the according "event"
if(currentActive)
{
OnObjectBecameActive(obj);
}
else
{
OnObjectBecameInactive(obj);
}
}
// store the current value
_lastActiveSelfState[provider] = currentActive;
}
}
}
This is your watcher class you currently already have anyway.
Then on all the objects you want to be able to watch you use
public class ActiveSelfProvider : MonoBehaviour
{
private static readonly HashSet<ActiveSelfProvider> instances = new HashSet<ActiveSelfProvider>();
public static HashSet<ActiveSelfProvider> Instances => new HashSet<ActiveSelfProvider>(instances);
private void Awake()
{
// register your self to the existing instances
instances.Add(this);
}
private void OnDestroy()
{
// remove yourself from the existing instances
instances.Remove(this);
}
}
If this is "efficient enough" for you use case you would have to test ;)
If you want to go super fancy, someone once made a Transform Interceptor .. a quite nasty hack which on compile time overrules parts of the Unity built-in Transform property setters to hook in callbacks.
One probably could create something like this also for SetActive ;)
Note: Typed on smartphone but I hope the idea gets clear
Yes, try with OnEnable and OnDisable.
// Implement OnDisable and OnEnable script functions.
// These functions will be called when the attached GameObject
// is toggled.
// This example also supports the Editor. The Update function
// will be called, for example, when the position of the
// GameObject is changed.
using UnityEngine;
[ExecuteInEditMode]
public class PrintOnOff : MonoBehaviour
{
void OnDisable()
{
Debug.Log("PrintOnDisable: script was disabled");
}
void OnEnable()
{
Debug.Log("PrintOnEnable: script was enabled");
}
void Update()
{
#if UNITY_EDITOR
Debug.Log("Editor causes this Update");
#endif
}
}

How to check that the script has loaded data and render it once in another script Unity

First time on StackOverflow and my english is very bad, so I apologize if the question is fool or poorly formulated.
I have a script that loads Scriptable Object data and creates a List with other objects based on it.
public class AntContainer : MonoBehaviour
{
private List<AntType> _types;
public IEnumerable<AntType> Types { get => _types; }
private void Start()
{
_types = new List<AntType>();
List<AntTypeData> data = Resources.LoadAll<AntTypeData>("ScriptableObjects/AntTypes").ToList();
data = data.OrderBy((x) => x.Price).ToList();
foreach (var type in data)
{
_types.Add(new AntType(type));
}
}
}
Then I need to draw this List in the UI script
public class AntShopPanel : MonoBehaviour
{
[SerializeField] private GameObject _antShopElementTemplate;
[SerializeField] private Transform _parent;
[SerializeField] private AntNursery _nursery;
[SerializeField] private AntContainer _container;
private void Start()
{
if (_container != null)
{
foreach (var type in _container.Types)
{
GameObject AntButton = Instantiate(_antShopElementTemplate, _parent);
AntShopElementPresenter presenter = AntButton.GetComponent<AntShopElementPresenter>();
presenter.Present(type);
presenter.SetBuyButtonOnClick(() => _nursery.AddEgg(type));
}
}
}
}
As you can see, both scripts perform actions in the Start method, so if the UI script is initialized earlier and the data is not loaded, then there will be nothing to draw.
How can I make sure that the data is loaded before the UI script request data? Or how can this problem be solved in a different way?
It's a good rule of thumb to do all of your initialization steps inside of Unity's Awake function.
Changing your AntContainer.Start() method to Awake() instead will ensure that everything is initialized before all of your Start functions start getting called.
Like so:
public class AntContainer : MonoBehaviour
{
private List<AntType> _types;
public IEnumerable<AntType> Types { get => _types; }
private void Awake()
{
_types = new List<AntType>();
List<AntTypeData> data = Resources.LoadAll<AntTypeData>("ScriptableObjects/AntTypes").ToList();
data = data.OrderBy((x) => x.Price).ToList();
foreach (var type in data)
{
_types.Add(new AntType(type));
}
}
}

Unity 3d pass bool variable between two objects

how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html

Are command patterns efficient enough, and what's the concrete benefit of it?

So today I am learning and implementing Command Patterns for handling input and movement for objects.
So my question is:
Am I getting the implementation of Command Patterns right or do I need to modify it? If so, can somebody give me a little example on to improve it.
I know that it improves code reusability. But what difference does it make when I just use a simple MovementScript.cs to my game object component? Wouldn't it just be the same and take less time to write rather than making a whole Command Pattern?
The one I attached to the gameobject is only the InputHandler. Here's my code which involves moving an object:
This is my Input Handler or as far as I know as The Client
public class InputHandler : MonoBehaviour
{
GameObject theObject;
public Command buttonA, buttonD;
public float acceleration, maxSpeed;
Movement moves;
void Awake()
{
theObject = gameObject;
moves = new Movement(theObject, acceleration, maxSpeed);
}
void Start()
{
buttonA = new MoveLeft(moves);
buttonD = new MoveRight(moves);
}
void Update()
{
HandleInput();
}
public void HandleInput()
{
if (Input.GetKey(KeyCode.A))
{
buttonA.Execute();
}
else if (Input.GetKey(KeyCode.D))
{
buttonD.Execute();
}
}
}
The Command abstract class
public abstract class Command
{
//The Receiver of the command..
protected IReceiver receiver = null;
public Command(IReceiver receiver)
{
this.receiver = receiver;
}
public abstract void Execute();
}
The Receiver class (where I implemented the logic, which is the movement)
public class Movement : IReceiver
{
public ACTION_LIST currentMoves;
private GameObject theObject;
private float acceleration;
private float maxspeed;
public Movement(GameObject theObject, float acceleration, float maxspeed)
{
this.theObject = theObject;
this.acceleration = acceleration;
this.maxspeed = maxspeed;
}
public void Action(ACTION_LIST moves)
{
if (moves == ACTION_LIST.MOVERIGHT)
MoveRight(theObject);
else if (moves == ACTION_LIST.MOVELEFT)
MoveLeft(theObject);
}
public void MoveRight(GameObject obj)
{
obj.GetComponent<Rigidbody2D>().AddForce(new Vector2(acceleration, obj.GetComponent<Rigidbody2D>().velocity.y));
}
public void MoveLeft(GameObject obj)
{
obj.GetComponent<Rigidbody2D>().AddForce(new Vector2(-acceleration, obj.GetComponent<Rigidbody2D>().velocity.y));
}
}
Interface of receiver, to make things easier..
public enum ACTION_LIST
{
MOVERIGHT,
MOVELEFT
}
public interface IReceiver
{
void Action(ACTION_LIST moves);
}
The concrete command. I only posted 1 of the movements..
public class MoveRight : Command
{
public MoveRight(IReceiver receiver):base(receiver)
{
}
public override void Execute()
{
receiver.Action(ACTION_LIST.MOVERIGHT);
}
}
I disagree with Joe Blow and others saying that Unity is a bunch of scripts and not OOP. I use main script with single entry point and create all objects and components dynamically. I even use interfaces, mock and unit tests.
So, using Command pattern is ok. But I don't see a reason to use it in your case. Command pattern could be very handy in case when you need a stack of commands to be able Do() and Undo() your commands (Editor, Strategy game). Please read more here: http://gameprogrammingpatterns.com/command.html

Categories