Hi yes that works. When i hoover over the enemy with the AIConversant i get back this message in the console. Maybe the bug is in the playercontroller? I cant see any
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using System;
using RPG.Movement;
using RPG.Combat;
using RPG.Attributes;
using UnityEngine.EventSystems;
using GameDevTV.Inventories;
namespace RPG.Control
{
public class PlayerController : MonoBehaviour
{
Health health;
// a struct is a box that stores variables. can access the variables with its name, CursorMapping.type = set its value.
// make variables public so they are serializable and unity will show them in the editor.
[System.Serializable]
struct CursorMapping
{
public CursorType type;
// cursor sprite.
public Texture2D texture;
// will make sure the structs cursor in unity follows our mouse when we add it to Cursor.SetCursor.
public Vector2 hotspot;
}
[SerializeField] CursorMapping[] cursorMappings = null;
[SerializeField] float maxNavMeshProjectionDistance = 1f;
[SerializeField] float raycastRadious = 0.1f;
bool isDraggingUI = false;
private void Awake()
{
health = GetComponent<Health>();
}
// Update is called once per frame
void Update()
{
// Use items in actionslots,hotbar.
CheckSpecialAbilityKeys();
// if we are interacting with UI we do nothing else.
if (InteractWithUI()) return;
// if player is dead do nothing. cant move when dead.
if (health.IsDead())
{ // show the none cursor type.
SetCursor(CursorType.None);
return;
}
// if we clicked an enemy skip rest of the body and exit update. we do this so if
// we click an enemy we wont use interactwithmovement to move to it.
//if (InteractWithCombat()) return;
// deals with the raycastables. if we can interact with component we return.
if (InteractWithComponent()) return;
if (InteractWithMovement()) return;
// if we didnt interact with UI, component or movement we set the cursor to type none.
SetCursor(CursorType.None);
}
private void CheckSpecialAbilityKeys()
{
var actionStore = GetComponent<ActionStore>();
if (Input.GetKeyDown(KeyCode.Alpha1))
{
actionStore.Use(0, gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
actionStore.Use(1, gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
actionStore.Use(2, gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha4))
{
actionStore.Use(3, gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha5))
{
actionStore.Use(4, gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha6))
{
actionStore.Use(5, gameObject);
}
}
// raycasts through the world getting all of the raycast hits. we go over all the hits and get the
// gameobjects. then we get all the components on the gameobjects that implements IRaycastable. The first
// raycastable that can handle the raycast sets the cursortype.
private bool InteractWithComponent()
{
// returns sorted list of hit results. returns all the things the ray hits.
RaycastHit[] hits = RaycastAllSorted();
// codeblock will run for each hit in the hits array.
foreach (RaycastHit hit in hits)
{
// get all the raycastable components on the gameobject we hit.
IRaycastable[] raycastables = hit.transform.GetComponents<IRaycastable>();
// go through each raycastable component and check if it can the handle raycast.
foreach (IRaycastable raycastable in raycastables)
{
// pass in the playercontroller component. if the handleraycast is true return true and set cursor.
if (raycastable.HandleRaycast(this))
{
SetCursor(raycastable.GetCursorType());
return true;
}
}
}
// no racastable components that could handle the raycast.
return false;
}
// sorts raycast hits based on distance. this way we always get the objects closest to us
// when we search for a raycastable.
RaycastHit[] RaycastAllSorted()
{
// get all the rays. use a spherecastall with a ray and a radious. this way we get a
// fatter raycast so its easier to hover over enemies.
RaycastHit[] hits = Physics.SphereCastAll(GetMouseRay(), raycastRadious);
// array of floats with lenght same as hits.
float[] distances = new float[hits.Length];
// add for each hit the raycast distance to distances array.
for (int i = 0; i < hits.Length; i++)
{
distances[i] = hits[i].distance;
}
// sorts the hits array based on the distances array.
Array.Sort(distances, hits);
// gets sorted list with closest array first.
return hits;
}
private bool InteractWithUI()
{
// can release it when we are not over ui.
if (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1))
{
isDraggingUI = false;
}
// are our mouse over a game object that is a piece of UI, not a regular gameobject. returns true if we are.
// return true if we are over a UI. This can place an eventsystem in your scene, must make sure we have one in every scene.
if (EventSystem.current.IsPointerOverGameObject())
{
// can only set it if we are over ui.
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1))
{
isDraggingUI = true;
}
SetCursor(CursorType.UI);
return true;
}
// if we are dragging we are interacting with ui.
if (isDraggingUI == true)
{
return true;
}
// if we are not over a UI return false.
return false;
}
// move to clicked position.
private bool InteractWithMovement()
{
// target position we move to.
Vector3 target;
// we raycast to the navmesh, if we hit some point on the navmesh, we move there. and set cursor to move.
bool hasHit = RaycastNavmesh(out target);
if (hasHit)
{
// If we cant move to target return false so we dont move. Its out of range.
if (!GetComponent<Mover>().CanMoveTo(target)) return false;
if (Input.GetMouseButton(1))
{
GetComponent<Mover>().StartMoveAction(target, 1f);
}
SetCursor(CursorType.Movement);
return true;
}
return false;
}
// see if we hover over an area with navmesh, if not dont change the cursor to move.
// returns true if we hit a navmesh and an out paramater that will give us the location we should start moving to on the navmesh.
private bool RaycastNavmesh(out Vector3 target)
{
target = new Vector3();
// get hit point of ray and move to clicked position.
RaycastHit hit;
// Pass in the ray itself and hit stores the information about
// where the raycast hits. the ray shoots continuely out of the mouse without clicking.
bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
// if we dont hit anything return false.
if (!hasHit) return false;
// A hit on the navmesh.
// 1 sample point on navmesh, if theres no navmesh we cant move to it.
NavMeshHit navMeshHit;
// Finds nearest navmesh point. Give it a position, resulting location, maxdistance it should try and find the nearest navmeshpoint, area mask just use the default.
bool hasCastToNavMesh = NavMesh.SamplePosition(hit.point, out navMeshHit, maxNavMeshProjectionDistance, NavMesh.AllAreas);
// return false if it dont find a navmesh point.
if (!hasCastToNavMesh) return false;
// get the position on the navmesh. this is returned from the function.
target = navMeshHit.position;
return true;
}
// gets cursormapping from cursormapping array in unity, sets the values so we can use the cursor.
private void SetCursor(CursorType type)
{
// gets the correct cursormapping.
CursorMapping mapping = GetCursorMapping(type);
// use unity method for setting cursor. takes a texture2d(the cursor sprite), a vector2d hotspot(cursors location, which follows the mouse),
// cursermode set it always to auto.
Cursor.SetCursor(mapping.texture, mapping.hotspot, CursorMode.Auto);
}
// returns cursor mapping from cursormapping array.
private CursorMapping GetCursorMapping(CursorType type)
{
foreach (CursorMapping mapping in cursorMappings)
{
if(mapping.type == type)
{
return mapping;
}
}
// if we dont find anything return the first in the array.
return cursorMappings[0];
}
// get ray from camera to clicked position.
private static Ray GetMouseRay()
{
// when we hover our mouse over the scene we get a ray from our
// camera origin to the mouse position.
return Camera.main.ScreenPointToRay(Input.mousePosition);
}
// no longer in use.
// we dont use this code anymore, implemented alternative in combattarget. this just shows how it can be done.
private bool InteractWithCombat()
{
// returns list of hit results. returns all the things the ray hits.
RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
// codeblock will run for each hit in the hits array.
foreach (RaycastHit hit in hits)
{
CombatTarget target = hit.transform.GetComponent<CombatTarget>();
// if we click on something that does not have a combat target, stop.
// continue means skip the rest of the code and go directly to the next item in the loop.
if (target == null) continue;
// if we execute the code below that means we have found a hit with a CombatTarget that is
// not null(it has a health component) and is not dead.
if (GetComponent<Fighter>().CanAttack(target.gameObject) == false)
{
continue;
}
// if we clicked the left mouse button, attack! or are holding it down while
// we point on the enemy, attack!
if (Input.GetMouseButton(0))
{
// we pass in the gameobject of the target so we know who we attacked.
GetComponent<Fighter>().Attack(target.gameObject);
}
// if we have found an enemy change the cursor. must do this before we return.
SetCursor(CursorType.Combat);
// return true if target was not null or dead.
return true;
}
// return false if all targets was null.
return false;
}
}
}