2

Alright so basically the issue that I've been having is that for some reason a GameObject is interfering with the OnPointerEnter function. I'm pretty sure that OnPointerEnter detects only UI. So that's why I'm extremely confused when seeing that a specific GameObject in this case the PlayerLogic GameObject (which you can see in the screenshot) is for some reason interfering with the detection of UI elements. The reason I believe it is this specific GameObject is because once I do PlayerLogic.SetActive(false); OnPointerEnter starts to work again, and I'm also sure that it isn't any of the children of PlayerLogic because I've tried turning them off specifically and it still didn't work.

Inspector of the PlayerLogic object

Hierarchy

The code I'm using to test OnPointerEnter

After some testing I've realized that its the specific issue lies within the Player script on the PlayerLogic GameObject. Now what confuses me is that once I turn off the Player component OnPointer doesn't work, but if I were to remove the Player component completely from the PlayerLogic GameObject OnPointerEnter works.

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections;
using System.Collections.Generic;

public class Player : MonoBehaviour, TakeDamage {

    [SerializeField] private Animator playerAnimator;

    [SerializeField] private Transform mainCameraTransform;
    private bool isRunning = false; 

    [SerializeField] private CharacterController controller;
    public float speed = 10f;
    [SerializeField] private float jumpForce = 3f;
    [SerializeField] private float gravity = -10000.81f;
    Vector3 velocity;
    Vector3 desiredMoveDirection;

    private float dashSpeed = 30f;

    private float mouseX;
    private float mouseY;
    [SerializeField]
    private Transform Target;
    [SerializeField]
    private Transform player;
    
    private float turnSmoothVelocity;    

    private float time = 0f;
    public bool playerIsAttacking = false;

    [SerializeField] private Slider playerHealth, playerMana;
    [SerializeField] private TextMeshProUGUI healthText, manaText; 

    private Vector3 originalSpawnPos;
    private bool playerIsDead = false;

    [SerializeField] private LayerMask enemyLayerMask;
    [SerializeField] private Transform playerLook;

    private ShowHPBar obj;
    private bool HPBarShown = false;
    private bool unshowingHPBar = false;
    public bool lookingAtEnemy = false;
    public RaycastHit hit;

    [SerializeField] private Canvas abilityCanvas;
    [SerializeField] private Slider CD1;
    [SerializeField] private Slider CD2;
    [SerializeField] private Slider CD3;
    public List<Ability> currentlyEquippedAbilites = new List<Ability>();
    public List<string> abilityTexts = new List<string>();
    public float[] abilityCooldowns = new float[3]; 
    private float manaRegenTime;
    //public List<Image> abilityImages = new List<Image>();

    private void Awake() {
        Cursor.visible = false;
        Cursor.lockState = CursorLockMode.Locked;
    }

    private void Start() {
        playerHealth.onValueChanged.AddListener(delegate {OnValueChangedHealth(); });
        playerMana.onValueChanged.AddListener(delegate {OnValueChangedMana(); });
        originalSpawnPos = transform.position;
    }

