Instantiating Parried Arrows

Hello all

So… I’m trying to create an arrow parrying system in my own combat system. Essentially, a solution to help me parry arrows by playing a parrying animation

Here is the code that I came up with, for this system:

using UnityEngine;
using RPG.States.Player;
using RPG.InputReading;
using RPG.Core;
using RPG.Combat;
using System.Collections;
using RPG.Skills;
using RPG.Attributes;

namespace RPG.Parrying {

public class ShieldParryingSystem : MonoBehaviour
{
    // This script exists on the player, and contains a toggled sphere collider reference to it.
    // If the player is parrying (InputReader.IsParrying), and there's a projectile in the sphere range,
    // and assuming he is face to face with the arrow, then he can 
    // fire the arrow back at whoever fired the arrow at him

    // PRIVATE
    private SphereCollider sphereCollider;
    private PlayerStateMachine player;
    private InputReader inputReader;
    private bool hasParried = false;
    private CooldownTokenManager cooldownTokenManager;
    [SerializeField] Projectile projectileInRange = null;

    private void Awake() 
    {
        sphereCollider = GetComponent<SphereCollider>();

        if (sphereCollider != null) 
        {
            sphereCollider.enabled = false;
        }
        else Debug.Log($"Incoming Ranged Attack Detector not found on Player");

        cooldownTokenManager = GetComponentInParent<CooldownTokenManager>();
        if (cooldownTokenManager != null) 
        {
            Debug.Log($"Cooldown Token Manager found on player parent");
        }
        else Debug.Log($"Cooldown Token Manager not found on player parent");

        player = GameObject.FindWithTag("Player")?.GetComponent<PlayerStateMachine>();
        if (player != null) 
        {
            Debug.Log($"Player State Machine found");
            inputReader = player.GetComponent<InputReader>();
            if (inputReader != null) 
            {
                Debug.Log($"Input Reader found");
            }
            else Debug.Log($"InputReader not found on the player");
        }
        else Debug.Log($"Player State Machine not found");
    }

    private void OnEnable()
    {
        if (inputReader != null) inputReader.ParryingEvent += OnArrowDeflectionPerformed;
    }

    private void OnDisable()
    {
        if (inputReader != null) inputReader.ParryingEvent -= OnArrowDeflectionPerformed;
    }

    private void OnArrowDeflectionPerformed()
    {
        Debug.Log($"Shield Parrying Deflecting Arrow");
        // if (!hasParried)
        {
            Debug.Log($"Check if parrying is on Cooldown");
            if (IsParryOnCooldown())
            {
                Debug.Log($"ShieldParryingSystem says parrying is on cooldown");
                return;
            }
            else
            {
                Debug.Log($"ShieldParryingSystem says parrying is not on cooldown");
                ActivateTrigger();
                hasParried = true;
                cooldownTokenManager.SetCooldown("ParryArrowDeflectCooldown", player.GetComponent<Fighter>().GetCurrentShieldConfig().GetParryingCooldownDuration(), false);
                StartCoroutine(StartDelayedDeactivation(0.3f));
            }
        }
    }

    private bool IsParryOnCooldown()
    {
        return cooldownTokenManager.HasCooldown("ParryArrowDeflectCooldown");
    }

    private void ActivateTrigger()
    {
        sphereCollider.enabled = true;
        Debug.Log($"Incoming Ranged Attack Detector for player has been activated");
    }

    private void DeactivateTrigger()
    {
        sphereCollider.enabled = false;
        Debug.Log($"Incoming Ranged Attack Detector for player has been deactivated");
    }

    private IEnumerator StartDelayedDeactivation(float delay)
    {
        yield return new WaitForSeconds(delay);
        DeactivateTrigger();
        Debug.Log($"Deactivation delayed and trigger deactivated");
    }

    private void OnTriggerEnter(Collider other) 
    {
        if (other.TryGetComponent<Projectile>(out var projectile))
            {
                Debug.Log($"{other.gameObject.name} has gotten into player range");

                if (GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab() != null) {
                projectileInRange = Instantiate(projectile, GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab().transform);
                projectileInRange.SetTarget(projectile.GetInstigator().GetComponent<Health>(), GetComponentInParent<Fighter>().gameObject, projectile.GetDamage(), Skill.None);
                Debug.Log($"Projectile instantiated and set to fire back at instigator");
                }
            }
            else Debug.Log($"Collider does not contain a projectile");
        }
    }
}

