Homing Arrows not Homing

OK I know I’m everywhere all of a sudden today, but I decided to delay the wanted system for a day until I figure out how to reactivate my homing arrows (I should’ve done this sooner!), because right now firing arrows using this function:

    // Called on the ranged animation event lines:
    private void Shoot() 
    {
        // (TEMP) Temporarily here, to ensure ghost enemies deal no damage and mess with the NPC hunting for enemies system:
        if (GetComponent<Health>().IsDead()) return;

        // In very simple terms, this function is the 'Hit()' function for 
        // Ranged (Projectile) Weapons, to be called in 'Projectile.cs' (for simplicity in naming's sake)
        // Hit();

        // RPG to Third Person Conversion changes are what the rest of this function is all about:

        if (!currentWeaponConfig.HasProjectile()) return;

        if (TryGetComponent(out ITargetProvider targetProvider))
        {
            float damage = GetDamage();
            GameObject targetObject = targetProvider.GetTarget();

            if (targetObject == null && GetComponent<EnemyStateMachine>())
            {
                targetObject = GameObject.FindWithTag("Player");
            }

            // TEST:
            // damage *= UnityEngine.Random.Range(0f, 1.25f);
            // if (UnityEngine.Random.Range(0f, 100f) > 95f) damage *= 2.0f;

            // TEST (DELETE THE IF STATEMENT AND ITS CONTENTS, AND THE 'currentAnimal' AND ITS LINKS, IF FAILED):
            if (GetComponent<PlayerStateMachine>()) {

            // Use the 'currentAnimal' for the PLAYER ONLY

            if (targetObject != null)
            {
                // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
            }
            else
            {
                // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target), the last one (added 14/6/2024) is to offset the arrows based on the animal being driven
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
            }

                // Either way, you just fired an arrow, so use an ammo ('CheckAmmoQuantity' won't let you get this far without having arrows, so keep that in mind)
                if (currentWeaponConfig.GetAmmunitionItem())
                {
                    GetComponent<Quiver>().SpendAmmo(1);
                }
            }

            else 
            {

                // Normal action for the enemy and other NPCs (who don't have a 'CurrentAnimal' - TEMPORARY SOLUTION)

                    if (targetObject != null)
                    {
                        // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                        currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
                    }
                    else
                    {
                        // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target), the last one (added 14/6/2024) is to offset the arrows based on the animal being driven
                        currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
                    }

                    // If you have enough ammo, fire and use the ammo
                    if (currentWeaponConfig.GetAmmunitionItem())
                    {
                        GetComponent<Quiver>().SpendAmmo(1);
                    }
                }
            }
        }

(It’s modified, I know)

Fires straight arrows that are not homing, regardless of whether the projectile itself is set to homing or not (CORRECTION, IT DOESN’T DO ANY SORT OF HOMING WORK IN FREELOOK MODE… I’LL DEVELOP SOMETHING FOR THAT)

It does work fine for targeting state though, but I also want to do the same for my new ranger aiming state, where I can get homing arrows to… aim for whoever is under the aim reticle

Can someone please review this for me?

(If it helps, here’s my entire ‘Projectile.cs’ script as well):

// If you plan to make a destructive environment of some sort, and maybe shoot it with projectiles, 
// make sure to give it a specific layer and go to 'Edit -> Project Settings -> Physics', and in 
// the big triangular matrix at the bottom, tick 'Projectile/(Whatever your layer name is)'

using UnityEngine;
using RPG.Attributes; // to access 'Health.cs'
using UnityEngine.Events;
using RPG.Skills;
using RPG.Movement;
using System.Collections.Generic;
using System.Collections;
using RPG.Animals;
using RPG.States.PlayerOnAnimal;

namespace RPG.Combat {

    public class Projectile : MonoBehaviour
    {
    
    [SerializeField] bool isHoming = true;      // determines if our missiles are homing (persuasive) or not
    [SerializeField] float speed = 1.0f;        // The speed of our Arrow being fired at its target
    [SerializeField] GameObject hitEffect = null;      // the hit effect when a projectile hits its target (particle system)
    [SerializeField] float maxLifeTime = 10.0f;      // the maximum lifetime of our Projectile
    [SerializeField] GameObject[] destroyOnHit = null;  // an array of objects to be destroyed once a Projectile hits an object
    [SerializeField] float lifeAfterImpact = 2.0f;     // the life of a Projectile after it has impacted a target
    [SerializeField] UnityEvent onHit;  // projectile hitting target audio
    [Range(0f, 0.5f)]
    [Tooltip("How hard is the knockback from the impact with one of these projectiles?")]
    [SerializeField] float projectileKnockbackFactor; // how badly does this projectile knock someone back, based on dealt damage?

    [Tooltip("If the speed of the arrow, in 'ShieldParryingSystem.cs', for any reason is set to zero when the shield parries the arrow, use this value instead")]
    [SerializeField] float alternativeSpeedVariable;

    Health target = null;      // The target of our Projectile must have health for us to attack them
    GameObject instigator = null;   // Instigator (for us to gain xp as we kill enemies)
    float damage = 0.0f;        // Damage done to the target of our projectile (passed in from 'WeaponConfig.cs')
    Vector3 targetPoint;    // the target position of our Ranged Abilities

    private void Start() {

    // When firing an arrow, the first thing we want to do
    // is look at our target (once only. If we put this
    // in update, it will turn into a Homing Missile)
    // transform.LookAt(GetAimLocation());

    // For third person, we need this:
    if (target) transform.LookAt(GetAimLocation());

    }
    
    // Update is called once per frame
    void Update()
    {

        // This function shoots the arrow at a target. First, we check
        //  if we have a target or not. If there's no target, 
        // quit this function. 
        
        // If we have a target, and he's not dead, we want the arrow
        // to look at our target, and then move towards it (on the Z-axis),
        // via Vector3.forward, at the given Speed variable, 
        // over Time.deltaTime (time in frames per second) 

        // if (target == null) return;

        if (target != null && isHoming && !target.IsDead()) {

        transform.LookAt(GetAimLocation()); // makes our missile a homing missile 
                                            // (only if we assigned it as a homing missile)
                                            // after we checked that the target is not dead,
                                            // and our missile is homing

        }

        transform.Translate(Vector3.forward * speed * Time.deltaTime);

    }

    Skill skill;

        public void SetTarget(Health target, GameObject instigator, float damage, Skill skill) {

            // This function calls 'SetTarget' below, so we can aim at our enemies using our abilities
            SetTarget(instigator, damage, target, Vector3.zero, skill);

        }

        public void SetTarget(Vector3 targetPoint, GameObject instigator, float damage, Skill skill) {

            SetTarget(instigator, damage, null, targetPoint, skill);

        }

        /// <summary>
        /// This function sets the target of our projectile. 1. It sets the target the projectile is aiming at, 2. it keeps track of him,
        /// 3. It sets the damage destined to the projectile target, 4. It keeps track of who is responsible for firing that projectile, and 5.
        /// it sets the skill that will get the XP for firing that hit
        /// </summary>
        /// <param name="instigator"></param>
        /// <param name="damage"></param>
        /// <param name="target"></param>
        /// <param name="targetPoint"></param>
        /// <param name="skill"></param>
        public void SetTarget(GameObject instigator, float damage, Health target = null, Vector3 targetPoint = default, Skill skill = Skill.Ranged) {

            this.target = target;
            // our Target is now the GameObject holding this script
            this.targetPoint = targetPoint;
            // keep track of the target we are firing our Projectile abilities at
            this.damage = damage;
            // The damage dealt by our Projectile = damage from SetTarget ('float damage' argument)
            this.instigator = instigator;
            // The instigator is set to be an instance of whoever holds this script
            this.skill = skill;
            // The skill to train when a projectile is fired
            Destroy(gameObject, maxLifeTime);   // destroys our Projectile after its maximum lifetime (so it doesn't take a toll on our computer)
        }

        private Vector3 GetAimLocation() {

            if (target == null) return targetPoint; // if there is no target, just head to the targetPoint

            // This function aims the projectile at the heart (Z-Axis) of the target, by getting the target's 
            // Capsule Collider height, and then adding 1/2 of its height, so our arrow lands into the target's chest 
            // (midway through the CapsuleCollider). It also checks if the Target has a CapsuleCollider or not. If not, just aim the 
            // arrow at the target feet (its original aim point)

            // CapsuleCollider targetCapsule = target.GetComponent<CapsuleCollider>();
            CharacterController targetController = target.GetComponent<CharacterController>();
            
            if (targetController == null) {
                return target.transform.position;
            }

            return target.transform.position + Vector3.up * targetController.height / 2;

        }

        // Since I have two colliders on my player (one Character Controller, and a Capsule Collider for my Animal-Mounting System)
        // the damage gets dealt twice. To combat that, this list ensures that the health component is only accessed by the Projectile once
        // so the damage happens once only (done in the 'OnTriggerEnter' function below):
        List<Health> alreadyHit = new List<Health>();

        // New Code, for Third Person projectiles:
        private void OnTriggerEnter(Collider other)
        {
            IAnimal animal = other.GetComponentInParent<IAnimal>(); // you can also use the 'PlayerOnAnimalStateMachine.cs' type here if you want
            if (animal != null)
            {
                Animal currentAnimal = animal.GetAnimal(); // the animal currently driven by the player
                PlayerOnAnimalStateMachine playerStateMachine = instigator.GetComponent<PlayerOnAnimalStateMachine>(); // the player
                if (playerStateMachine != null && currentAnimal == playerStateMachine.GetAnimal())
                {
                    // this animal this arrow hit = animal driven by player, and instigator = player? 
                    // Ignore the arrow from the driver (i.e: the player)
                    Debug.Log($"Player is attempting to hit the animal he's mounting, ignore");
                    return;
                }
            }
            else Debug.Log($"Projectile hit {other.gameObject.name}"); // Safety Log

            // The following if statement is for cases when the enemy needs to dodge a projectile:
            if (other.TryGetComponent<IncomingRangedAttackDetector>(out var detector))
            {
                if (detector.parentStateMachine.gameObject == instigator)
                {
                    Debug.Log($"This is my projectile, ignore");
                }
                else
                {
                    Debug.Log($"An outsider is trying to attack me, I should defend myself");
                    if (detector.GetComponentInParent<Health>().IsDead()) 
                    {
                        Debug.Log($"I am dead, can't do anything now");
                        return;
                    }

                    Vector3 randomDirection = Vector3.zero;
                    int randomRoll = Random.Range(0, 4);
                    switch (randomRoll)
                    {
                        case 0:
                            randomDirection = new Vector3(-1, 0, 0);
                            break;

                        case 1:
                            randomDirection = new Vector3(1, 0, 0);
                            break;

                        case 2:
                            randomDirection = new Vector3(0, -1, 0);
                            break;

                        case 3:
                            randomDirection = new Vector3(0, 1, 0);
                            break;
                    }
                    detector.parentStateMachine.SwitchState(new EnemyDodgingState(detector.parentStateMachine, randomDirection));
                }
                return;
            }

            // This function tells the projectile holding this script what to do
            // when it hits something, depending on whether it's a target, or just 
            // something in the way

            // if he's dead, just keep going. Don't try to damage him again...:
            // if (other.GetComponent<Health>().IsDead()) return;

            if (other.gameObject == instigator) return; // don't hit yourself

            // if (other.TryGetComponent(out Health health)) health.TakeDamage(instigator, damage, instigator.GetComponent<Fighter>().GetCurrentWeaponConfig().GetSkill());

            // TEST (if failed, uncomment the line above)
            if (other.TryGetComponent(out Health health) && !alreadyHit.Contains(health))
            {
                if (other.GetComponent<Health>().IsDead()) return;
                alreadyHit.Add(health);

                // Randomize the Damage:
                // damage *= UnityEngine.Random.Range(0f, 1.25f);
                // if (UnityEngine.Random.Range(0,100) > 95) damage *= 2.0f;
                health.TakeDamage(instigator, damage, instigator.GetComponent<Fighter>().GetCurrentWeaponConfig().GetSkill());
            }

            if (other.TryGetComponent(out ForceReceiver forceReceiver)) 
            {
                forceReceiver.AddForce(transform.forward * damage * projectileKnockbackFactor, true); // multiplied by 0.5f so as to neutralize the knockback impact
            }

            speed = 0;

            onHit.Invoke();

            if (hitEffect != null) Instantiate(hitEffect, transform.position, transform.rotation);

            foreach (GameObject toDestroy in destroyOnHit) Destroy(toDestroy);

            Destroy(gameObject, lifeAfterImpact);

            Debug.Log($"Instigator = {instigator.name}");
            Debug.Log($"Collider = {other.name}");
            Debug.Log($"Damage Dealt = {damage}");
        }

        // Class needed for third person transition (nullifying the targets):
        public void SetupNoTarget(GameObject instigator, float damage) 
        {
            this.instigator = instigator;
            this.damage = damage;
            this.target = null;
            isHoming = false;
        }

        public GameObject GetInstigator() 
        {
            return instigator;
        }

        public float GetSpeed() 
        {
            return speed;
        }

        public void SetSpeed(float speed) 
        {
            this.speed = speed;
        }

        public float GetAlternativeSpeedVariable() 
        {
            return alternativeSpeedVariable;
        }

        public float GetDamage() 
        {
            return damage;
        }

        public Vector3 GetTargetPoint() 
        {
            return targetPoint;
        }

    }

}

BUT MY REALLY, REALLY, REALLY, REALLY BIG PROBLEM, IS THAT I DID NOT COUNT FOR ANY OF THESE WHEN DEVELOPING MY RANGER AIMING STATE :sweat_smile:

I just want to ask a question why is your code covered in comments on everyline good naming conventions, mean that you should not need comments?

my attention span is really really short. Without comments, I’ll struggle to understand what was going on really easily down the line

(and some of this code is ancient. I was writing comments back then to make sure that I don’t easily lose track with the lectures later on)

I mean it’s down to user prefrence, I do keep comments for some stuff, but the naming convention of a line should help with that, unless you still don’t understand the code.

This is a current code I am using to do a flashlight, bear in mind this is c++ but still applies if i give this to anyone they can read it without need for comments., Refactoring the spotlight component into an advidly named updatespotlight is also good

void AGun::DrainBattery(float DeltaTime)
{
	
	if (bIsFlashlightActive && CurrentBatteryLevel > 0 && SpotLightComponents.Num() > 0)
	{
		
		UpdateSpotlights();

		
		CurrentBatteryLevel -= BatteryDrainRate * DeltaTime;
		CurrentBatteryLevel = FMath::Clamp(CurrentBatteryLevel, 0.0f, MaxBatteryLevel);

		
		if (CurrentBatteryLevel <= 0)
		{
			bIsFlashlightActive = false;
			UpdateSpotlights(); 
		}
	}
	else
	{
		
		UpdateSpotlights();
	}
}

Well, like I said, some of this code is very old and absolutely needs cleaning up. I just didn’t have the time for that just yet

Sometimes I’ll need the old code, so I probably won’t clean it up until the absolute end of the production run of this project

OK I might be the paranoid one, but… sometimes the homing arrows’ Projectile Trail Renderer goes blue midway through for some reason. I don’t know why, but I need help debugging that, because it’s really confusing (Edit: There’s no errors… For some reason, I had 2 trails on top of each other)

Above all else, I want to suggest a solution for homing missiles when we got no target. We can fire that thing straight ahead, and if, within a radius of the missile, it finds someone, why don’t we get it to turn on to that and aim for them? That would make it a lot more fun, xD

I have other suggestions, but that’s one for now

And since I developed my own Ranger State, I need to consider making adjustments for that too

OK SO… I THINK I SOLVED ALL THE PROBLEMS THAT REACTIVATING MY HOMING BOW AND ARROWS WOULD RESULT IN. HERE’S WHAT I HAD, AND WHAT I DID:

  1. Trail Renderer Changing Colors. This was a mistake from my end, because I had two of them on the arrow’s back in the hierarchy. I did the dumb thing and deleted the renderer from the root projectile, and gave each projectile it’s own Unique render

  2. Not working Bow, causing NREs and other problems. I ended up checking the hierarchy of the weaponconfig, fixed it and got it to work again

  3. In my brand new RangerAimingState.cs, the homing system was not working at all, so I developed a unique solution. Here’s where things get really interesting:

I had a function called ‘CheckWhoIsUnderTheAimReticle()’, which… as the name suggests, checks who is under the aim reticle, and changes its color based on who is under the reticle. If it’s an enemy, the color goes red, and if it’s an ally, it goes blue (I can’t think of a color that does not recommend you not to fire at your allies, xD, but I’m keeping friendly fire open and dealing with the consequences, lel).

Essentially, the function fires a raycast and only focuses on the “Character” LayerMask (will add “Animal” later, when I get to that part of hunting animals down). If you want the code, including the integration of ‘CheckAimReticleVictim’, here you go:

    private void CheckWhoIsUnderTheAimReticle() 
    {
        // This function works in a bit of a weird way, because there are layers of RangeFinders on the player that get 
        // detected first by the Raycast. The function essentially uses the point of detection from the first raycast's landing
        // position, (which started from the 'rangerAimingCamera', and then lands on the player's detectors), to fire a 
        // second raycast to get the original enemy target, because I can't shake off the 'Player' tag on the player, 
        // which is there first, without destroying so many other systems that rely on it... 
        // (and Layers? They're limited, so be careful using them)

        if (stateMachine.rangerAimingCamera == null)
        {
            Debug.LogWarning("Ranger Aiming Camera is null");
            return;
        }

        Vector3 directionToTarget = stateMachine.rangerAimingCamera.transform.forward; // direction of raycast, from the Ranger's Aiming Camera

        if (Physics.Raycast(stateMachine.rangerAimingCamera.transform.position, directionToTarget, out RaycastHit hit, maxDistance, layerMask))
        {
            Debug.Log($"Hit Object: {hit.collider.name} with tag: {hit.collider.tag}");
            targetEnemy = hit.collider.GetComponent<EnemyStateMachine>();
            // Change the color of the reticle, based on who it's landing on:
            if (hit.collider.CompareTag("Enemy"))
            {
                stateMachine.AimReticle.color = Color.red; // Enemy
                stateMachine.CurrentAimReticleVictim = hit.collider.GetComponent<EnemyStateMachine>();
            }
            else if (hit.collider.CompareTag("PlayerAlly"))
            {
                stateMachine.AimReticle.color = Color.blue; // Player allies
                stateMachine.CurrentAimReticleVictim = hit.collider.GetComponent<EnemyStateMachine>();
            }
        }
        else
        {
            stateMachine.AimReticle.color = originalAimReticleColor;
            stateMachine.CurrentAimReticleVictim = null;
        }
    }

I do have to mention though, based on my personal setup, I temporarily change the tag of my player allies when the aim reticles are working, because I can’t keep them permanently (destroys a part of my system), and I need to classify them as well (so it needs to change)

So the tags are, based on whether the enemy is an ally or not (and not to mentioned, integrated in the saving system), being toggled when the aim reticle is being toggled

I figured if that creation of mine works perfectly, why not introduces a dynamic variable in ‘PlayerStateMachine.cs’, called ‘CurrentAimReticleVictim’, which… detects who is currently the victim of the aim reticle, and return null if none is found?

The idea is, if you’re aiming at someone, the homing arrow will aim at them, and will continue moving like a normal arrow if it can’t find someone otherwise

So, I did that, and the variable worked, but how do I get it? Interfaces (without an Interface, I risk circular dependencies… I ain’t trying to create huge problems here, lol), I created this interface:

using RPG.States.Enemies;

public interface IAimReticleTargetProvider 
{
    EnemyStateMachine GetCurrentAimReticleVictim();
}

and then implemented it in ‘PlayerStateMachine.cs’:

    public class PlayerStateMachine : StateMachine, ITargetProvider, IAimReticleTargetProvider

// the 'CurrentAimReticleVictim' Variable:
        [field: Tooltip("Used in Ranger Aiming States, who is the victim our (assuming it's a homing one) projectile is aiming for?")]
        [field: SerializeField] public EnemyStateMachine CurrentAimReticleVictim {get; set;}

// implementing the Interface:
        public EnemyStateMachine GetCurrentAimReticleVictim()
        {
            return CurrentAimReticleVictim;
        }