    private void Update() {
        if (!playerIsDead) {
            PlayerMovementAndRotation();
        }
        PlayerDash();
        PlayerRun();
        PlayerSeeEnemyHealth();
        PlayerActivateAbility();

        if (manaRegenTime > 0.5f) {
            playerMana.value += playerMana.maxValue/100; 
            manaRegenTime = 0;
        }
        playerLook.rotation = mainCameraTransform.rotation;
        time += Time.deltaTime;
        manaRegenTime += Time.deltaTime;

        #region Ability Cooldowns
        if (currentlyEquippedAbilites.Count > 0) {
            if (currentlyEquippedAbilites[0].cooldown <= abilityCooldowns[0])
            {
                currentlyEquippedAbilites[0].isOnCooldown = false;
                abilityCooldowns[0] = 0;
                CD1.value = 0;
            }
            else if (currentlyEquippedAbilites[0].isOnCooldown) { 
                abilityCooldowns[0] += Time.deltaTime; 
                CD1.value = currentlyEquippedAbilites[0].cooldown - abilityCooldowns[0];
            }
        }

        if (currentlyEquippedAbilites.Count > 1) {
            if (currentlyEquippedAbilites[1].cooldown <= abilityCooldowns[1])
            {
                currentlyEquippedAbilites[1].isOnCooldown = false;
                abilityCooldowns[1] = 0;
                CD2.value = 0;
            }
            else if (currentlyEquippedAbilites[1].isOnCooldown) { 
                abilityCooldowns[1] += Time.deltaTime; 
                CD2.value = currentlyEquippedAbilites[1].cooldown - abilityCooldowns[1];
            }
        }

        if (currentlyEquippedAbilites.Count > 2) {
            if (currentlyEquippedAbilites[2].cooldown <= abilityCooldowns[2])
            {
                currentlyEquippedAbilites[2].isOnCooldown = false;
                abilityCooldowns[2] = 0;
                CD3.value = 0;
            }
            else if (currentlyEquippedAbilites[2].isOnCooldown) { 
                abilityCooldowns[2] += Time.deltaTime; 
                CD3.value = currentlyEquippedAbilites[2].cooldown - abilityCooldowns[2];
            }
        }
        #endregion
    }

    private void PlayerRun() {
        if (Input.GetKey(KeybindsScript.RunKey) && (Input.GetAxisRaw("Horizontal") != 0 || Input.GetAxisRaw("Vertical") != 0)) {
            playerAnimator.SetInteger("isRunning", 1);
            playerAnimator.SetInteger("isIdle", 0);
            playerAnimator.SetInteger("isWalking", 0);
            speed = 15f;
        }
        else if (Input.GetAxisRaw("Horizontal") != 0 || Input.GetAxisRaw("Vertical") != 0) {
            playerAnimator.SetInteger("isWalking", 1);
            playerAnimator.SetInteger("isIdle", 0);
            playerAnimator.SetInteger("isRunning", 0);
            speed = 10f;
        }
        else {
            playerAnimator.SetInteger("isRunning", 0);
            playerAnimator.SetInteger("isWalking", 0);
            playerAnimator.SetInteger("isIdle", 1);
            speed = 10f;
        }
    }

    private void PlayerMovementAndRotation() {
        bool isGrounded = controller.isGrounded;

        if (isGrounded && velocity.y < 0) {
            velocity.y = -2f;
        }
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");
        Vector3 moveDir = (transform.right*horizontal+transform.forward*vertical).normalized;

        controller.Move(moveDir*Time.deltaTime*speed);
        transform.eulerAngles = new Vector3(0f, mainCameraTransform.eulerAngles.y, 0f);
        

        if (Input.GetKeyDown(KeybindsScript.JumpKey) && isGrounded) {
            velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
        }

        velocity.y += gravity * Time.deltaTime;

        controller.Move(velocity * Time.deltaTime);
    }

    private void PlayerDash() {
        if (Input.GetKeyDown(KeybindsScript.DashKeybind) && isRunning == false) {
            if (Input.GetKey(KeybindsScript.MovementKeyBackward)) {
                StartCoroutine(PlayerDashTiming(2));
            }
            else if (Input.GetKey(KeybindsScript.MovementKeyRight)) {
                StartCoroutine(PlayerDashTiming(3));
            }
            else if (Input.GetKey(KeybindsScript.MovementKeyLeft)) {
                StartCoroutine(PlayerDashTiming(4));
            }
            else {
                StartCoroutine(PlayerDashTiming(1));
            }
        }
    }

    private void PlayerSeeEnemyHealth() {
        if (Physics.Raycast(playerLook.position, playerLook.forward, out hit, 1000f, enemyLayerMask)) {
            obj = hit.transform.gameObject.GetComponent<ShowHPBar>();
            if (obj != null && !HPBarShown && !unshowingHPBar) {obj.ShowHPBarFunction(); HPBarShown = true;}
            lookingAtEnemy = true;
        }
        else {
            if (obj != null && HPBarShown) {StartCoroutine(UnShowHPBar(obj)); HPBarShown = false;}
        }
    }

