Merging the Third Person Controller Course with the RPG Course

I wrote that almost a day ago, Ed… Obviously I took an hour break (I just came back 30 minutes ago), and obviously I have a backup of my work :smiley: (it takes 25 minutes to make one, not 25 hours :stuck_out_tongue:)

Well that’s what living on opposite sides of the globe will do :laughing:

1 Like

wait till you see the new magic effect I discovered trying to get this algorithm to work… Inventory sizes are now getting multiplied the more I touch them

Totally nothing suspicious going on here :smiley:

kinda disagree with this one tbh… What makes a simple algorithm work, and a slightly more complex version fail? I’m 99% sure something new is responsible for this problem, but I’m struggling to wrap my head around what’s going on in that algorithm, so I don’t know how to fix it (yet)

Edit 1: OK I don’t think I see where the problem is… The half before the final ‘foreach’ loop of the new algorithm pretty much never gets called after you pickup the first item

Edit 2: Looks like it’ll only go to the first if statement when you’re picking your first item up, but most of the time it’ll just skip to the foreach loop at the bottom of the function (and the number of logs coming out of this are quite large, 100 calls, and that’s exactly 2x the size of my Player’s Inventory)… Something fishy is going on

Edit 3: Tested the system as-is outside of the engine, don’t think I need performance optimization measures tbh… I’ll just fully test the game as-is for now

Edit 4: No Shop Optimization issues either (now I remember I solved this with Brian before), just a “Shop won’t open for some reason after a bit of gameplay, unless you “Save and Quit” and return to the game” issue (haven’t solved this one with Brian yet)

I got some ally enemy logic to work. Right now, they can intelligently hunt down the enemies of the player. As simple as the algorithm was, it took me an insanely long time to get it right… Sooner or later, I’ll fix a few weird bugs and then get them to enter and leave the player’s AggroGroup through conversations

Hello, I’m trying to integrate this by my self since my code is already completely different from the rpg course at this point however I’m still a beginner and i cant figure out one thing, since this is my first experience with the new input system i was wandering if

----- there’s a way to make the y of the camera (zoom in zoom out ) change with the mouse scroll and leave only the x on mouse delta ?

the third person course and the guide here as far as i notice use the mouse delta for the y and i don’t see an option when i add the script.is there a different input script i need to add to the camera?

the game I’m making is still using a top down view .

I’ve done something like this in one of my other games. It requires some extra setup, which I’ll post here when I get home from work. It’s not the same as Third Person Follow, so it builds from the Virtual Camera up.

1 Like

thank you so much brain , help full as ever .by the way i loved what you did with this merging .its a total upgrade for those that finished the basic course.By the way , why dont u release your own courses .pretty sure many of us would by it with out even thinking.

1 Like

I have a face made for radio and a voice made for typing…

Plus I generally don’t have the time required to record videos, as the rest of the family does enjoy their TV time.

So this is what I used in Spellborn Hunter… first, a script… In this case, I had an InputManager that linked UnityEvents to Input Actions, but it’s easily adaptable to the InputReader

using Cinemachine;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;


namespace TkrainDesigns.Cinematics
{
    [RequireComponent(typeof(CinemachineVirtualCamera))]
    public class FollowCameraZoomAndRotate : MonoBehaviour
    {
        [SerializeField] private float zoomSensitivity = 1.0f;
        [SerializeField] private float minimumZoom = 5;
        [SerializeField] private float maximumZoom = 20;
        [SerializeField] private float zoomSpeed = 5;
        [SerializeField] private float turnSensitivity = .1f;

        [SerializeField] CinemachineFramingTransposer transposer;

        private void Awake()
        {
            CinemachineVirtualCamera cam = GetComponent<CinemachineVirtualCamera>();
            transposer = cam.GetCinemachineComponent<CinemachineFramingTransposer>();
        }

        private float currentZoom = 20;

        private float desiredZoom = 5;

        private float currentRotation = 0;
        private float desiredRotation = 0;
        private float wheelValue;

        // Called by new input system.  
        public void OnScrollWheelTurn(InputAction.CallbackContext context)
        {
            if (EventSystem.current.IsPointerOverGameObject()) return;
            wheelValue = context.ReadValue<float>();
            desiredZoom -= context.ReadValue<float>() * zoomSensitivity;
            desiredZoom = Mathf.Clamp(desiredZoom, minimumZoom, maximumZoom);
        }

        private bool rotatePressed = false;
        private Vector2 lastFrameMousePosition;

        // called by new input system.  
        public void OnRotateButtonPressed(InputAction.CallbackContext context)
        {
            if (EventSystem.current.IsPointerOverGameObject()) return;
            bool currentRotationPressed = context.ReadValueAsButton();
            if (currentRotationPressed && !rotatePressed)
            {
                lastFrameMousePosition = Mouse.current.position.ReadValue();
            }

            rotatePressed = currentRotationPressed;
        }


        private void Update()
        {
            Rect screen = new Rect(0, 0, Screen.width, Screen.height);
            if (!screen.Contains(Mouse.current.position.ReadValue()))
            {
                return;
            }

            currentZoom = Mathf.Lerp(currentZoom, desiredZoom, Time.deltaTime * zoomSpeed);
            transposer.m_CameraDistance = currentZoom;
            if (rotatePressed)
            {
                ManageRotation();
            }
        }


        private void ManageRotation()
        {
            Vector2 currentPosition = Mouse.current.position.ReadValue();
            Vector2 deltaVector2 = currentPosition - lastFrameMousePosition;

            float delta = 0;

            if (Mathf.Abs(deltaVector2.x) > Mathf.Abs(deltaVector2.y))
            {
                delta = lastFrameMousePosition.y > Screen.height / 2.0f ? -deltaVector2.x : deltaVector2.x;
            }
            else
            {
                delta = lastFrameMousePosition.x < Screen.width / 2.0f ? -deltaVector2.y : deltaVector2.y;
            }

            lastFrameMousePosition = currentPosition;

            desiredRotation += (delta * turnSensitivity * Time.deltaTime);
            currentRotation = Mathf.Lerp(currentRotation, desiredRotation, Time.deltaTime * zoomSpeed);
            Vector3 eulers = transform.eulerAngles;
            eulers.y = currentRotation;
            transform.eulerAngles = eulers;
        }
    }
}

This script then goes on a regular Virtual Camera with a Framing Transposer. Adjust to taste

quietly copy-pastes the function, and vanishes like nothing ever happened :stuck_out_tongue:

if you go on TV, I’m watching it till the end :stuck_out_tongue:

thanks , il try this .i will adapt it if necessary

My game is a shooter top down rpg that includes camera rotation and look at mouse
here is my code for the Movement State .this uses the free look camera and the targeting animations.
this is pretty much a blend bwt the 2 states in my opinion.

using RPG.States.Player;
using UnityEngine;

public class PlayerFreeMouseAim : PlayerBaseState
{
    public PlayerFreeMouseAim(PlayerStateMachine stateMachine) : base(stateMachine)
    {
    }

       private Vector3 movementDirection;
        private Vector3 lookingDirection;//the direction that the player will look at .this will be normalized
        float speed=7f;//multiplayer for movement speed
        float zvel;
        float xvel;

        private static readonly int zVelocityID = Animator.StringToHash("TargetingForward");
        private static readonly int xVelocityID = Animator.StringToHash("TargetingRight");
        private static readonly int TargetingBlendTreeHash = Animator.StringToHash("TargetingBlendTree");
                    
        public override void Enter()
        { 
stateMachine.Animator.CrossFadeInFixedTime(TargetingBlendTreeHash,stateMachine.CrossFadeDuration);
        }

         public override void Tick(float deltaTime)
        {
                if (stateMachine.InputReader.IsAttacking)
        {
            stateMachine.SwitchState(new PlayerAttackingState(stateMachine,0));
        }
        
            Vector3 movement = CalculateMovement();
            stateMachine.CharacterController.Move(movement * (stateMachine.FreeLookMovementSpeed * deltaTime));
            UpdateAnimator();
            AimTowardsMouse();
        }

        private void UpdateAnimator() //UPDATES THE ANIMATOR BASED ON THE LOCAL VELOCITY()
        {
             Vector3 velocity = stateMachine.CharacterController.velocity;
            Vector3 localVelocity = stateMachine.transform.InverseTransformDirection(velocity);
            float zvel = localVelocity.z;
            float xvel = localVelocity.x;
            stateMachine.Animator.SetFloat(xVelocityID,xvel,0.1f,Time.deltaTime);//0.1 is the time for the transition aka DampTime
            stateMachine.Animator.SetFloat(zVelocityID,zvel,0.1f,Time.deltaTime);
        }
       
        public override void Exit()
        {
        }

   private Vector3 CalculateMovement()//THIS CALCULATES THE MOVEMENT
        {
            Vector3 forward = stateMachine.MainCameraTransform.forward;
            Vector3 right = stateMachine.MainCameraTransform.right;
            forward.y = 0;
            right.y = 0;
            forward.Normalize();
            right.Normalize();
            Vector3 movement = right * stateMachine.InputReader.MovementValue.x;
            movement += forward * stateMachine.InputReader.MovementValue.y;
            return Vector3.Min(movement, movement.normalized);
        }

    private void AimTowardsMouse()//THIS TELLS THE CHARACTER WHERE TO LOOK AT
    {
        Ray ray = Camera.main.ScreenPointToRay(stateMachine.InputReader.aimInput);
        if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity, stateMachine.aimLayer))
        {
            lookingDirection = hitInfo.point - stateMachine.transform.position;//point its giving us the exact position of the hit
            lookingDirection.y = 0;
            lookingDirection.Normalize();     //removes the lenght of the vector (gives us  only direction)
            stateMachine.transform.forward = lookingDirection;//making the character forward be towards the m ouse location
            // aim.position=new Vector3(hitInfo.point.x,transform.position.y+1,hitInfo.point.z);
        }
    }
}

This is what worked for me for melle hit recognition/it pretty much never missed when it wasn’t suppose to. Keep in mind I’m not using a targeter and this is only a prototype, some code should be refactored .
Hope it helps.


using System.Collections.Generic;
using RPG.Combat;
using UnityEngine;

public class MelleHitChecker : MonoBehaviour
//this class should be on the weapon prefab(the one that is instanciated on the characters)


{   Fighter fighter;
    
    bool canDealDamage;//used to see if that object has already received damage
    public List<GameObject>hasDealthDamage;//a list of the entities that where already damaged so we dont damaged them again on the same animation
    
    [SerializeField]private float weaponLenght;//this is the lenght of the raycast we are creating.i make it just a litle longer than the weapon

    private void Awake()
    {
        fighter=GetComponentInParent<Fighter>();//we use this to deal damage

    }
    private void Start()
    {
        canDealDamage = false;
        hasDealthDamage=new List<GameObject>();


    }


    void Update()
    {
        if(canDealDamage)
        {
            MelleRaycast();//NOTE : my weaponds by default are aligned with the forward axis(z)
                           //your could be alligned on the y or x (transform.up,transform.right)
        }
        
    }
    private  void MelleRaycast()
    {//this is creating a raycast in the midle of the melle weapond and than checks if it hit something
        RaycastHit  hit;
        int layerMask=1<<8;//layer masks to ignore all the layer masks(8 not included)
        if(Physics.Raycast(transform.position,transform.forward,out  hit,weaponLenght,layerMask))
        {
            if(!hasDealthDamage.Contains(hit.transform.gameObject))
            {
                Debug.Log(hit.transform.gameObject.name);//damage the enemys
                fighter.DealDamageToTarget(hit.transform.gameObject);//deals damage to the hitted target
                hasDealthDamage.Add(hit.transform.gameObject);//adds the hitted target to the list so it doesnt get damaged again
                
            }

        }
    }

    
    // ANIMATION EVENT
    public void StartDealDamage()
    {//we will palce it at the beggining of the animation
     //we use it to tell the raycast when it can start dealing damage


        canDealDamage=true;
        hasDealthDamage.Clear();//we remove every target from the list so we can damage them again
    }


    // ANIMATION EVENT
    public void EndDealDamage()
    {//we use thsi at the end of the animation when we want the animation to not deal damage anymore
      canDealDamage=false;
    }
    

    //THIS IS CALLED BY UNITY
    private void OnDrawGizmos()
    {//we use this to see the raycast 

        
        Gizmos.color = Color.green;
        Gizmos.DrawLine(transform.position,transform.position+transform.forward*weaponLenght);
        
    }


}

for this to work you need to add the StartDealDamage/EndDealDamage in fighter so you can than call them from the animation.animation avents cant be called from childrens but only from components on the same parents(i may be wrong).we also need to call it from the instanciated weapond .

here if fighter :

 void StartDealDamage()

        {
            currentWeaponConfig.StartDealDamage();

        }
        void EndDealDamage()
        {
            currentWeaponConfig.EndDealDamage();

        }

here is weapon config:

  public void StartDealDamage() //animationEvent
        {
          weaponInstance.GetComponent<MelleHitChecker>().StartDealDamage();
        }
        public void EndDealDamage()//animationEvent
        {
             weaponInstance.GetComponent<MelleHitChecker>().EndDealDamage();
        }

in case you dont know how to get the instance weapond here is how i did it in weapon config.

public Weapon Spawn(Transform rightHandT,Transform leftHandT,Animator animator)
        {

            DestroyOldWeapond(rightHandT,leftHandT);
            Weapon weapon=null;

            if (equippedPrefab!=null)
            {
                Transform handTransform = GetTransform(rightHandT, leftHandT);

                 weapon =Instantiate(equippedPrefab, handTransform);
                weapon.gameObject.name=weaponName;
                weaponInstance=weapon;                       //<<<<<<<<<<< //HERE
                

            }
            var overrideController=animator.runtimeAnimatorController as AnimatorOverrideController;
            if (animatorOverride!=null){
               animator.runtimeAnimatorController=animatorOverride;
            }
            else if (overrideController!=null)
            {
                
                animator.runtimeAnimatorController=overrideController.runtimeAnimatorController;
            }
            return weapon;

        }

Good Luck

Privacy & Terms