Shooting over water still doesn't work

I don’t understand, I think I have the checks for in range and can attack the same as what @sampattuzzi changed them too… why won’t this work?

Fighter.cs
using System;
using System.Collections.Generic;
using RPG.Core;
using RPG.Movement;
using RPG.Attributes;
using RPG.Saving;
using RPG.Stats;
using UnityEngine;
using UnityEngine.Events;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction, ISaveable, IModifierProvider
    {
        // SHIPPING DATA //

        ActionScheduler actionScheduler;
        Animator animator;
        BaseStats baseStatsScript;


        // CHANGING DATA //

        [SerializeField] UnityEvent launchProjectile;
        [SerializeField] UnityEvent launchMagic;
        Mover moverScript;
        [SerializeField] float timeSinceLastAttack = Mathf.Infinity;
        [Space(10)]
        [SerializeField] Transform rightHandTransform;
        [SerializeField] Transform leftHandTransform;
        [Space(10)]
        [SerializeField] WeaponSO weaponSO;
        [Space(5)]
        [SerializeField] WeaponSO defaultWeaponSO;
        [Header("Debug only DO NOT CHANGE")]
        [Space(10)]
        [SerializeField] Health target;
        [SerializeField] WeaponComponent currentWeapon;



        // * CODE * //
        private void Awake()
        {
            moverScript = GetComponent<Mover>();
            actionScheduler = GetComponent<ActionScheduler>();
            animator = GetComponent<Animator>();
            baseStatsScript = GetComponent<BaseStats>();
        }

        void Start()
        {
            if (weaponSO == null)
            {
                EquipWeapon(defaultWeaponSO);
                weaponSO = defaultWeaponSO;
                //print(this + " script just loaded " + defaultWeapon + " on " + name);
            }
        }

        void Update()
        {
            timeSinceLastAttack += Time.deltaTime;
            if (target != null && target.IsDead()) target = null;
            if (target == null) return;
            if (!GetIsInRange(target.transform))
            {
                MoveToAttack();
            }
            else
            {
                moverScript.Cancel();
                AttackBehavior();
            }
        }

        public void EquipWeapon(WeaponSO weaponSO)
        {
            if (weaponSO == null) return;
            Animator animator = GetComponent<Animator>();
            weaponSO.SpawnWeapon(rightHandTransform, leftHandTransform, animator);
            currentWeapon = weaponSO.GetWeaponEquipped();
        }

        private void MoveToAttack()
        {
            if (target == null) return;
            moverScript.MoveToTarget(target.transform.position);
            if (GetIsInRange(target.transform))
            {
                moverScript.Cancel();
                AttackBehavior();
            }
        }

        private void AttackBehavior()
        {
            transform.LookAt(target.transform);
            if (timeSinceLastAttack >= weaponSO.GetTimeBetweenAttacks())
            {
                animator.SetBool("stopAttack", false);
                animator.SetTrigger("attack");
                timeSinceLastAttack = 0;
            }
        }

        // Animation Event from * RPG-Character@Unarmed-Attack-L3 *
        void Hit()
        {
            if (target == null) return;
            currentWeapon.OnHit();
            float damageTaken = baseStatsScript.GetStat(Stat.Damage);
            target.TakeDamage(gameObject, damageTaken);
            //print(name + " did " + damageTaken + " with " + weaponSO.name);
        }
        // Animation Event from * RPG-Character@2Hand-Bow-Attack1 *
        void Shoot()
        {
            if (target == null) return;
            SpawnProjectile();
            if (weaponSO.GetIsMagic())
            {
                launchMagic.Invoke();
            }
            else
            {
                launchProjectile.Invoke();
            }
        }

        private void SpawnProjectile()
        {
            WeaponComponent newestProjectile =
                Instantiate(weaponSO.GetProjectile(), currentWeapon.transform.position, Quaternion.identity);
            newestProjectile.GetComponent<Projectile>().SetTargetToHit(target);
            newestProjectile.GetComponent<Projectile>().SetInstigator(gameObject);
            newestProjectile.GetComponent<Projectile>().weaponSO = weaponSO;
        }

        private bool GetIsInRange(Transform targetInQuestion)
        {
            var answer = Vector3.Distance(transform.position, targetInQuestion.position) <= weaponSO.GetWeaponRange();
            print("GetIsInRange() says " + answer);
            return answer;
        }

        public bool CanAttack(GameObject combatTarget)
        {
            if (combatTarget == null) return false;
            if (!GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) && !GetIsInRange(combatTarget.transform)) return false;
            Health targetToTest = combatTarget.GetComponent<Health>();
            return targetToTest != null && !targetToTest.IsDead();
        }

        public void Attack(GameObject combatTarget)
        {
            actionScheduler.StartAction(this);
            target = combatTarget.GetComponent<Health>();
            //print("take that " + combatTarget + " you flat faced heathen!");
        }

        public void Cancel()
        {
            target = null;
            animator.ResetTrigger("attack");
            animator.SetBool("stopAttack", true);
            moverScript.Cancel();
        }

        public WeaponSO SetWeaponSO(WeaponSO sO)
        {
            weaponSO = sO;
            return null;
        }

        public GameObject SetCurrentWeapon(WeaponComponent weaponEquipped)
        {
            currentWeapon = weaponEquipped;
            return null;
        }

        public object CaptureState()
        {
            return weaponSO.name;
        }

        public void RestoreState(object state)
        { 
            string savedWeaponName = (string)state;
            weaponSO = UnityEngine.Resources.Load<WeaponSO>(savedWeaponName);
            EquipWeapon(weaponSO);
        }

        public Health GetTarget()
        {
            if (target == null) return null;
            return target;
        }

        public IEnumerable<float> GetAdditiveModifiers(Stat stat)
        {
            if (stat == Stat.Damage)
            {
                yield return weaponSO.GetWeaponDamage();
            }
        }

        public IEnumerable<float> GetPercentageModifiers(Stat stat)
        {
            if (stat == Stat.Damage)
            {
                yield return weaponSO.GetPercentageBonus();
            }
        }
    }
}
Mover.cs
using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
using RPG.Saving;
using System.Collections.Generic;
using RPG.Attributes;

namespace RPG.Movement
{
    public class Mover : MonoBehaviour, IAction, ISaveable
    {
        // SHIPPING DATA //
        NavMeshAgent navMeshAgent;
        Animator playerAnimator;
        ActionScheduler actionScheduler;
        Health healthScript;

        // CHANGING DATA //
        [SerializeField] float maxNavPathLength = 40f;

        // * CODE * //

        void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
            playerAnimator = GetComponent<Animator>();
            actionScheduler = GetComponent<ActionScheduler>();
            healthScript = GetComponent<Health>();
        }

        void Update()
        {
            navMeshAgent.enabled = !healthScript.IsDead();
            UpdateAnimator();
        }

        public void Cancel()
        {
            //playerAnimator.applyRootMotion = false;
            navMeshAgent.isStopped = true;
        }

        public void StartMovementAction(Vector3 destination)
        {
            //playerAnimator.applyRootMotion = true;
            actionScheduler.StartAction(this);
            navMeshAgent.isStopped = false;
            navMeshAgent.destination = destination;
        }

        public bool CanMoveTo(Vector3 destination)
        {
            NavMeshPath path = new NavMeshPath();
            bool hasPath = NavMesh.CalculatePath(transform.position, destination, NavMesh.AllAreas, path);

            if (!hasPath) return false;
            if (path.status != NavMeshPathStatus.PathComplete) return false;
            if (GetPathLength(path) > maxNavPathLength) return false;

            return true;
        }

        private float GetPathLength(NavMeshPath path)
        {
            float total = 0;

            if (path.corners.Length < 2) return total;
            for (int i = 0; i < path.corners.Length - 1; i++)
            {
                total += Vector3.Distance(path.corners[i], path.corners[i + 1]);
            }
            return total;
        }

        public void MoveToTarget(Vector3 destination)
        {
            //playerAnimator.applyRootMotion = true;
            navMeshAgent.isStopped = false;
            navMeshAgent.destination = destination;
        }

        private void UpdateAnimator()
        {
            Vector3 velocity = navMeshAgent.velocity;  // gets a global velocity of where we are in the world
            Vector3 localVelocity = transform.InverseTransformDirection(velocity);  // converts that into something local so the animator has a reference for what animation at what z speed
            float speed = localVelocity.z;  // our final z velocity
            playerAnimator.SetFloat("fowardSpeed", speed);
        }

        public object CaptureState()
        {
            Dictionary<string, object> moverDataDict = new Dictionary<string, object>();
            moverDataDict["position"] = new SerializableVector3(transform.position);
            moverDataDict["rotation"] = new SerializableVector3(transform.eulerAngles);
            return moverDataDict;
        }

        public void RestoreState(object state)
        {
            Dictionary<string, object> moverDataDict = (Dictionary<string, object>)state;
            var loadedPosition = ((SerializableVector3)moverDataDict["position"]).ToVector();
            var loadedRotation = ((SerializableVector3)moverDataDict["rotation"]).ToVector();
            GetComponent<NavMeshAgent>().Warp(loadedPosition);
            transform.eulerAngles = loadedRotation;
        }
    }
}

and just in case…

PlayerController.cs
using UnityEngine;
using RPG.Movement;
using RPG.Combat;
using RPG.Attributes;
using System;
using UnityEngine.EventSystems;
using UnityEngine.AI;

namespace RPG.Control
{
    public class PlayerController : MonoBehaviour
    {
        // shipping data //
        Health healthScript;

        [Serializable]
        struct CursorMapping
        {
            public CursorType type;
            public Texture2D texture;
            public Vector2 hotspot;
        }

        [SerializeField] CursorMapping[] cursorMappings;
        [SerializeField] float maxNavMeshProjectionDistance = 1f;
        [SerializeField] float SpherecastRadius = 0.5f;

        // code //
        void Awake()
        {
            healthScript = GetComponent<Health>();
        }

        void Update()
        {
            if (InteractWithUI())
            {
                SetCursor(CursorType.UI);
                return;
            }
            if (healthScript.IsDead())
            {
                SetCursor(CursorType.None);
                return;
            }
            if (InteractWithComponent())
            {
                return;
            }
            if (InteractWithMovement()) return;
            SetCursor(CursorType.None);
        }

        private bool InteractWithComponent()
        {
            RaycastHit[] hits = RaycastAllSorted();
            foreach (RaycastHit raycastHit in hits)
            {
                if (!GetComponent<Mover>().CanMoveTo(raycastHit.transform.position)) return false;

                IRayCastable[] rayCastables = raycastHit.transform.GetComponents<IRayCastable>();
                foreach (IRayCastable rayCastable in rayCastables)
                {
                    if (rayCastable.HandleRaycast(this))
                    {
                        SetCursor(rayCastable.GetCursorType());
                        return true;
                    }
                }
            }
            return false;
        }

        RaycastHit[] RaycastAllSorted()
        {
            RaycastHit[] hits = Physics.SphereCastAll(GetMouseRay(), SpherecastRadius);
            float[] distances = new float[hits.Length];
            for (int i = 0; i < hits.Length; i++)
            {
                distances[i] = hits[i].distance;
            }
            Array.Sort(distances, hits);
            return hits;
        }

        private bool InteractWithUI()
        {
            return EventSystem.current.IsPointerOverGameObject(); // UI = true
        }

        private bool InteractWithMovement()
        {
            Vector3 target;
            bool hasHit = RaycastNavmesh(out target);
            if (hasHit)
            {
                if (!GetComponent<Mover>().CanMoveTo(target)) return false;

                if (Input.GetMouseButton(0))
                {
                    GetComponent<Mover>().StartMovementAction(target);
                }
                SetCursor(CursorType.Movement);
                return true;
            }
            return false;
        }

        private bool RaycastNavmesh(out Vector3 target)
        {
            target = new Vector3();
            RaycastHit hit;
            bool hasHit = Physics.Raycast(GetMouseRay(), out hit);

            if (!hasHit) return false;
            NavMeshHit navMeshHit;
            bool hasCasToNavMesh = NavMesh.SamplePosition(
                hit.point, out navMeshHit, maxNavMeshProjectionDistance, NavMesh.AllAreas);

            if (!hasCasToNavMesh) return false;

            target = navMeshHit.position;

            
            return true;
        }

        public void SetCursor(CursorType type)
        {
            CursorMapping mapping = GetCursorMapping(type);
            Cursor.SetCursor(mapping.texture, mapping.hotspot, CursorMode.Auto);
        }

        private CursorMapping GetCursorMapping(CursorType type)
        {
            foreach (CursorMapping mapping in cursorMappings)
            {
                if (mapping.type == type)
                {
                    return mapping;
                }
            }
            return cursorMappings[0];
        }

        private static Ray GetMouseRay()
        {
            return Camera.main.ScreenPointToRay(Input.mousePosition);
        }
    }
}

All the magic here should be in Fighter, and that looks right.
So question 1) You’ve cleverly put in a debug statement in GetIsInRange(), does it say True and the attack isn’t happening? It’s possible that you’re just not getting in range.

Here’s a quick check… add this to Fighter.cs

void OnDrawGizmos()
{
    if(weaponSO==null) return;
    Gizmos.DrawWireSphere(transform.position, weaponSO.GetWeaponRange());
}

It says true at first…then false twice… one for me and one for the enemy… but not sure why.
Also, it doesn’t say either way unless I can click on the enemy, it doesn’t say on hover…

btw you can hit esc during that cutscene

Boy, this one was tougher to nail down than I thought it would be. Using your project I’d already downloaded, I decided to Sgt. Pepper this thing with Debug.Log statements… I found it odd that it wasn’t even checking the distance when I moused over the offending bad guy… that simply didn’t make any sense…
So I did a Find all References, and got back to the CombatTarget. I decided to make the very first line of the HandleRaycast to be Debug.Log($"{name} has been moused over");
Interestingly enough, the guard never got the debug.log… nor did anything else “out of range”…
Well… from there, Find all References leads you to the PlayerController…

private bool InteractWithComponent()
        {
            RaycastHit[] hits = RaycastAllSorted();
            foreach (RaycastHit raycastHit in hits)
            {   //The line below this is the culprit, comment it out!
                if (!GetComponent<Mover>().CanMoveTo(raycastHit.transform.position)) return false;

                IRayCastable[] rayCastables = raycastHit.transform.GetComponents<IRayCastable>();
                foreach (IRayCastable rayCastable in rayCastables)
                {
                    if (rayCastable.HandleRaycast(this))
                    {
                        SetCursor(rayCastable.GetCursorType());
                        return true;
                    }
                }
            }
            return false;
        }

It turns out you’ve got a line in here that isn’t in the current version of the Git project, I’ve put a comment above it to show which one to remove.
This line was checking to see if something was able to be moved to and then simply not even checking HandleRayCast if it wasn’t. Since we’re going with a model where the IRayCastables are responsible for those decisions, best to remove this line… sure enough, it works fit as a fiddle.

1 Like

If you were referring to my video in the flame topic, my recording software frowns on the Escape key

1 Like

Thanks so much for putting the calories into finding that, I really appreciate it!

I must have thought as some point if I need to have that there because if you take that line out, you can click on pickups far away, but it does indeed break shooting over water… and I just couldn’t decipher that is what was breaking everything… but now that you point it out, in context that line was not well thought out… so thank you!

now to figure out how to exclude pickups…

boom!

if (!GetComponent<Mover>().CanMoveTo(raycastHit.transform.position)
   && raycastHit.collider.GetComponent<WeaponPickup>()!= null) return false;

Thanks again!!!

Remember that under the new scheme, IRayCastables are responsible for checking these things… so a good hint might be to put that distance check in the Pickup’s HandleRayCastEvent();

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms