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

So question, what if I just start my Rpg project with the 3rd person combat lecture and then add the other rpg course on top of it? This could be done correct? (I have not started any of them but going to.) I figured this would be the easiest way to merge the two.

you need both (the 3rd person + the entire RPG Series) to get this merge to work

It’s so worth it though

Sharing the result of adding the systems from RPG course and Third Person Course into my custom controller -

The Inventory, Stats, Shops from gamedev.tv assets are modified to work with mobile… For the controller im using state pattern from the 3rd person course , but heavily modified esp the attack data reaction data, hit data, effects data etc…

https://www.tiktok.com/@hobbycoding/video/7377596300985928966?is_from_webapp=1&sender_device=pc&web_id=7377597529561728520

2 Likes

thanks.
I’ll see you in the future.

How do i close the other inventory with the new system? when i toggle a container the other inventory pops up as it is suppose 2 but when i toggle the inventory off i want to somehow turn the other inventory off and not appear again on the next toggle

public class OtherInventory : MonoBehaviour, IRaycastable
    {
        private ShowHideUI showHideUI;
        private InputReader inputReader;
        
        private void Awake()
        {
          inputReader=GameObject.FindGameObjectWithTag("Player").GetComponent<InputReader>();
          showHideUI = FindObjectsOfType<ShowHideUI>().FirstOrDefault(s => s.HasOtherInventory);
        }

        public CursorType GetCursorType()
        {
            return CursorType.Pickup;
        }

        public bool HandleRaycast(PlayerController callingController)
        {
            
            if (inputReader.GetControls().Player.Interract.triggered)
            {
                showHideUI.ShowOtherInventory(gameObject);
            }
            return true;
        }
    }
}
namespace RPG.UI
{
    public class ShowHideUI : MonoBehaviour
    {
        
        [SerializeField] GameObject uiContainer = null;
        [SerializeField] GameObject otherInventoryContainer=null;
        [SerializeField] InventoryUI otherInventoryUI = null;

        [SerializeField] InputReader inputReader;
        InputControlls controls;
        public bool HasOtherInventory => otherInventoryContainer != null;

        // Start is called before the first frame update
        void Start()
        {
             //InputControlls controls=inputReader.GetControls();
            // uiContainer.SetActive(false);
            // if (otherInventoryContainer != null)
            // {
            //     otherInventoryContainer.SetActive(false);
            // }
            
        }

      
        void Update()
        {

        //    if(controls.UI.Inventory.triggered)
        //    {
        //     if (otherInventoryContainer != null)
        //         {
        //             otherInventoryContainer.SetActive(false);
        //         }
        //    }


            
            // if (Input.GetKeyDown(toggleKey))
            // {
            //     if (otherInventoryContainer != null)
            //     {
            //         otherInventoryContainer.SetActive(false);
            //     }
            //     Toggle();
            // }
        }

        public void Toggle()
        {
            if (uiContainer!=null){
            uiContainer.SetActive(!uiContainer.activeSelf);
            }
        }
        public void ShowOtherInventory(GameObject go)
        {
            uiContainer.SetActive(true);
            otherInventoryContainer.SetActive(true);
            otherInventoryUI.Setup(go);
        }

        public void RemoveOtherInventory()
        {
            if(otherInventoryContainer!=null)
              
            {otherInventoryContainer.SetActive(false);
            uiContainer.SetActive(false);
            }
           
        }
      
    }


    public class InventoryWindow : WindowController
    {

       



         
        protected override void Subscribe()
        { 
            GameObject player = GameObject.FindWithTag("Player");
            
            player.GetComponent<InputReader>().InventoryEvent += ToggleWindow;
            
            gameObject.SetActive(false);
            
            
            
        }

        protected override void Unsubscribe()
        {
            GameObject player = GameObject.FindWithTag("Player");
           
            player.GetComponent<InputReader>().InventoryEvent -= ToggleWindow;
           
        }
        
    }

i know its probably a stupid question, this is do to me not understanding the new way the ui works properly.

Honestly, I didn’t really work on that particular problem. It seems to be closing and not reopening automatically on my end.

I’m not seeing any issues in a code read through, but that’s not always a good indicator. @bahaa has a working edition of the Pickup and Bank, so he might catch something I missed.

At this point in time, I should be hired as a teaching assistant as well :stuck_out_tongue:

Anyway, jokes aside, umm… I removed my ‘ShowHideUI’ from the scene and replaced it with a new ‘ThirdPersonShowHIdeUI.cs’ script. Here you go:

using GameDevTV.UI.Inventories;
using UnityEngine;

public class ThirdPersonShowHideUI : MonoBehaviour
{
    public GameObject UIContainer = null; // player Inventory
    public GameObject otherInventoryContainer = null; // player bank
    public InventoryUI otherInventoryUI = null; // the UI of the player bank

    public bool HasOtherInventory => otherInventoryContainer != null;

    void Start() 
    {
        // when the game starts, deactivate the inventory, and the bank, if the bank exists:
        UIContainer.SetActive(false);
        if (otherInventoryContainer != null) otherInventoryContainer.SetActive(false);
    }

    public void ShowOtherInventory(GameObject otherInventory)
    {
        // Displaying the bank, when this function is called (mainly through a 'System.Action Callback', in 'OtherInventory.cs'):
        UIContainer.SetActive(true);
        otherInventoryContainer.SetActive(true);
        otherInventoryUI.Setup(otherInventory);
    }

    public static void CloseAllWindows() 
    {
        foreach (ThirdPersonShowHideUI tpShowHideUI in FindObjectsOfType<ThirdPersonShowHideUI>()) 
        {
            tpShowHideUI.UIContainer.SetActive(false);
            if (tpShowHideUI.otherInventoryUI != null) 
            {
                tpShowHideUI.otherInventoryContainer.SetActive(false);
            }
        }
    }
}

Wherever you got ‘ShowHideUI’, delete it and place this one instead. When you hit your ‘X’ button, you want to connect the ‘CloseAllWindows’ function to it, that’ll close everything (or you can customize it to your needs. I can’t baby-walk you step by step, as this will demand a lot of my attention, so I’m sure you’ll find your way around things. If you need specific help though, just ask)

Just a heads up though, the bank system is not optimized for performance. @Brian_Trotter we will want to get a working Object Pooling solution working at some point in time. Of all the annoying bugs I dealt with, including yesterday’s “My NPCs are attacking me for no reason” that took me 8 hours to figure out and fix (I accidentally set myself up for this 2 days ago), this is the worst bug for me :sweat_smile: (even the pickup inventory-based system that I created suffers because of this). It dramatically affects the performance of the gameplay when it comes to slot transfers on the long run

(Now that I re-read a bit of your past conversation, I noticed you’re working on a top-down. I converted mine into a pure third person game, so I really can’t be much of help beyond this at this point in time)

Privacy & Terms