Duplicated objects are not working 'per instance' when I intended them to do [duplicate] - unity3d

This question already has answers here:
How to detect click/touch events on UI and GameObjects
(4 answers)
Closed 5 years ago.
using UnityEngine;
public class LinkEnd : MonoBehaviour
{
public GameObject linkTarget;
private PointEffector2D effector;
private CircleCollider2D contact;
private AimSystem aimer;
private float distFromLink = .2f;
public bool connected;
private void Start()
{
aimer = GetComponent<AimSystem>();
}
private void Update()
{
SyncPosition();
ReactToInput();
}
public void ConnectLinkEnd(Rigidbody2D endRB)
{
HingeJoint2D joint = GetComponent<HingeJoint2D>();
if (GetComponent<HingeJoint2D>() == null)
{
joint = gameObject.AddComponent<HingeJoint2D>();
}
joint.autoConfigureConnectedAnchor = false;
joint.connectedBody = endRB;
joint.anchor = Vector2.zero;
joint.connectedAnchor = new Vector2(0f, -distFromLink);
}
private void SyncPosition()
{
if (linkTarget != null)
{
if (Vector2.Distance(transform.position, contact.transform.position) <= 0.1f)
{
connected = true;
effector.enabled = false;
contact.usedByEffector = false;
}
}
if (connected)
{
GetComponent<Rigidbody2D>().isKinematic = true;
GetComponent<Rigidbody2D>().position = linkTarget.transform.position;
}
else
GetComponent<Rigidbody2D>().isKinematic = false;
}
private void ReactToInput()
{
if (Input.GetKeyUp(KeyCode.Mouse0) || Input.GetKey(KeyCode.Mouse1))
{
connected = false;
}
}
public void OnTriggerEnter2D(Collider2D collision)
{
if (collision.GetComponent<PointEffector2D>() != null)
{
connected = true;
linkTarget = collision.gameObject;
effector = linkTarget.GetComponent<PointEffector2D>();
contact = linkTarget.GetComponent<CircleCollider2D>();
}
}
public void OnTriggerExit2D(Collider2D collision)
{
connected = false;
contact.usedByEffector = true;
effector.enabled = true;
}
}
This is an object that pins its position to another mobile object on collision, and it's supposed to stay that way until it's 'detached' by player action.
It's working almost fine, but it's not working 'per instance.'
Whether this object is a prefab or not, ReactToInput() is affecting all instances of it unlike how I wanted.
I'm missing some per instance specification here and I'm not seeing where.
Any suggestion will help and be appreciated!
++ The method ReactToInput() is triggered by key inputs. I wanted this method to be called when Player's attack 'method' happens which are bound to those key inputs, but I did what I've done only because I couldn't find an elegant way to execute it otherwise, and am really hoping there's a better way rather than using tags or GetComponent to specific object since it's supossed to affect other objects as well.

These methods are what you are looking for.
/// <summary>
/// OnMouseDown is called when the user has pressed the mouse button while
/// over the GUIElement or Collider.
/// </summary>
void OnMouseDown()
{
Debug.Log("Hi!");
}
/// <summary>
/// OnMouseUp is called when the user has released the mouse button.
/// </summary>
void OnMouseUp()
{
Debug.Log("Bye!");
}
MonoBehaviour provides many event callbacks other than Update(), and these two are some of them. The full list of event callbacks you can use for MonoBehaviour is explained in the official Monobehaviour page.
There are two methods for OnMouseUp():
OnMouseUp is called when the user has released the mouse button.
OnMouseUpAsButton is only called when the mouse is released over the
same GUIElement or Collider as it was pressed.
The GameObject your script is attached to is requires to have GUIElement or Collider as described in the manual page to use these functions.
If you do not want to use these methods, you could alternatively write your own custom InputModule, raycast to the mouse position on the screen to find which object is clicked, and send MouseButtonDown() event to a clicked GameObject.
I had to implement a custom input module to do this plus a couple of other stuff and I assure you writing custom InputModules is a headache.
EDIT:
If many different classes need to be notified when something happens, and who listens to such cases is unknown, event is a good option.
If you are using events, each event listener class such as LinkEnd is responsible to register and remove itself to such event.
Below is an example of how you could achieve this behaviour:
class Player
{
public delegate void OnSkillAFiredListener(object obj, SkillAFiredEventArgs args);
public static event OnSkillAFiredListener SkillAPressed = delegate { };
// ...
}
class LinkEnd
{
void OnEnable()
{
Player.SkillAPressed += WhatToDoWhenSkillAFired;
}
void OnDisable()
{
Player.SkillAPressed -= WhatToDoWhenSkillAFired;
}
void OnDestroy()
{
Player.SkillAPressed -= WhatToDoWhenSkillAFired;
}
public void WhatToDoWhenSkillAFired(object obj, SkillAFiredEventArgs args)
{
// get info from args
float someInfo = args.someInfo
// do something..
Bark();
}
// ...
}
It's necessary to deregister from the event in both OnDisable() and OnDestory() to avoid memory leaks (some claim such memory leaks are very minor).
Look for Observer and Publisher/Subscriber pattern to learn more about these approaches. It may not be very related to your case, but the Mediator Pattern is something that's often compared with the Observer Pattern so you might be interested to check it as well.

Related

Game objects are spawned without the target being scanned

I have multiple game objects as children of ImageTarget.
Ar camera
world center mode= first_target
Track device pose checked and positional
As i initiate the game mode.
All the objects start to fall(sphere collider and mesh renderer turns off).
As i scan the target the object have already fallen through the plane i had under them(the plane has a mesh collider on it).
If I the scan the target as soon as I initiate Game mode all goes according to plan, the spheres collide with the plane and stay on top of it.
Is it possible to freeze the Y axis of the objects until the target is scanned and how do i enable extended tracking (Objects pass through the plane as soon as i move the camera away from the target and re-scan the target).
Vuforia has the DefaultTrackableEventHandler .. the code was hard to find (source) but it looks like this
/*==============================================================================
Copyright (c) 2017 PTC Inc. All Rights Reserved.
Copyright (c) 2010-2014 Qualcomm Connected Experiences, Inc.
All Rights Reserved.
Confidential and Proprietary - Protected under copyright and other laws.
==============================================================================*/
/*
* Modified by PauloSalvatore on 04/03/2018 - 15:38 (GMT-3)
*
* Change Log:
*
* Track Events added on Inspector
* Custom events can be added to be invoked during initialization,
* when appear start, when object is appearing and when disappear start.
*/
using UnityEngine;
using UnityEngine.Events;
using Vuforia;
[System.Serializable]
public class TrackEvents
{
#region PUBLIC_EVENTS
public UnityEvent onInitialized;
public UnityEvent onAppear;
public UnityEvent isAppearing;
public UnityEvent onDisappear;
#endregion PUBLIC_EVENTS
}
/// <summary>
/// A custom handler that implements the ITrackableEventHandler interface.
/// </summary>
public class DefaultTrackableEventHandler : MonoBehaviour, ITrackableEventHandler
{
#region PUBLIC_EVENTS
public TrackEvents trackEvents;
#endregion PUBLIC_EVENTS
#region PRIVATE_MEMBER_VARIABLES
protected TrackableBehaviour mTrackableBehaviour;
#endregion PRIVATE_MEMBER_VARIABLES
#region UNTIY_MONOBEHAVIOUR_METHODS
protected virtual void Start()
{
mTrackableBehaviour = GetComponent<TrackableBehaviour>();
if (mTrackableBehaviour)
mTrackableBehaviour.RegisterTrackableEventHandler(this);
// onInitialized custom events
if (trackEvents.onInitialized != null)
trackEvents.onInitialized.Invoke();
}
protected virtual void Update()
{
// isAppearing custom events
if (trackEvents.isAppearing != null)
trackEvents.isAppearing.Invoke();
}
#endregion UNTIY_MONOBEHAVIOUR_METHODS
#region PUBLIC_METHODS
/// <summary>
/// Implementation of the ITrackableEventHandler function called when the
/// tracking state changes.
/// </summary>
public void OnTrackableStateChanged(
TrackableBehaviour.Status previousStatus,
TrackableBehaviour.Status newStatus)
{
if (newStatus == TrackableBehaviour.Status.DETECTED ||
newStatus == TrackableBehaviour.Status.TRACKED ||
newStatus == TrackableBehaviour.Status.EXTENDED_TRACKED)
{
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " found");
OnTrackingFound();
}
else if (previousStatus == TrackableBehaviour.Status.TRACKED &&
newStatus == TrackableBehaviour.Status.NOT_FOUND)
{
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " lost");
OnTrackingLost();
}
else
{
// For combo of previousStatus=UNKNOWN + newStatus=UNKNOWN|NOT_FOUND
// Vuforia is starting, but tracking has not been lost or found yet
// Call OnTrackingLost() to hide the augmentations
OnTrackingLost();
}
}
#endregion PUBLIC_METHODS
#region PRIVATE_METHODS
protected virtual void OnTrackingFound()
{
var rendererComponents = GetComponentsInChildren<Renderer>(true);
var colliderComponents = GetComponentsInChildren<Collider>(true);
var canvasComponents = GetComponentsInChildren<Canvas>(true);
// Enable rendering:
foreach (var component in rendererComponents)
component.enabled = true;
// Enable colliders:
foreach (var component in colliderComponents)
component.enabled = true;
// Enable canvas:
foreach (var component in canvasComponents)
component.enabled = true;
// onAppear custom events
if (trackEvents.onAppear != null)
trackEvents.onAppear.Invoke();
}
protected virtual void OnTrackingLost()
{
var rendererComponents = GetComponentsInChildren<Renderer>(true);
var colliderComponents = GetComponentsInChildren<Collider>(true);
var canvasComponents = GetComponentsInChildren<Canvas>(true);
// Disable rendering:
foreach (var component in rendererComponents)
component.enabled = false;
// Disable colliders:
foreach (var component in colliderComponents)
component.enabled = false;
// Disable canvas:
foreach (var component in canvasComponents)
component.enabled = false;
// onDisappear custom events
if (trackEvents.onDisappear != null)
trackEvents.onDisappear.Invoke();
}
#endregion PRIVATE_METHODS
}
This should be placed on the according ImageTarget or whatever target you are using by defualt afaik. As you can see they are disabling the Renderer, Collider etc if the target is lost ... I personally would always remove this and instead replace it by UnityEvent so I can decide later what should be happening and what not.
Since the two methods OnTrackingFound() and OnTrackingLost() are virtual you can inherit from DefaultTrackableEventHandler and override/replace their functionality. By not also calling base.OnTrackingFound() or base.OnTrackingLost() we are telling c# to not execute whatever the parent class originally implemented but to only use what we implement:
// This is used to directly pass a string value into the event
// I'll explain why later ...
[Serializable]
public class VuforiaTargetFoundEvent : UnityEvent<string, Transform> { }
public MyTrackableEventHandler : DefaultTrackableEventHandler
{
// Give this specific VuforiaTarget a certain custom ID
// We will pass it dynamically into the UnityEvent
// so every listener automatically also knows WHICH target
// was lost or found
public string TargetID;
public VuforiaTargetEvent _OnTrackingFound;
public VuforiaTargetEvent _OnTrackingLost;
protected override void OnTrackingFound()
{
// call _OnTrackingFound with your specific target ID and
// also pass in the Transform so every listener can know
// WHICH target was found and WHERE it is positioned
_OnTrackingFound?
}
protected override void OnTrackingLost()
{
// call _OnTrackingLost with your specific target ID and
// also pass in the Transform so every listener can know
// WHICH target was lost and WHERE it was last positioned
_OnTrackingLost?
}
}
Simply place this on the Vuforia target instead of the DefaultTrackableEventHandler (if it was even there already) so now by default none of the Renderer,Collider etc in children will be disabled. (If you still need it you can ofcourse again add the base.OnTrackingLost() and base.OnTrackingFound() or alternatively implement it in a separate script and reference the according methods as callbacks in our just newly added UnityEvents ;) )
Now to your falling objects. At the beginning set useGravity to false so they don't fall down anymore. Then as a callback once the imagetarget was found enable it.
public GravityEnabler : MonoBehaviour
{
// either reference this in the Inspector ...
public RigidBody _rigidBody;
// also this either reference it in the Inspector ...
public MyTrackableEventHandler target;
// ... or get them on runtime
private void Awake()
{
if(!_rigidBody) _rigidBody = GetComponent<RigidBody>();
if(!target= target = FindObjectOfType<MyTrackableEventHandler>();
// before start disable gravity
_rigidBody.useGravity = false;
// setup the callback for the target
target._OnTrackingFound.AddListener(OnTargetScanned);
target._OnTrackingLost.AddListener(OnTargetLost);
}
privtae void OnDestroy()
{
// If this object gets destroyed be sure to remove the callbacks
// otherwise you would get exceptions because the callbacks
// would still exist but point to a NULL reference
target._OnTrackingFound.RemoveListener(OnTargetScanned);
target._OnTrackingLost.RemoveListener(OnTargetLost);
}
public void OnTargetFound(string targetID, Transform targetTransform)
{
_rigidBody.useGravity = true;
}
public void OnTargetLost(string targetID, Transform targetTransform)
{
// If you need to do anything here
// maybe you want to stop the falling again when the target is lost
_rigidBody.useGravity = false;
_rigidBody.velocity = Vector3.zero;
}
}
Place this on every of your faling objects and maybe setup the references already in the Inspector if possible (a bit more efficient).

Why delegate event show NullReferenceException?

I follow the tutorial write the code ,but still show this error,the script already attach to the game object
This issue caused by the Execution Order of Event Functions.
When I checked logs, OnEnable of GameUI was called before Awake of GameController.
So, GameController.instance is null when you access GameController.instance.OnGameInfoChanged in void OnEnable() in GameUI.cs.
In Unity Manual for Execution Order of Event Functions, it says Awake is before OnEnable, I guess it doesn't guarantee always.
So, I think you'd better to modify GameUI.cs like below.
public class GameUI : MonoBehaviour
{
public Text timeLabel;
private bool isInitialized = false;
void Start()
{
isInitialized = true;
GameController.instance.OnGameInfoChanged += this.OnGameInfoChanged;
}
void OnEnable()
{
if (isInitialized)
GameController.instance.OnGameInfoChanged += this.OnGameInfoChanged;
}
void OnDisable()
{
GameController.instance.OnGameInfoChanged -= this.OnGameInfoChanged;
}
void OnGameInfoChanged(GameType type)
{
//...
}
}

How to handle animation state change for a collection of attacks(instances of a class)?[2D]

I have an Animation controller that plays animations(2d with no exit time) when the proper state is triggered. This is usually done through a bool like "isJumping" or "isDucking" from the behaviors themselves.
I've done it this way so that the behaviors aren't coupled to the animation state. The Animation Controller knows about the behaviors.
I have an "Attack" behavior that holds data about it such as damage, speed, delay. I have an "AttackCombo" Script that holds an array of "Attack" behaviors. On input it will execute the attack logic and iterate to the next Attack within the array. This allows me to customize an attack and put it into a combo, however with this method I can't use a bool like "IsAttacking" as the behavior has multiple instances unlike jump or duck. I don't want to have to make an individual class for each attack in a combo, i'm sure there's a better more modular way to handle my problem.
I'm open to better ways to handle animation in general, i've done research and can't find much regarding best practices with 2d transitions for Unity.
public class AnimationManager: MonoBehaviour
{
private Animator animator;
private InputState inputState;
private ColisionState colisionState;
private Walk walkBehavior;
private Duck duckBehavior;
private void Awake()
{
inputState = GetComponent<InputState>();
colisionState = GetComponent<ColisionState>();
animator = GetComponent<Animator>();
walkBehavior = GetComponent<Walk>();
duckBehavior= GetComponent<Duck>();
}
private void Update()
{
if (colisionState.isStanding)
{
ChangeAnimationState(0);
}
if (inputState.absVelX > 0)
{
ChangeAnimationState(1);
}
if (inputState.absVelY > 0)
{
ChangeAnimationState(2);
}
if (duckBehavior.isDucking)
{
ChangeAnimationState(3);
}
}
private void ChangeAnimationState(int value)
{
animator.SetInteger("AnimState", value);
}
}
public class AttackCombo : MonoBehaviour
{
public Attack[] Attacks;
public int CurrentIndex;
private void Update()
{
if (CanAttack)
Attacks[CurrentIndex].OnAttack();
// Normally this would let the Animation Controller to play
the animation but since there are multiple states depending on the index
this method won't work.
Attacks[CurrentIndex].isAttacking = true;
CurrentIndex++;
}
}

Multiplayer [SyncEvent] problem with non-player objects

I'm developing a multiplayer game based on turns.
So I have an script named gameController which is the one who has the global timer to alter the turns and also choose which players are attacking and which players are defending in the current turn.
This script is a singleton attached to a gameObject with a network identity on it (This network identity has no checkboxes marked to be controller by the server). I tried to have this game object spawned to all the clients when the server connects and also to have the game object already in the scene (booth cases aren't working).
Well, the main problem is that in the gameController script I have a checker in the update function to check if any new player is connected. In case is connected, it should call a syncEvent named EventOnNewPlayerAddedDelegate (I tried to call it directly, also using [command] and using [ ClientRpc]) to let the players know that they have to call their function named "OnRegisterPlayer", which is a function in their own player script that calls a function on gameController script, passing the player object (I tried via command and rpc also), something like this: GameController.instance.RegisterPlayer(this);
So anyone knows how can I trigger this SyncEvent to register the player to a non-player object controlled by the server?
Thank you very much.
I attach here a brief of booth scripts to make it easier to understand:
GameController:
public class GameController : NetworkBehaviour
{
public delegate void OnNewPlayerAddedDelegate();
[SyncEvent]
public event OnNewPlayerAddedDelegate EventOnNewPlayerAddedDelegate;
List<GamePlayerManager> players = new List<GamePlayerManager>();
public static GameController instance { get; private set; }
float timePerTorn = 30f;
bool isCountdown = false;
[System.NonSerialized]
public float countdownTime;
int roundNumber = 0;
int NumberOfPlayers;
int NumberOfPlayersChanged;
void Awake()
{
Debug.Log("Awaking ");
if (instance != null)
{
Debug.Log("Destoring ");
DestroyImmediate(this);
return;
}
instance = this;
}
// Use this for initialization
void Start()
{
if (!isServer)
{
return;
}
players = new List<GamePlayerManager>();
StartCountdown(5f);//20
}
void Update()
{
if (isServer)
{
if (isCountdown)
{
countdownTime -= Time.deltaTime;
if (countdownTime <= 0)
{
AlterGlobalTurns();
}
}
}
NumberOfPlayers = NetworkManager.singleton.numPlayers;
if (NumberOfPlayersChanged != NumberOfPlayers)
{
Debug.Log("num of players changed ---> " + NumberOfPlayers);
NumberOfPlayersChanged = NumberOfPlayers;
EventOnNewPlayerAddedDelegate();
//RpcNewPlayerAdded();
//CmdNewPlayerAdded();
}
}
[ClientRpc]
void RpcNewPlayerAdded()
{
Debug.Log("---------------- RpcNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
[Command]
void CmdNewPlayerAdded()
{
Debug.Log("---------------- CmdNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
public void RegisterPlayer(GamePlayerManager player)
{
Debug.Log("player ---> " + player.name);
if (players.Contains(player))
{
return;
}
Debug.Log("players ---> " + players);
players.Add(player);
}
}
PlayerScript:
public class GamePlayerManager : NetworkBehaviour
{
[System.NonSerialized]
public bool isPlayingOnTorn = true;
void Awake()
{
GameController.instance.EventOnNewPlayerAddedDelegate += OnRegisterPlayer;
}
private void Start()
{
if (!isLocalPlayer)
{
return;
}
}
public override void OnStartServer()
{
GameObject gc = (GameObject)Instantiate(NetworkManager.singleton.spawnPrefabs[2], transform.position, transform.rotation);
NetworkServer.Spawn(gc);
}
void OnRegisterPlayer(){
if (isLocalPlayer)
{
//GameController.instance.RegisterPlayer(this);
//RpcRegisterPlayer();
CmdRegisterPlayer();
}
}
[Command]
void CmdRegisterPlayer(){
Debug.Log("-------------Command Register player -------------");
GameController.instance.RegisterPlayer(this);
}
[ClientRpc]
void RpcRegisterPlayer()
{
Debug.Log("------------- RPC REgister Player -------------");
GameController.instance.RegisterPlayer(this);
}
}
I think I already saw the problem here.
The problem is that GameController is spawned by the playerScript(just if the player is also the host) in the function OnStartServer(). So the first problem is that the GameController doesn't detect the host player because it is not a new connections. And the second problem is when a second client is connected, the GameController detect the client connection and send the event signal faster than the client wakeup, so when the client is already working the signal is gone.
I solved the problem deleting this signal and checking directly in the playerScript if the GameController exists then check if the player is already registered.
My question now is that if there is anyway to instance and awake the server objects before the player(Host), which I understand that the answer is "no" because as the player is the host it needs to be running to have the server.
And the second question is if there is anyway or any signal to know that a new player is connected, and wait until it is awaked.
Thank you very much.
You can check the hole thread at Unity forums: https://forum.unity.com/threads/multiplayer-syncevent-problem-with-non-player-objects.589309/

Unity3D Programmatically Assign EventTrigger Handlers

In the new Unity3D UI (Unity > 4.6), I'm trying to create a simple script I can attach to a UI component (Image, Text, etc) that will allow me to wedge in a custom tooltip handler. So what I need is to capture a PointerEnter and PointerExit on my component. So far I'm doing the following with no success. I'm seeing the EVentTrigger component show up but can't get my delegates to fire to save my life.
Any ideas?
public class TooltipTrigger : MonoBehaviour {
public string value;
void Start() {
EventTrigger et = this.gameObject.GetComponent<EventTrigger>();
if (et == null)
et = this.gameObject.AddComponent<EventTrigger>();
EventTrigger.Entry entry;
UnityAction<BaseEventData> call;
entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerEnter;
call = new UnityAction<BaseEventData>(pointerEnter);
entry.callback = new EventTrigger.TriggerEvent();
entry.callback.AddListener(call);
et.delegates.Add(entry);
entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerExit;
call = new UnityAction<BaseEventData>(pointerExit);
entry.callback = new EventTrigger.TriggerEvent();
entry.callback.AddListener(call);
et.delegates.Add(entry);
}
private void pointerEnter(BaseEventData eventData) {
print("pointer enter");
}
private void pointerExit(BaseEventData eventData) {
print("pointer exit");
}
}
Also... the other method I can find when poking around the forums and documentations is to add event handlers via interface implementations such as:
public class TooltipTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {
public string value;
public void OnPointerEnter(PointerEventData data) {
Debug.Log("Enter!");
}
public void OnPointerExit(PointerEventData data) {
Debug.Log("Exit!");
}
}
Neither of these methods seems to be working for me.
Second method (implementation of IPointerEnterHandler and IPointerExitHandler interfaces) is what you're looking for. But to trigger OnPointerEnter and OnPointerExit methods your scene must contain GameObject named "EventSystem" with EventSystem-component (this GameObject created automatically when you add any UI-element to the scene, and if its not here - create it by yourself) and components for different input methods (such as StandaloneInputModule and TouchInputModule).
Also Canvas (your button's root object with Canvas component) must have GraphicRaycaster component to be able to detect UI-elements by raycasting into them.
I just tested code from your post and its works just fine.