    public void PlayerEquipAbility(Ability ability, int place) {
        if (currentlyEquippedAbilites.Count < place) {currentlyEquippedAbilites.Add(ability);}
        else {currentlyEquippedAbilites[place-1] = ability;}

        //if (abilityImages.Count < place) {abilityImages.Add(ability.icon);}
        //else {abilityImages.Add(ability.icon);}
        if (abilityTexts.Count < place) {abilityTexts.Add(ability.name);}
        else {abilityTexts[place-1] = ability.name;}
        for (int i=0;i < abilityTexts.Count;++i) {
            abilityCanvas.transform.GetChild(i).GetChild(0).GetComponent<TextMeshProUGUI>().text = abilityTexts[i];
            abilityCanvas.transform.GetChild(i).GetChild(1).GetComponent<Slider>().maxValue = ability.cooldown;
        }
    }

    private void PlayerActivateAbility() { 
        if (Input.GetKeyDown(KeyCode.Alpha1)) {
            if (currentlyEquippedAbilites[0] != null) {
                if (currentlyEquippedAbilites[0].manaCost <= playerMana.value && !currentlyEquippedAbilites[0].isOnCooldown) {
                    ParticleEffect pe = currentlyEquippedAbilites[0].script.gameObject.GetComponent<ParticleEffect>();
                    playerMana.value -= currentlyEquippedAbilites[0].manaCost;
                    currentlyEquippedAbilites[0].isOnCooldown = true;
                    if (pe != null) {pe.PlayAnimation();}
                }
            }
        }
    }


    public void OnValueChangedHealth() {
        healthText.text = playerHealth.value + "/" + PlayerStats.HealthPoints;
    }

    public void OnValueChangedMana() {
        manaText.text = playerMana.value + "/" + PlayerStats.ManaAmount;
    }

    public void TakeDamageFunction(float damage) {
        playerHealth.value -= damage;
        if (playerHealth.value <= 0) {
            StartCoroutine(PlayerDeath());
        }
    }


    IEnumerator PlayerDashTiming(int x) {
        isRunning = true;
        float time = 0f;
        Vector3 savedVector = Vector3.zero;
        switch (x) {
            case 1: 
            savedVector = transform.forward;
            break;
            case 2:
            savedVector = -transform.forward;
            break;
            case 3:
            savedVector = transform.right;
            break;
            case 4:
            savedVector = -transform.right;
            break;
        }
        while(time < .3f)
        {
            time += Time.deltaTime;
            controller.Move(savedVector * dashSpeed * Time.deltaTime);
            yield return null; 
        }
        yield return new WaitForSeconds(1.5f);
        isRunning = false;
    }

    IEnumerator PlayerDeath() {
        //Respawn
        playerIsDead = true;
        playerAnimator.enabled = false;
        yield return new WaitForSeconds(1f);
        playerHealth.value = 100;
        transform.position = originalSpawnPos;
        yield return new WaitForSeconds(0.1f);
        playerIsDead = false;
        playerAnimator.enabled = true;
    }

    IEnumerator UnShowHPBar(ShowHPBar obj) {
        unshowingHPBar = true;
        yield return new WaitForSeconds(1.5f);
        obj.ShowHPBarFunction();
        unshowingHPBar = false;
    }
}

This is the Player.cs script.

derHugo
  • 83,094
  • 9
  • 75
  • 115
user12791203
  • 41
  • 1
  • 5

1 Answers1

3

I'm pretty sure that OnPointerEnter detects only UI.

No.

It works "out of the box" on UI because by default every Canvas contains a GraphicsRaycaster component which is then used and handled by the EventSystem.

For non-UI 3D objects you have to make sure

  • your objects have 3D Colliders
  • the Colliders are on the same object as the IPointerXY interfaces
  • on your Camera there is a PhysicsRayster component

