Unity RPG Offhand/Shield equipment slot (W/ added Dual Wielding)

,

Hello all, I have been hard at work expanding upon the RPG course (mainly the combat and inventory systems thus far), but with the help of @Brian_Trotter I was able to expand the equipment system to include equipping a Shield, Dual-Wielding, and Two-handed weapons and I wanted to share my results on here to help guide anyone who may wish to do so in their own RPG in the future.

So for the Shield, I had to create a separate config similar to the WeaponConfig.cs with 2 scripts Shield.cs & ShieldConfig.cs

Shield.cs

 using UnityEngine;
using UnityEngine.Events;

namespace RPG.Combat
{
   public class Shield: MonoBehaviour
{
    [SerializeField] UnityEvent onHit;

    public void OnHit()
    {
        onHit.Invoke();
    }
}
}

ShieldConfig.cs

using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Inventories;
using RPG.Stats;

namespace RPG.Combat
{
 [CreateAssetMenu(fileName = "Shield", menuName = "Shields/Make New Shield", order = 0)]
  public class ShieldConfig : EquipableItem, IModifierProvider
{
    [SerializeField] Shield equippedPrefab;
    [SerializeField] bool isRightHanded;

    [SerializeField] float defenseBonus = 0;
    [SerializeField] float percentageBonus = 0;

    const string shieldName = "Shield";
    public Shield SpawnEquipableItem(Transform rightHand, Transform leftHand)
    {
        DestroyOldEquipableItem(rightHand, leftHand);

        Shield shield = null;
        if (equippedPrefab != null)
        {
            Transform handTransform = GetTransform(rightHand, leftHand);
            shield = Instantiate(equippedPrefab, handTransform);
            shield.gameObject.name = shieldName;
        }

        return shield;
    }

    public Transform GetTransform(Transform rightHand, Transform leftHand)
    {
        Transform handTransform;
        if (isRightHanded) handTransform = rightHand;
        else handTransform = leftHand;
        return handTransform;
    }

    void DestroyOldEquipableItem(Transform rightHand, Transform leftHand)
    {
        Transform oldWeapon = rightHand.Find(shieldName);
        if (oldWeapon == null)
        {
            oldWeapon = leftHand.Find(shieldName);
        }
        if (oldWeapon == null) return;

        oldWeapon.name = "DESTROYING";
        Destroy(oldWeapon.gameObject);
    }
//Add Armor to enum in Stats.cs
    public IEnumerable<float> GetAdditiveModifiers(Stat stat)
    {
        if (stat == Stat.Armor)
        {
            yield return defenseBonus;
        }
    }

    public IEnumerable<float> GetPercentageModifiers(Stat stat)
    {
        if (stat == Stat.Armor)
        {
            yield return percentageBonus;
        }
    }
}

With these, you would be able to create a shield prefab similarly to a weapon prefab in the unity editor.
Note: Don’t forget to add a EquipmentSlot UI that can accept a shield/offhand as an equipable item.

For Dual-wielding and Two-handed weapons, I modified the WeaponConfig.cs for Dual-wielding you would have to duplicate the processes in Spawn() to add a secondary weapon prefab to instantiate. For Two-handed weapons, I added a public enum to differentiate between lefthand, righthand, or both as well as a bool.

WeaponConfig.cs

using System.Collections.Generic;
using UnityEngine;
using RPG.Attributes;
using GameDevTV.Inventories;
using RPG.Stats;
namespace RPG.Combat
{
                                                                                                                   
[CreateAssetMenu(fileName = "Weapon", menuName = "Weapons/Make New Weapon", order = 0)]
     public class WeaponConfig : EquipableItem, IModifierProvider
{

    [SerializeField] Weapon equippedPrefab = null;
    [SerializeField] Weapon equippedPrefab2 = null;
    
    public bool isTwoHanded = false;
    public bool isDualWield = false;
    
    [SerializeField] Projectile projectile = null;
    [SerializeField] float attackBonus = 1;
    public enum WeaponHands { right, left, both }
    [SerializeField] WeaponHands mainHand;

    const string weaponName = "Weapon";
    const string weaponName2 = "Weapon2";

