Merging the Third Person Controller Course with the RPG Course

That is exactly what the first topic I linked above covers.

1 Like

I’m reading it through, believe me… I’m just seeing what can be extracted and placed into our script, since that topic is dedicated to solving the RowUI issue for shops… my issue is the UI for the inventory

Anyway, I’ll go grab something to eat for the time being (it’s 7PM, didn’t eat anything today), and then return to solve this issue

Look at the one above it. It is specifically about inventory.

1 Like

OK I’m a little confused… where? Who? @edc237 (AHH, NEVERMIND… SILLY ME… I HAVE COMPLETELY MISSED THAT COMMENT!)

(I’ll also go implement that solution tbh, that sounds like a solution to a nightmare waiting to happen)

OK so… 3 things to do:

  1. Fix the whole ‘X’ button working from anywhere and deactivating the player, when it’s not supposed to work unless he’s near the bank (Ahh… the dumb things that happen when you forget your ‘if’ statements)
  2. Wait for Brian’s solution for the bank UI
  3. Go through the article and fix the same issue that post writer has

This is the one that covers inventory.

yup, found it. Thank you @edc237 - now it’s time for me to choose which of the approaches mentioned in there to go with… I’ll probably go with the first though, let’s see what happens (I’ll backup my project first)

Edit 1: The first approach of this post failed… apparently it did not work AT ALL for me… In fact, removing ‘InventoryUI.Redraw()’ to replace it with the dictionary approach just ensured that I can’t pick anything off the ground, and more importantly, nothing goes into the inventory… So unless Brian hops on board, I’ll try analyze and understand what’s going on

If it helps though, here is my current ‘InventoryUI.cs’ script:

using UnityEngine;
using GameDevTV.Inventories;
using TMPro;
using UnityEngine.Events;
using System.Linq;
using System.Collections.Generic;

namespace GameDevTV.UI.Inventories
{
    /// <summary>
    /// To be placed on the root of the inventory UI. Handles spawning all the
    /// inventory slot prefabs.
    /// </summary>
    public class InventoryUI : MonoBehaviour
    {
        // CONFIG DATA
        [SerializeField] InventorySlotUI InventoryItemPrefab = null;

        // BANK System CONFIG DATA:
        [SerializeField] bool isPlayerInventory = true;
        [SerializeField] TextMeshProUGUI Title;

        // 3 bank <-> Inventory states: 
        // 'otherInventoryUI' (bank) is open, 
        // 'otherInventoryUI' (bank) is closed, 
        // YOU are the 'otherInventoryUI' (bank)

        public bool IsOtherInventory => !isPlayerInventory;
        public Inventory SelectedInventory => selectedInventory;

        [SerializeField] private UnityEvent EnabledEvent;
        [SerializeField] private UnityEvent DisabledEvent;

        // CACHE
        Inventory selectedInventory;

        // delete if failed
        // InventorySlotUI[] itemUIs;  // the items that get transferred, get refreshed

        // LIFECYCLE METHODS

        private void Awake() 
        {
            // if we are dealing with the player Inventory, set it up (just don't do it for the Players' bank)
            if (isPlayerInventory) {
            selectedInventory = Inventory.GetPlayerInventory();
            selectedInventory.inventoryUpdated += Redraw;
            }        
        }

        private void Start()
        {
            // Redraw the PLAYER INVENTORY, JUST NOT THE BANK!
            if (isPlayerInventory) Redraw();
        }

        // PRIVATE

        private void Redraw()
        {
            foreach (Transform child in transform)
            {
                Destroy(child.gameObject);
            }

            for (int i = 0; i < selectedInventory.GetSize(); i++)
            {
                var itemUI = Instantiate(InventoryItemPrefab, transform);
                itemUI.Setup(selectedInventory, i);
            }
        }

        // PUBLIC

        public bool Setup(GameObject user) {
            if (user.TryGetComponent(out selectedInventory)) {
                // if the object has an inventory (our bank in this case), set the Selected Inventory Up, 
                // Subscribe to the Redraw() changes, and then redraw the inventory (to boot it up for the first time):
                selectedInventory.inventoryUpdated += Redraw;
                Title.text = selectedInventory.name;
                Redraw();
                return true;
            }
            return false;
        }

        private void OnEnable()
        {
            EnabledEvent?.Invoke();
        }

        private void OnDisable()
        {
            DisabledEvent?.Invoke();
        }

        public void TransferAllInventory()
        {
            if (selectedInventory != null)
            {
                selectedInventory.TransferAllInventory(GetOtherInventory());
            }
        }

        private Inventory GetOtherInventory()
        {

            if (IsOtherInventory) return Inventory.GetPlayerInventory();

            // return the FIRST other Inventory it finds (i.e: The bank), or null:
            var otherInventoryUI = FindObjectsOfType<InventoryUI>().FirstOrDefault(ui => ui.IsOtherInventory);

            //Check to see if it's not null (should never happen), if it's Active, and if the otherInventory's inventory is valid.
            if (otherInventoryUI != null && otherInventoryUI.gameObject.activeSelf && otherInventoryUI.SelectedInventory != null)
            {
                Inventory otherInventory = otherInventoryUI.SelectedInventory;
                return otherInventory;
            }

            return null;
        
        }

    }

}

Once we actually get a solution for this, I’ll go also attempt to solve the lag spikes of the shop, from this thread… I just noticed I have these too

Edit 1: I can’t believe it’s been 12 hours, and I still can’t figure out an alternative solution to a stupidly easy algorithm… -_* (WHYYY :confused:)

Edit 2: A little bit of an update. I ended up using this Algorithm:

// ------------ TEST AREA: Rewriting the 'Redraw()' algorithm, so that the banking and/or inventory moving stuff around, works faster --------------

        int playerInventorySize;
        Dictionary<int, InventorySlotUI> inventorySlotCache;

        private void Redraw() 
        {
            int sizeCheck = selectedInventory.GetSize();

            if (inventorySlotCache == null || playerInventorySize != sizeCheck) 
            {
                foreach (Transform child in transform) 
                {
                    Destroy(child.gameObject);
                }

                inventorySlotCache = new Dictionary<int, InventorySlotUI>();
                playerInventorySize = sizeCheck;
                for (int i = 0; i < playerInventorySize; i++) 
                {
                    var itemUI = Instantiate(InventoryItemPrefab, transform);
                    inventorySlotCache[i] = itemUI;
                    itemUI.Setup(selectedInventory, i);
                }

                // Set content window to show top row
                transform.localPosition = new Vector3(transform.localPosition.x, 0, transform.localPosition.z);
            }

            foreach (KeyValuePair<int, InventorySlotUI> inventorySlot in inventorySlotCache) 
            {
                var itemUI = inventorySlot.Value;
                itemUI.Setup(selectedInventory, inventorySlot.Key);
            }
        }

        // ---------------------------------------------- END OF TEST --------------------------------------------------------------------------------------

But there’s a major problem. Whilst it works well for Drag and Drop, and clearly it reduces the time to drag and drop stuff around, this algorithm flat out blocks inventory slots from ever being effective for “Click-to-Equip”, after the first time we use the inventory slot. In simple terms, you click it once, it works. You click it again, it’s never going to work, regardless of what you do, and this goes for both the bank and the inventory…

What’s going on here? I’m just trying not to accidentally destroy my entire project to a point of beyond-repair

1 Like

You said you would back up the project, so it will not be beyond repair. Unfortunately, you have added so many extra things that a solution for the lag based on the standard project will require a lot of extra work. You have all of tools in front of you. Take a break, go for a walk, read a book. Do something else for an hour or two and then come back to it. Your subconscious mind works better on problems like this if you conscious mind is distracted.

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