and then I wanted to declare this in ‘Fighter.cs’. Fortunately, the ‘RangerAimingState.cs’ and my ‘TargetingState.cs’ will NEVER collide, so I cheated and used a ‘return’ if we get the victim enemy before ‘ITargetProvider’ (like I said, they won’t collide, so I won’t bother with it much):

    // Called on the ranged animation event lines:
    private void Shoot() 
    {
        // (TEMP) Temporarily here, to ensure ghost enemies deal no damage and mess with the NPC hunting for enemies system:
        if (GetComponent<Health>().IsDead()) return;

        // In very simple terms, this function is the 'Hit()' function for 
        // Ranged (Projectile) Weapons, to be called in 'Projectile.cs' (for simplicity in naming's sake)
        // Hit();

        // RPG to Third Person Conversion changes are what the rest of this function is all about:

        if (!currentWeaponConfig.HasProjectile()) return;

        // SOLUTION TO GET THE PLAYER, WHILST IN RANGER AIMING MODE, TO ACTUALLY GET HIS HOMING BOW TO AIM FOR THE VICTIM
        if (TryGetComponent(out IAimReticleTargetProvider aimReticleTarget)) // Only the player has the 'PlayerStateMachine.cs' script, the only implementer of the interface that he can extract 'IAimReticleTargetProvider' from
        {
            float damage = GetDamage();
            EnemyStateMachine victimEnemy = aimReticleTarget.GetCurrentAimReticleVictim();

            if (victimEnemy != null)
            {
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, victimEnemy.GetComponent<Health>(), gameObject, damage);
                return;
            }
        }

        if (TryGetComponent(out ITargetProvider targetProvider))
        {
            float damage = GetDamage();
            GameObject targetObject = targetProvider.GetTarget();

            if (targetObject == null && GetComponent<EnemyStateMachine>()) // Enemy will directly aim at the player... that's his target lol
            {
                targetObject = GameObject.FindWithTag("Player");
            }

            // TEST:
            // damage *= UnityEngine.Random.Range(0f, 1.25f);
            // if (UnityEngine.Random.Range(0f, 100f) > 95f) damage *= 2.0f;

            // TEST (DELETE THE IF STATEMENT AND ITS CONTENTS, AND THE 'currentAnimal' AND ITS LINKS, IF FAILED):
            if (GetComponent<PlayerStateMachine>()) {

            // Use the 'currentAnimal' for the PLAYER ONLY

            if (targetObject != null)
            {
                // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
            }
            else
            {
                // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target), the last one (added 14/6/2024) is to offset the arrows based on the animal being driven
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
            }

                // Either way, you just fired an arrow, so use an ammo ('CheckAmmoQuantity' won't let you get this far without having arrows, so keep that in mind)
                if (currentWeaponConfig.GetAmmunitionItem())
                {
                    GetComponent<Quiver>().SpendAmmo(1);
                }
            }

            else 
            {

                // Normal action for the enemy and other NPCs (who don't have a 'CurrentAnimal' - TEMPORARY SOLUTION)

                    if (targetObject != null)
                    {
                        // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                        currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
                    }
                    else
                    {
                        // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target), the last one (added 14/6/2024) is to offset the arrows based on the animal being driven
                        currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
                    }

                    // If you have enough ammo, fire and use the ammo
                    if (currentWeaponConfig.GetAmmunitionItem())
                    {
                        GetComponent<Quiver>().SpendAmmo(1);
                    }
                }
            }
        }

and, voila, it works perfectly (until you start driving animals… I’ll get that fixed on the fly too, right now!)

The more I implement interfaces, the more comfortable I get with them, and the more I become familiar with them

And the best part of it all? The Aim Reticle truly holds value now. If the color is white, it means it’ll act like a regular arrow, because it has nothing to aim for, and that’s not really what you want (because for the homing bow… it has no target, so it’ll just fly straight ahead). If the color is red or blue, that’s a homing arrow that’ll never miss (I’ll modify that “never miss” part, to make it more exciting down the line), so you need to get your aim right because now it actually does make a significant difference

I’ve never been this happy about something I created in a while… kinda compensates for my lack of social life trying to build that thing lol

Getting them homing in the first place would make a lot more sense.

In all seriousness, though, review the lessons from the original Core Combat course on Homing missiles. You have to pass the target into the Projectile so that it knows what to home in on. It’s pretty much that simple.

Ehh I did something that would probably make sense (I can’t do much right now. My laptop recently became incredibly slow, again, and right now it’s running a full scan that makes it impossible to do anything else. One scan usually takes 20-24 hours :sweat_smile:)

Anyway, I used the Aim Reticle that I recently introduced to my game, and got a hold of the enemy underneath the reticle, where I instructed it to change colors, based on who is under the reticle (I used an Interface to get that piece of data. If it’s an ally, go blue. If it’s an enemy, go red. If it’s neither, go white). If it finds someone, it’ll turn into a homing missile. If it doesn’t, just move forward like a normal missile (because you got nothing to aim at)

The best part? It’ll make sure that it’s possible to turn into a homing missile, but you gotta be accurate with your hits (because if you miss your target, then that’s a normal missile, which is probably (80%) going to waste). That will really intrigue the players to focus on what they’re aiming at, so as not to waste arrows

If you have any better ideas, please suggest me some (but lowkey, I like this one, but I’m also open to hearing different opinions :slight_smile:)

For now, I’m waiting for the scan to end to re-activate the magic fireballs. This will be an individual state in and of itself, away from the ranger state, because the animations will be vastly more than the ranger ones (and to keep things clean for the future, for myself). For rangers, I might update it down the line, but for now all I see is that you only need one single universal animation

For magic, you’ll probably need a lot more, based on your weapon (so there’s a connection between a new set of strings I created in ‘WeaponConfig.cs’ and that new state to be formed)

Sorry for the off-rant :sweat_smile:

Privacy & Terms