For non-UI 2D objects quite similar

  • your objects have a 2D Collider
  • the colliders are on the same object as the IPointerXY interfaces
  • the Camera has a Physics2DRaycaster component

Note that it is possible that any other collider or in general raycast blocking object is between your input and the target object which would also prevent the OnPointerXY to be triggered on your objects.

The CharacterController

is simply a capsule shaped Collider which can be told to move in some direction from a script.

which is probably blocking the input.


Now with your Player code:

You do

Cursor.lockState = CursorLockMode.Locked;

in Awake so even if you turn it off afterwards this already took effect.

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Well I don't have neither a PhysicsRaycaster nor a Physics2DRaycaster on my camera. So I'm still confused on how the PlayerLogic object could possibly have anything to do with the OnPointerEnter function? – user12791203 Dec 09 '21 at 07:44
  • @user12791203 I think the `CharacterController` might be interfering .. the `CharacterController` basically **is** a Collider so it is probably in the way somehow ... for `Player` I don't have any code or reference – derHugo Dec 09 '21 at 07:49
  • Does your `Player` script maybe somehow enable and disable the `CharacterController`? – derHugo Dec 09 '21 at 08:01
  • Hmm... I don't think that the CharacterController is interfering since once I make it inactive OnPointerEnter still doesn't work. I don't think it's the code in Player either since when I set Player to inactive OnPointerEnter still doesn't work. It starts working when I completely remove Player from the PlayerLogic object. The script doesn't disable nor enable the character controller in any way, I'll add it in the question so you can see. – user12791203 Dec 09 '21 at 08:05
  • @user12791203 but your `Player` component locks and hides the cursor .. could that be the issue? (Don't understand why we were both downvoted btw .. but that's some "welcoming" guys on StackOverflow sadly) – derHugo Dec 09 '21 at 08:23
  • yeah... I got used to the "welcoming" guys at this point. It works! Thanks I've been going at this for a while now! I just realised why it didn't work when I set Player to inactive... It's because I turned Player off in runtime so the Awake was already called. I feel kinda stupid now it was pretty obvious :D. – user12791203 Dec 09 '21 at 08:33
  • @derHugo's answer is good. (I upvoted it just now). But, do remember you need to also ensure you have an EventSystem in your project. – Jay Koutavas Dec 23 '21 at 04:06
  • @JayKoutavas it was already the case for OPs question (see screenshots) since without it also the Canvas/UI itself wouldn't work already .. so I didn't explicitly include it again ;) – derHugo Dec 23 '21 at 08:09
  • Well, I called this out because having a Canvas/UI is optional, but the EventSystem is not (I'm doing events directly on a GameObject.) – Jay Koutavas Dec 28 '21 at 02:13
  • This isn't true: "the Colliders are on the same object as the IPointerXY interfaces" - they have to be 'on the object or its descendant/child'. This is core to Unity's physics (collisions) engine and is in the docs: collider-events 'bubble-up' the gameobject-hierarchy by at least one step (I believe it's exactly zero-or-one hops, but I found this answer by accident when looking for official docs confirming that). – Adam Jan 19 '22 at 05:56
  • 1
    @Adam I think this only happens if there is a Rigidbody in which case the children colliders are basically all treated as one single collider of the Rigidbody. Also note the question here is about Raycast hits, not about collision events ;) – derHugo Jan 19 '22 at 08:50
  • 1
    The RB isn't necessary - IPointer* interfaces still bubble-up (we're seeing bubble up on EventSystem driven IPointer hits in 3D scene). It was the cause of an unexpected bug on a project I'm on, hence I came looking for 'what's the actual rules/definition here, so we can make sure no-one gets it wrong in future?' – Adam Jan 20 '22 at 09:13
  • 1
    Update/clarification: IPointer* interfaces - by trial-and-error - have changed the Unity rules on collider processing. I don't know why. It's undocumented but consistent. Thanks, Unity! – Adam Jan 22 '22 at 19:08