Skip to main content
added 4113 characters in body
Source Link
Pheonix2105
  • 554
  • 7
  • 18

Player Interactable RayCaster

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class PlayerInteractable_Raycaster_Comp : PlayerComponent
{
    [Range(0, 100f)]
    public float raycastRange = 100f;

    private PlayerController _controller;
    private PlayerCamera _playerCamera;

    public IInteractable interactableCurrentTarget;
    public IInteractable CurrentInteractable => interactableCurrentTarget;

    public IInteractable lastInteractableTarget;
    public IInteractable LastInteractable => lastInteractableTarget;


    public InteractableEvent OnInteractable;

    // Start is called before the first frame update
    public override void Start()
    {

        _controller = transform.GetComponent<PlayerController>();
        base.Start();
    }

    // Update is called once per frame
    void Update()
    {
        CheckForInteractables();
    }

    public void Reset()
    {
        interactableCurrentTarget = null;
        lastInteractableTarget = null;
    }

    void CheckForInteractables()
    {
        //Create a ray in the world using the middle of the viewport as the start point
        Vector3 rayOrigin = _controller.PlayerCamera.cameraComp.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));

        int allLayersButPlayer = ~LayerMask.GetMask("Player");

        if (Physics.Raycast(rayOrigin, _controller.PlayerCamera.cameraComp.transform.forward, out RaycastHit hitInfo, raycastRange, allLayersButPlayer))
        {
            interactableCurrentTarget = hitInfo.collider.gameObject.GetComponent<IInteractable>();
            float distanceFromInteractable = Vector3.Distance(transform.position, hitInfo.collider.transform.position);

            //Check the hitwidget interface exists
            if (interactableCurrentTarget != null)
            {
                interactableCurrentTarget.OnTargetted();
                //if the last interactable is not equal to the current one, that means this is a new one
                if (interactableCurrentTarget != lastInteractableTarget)
                {
                    if (lastInteractableTarget != null && lastInteractableTarget.Transform.gameObject != null)
                    {
                        lastInteractableTarget.OnTargetDropped();
                    }
                    //set this new widget as the last used
                    lastInteractableTarget = interactableCurrentTarget;
                }
                //else if it is the same widget as last time
                else if (interactableCurrentTarget == lastInteractableTarget)
                {
                    //do nothing for now
                }

                //If the distance between the player and interactable is less than the widgets interaction distance
                if (distanceFromInteractable < interactableCurrentTarget.InteractionDistance)
                {
                      OnInteractable.Invoke(interactableCurrentTarget);
                }

            }
            //Else If ray hits nothing
            else
            {
                ////Check the last interactable isnt null
                if (lastInteractableTarget != null)
                {
                    if (lastInteractableTarget.gameObject != null)
                    {

                        //if it isnt call on target dropped
                        lastInteractableTarget.OnTargetDropped();
                        lastInteractableTarget = null;
                    }
                }
            }

        }

    }
}

Player Interactable RayCaster

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class PlayerInteractable_Raycaster_Comp : PlayerComponent
{
    [Range(0, 100f)]
    public float raycastRange = 100f;

    private PlayerController _controller;
    private PlayerCamera _playerCamera;

    public IInteractable interactableCurrentTarget;
    public IInteractable CurrentInteractable => interactableCurrentTarget;

    public IInteractable lastInteractableTarget;
    public IInteractable LastInteractable => lastInteractableTarget;


    public InteractableEvent OnInteractable;

    // Start is called before the first frame update
    public override void Start()
    {

        _controller = transform.GetComponent<PlayerController>();
        base.Start();
    }

    // Update is called once per frame
    void Update()
    {
        CheckForInteractables();
    }

    public void Reset()
    {
        interactableCurrentTarget = null;
        lastInteractableTarget = null;
    }

    void CheckForInteractables()
    {
        //Create a ray in the world using the middle of the viewport as the start point
        Vector3 rayOrigin = _controller.PlayerCamera.cameraComp.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));

        int allLayersButPlayer = ~LayerMask.GetMask("Player");

        if (Physics.Raycast(rayOrigin, _controller.PlayerCamera.cameraComp.transform.forward, out RaycastHit hitInfo, raycastRange, allLayersButPlayer))
        {
            interactableCurrentTarget = hitInfo.collider.gameObject.GetComponent<IInteractable>();
            float distanceFromInteractable = Vector3.Distance(transform.position, hitInfo.collider.transform.position);

            //Check the hitwidget interface exists
            if (interactableCurrentTarget != null)
            {
                interactableCurrentTarget.OnTargetted();
                //if the last interactable is not equal to the current one, that means this is a new one
                if (interactableCurrentTarget != lastInteractableTarget)
                {
                    if (lastInteractableTarget != null && lastInteractableTarget.Transform.gameObject != null)
                    {
                        lastInteractableTarget.OnTargetDropped();
                    }
                    //set this new widget as the last used
                    lastInteractableTarget = interactableCurrentTarget;
                }
                //else if it is the same widget as last time
                else if (interactableCurrentTarget == lastInteractableTarget)
                {
                    //do nothing for now
                }

                //If the distance between the player and interactable is less than the widgets interaction distance
                if (distanceFromInteractable < interactableCurrentTarget.InteractionDistance)
                {
                      OnInteractable.Invoke(interactableCurrentTarget);
                }

            }
            //Else If ray hits nothing
            else
            {
                ////Check the last interactable isnt null
                if (lastInteractableTarget != null)
                {
                    if (lastInteractableTarget.gameObject != null)
                    {

                        //if it isnt call on target dropped
                        lastInteractableTarget.OnTargetDropped();
                        lastInteractableTarget = null;
                    }
                }
            }

        }

    }
}
Source Link
Pheonix2105
  • 554
  • 7
  • 18

State Machine implementation issues

I have been working on a project for a while now, and i'm reaching the point where my character controller is starting to do more than just move/jump/crouch so I split the interaction stuff off into its own State machine, there are only two states so far for the interaction FSM but I am having problems with input firing in both states.

For example:

Target Object > Press E to fire Interaction > Is Carryable Type > Pick up > Move Around > Press E to fire release carry > Item is dropped > Frame Passes > I shoot straight through the first default state (idle) back to Player_Interaction_Carry resulting in the object being picked back up before it can even hit the floor.

I understand what the issue is, as I am holding a Object, pressing E to release the object works, but then next frame I pass straight through Interaction_idle (E is still down because its a fractions of a second) again to Interaction_Carry.

I thought a state machine would help me fix such problems in that the input would be 'per state'? have I implemented the states or state machine incorrectly? Perhaps I need an event input queue?

Any help appreciated, I have included my statemachine component, and the two interaction states.

Apologies if there is a lot to read, I also couldnt manage to split the scripts into seperate code blocks for easier reading.

Thank you

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class EntityStateMachine_Comp : MonoBehaviour
{
    public BaseState currentState;
    private BaseState lastState;
    private PlayerController _playerController;

    private void Start()
    {
        _playerController = GetComponent<PlayerController>();
    }

    public void Update()
    {
        if (currentState != null)
        {
            currentState.OnUpdate();
        }
        
    }

    public void ChangeToState(BaseState nextState)
    {
        if (currentState != null)
        {
            currentState.OnExit();
        }

        Debug.Log("change to state called");
        Debug.Log(nextState.ToString());
        lastState = currentState;

        currentState = nextState;
        currentState.OnEnter();
    }

    public BaseState GetLastState()
    {
        if (lastState != null)
        {
            return lastState;
        }

        return null;
    }
}




public class Player_Interaction_Idle_State : BaseState
{

    public GameObject carryTarget;
    public IInteractable interactable;

    private PlayerInteractable_Raycaster_Comp _playerRaycaster;

    public Player_Interaction_Idle_State(PlayerController playerController, EntityStateMachine_Comp entityStateMachineComponent) : base(playerController, entityStateMachineComponent)
    {
    }

    public override void OnEnter()
    {
        _playerRaycaster = _playerController.interactableRaycast;
        _playerRaycaster.OnInteractable.AddListener(InteractWithWidget);
    }

    public override void OnExit()
    {
       
    }

    public void InteractWithWidget(IInteractable interactable)
    {
        InteractableType interactableType = interactable.InteractionType;


        if (Input.GetKeyDown(KeyCode.E))
        {
            switch (interactableType)
            {
                case InteractableType.USE:
                    interactable.OnInteract();
                    break;

                case InteractableType.CARRY:
                    _playerController.carryTarget = interactable.Transform.gameObject;
                    _playerController.isCarrying = true;
                    _stateMachineController.ChangeToState(new Player_Interaction_Carry_State(_playerController, _stateMachineController));
                    break;

                case InteractableType.LOOT:
                    _playerController.playerInventory.AddItem(interactable.Item, 1);

                    RemoveWidgetFromWorld(interactable);
                    break;
            }
        }
    }


    public void RemoveWidgetFromWorld(IInteractable interactable)
    {
        GameObject.Destroy(interactable.gameObject);
        //make sure to reset targets on raycaster 
        _playerController.interactableRaycast.Reset();
    }


    public override void OnUpdate()
    {

    }

}






public class Player_Interaction_Carry_State : BaseState
{

    private AttachmentPoint attachTarget;

    private GameObject _carryTarget;
    private ICarryable _carryTargetCarryable;

    public Player_Interaction_Carry_State(PlayerController playerController, EntityStateMachine_Comp entityStateMachineComponent) : base(playerController, entityStateMachineComponent)
    {
    }

    public override void OnEnter()
    {
        _carryTarget = _playerController.carryTarget;
        _carryTargetCarryable = _carryTarget.GetComponent<ICarryable>();
        CanCarry(_carryTargetCarryable);
        Debug.Log("On enter interaction carry");
    }

    public override void OnExit()
    {
      
    }

    public void CanCarry(ICarryable carryable)
    {
        attachTarget = _playerController.attachComp.GetNextUnoccupiedHand(carryable.CarryType);

        if (attachTarget != null)
        {
            _playerController.isCarrying = true;
        }
    }

    public override void OnUpdate()
    {
        if (Input.GetKeyDown(KeyCode.E) && _playerController.isCarrying)
        {
            //release?
            Debug.Log("E Down Called, Player is carrying and currentinteractable target is same as carry target");
            _playerController.isCarrying = false;
            //interactable.Transform.parent = null;
            Debug.Log("Release object");
            _carryTargetCarryable.OnDropped();
            attachTarget.Detach(_carryTarget.transform);

            attachTarget = null;
            _playerController.carryTarget = null;

            _stateMachineController.ChangeToState(new Player_Interaction_Idle_State(_playerController, _stateMachineController));
        }
        else if (_playerController.isCarrying)
        {
            if (_playerController.carryTarget != null && attachTarget != null)
            {
                _carryTargetCarryable.OnCarried();
                attachTarget.Attach(_carryTarget.transform);
            }
        }
    }

}