Different cursor affordance for paths too long

As a bit of extracurricular activity, I’m not extra happy about how we signal that a player cannot move to a certain place if it’s too far, because I feel using the no activity cursor might tell a player that an area is unreachable tout court, while in fact it’s just a matter of indicating some waypoints.

So I added a second out parameter in RaycastNavMesh, which now looks like this:

private bool RaycastNavMesh(out Vector3 target, out bool isPathTooLong)
        {
            isPathTooLong = false;
            target = new Vector3();

            RaycastHit raycastHit;
            bool hasHit = Physics.Raycast(GetMouseRay(), out raycastHit);
            if (!hasHit) { return false; }

            NavMeshHit navMeshHit;
            bool hasCastToNavMesh = NavMesh.SamplePosition(raycastHit.point, out navMeshHit, _maxNavMeshProjectionDistance, NavMesh.AllAreas);
            if (!hasCastToNavMesh) { return false; }

            target = navMeshHit.position;

            NavMeshPath path = new NavMeshPath();
            bool hasPath = NavMesh.CalculatePath(transform.position, target, NavMesh.AllAreas, path);
            if (!hasPath) { return false; }
            if (path.status != NavMeshPathStatus.PathComplete) { return false; }
            if (GetPathLength(path) > _maxNavPathLength) 
            {
                isPathTooLong = true;
                return false;
            }

            return true;
        }

and then I check it to assign the correct cursor in InteractWithMovement like this:

private bool InteractWithMovement()
        {
            bool isPathTooLong;
            Vector3 target;
            bool hasHit = RaycastNavMesh(out target, out isPathTooLong);

            if (hasHit)
            {
                if (Input.GetMouseButton(0))
                {
                    float speedModifier = 1f;
                    if (_isWalking)
                    {
                        speedModifier = _walkingSpeedFraction;
                    }
                    _mover.StartMoveAction(target, speedModifier);
                }
                SetCursor(CursorType.Movement);
                return true;
            }

            if (isPathTooLong)
            {
                SetCursor(CursorType.PathTooLong);
            }
            else 
            {
            SetCursor(CursorType.None);
            }
            
            return false;
        }

Finally, I removed the last SetCursor(CursorType.None) from Update, because it’s now reduntant (RaycastNavMesh already covers the case when the cursor is over nothing) and interferes with this new cursor setting (from what I could debug, if RaycastNavMesh() returns false, InteractWithMovement() returns false too, which means that Updates does not return after calling it and moves on to that one last cursor set, overwriting whatever InteractWithMovement did, which causes flickering):

private void Update()
        {
            if (Input.GetKeyDown(KeyCode.CapsLock))
            {
                _isWalking = !_isWalking;
            }

            if (InteractWithUI()) { return; }
            if (_health.IsDead())
            {
                SetCursor(CursorType.None);
                return; 
            }
            if (InteractWithComponent()) { return; }
            if (InteractWithMovement()) { return; }
        }

I tested it, and it all seems to work fine, new and old cursor types displaying correctly, including showing the none cursor if we’re pointing over unreachable areas or outside the map.
I don’t know if anyone could find this interesting, but I thought I could share. Also, it would be interesting to know if I did more or less the right thing, or if this is a kludge and there’s a better way to do it.

2 Likes

Nice work!

Good idea and really useful feedback to the player.

Since there are now 3 possible CursorType values that InteractwithMovement() might be setting, perhaps it would make sense to restructure and have all InteractWith...() methods (in case there might be another special one again) simply return the resulting CursorType value as an out parameter instead and only have one place (Update()) actually be setting it? :thinking:

Expanding this thread of thought, there might be other parts in the game that might influence the cursor (for example one could temporarily overload it with a “saving” or “loading” icon when the saving system is doing its job, and the CinematicTrigger might set one as well), so it might at some point be useful to have a separate CursorAffordanceManager to deal with it…

Privacy & Terms