    public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
    {
        DestroyOldWeapon(rightHand, leftHand);
        DestroyOtherWeapon(rightHand, leftHand);

        Weapon weapon = null;
        Weapon weapon2 = null;

        if (equippedPrefab != null)
        {
            Transform handTransform = GetMainHand(rightHand, leftHand);
            if (mainHand == WeaponHands.left)
            {
                weapon = Instantiate(equippedPrefab, handTransform);
            }
            if (mainHand == WeaponHands.right)
            {
                weapon = Instantiate(equippedPrefab, handTransform);
                
            }
            if (mainHand == WeaponHands.both)
            {
                weapon = Instantiate(equippedPrefab, rightHand);
                if (equippedPrefab2 != null && isDualWield)
                {
                    weapon2 = Instantiate(equippedPrefab2, leftHand);
                    weapon2.gameObject.name = weaponName2;
                }
                 
            }
      //No changes in Spawn() beyond this point...
    }


    private void DestroyOtherWeapon(Transform rightHand, Transform leftHand)
    {
        Transform oldWeapon2 = leftHand.Find(weaponName2);

        if (oldWeapon2 == null)
        {
            oldWeapon2 = rightHand.Find(weaponName2);
        }

        if (oldWeapon2 == null) return;

        oldWeapon2.name = "DESTROYING";

        Destroy(oldWeapon2.gameObject);
    }
//Replaced GetTransform() with GetMainHand()
    private Transform GetMainHand(Transform rightHand, Transform leftHand)
    {
        Transform handTransform;
        if (mainHand == WeaponHands.right) return rightHand;
        if (mainHand == WeaponHands.left) return leftHand;
        if (mainHand == WeaponHands.both)
        {
            if (rightHand && !leftHand)
            {
                return rightHand;
            }
            if (leftHand && !rightHand)
            {
                return leftHand;
            }
        }
        return handTransform = rightHand;
    }
       //No further changes beyond this point...

}

Finally, the changes that need to be made to the Inventory system to accept the shield and other item configs are by modifying MaxAcceptable() in EquipmentSlotUI.cs in order to ensure that you cannot equip a shield while wielding a dual/two-handed weapon and vice-versa.

     public int MaxAcceptable(InventoryItem item)
     {
        {
            EquipableItem equipableItem = item as EquipableItem;
            if (equipableItem == null) return 0;
            if ((equipLocation == EquipLocation.Weapon) && item is WeaponConfig weaponConfig)
            {
                if (weaponConfig.isTwoHanded && (playerEquipment.GetItemInSlot(EquipLocation.Offhand) != null))
                    return 0;
            }

            if (equipLocation == EquipLocation.Offhand)
            {
                EquipableItem mainHand = playerEquipment.GetItemInSlot(EquipLocation.Weapon);
                if (mainHand is WeaponConfig mainHandConfig)
                {
                    if (mainHandConfig.isTwoHanded) return 0;
                }
            }
            //The line below only makes sense after completing the Shops and Abilities course
            //if (!equipableItem.CanEquip(equipLocation, playerEquipment)) return 0; 
            if (GetItem() != null) return 0;

            return 1;
        }
    }

Edit: It was brought to my attention by @Maximilien that you will need to go into the Fighter.cs and practically copy the EquipWeapon() methods, but calling the Shield Config instead.

    //add these to configurables
    ShieldConfig currentShield;
    LazyValue<Shield> currentEquippedShield;
    private void Awake()
    {
        currentWeaponConfig = defaultWeapon;
        currentWeapon = new LazyValue<Weapon>(SetupDefaultWeapon);
        
        currentShield = defaultShield;
        currentEquippedShield = new LazyValue<Shield>(null);

        equipment = GetComponent<Equipment>();
        if (equipment)
        {
            equipment.equipmentUpdated += UpdateWeapon;
        }
    }

Add Underneath EquipWeapon()

   public void EquipShield(ShieldConfig shield)
    {
        currentShield = shield;
        currentEquippedShield.value = AttachShield(shield);
    }
   private Shield AttachShield(ShieldConfig shield)
    {
        return shield.SpawnEquipableItem(rightHandTransform, leftHandTransform);
    }

    // use to destroy Shield/offhand prefab when the item is removed
    private void DestroyOffHandItem()
    {
        Shield offHandWeapon = leftHandTransform.GetComponentInChildren<Shield>();
        if(offHandWeapon == null)
        {
            return;
        }
        DestroyImmediate(offHandWeapon.gameObject);

    }

Then Modify UpdateWeapon