The idea was to create a mid-level trigger around the player, and activate and deactivate its sphere collider when parrying occurs, through an Event-based system (I’m trying to get better with events). When the arrow is close enough to be in the trigger area of the player, and assuming it activates (i.e: Player has hit the parrying button), it fires the arrow back, essentially creating the parry system that I’ve been trying to create

However, I have a problem that I can’t seem to get rid of, or figure out a solution for, and it always shows up in the Unity Editor (about 60% of the time):

Cannot instantiate objects with a parent which is persistent. New object will be created without a parent.
UnityEngine.Object:Instantiate<RPG.Combat.Projectile> (RPG.Combat.Projectile,UnityEngine.Transform)
RPG.Parrying.ShieldParryingSystem:OnTriggerEnter (UnityEngine.Collider) (at Assets/Project Backup/Scripts/Parrying/ShieldParryingSystem.cs:121)

This leads to the ‘Instantiate’ line in ‘OnTriggerEnter()’

When this error shows up, the Arrow is instantiated in its place, until its timer runs out, and any arrow instantiated nearby does the same, where it gets frozen in time, essentially messing up the entire parrying system

and the entire problem is in ‘OnTriggerEnter()’

I’m guessing it’s because I’m trying to instantiate the arrow from the position of the player’s shield that does the parrying (which is dynamic in the scene, since it can always change), but I don’t know any other way to go around this (I tried creating an empty gameObject and placed it on the player, roughly where the arrow should be instantiated from, and instantiate it from there, but that attempt failed too (mainly because it was starting from the sky for some reason, and then following the player as it falls to the ground from there… don’t ask, it was a really weird bug))

Anyone knows how to fix this?

Solved the instantiation issue, by changing up my ‘OnTriggerEnter’ function a little bit:

    private void OnTriggerEnter(Collider other) 
    {
        if (other.TryGetComponent<Projectile>(out var projectile))
            {
                Debug.Log($"{other.gameObject.name} has gotten into player range");

                // projectileInRange = Instantiate(projectile, GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab().transform);
                // projectileInRange.SetTarget(projectile.GetInstigator().GetComponent<Health>(), GetComponentInParent<Fighter>().gameObject, projectile.GetDamage(), Skill.None);

                projectileInRange = Instantiate(projectile);
                projectileInRange.transform.forward = GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab().transform.forward;
                projectileInRange.SetTarget(projectile.GetInstigator().GetComponent<Health>(), GetComponentInParent<Fighter>().gameObject, projectile.GetDamage(), Skill.None);

                Debug.Log($"Projectile instantiated and set to fire back at instigator");
            }
            else Debug.Log($"Collider does not contain a projectile");
        }

but arrows are still somehow sometimes frozen in the air (i.e: not even falling by gravity… Just, stuck in the air), and this bug needs to be fixed

After 12 long hours of searching for “what’s going on here”, and pretty much going wild about it :sweat_smile:, I came to realize it’s because the arrow has a speed of zero (as simple as it sounds, it actually took me 12 hours to figure it out) for god knows what reason when we parry it sometime.

SOO… I invented a speed booster variable, and told the code “hey, if the arrow is not moving for any reason, use the booster variable instead” :stuck_out_tongue:

private void OnTriggerEnter(Collider other)

    {

        if (other.TryGetComponent<Projectile>(out var projectile))

            {

                Debug.Log($"{other.gameObject.name} has gotten into player range");

                // projectileInRange = Instantiate(projectile, GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab().transform);

                // projectileInRange.SetTarget(projectile.GetInstigator().GetComponent<Health>(), GetComponentInParent<Fighter>().gameObject, projectile.GetDamage(), Skill.None);

                projectileInRange = Instantiate(projectile);

                projectileInRange.transform.forward = GetComponentInParent<Fighter>().GetCurrentShieldConfig().GetEquippedPrefab().transform.forward;

                projectileInRange.SetTarget(projectile.GetInstigator().GetComponent<Health>(), GetComponentInParent<Fighter>().gameObject, projectile.GetDamage(), Skill.None);

                if (projectileInRange.GetSpeed() == 0)

                {

                    projectileInRange.SetSpeed(projectileInRange.GetAlternativeSpeedVariable());

                    Debug.Log($"Arrow Alternative Speed Variable used");

                }

                Debug.Log($"Projectile instantiated and set to fire back at instigator");

            }

            else Debug.Log($"Collider does not contain a projectile");

        }

and the next day, I introduced a delay to that parry when adding an attack force to the player doing the parrying to damage the enemy, because without it, you can collide with the arrow and end up with a weird glitch

1 Like

Nice work and good job documenting your process :slight_smile:

1 Like

Thank you Marc! :slight_smile: - hopefully there will be more topics on the way

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms