0
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.

derHugo
  • 83,094
  • 9
  • 75
  • 115
Arcana Affinity
  • 307
  • 7
  • 18
  • 1
    Input.GetKeyUp(KeyCode.Mouse0) will work for any mouse key press, no mater where is cursor on screen. Try raycasting from your cursor and calling reactToImput from it. – Woltus Jun 20 '17 at 14:24
  • @Woltus My current target system gets the GameObject via raycast. I couldn't pull it off working. When I try ReactToInput() to happen only to 'TargetSystemScript.TargetGameObject == this.gameobject' it spits out NullReferenceException at start. – Arcana Affinity Jun 20 '17 at 14:27
  • @Woltus Hence I wanted ReactToInput() to be called when player's certain method was initiated, but couldn't get my head around that way other than to designate it via using tag or GetComponent which both are not the ways I desire this to work. The player's action here need to affect many other gameObjects with same method, but each are different. – Arcana Affinity Jun 20 '17 at 14:35
  • If it's throwing a null pointer, you should wrap the statement in a `if(not null)` check (think about the behavior if the user is looking up into the sky). Alternatively, you need to call `ReactToInput()` from a single location, right now it's being called indiscriminately from each object's `Update()` function, which runs all the time every frame. – Draco18s no longer trusts SE Jun 20 '17 at 15:59
  • @Ho-JeongLee You should be using EventSystems or Raycast to do this not OnMouseXXX in the answer below. I suggest EventSystems because you won't have to deal with UI Raycast problem. So `OnPointerClick` is the key here. See the duplicated question. – Programmer Jun 20 '17 at 17:43

1 Answers1

0

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.

derHugo
  • 83,094
  • 9
  • 75
  • 115
Matt
  • 1,424
  • 1
  • 13
  • 17
  • Thanks for the detailed tips! I'm back home now so it's a big shame I can't really elaborate, but here my current more in depth: AymSystem.cs gets target(GameObject) via raycast so designating specific object is achieved. The input calls toggling connected Boolean is there only because the player input of those certain attack types use those inputs. Since what I wanted at the beggining was LinkEnd's ReactOnInput() being derived by Hit() || Push() || Pull() in player skills script I did the temporary attempt as so. I'm thinking event is what I want probably. Just haven't done it before so... – Arcana Affinity Jun 20 '17 at 16:01
  • Have you tried this? LinkEnd linkend = raycastHitGameObject.GetComponent(); if (linkend != null) { linkend.ReactToInput(); } – Matt Jun 20 '17 at 16:07
  • I'm sure that will work perfectly. However, the end goal with this specific scripts(LinkEnd && PlayerBasicAttack) was having a 'flag' for LinkEnd to use to initiate connected toggle when certain methods in PlayerBasicAttack happens without GetComponent. – Arcana Affinity Jun 20 '17 at 16:11
  • I am very skeptical about using GetComponent as you can tell. I know it will work, but the design of the game here is the mentioned basic abilities in PlayerBasicAttack.cs still working with very different gameObjects than LinkEnd. For that reason I wanted to minimize the iteration that would be necessary with doing GetComponent for every single objects behaviors derived by interaction with those basic abilities of player. – Arcana Affinity Jun 20 '17 at 16:14
  • I agree that `GetComponent()` and such are not very elegant. I also try to avoid using Unity types because I feel like the Unity framework often enforces bad practices and feel `global`s everywhere, and also it's hard to do Unit Tests if I use `MonoBehaviour`. So what I try to do is to make classes with pure C# until I need features of Unity Engine (such as raycasting or collision detections), and then port to Unity by making a container class or subclass of pure C# classes. That way I can have cleaner designs & have Unit Tests easily (assuming I dont test features from Unity Engine). – Matt Jun 20 '17 at 16:41
  • Added some introductory explanations for using `events`. I recommend you searching more on those topics. – Matt Jun 20 '17 at 16:44