   private void UpdateWeapon()
    {
        var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
        var shield = equipment.GetItemInSlot(EquipLocation.Offhand) as ShieldConfig;

        if (weapon == null)
        {
            EquipWeapon(defaultWeapon);
        }
        else
        {
            EquipWeapon(weapon);
        }
        if (shield != null)
        {
            EquipShield(shield);

        }
        else
        {
            DestroyOffHandItem();
        }

    }

and that should be all that you need to do, hope this helps anyone on their Gamedev journey as they will be able to add more depth with items such as shields and various weapon combinations now available.

Any comments or concerns are appreciated and Thank you for your time and huge Thanks to the Gamedev.TV team for their support and guidance.

6 Likes

Well done!!! I love seeing you guys take things to the next level!

2 Likes

Hi.

Thanks for sharing this.

I tryed only for the shield. I made one EquipedShieldPrefab.
I can loot it and equip it and also get stats modifier but, the Shield is never instancied.

public Shield SpawnEquipableItem(Transform rightHand, Transform leftHand)

is never called.

Did i miss something ?

I see Fighter Script call spawn for weapon (Attach Weapon) , but where should i do the same for shield plz ?

(When i Equip weapon , i see the GameObject Weapon under Right hand in Hierachy but when i equip Shield nothing under left hand )

Thank you.

2 Likes

Thank you for bringing this to my attention, I forgot to add the expansions to the Fighter.cs script for the Shield, it’s practically doing the same as the EquipWeapon(), but using the shield as a Lazy Value and calling the Shield Config instead.

So in Fighter.cs add in your configurables

    ShieldConfig currentShield;
    LazyValue<Shield> currentEquippedShield;
private void Awake()
    {
       //add the below
        currentShield = defaultShield;
        currentEquippedShield = new LazyValue<Shield>(null);
    }

Add underneath EquipWeapon(WeaponConfig weapon)

    public void EquipShield(ShieldConfig shield)
    {
        currentShield = shield;
        currentEquippedShield.value = AttachShield(shield);
    }
   private Shield AttachShield(ShieldConfig shield)
    {
        return shield.SpawnEquipableItem(rightHandTransform, leftHandTransform);
    }

Then rework UpdateWeapon()

    private void UpdateWeapon()
    {
        var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
        var shield = equipment.GetItemInSlot(EquipLocation.Offhand) as ShieldConfig;

        if (weapon == null)
        {
            EquipWeapon(defaultWeapon);
        }
        else
        {
            EquipWeapon(weapon);
        }
        if (shield != null)
        {
            EquipShield(shield);

        }
        else
        {
            DestroyOffHandItem();
        }

    }

Then Destroy the shield prefab when it’s removed

   private void DestroyOffHandItem()
    {
        Shield offHandWeapon = leftHandTransform.GetComponentInChildren<Shield>();
        if(offHandWeapon == null)
        {
            return;
        }
        DestroyImmediate(offHandWeapon.gameObject);

    }

Hope this solves the problem and good luck on your project!

3 Likes

Thanks , i made it :slight_smile:

I almost finished the course Shop & Abilities . ( At lecture 83 Armor and defense ) Now my shield is visible. I m really happy.

It’s a lot to assimilate because I did the 4 Courses in a row.
Thanks again for your help.

2 Likes

No problem, I’m currently at the same lecture you’re at as chance would have it :grin:. I do plan to make a few more future posts about shop items having trait requirements along with abilities that damage and/or heal over time in case you’re interested.

1 Like

Oh, that would be really interesting.

I’m trying to improve my game as best I can. I try to have as much functionality as possible before launching into the story and the maps. What I would like to do is group my traits and stats together so that I can assign stats to items. Brian explained to me that it was necessary to use only one enum. But I admit I have not tryed yet.

I will look for your new incoming posts.

Thx again.

1 Like

I didn’t say it was neccessary, just that it was possible (though if you want specific traits to improve naturally through the Progression, then yes, they should be in the Stats enum). :slight_smile:

1 Like

Hi Brian. Sorry , My English is bad. Trying my best to make other understand me. :slightly_smiling_face:.

3 Likes

Thanks for this. I used it so my shield can be seen visibly equipped now.

I haven’t tried the dual wield yet, but how do you handle the animation of this?

if the main hand is equipped with a sword(using animator override) and offhand a shield then you change the shield to another sword. granting you dual swords now both with animator overrides on both hands.

I just finished the inventory course so it might be explained in the future videos. if so you can ignore this question. :grinning_face_with_smiling_eyes:

2 Likes

I haven’t gotten the equipment system to do that quite yet, but for how I’ve set up Dual Wielding we’re kinda cheesing it a bit to save us from furthering the complexity of the equipment system.

In the WeaponConfig.cs you’ll see I added a second serializable weapon prefab and added a left-hand transform to instantiate the prefab to. Added with the logic of Two-Handed weapons (so the player can’t equip a shield and break the inventory system) we are actually using spawning one instance instead of two. So that one weapon scriptable object spawns two weapons, so the animator override will have to be one that has the dual wielding animations attached to the Dual Weapon scriptable object override. Hope this makes sense and clears things up.

2 Likes

Oh nice, I see. I first thought the weapons for both hands are individual weapons by themselves that’s why.:grinning_face_with_smiling_eyes:
I get it now. I will be also be trying it in my game when I get further into the course.
Right now, I am using this for the shield. Thank you.

2 Likes

Hey Thomas, how do you restore the state for the shield?

  • I tried saving while my shield is equipped, but it does not show the shield being instantiated on game restart.
  • when in play mode and I equip the shield then save(pressing S) and unequip the shield then load(pressing L). the shield is lost.

have you experienced something like this? Thanks.

2 Likes

Hmm, I don’t think I’ve ever experienced that, but I believe that it’s a bug in the saving system that is addressed in one of the later GameDev RPG Lectures (I want to say during the Shops & Abilities series, but don’t quote me on that).

1 Like

I see, I will just have to continue with the course then.
I might be trying to deal with something that is already fixed.
thank you. will get back here if the problem persists. :raised_hands:

1 Like

Hello, thanks for sharing this information.
I have a question about WeaponConfig.cs, after creating item, in the right hand dont destroy the weapon on scene… I hope you tell me where I could make a mistake, I think I did something wrong in the WeaponConfig.Spawn()…

1 Like

Paste in your WeaponConfig.Spawn() method and we’ll take a look.

2 Likes
 public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
        {
            DestroyOldWeapon(rightHand, leftHand);
            DestroyOtherWeapon(rightHand, leftHand);

            Weapon weapon = null;
            Weapon weapon2 = null;

            if (equippedPrefab != null)
            {
                Transform handTransform = GetMainHand(rightHand, leftHand);
                if (mainHand == WeaponHands.left)
                {
                    weapon = Instantiate(equippedPrefab, handTransform);
                }
                if (mainHand == WeaponHands.right)
                {
                    weapon = Instantiate(equippedPrefab, handTransform);

                }
                if (mainHand == WeaponHands.both)
                {
                    weapon = Instantiate(equippedPrefab, rightHand);
                    if (equippedPrefab2 != null && isDualWield)
                    {
                        weapon2 = Instantiate(equippedPrefab2, leftHand);
                        weapon2.gameObject.name = weaponName2;
                    }

                }
            }

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

            return weapon;
        }

After I remove the weapon from the equipment slot, the sword remains in right hand. Also, when I check the “isDualWield” box, the sword stays in right hand and disappears in my left hand when I take offdual swords.

1 Like

I also forgot to write that when items spawn in the right hand, they do not change to “Weapon” and the name remains the same as the ScriptableObject was signed

1 Like

Sorry to bother you, I found the problem, it was so obvious and stupid of me…

 public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
        {
            DestroyOldWeapon(rightHand, leftHand);
            DestroyOtherWeapon(rightHand, leftHand);

            Weapon weapon = null;
            Weapon weapon2 = null;

            if (equippedPrefab != null)
            {
                Transform handTransform = GetMainHand(rightHand, leftHand);
                if (mainHand == WeaponHands.left)
                {
                    weapon = Instantiate(equippedPrefab, handTransform);
                }
                if (mainHand == WeaponHands.right)
                {
                    weapon = Instantiate(equippedPrefab, handTransform);

                }
                if (mainHand == WeaponHands.both)
                {
                    weapon = Instantiate(equippedPrefab, rightHand);
                    if (equippedPrefab2 != null && isDualWield)
                    {
                        weapon2 = Instantiate(equippedPrefab2, leftHand);
                        weapon2.gameObject.name = weaponName2;
                    }

                }
                weapon.gameObject.name = weaponName; //this... (-_-)
            }
1 Like

Privacy & Terms