Merging the Third Person Controller Course with the RPG Course

soo… I’m guessing this code replaces this section, correct?:

if (IsInChaseRange()) // && IsAggro()) This line failed when we introduced projectiles, which kinda messed with patrolling for a bit...
            {

                // if you want the vision system to work, uncomment the if statement below:

                // if (CanSeePlayer()) {
                // Clearing key to ensure that at the end of the battle, the enemy finds the nearest waypoint
                stateMachine.Blackboard.Remove(NextPatrolPointIndexKey);
                stateMachine.SwitchState(new EnemyChasingState(stateMachine));
                return;
                // }
            }

Obviously I’ll need to add in the blackboard line as well before switching states

1 Like

Welp, almost there. The last part is, can we get that cooldown timer (P.S: I added it as a property named “CooldownTimer” instead of a 2-second magic string) to only work when the player is out of combat range for the enemy? Basically, when he’s out of the targeting state

It’s not a big dealbreaker, I just find it weird that the player can be having a boss fight, and if the player didn’t hit the boss for a whole 3 seconds, the boss would forget he was in a fight to begin with :sweat_smile: - that would be too easy

No, that’s not how the cooldown timer works. It will keep refreshing itself, though, as the player fights with the enemy, every time he hits, the timer resets…

Challenge: Go through the CooldownTokenManager.cs and find the optional parameter you need to set to add to the timer rather than reset the timer. With this, the more you fight, the longer the enemy stays mad.

let me guess, is it the ‘bool append = false’ in ‘CooldownTokenManager.SetCooldown()’? I’m guessing we will just need to switch that to true, when we’re calling it

1 Like

I tried setting the append to true in ‘OnTakenHit()’, as follows:

    CooldownTokenManager.SetCooldown("Aggro", CooldownTimer, true); // 'true' in the end basically allows the enemy to add up to his total anger time
    

but I realized the enemy will quickly get bored, far quicker than adding the timers. How do I debug this to show what I mean? I’m not the best with debugging timers tbh :sweat_smile:

Other than that, I’ll wait for the next projectile tutorial before addressing the patrolling issue

am I the only one who NEVER had an ‘OnWeaponChanged’ event before reading the new Projectile tutorial…?

Edit: A few more questions (five) for now at least (I’m not sure if the rest were just me adding stuff, or they were in there for a while yet…):

[SOLVED the first one]

  1. "where did you get ‘sphereCollider’ in the ‘Targeter.cs’ from? (Edit: I created a variable and then cached it in awake, when I noticed that the targeter script and (I’m guessing here…) the sphere we are referring to are on the same GameObject)

  2. Do we replace the current ‘GetAttackingRange()’:

public float GetAttackingRange() 
    {
        return currentWeaponConfig.GetRange(); // replacement for third person transition below
    }

with this one?

    public float GetAttackingRange() 
    {
        return currentWeaponConfig.GetTargetRange();
    }

[EDIT: I replaced them]
3. In your cleaned up version of ‘Fighter.cs’, I keep seeing nearly everywhere the use of ‘GetRange()’ instead of ‘GetTargetRange()’… is that on purpose, or should I replace them? Because I did replace them… :sweat_smile: (In fact, I completely deleted any reference to ‘weaponRange’ now and replaced all the references to it with ‘targetRange’ now… Not sure if that was the right or wrong step, but that seemed like the natural evolution step)

[GAVE IT A GO]
4. For some reason, before I get into getting the enemy to respond to my attacks, I tried to go ahead and fire arrows at the enemy from the player, but unless he’s extremely close (still), no damage will be done to the enemy. I checked the ‘targetRange’ effect, and it worked to get the range to be large (however swinging a sword from 10 meters away will be effectless for obvious reasons), and I also checked the sphere radius of the targeter, and it did expand. However, I still can’t hit an enemy from a large distance for some reason, even with a large target Range… How do I fix this? (There’s also a few errors coming up, but these are from personal experimentation. They have nothing to do with this problem)

Edit: I don’t know how this was missed out from the tutorial, but to strike an enemy from a distance, and he’s in the range of the weapon, here’s what I tried doing. I placed this in ‘Shoot()’:

                // If he's in range, hit him:
                if (Vector3.Distance(transform.position, targetObject.transform.position) < currentWeaponConfig.GetTargetRange()) 
                {
// ignore the 'GetSkill()' at the end, that's custom code I created for myself
                    targetObject.GetComponent<Health>().TakeDamage(gameObject, damage, currentWeaponConfig.GetSkill());
                    TryApplyHitForce(targetObject.GetComponent<Collider>(), transform.position);
                }

so now my ‘Shoot()’ looks like this:

// Called on the melee animation event lines:
    void Shoot() {

        // 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)
            {
                // (TEST) inject code here to gain experience if targetObject is dead (after the projectiles are fully fixed):
                
                // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);

                // If he's in range, hit him:
                if (Vector3.Distance(transform.position, targetObject.transform.position) < currentWeaponConfig.GetTargetRange()) 
                {
                    targetObject.GetComponent<Health>().TakeDamage(gameObject, damage, currentWeaponConfig.GetSkill());
                    TryApplyHitForce(targetObject.GetComponent<Collider>(), transform.position);
                }
            }
            else
            {
                // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
            }

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

Not sure if that’s right or wrong, but for now it seems to be working just fine (apart from no applied force properly done).

However, I must say that I would’ve preferred a thousand times if a projectile acted like a real projectile. In other words, fire the arrow straight ahead (if it’s a non-homing arrow), and if it hits something, damage that thing, instead of just damaging anything in your range… If it misses, it missed. That would make for a fun and realistic ranged interaction, instead of just, you know… always getting the shot. What fun is that?

  1. Are these projectiles going in a straight line, or do they just instantiate a particle system on the target (which is kind of what I’m seeing right now :sweat_smile:)? It would be nice if we fire a projectile with the idea of “if it hits, it hits. If it misses, it misses…”, and maybe even a trail for that

  2. I went ahead and added the enemy aggregation, and it worked marvelously… but I noticed something, because I have an AggroGroup involved in the mix as well: If you strike one guy, and you’re not in his range, he will chase you down. However, the aggroGroup does NOT care if you’re not in their range, and they’ll be hostile pretty much forever, but won’t act on it until you get into their chase range. Any chance we can get them to want to hunt you down as well until the cooldownTimer is over, regardless of their individual chase range? So when the timer is over (for each individual enemy), everyone in the aggroGroup mentioned in this lecture cools down as well, unless the player attacked them

In simple terms, when you aggregate one, and he’s part of an AggroGroup, aggregate everyone else as well in the AggroGroup, and get them to chase you, regardless of your range, until the initial cooldown timer is done (so initially, when one of the group is hit at first, everyone’s aggro cooldown timer works, and then unless the player attacks them individually, they no longer care and walk away, once that initial timer is done).

I also have a bug that my enemy for some reason actually damages my player twice the damage the UI shows up, but I’ll address that once these 5 questions are done (I tried deleting the hit events, that didn’t work. I tried erasing the damage modifier multiplication line (and checking them on the attacks’ inspector, but they were set to 1. I halved them, and still… the damage dealt was double what the damage text spawner showed on the screen). That didn’t work either… I think ‘OnTakenHit’ is being called twice somewhere for the enemies. The player, even more surprisingly, doesn’t have that problem). After a bit more analysis, I realized it’s like the enemy is calling ‘TryHit’ twice, but the damage spawner catches it once. I checked the event relays on the animation, seems like it’s only called once…

Sorry for the ton of questions…

A quick scan of the repo shows that it was added during the improved animation section.

fair enough… I wonder if we can use the same projectile algorithm for machine guns though (please don’t ask me what I’m doing… Some secrets are best kept as secrets until the big reveal :stuck_out_tongue:). Basically to fire at rapid rates (I can only think of one, and that would be to reduce the time to check between animations, probably based on the tag… so right now it checks at 0.8f, but for machine guns for example the animation would check every 0.1 second)

What changes would we need to make for that?

It was added in the complex animations so that the correct weapon animations can be selected in the blend trees.

My bad on that one. I made the proto for this lesson two weeks ago and life (and neck pain) got in the way of writing the lesson, so I missed the diff on that one. That’s exactly what I did as well, cached it in Awake. Since we know that a RangeFinder has a SphereCollider, it’s easy enough to grab a reference to same.

Not sure where you’re meaning…
Targeting range is grabbed off of the weaponConfig, but since it’s used in reaction to an event that actually provides the new WeaponConfig, there’s no real need to do so. More to the point, the AttackingRange is intended to be the actual hit range, where Targeting range is the distance at which you can target or the enemy will begin/end chasing, so they are two different things altogether.

GetRange() is the range you begin attacking/calculate TryHit
GetTargetingRange() is the range you begin chasing/targeting. Two completely different things, and each has their purpose. So… wrong step.

Here, I’m stumped. You’re saying a projectile only does damage if you’re “close”… damage should be done when the projectile hits the enemy, full stop. Distance doesn’t really matter here… Compare your Projectile.cs to my Projectile.cs

In terms of the sword and 10 meters away, see above notes.

Here’s my Shoot()…

        void Shoot()
        {
            if (!currentWeaponConfig.HasProjectile()) return;
            
            if (TryGetComponent(out ITargetProvider targetProvider))
            {
                float damage = GetComponent<BaseStats>().GetStat(Stat.Damage);
                GameObject targetObject = targetProvider.GetTarget();
                if (targetObject != null)
                {
                    currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
                }
                else
                {
                    currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
                }
            }
        }

No check for range here…

??? This is how the projectile is supposed to work, actually… It damages the first health it hits. No check for other targets in range. You hit or you don’t.

I don’t know how many times I have to say this… The Aggrogroup from the course does NOT mean the same thing as “Aggro”. It simply determines if a character is hostile when in range. To make the Aggrogroup all decide to hunt you down, you’ll need to do something similar to AggrevateOthers in the original Core Combat course. This “Aggro” section wasn’t exclusively for you and your respawned characters and dynamic aggrogroups, and for the time being I’ve left the original aggro groups out of the tutorial.

Here is how you would tackle this for nearby enemies

            Health.OnDamageTaken += () =>
            {
                foreach (Collider col in Physics.OverlapSphere(transform.position, 10f).Where(c=>c.TryGetComponent(out EnemyStateMachine e)))
                {
                    col.GetComponent<EnemyStateMachine>().CooldownTokenManager.SetCooldown("Aggro", aggroTime, true);
                }
            };

I suspect this has something to do with that range code I highlighted above…

Are you meaning the Cooldown? If only you could edit the Cooldown in an Attack…

There are other issues with bullet projectiles… They generally travel fast enough that rather than firing a projectile that moves over time (which the physics engine can totally allow the bullet to pass through without a hit!), you want to do a RayTrace and instantiate a hit effect on whatever the ray trace pops up first. Not going there for this tutorial, however.

oh I have a lot of questions now… I’ll edit this comment as I go and tackle them one by one. Here’s the first one:

and here’s mine, after I removed the if statement I had. Again, now any ranged hit will only work if they’re like incredibly close:

    // Called on the ranged animation event lines:
    void Shoot() {

        // 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)
            {
                // 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)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
            }

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

and if needed, this is my ‘GetDamage()’ to avoid confusion:

public int GetDamage() {

        // Players have a SkillStore component attached to them, and enemies do not. One way to classify them
        // apart is to check for the SkillStore component:

        // For Player (the only component in-game with a SkillStore on him):
        if (GetComponent<SkillStore>()) {

                if (currentWeaponConfig.GetSkill() == Skill.Attack)
                {
                    return (int)(GetComponent<BaseStats>().GetStatBySpecifiedLevel(Stat.Attack, GetComponent<SkillStore>().GetSkillLevel(Skill.Attack)) + currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus() / 100));
                }

                else if (currentWeaponConfig.GetSkill() == Skill.Ranged)
                {
                    return (int)(GetComponent<BaseStats>().GetStatBySpecifiedLevel(Stat.Ranged, GetComponent<SkillStore>().GetSkillLevel(Skill.Ranged)) + currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus() / 100));
                }

                else return (int)(GetComponent<BaseStats>().GetStatBySpecifiedLevel(Stat.Magic, GetComponent<SkillStore>().GetSkillLevel(Skill.Magic)) + currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus() / 100));

            }

            // For NPCs:
            else {

            if (currentWeaponConfig.GetSkill() == Skill.Attack) {
                    return (int) (GetComponent<BaseStats>().GetStat(Stat.Attack) + (currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus() / 100)));
                }

            else if (currentWeaponConfig.GetSkill() == Skill.Ranged) {
                    return (int) (GetComponent<BaseStats>().GetStat(Stat.Ranged) + (currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus()/100)));
                }

            else {
                    return (int) (GetComponent<BaseStats>().GetStat(Stat.Magic) + (currentWeaponConfig.GetDamage() * (1 + currentWeaponConfig.GetPercentageBonus()/100)));
                 }

            }

        }

I think I once had a serious problem with this one, and I went around it in a completely different way… I’m like 95% sure I never saw that event when I was working on these. I’ll go through that again after the projectiles are fixed

so… do we replace them though? I’m honestly a little confused here. I just use the new ‘GetAttackingRange()’ with this one, right? (Edit: Nope… they are both two different values. That’s what you get for not sleeping the whole night and splitting your sleep up…!):

    public float GetAttackingRange() 
    {
        return currentWeaponConfig.GetTargetRange();
    }

Trust me, I tried. This is what my ‘Projectile.cs’ looks like:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Core;     // for us to be able to access the Target's Health component
using RPG.Attributes; // to access 'Health.cs'
using UnityEngine.Events;
using RPG.Skills;
using RPG.Movement;

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

    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 'Weapon.cs')
    Vector3 targetPoint;    // the target position of our Ranged Abilities

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

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

        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>();
            
            if (targetCapsule == null) {
                
                return target.transform.position;

            }

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

        }

        // New Code, for Third Person projectiles:
        private void OnTriggerEnter(Collider other) 
        {
            // 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 (other.gameObject == instigator) return;
            if (other.TryGetComponent(out Health health)) health.TakeDamage(instigator, damage);
            if (other.TryGetComponent(out ForceReceiver forceReceiver)) forceReceiver.AddForce(transform.forward * damage, true);

            speed = 0;

            onHit.Invoke();

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

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

            Destroy(gameObject, lifeAfterImpact);

        }

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

    }

}

why do I have a suspicious feeling like I missed out on something important that gets the projectile to travel through the air by accident…?!

I did notice the projectiles make it there, but absolutely no damage is being dealt unless we are nearby… they also reward you no experience on the death of the enemy, even after you get closer. Do they need some distancing or something that I completely missed out on…?! I re-read both tutorial 40 and 41 to the absolute brink, and made sure everything is correct (apart from the small modifications I made for my own project, and that really doesn’t matter much tbh), and I still can’t see where I went so wrong…

I don’t think so… this problem was there before the tutorial you uploaded yesterday as well… I don’t even know why I didn’t bring this up earlier tbh

When you’re deciding to ATTACK, you want to get the Attacking Range
When you’re deciding to PURSUE, you want the TargetRange.
They don’t mix. I can’t be any clearer.

My guess is you stopped reading the end of the first section on Projectiles, where we put them on a different layer from the RangeFinders…

If the projectiles are continuing and just failing to damage the opponent… I’m not seeing any reason for that in Projectile, unless the projectile layer isn’t set to intersect with the characters. Also, make sure that your characters and projectile both have Rigidbodies on them (set to IsKinematic with no gravity).

Add some Debugs in Projectile OnTriggerEnter… Make sure that you’re also logging out the collider, the instigator, and the damage within the logs and make sure they’re getting set correctly.

[SKIP THIS COMMENT. THE IMPORTANT EDITS THAT NEED TO BE ADDRESSED ARE THE COMMENTS RIGHT AFTER EDC’S COMMENT, BELOW THIS ONE (and at the very very bottom of the last comment, I also have what may seem like a Unity issue, but I figured I’d ask here first)]

Yup, I believe I fixed that, unless issues prove otherwise

Not guilty your honor :stuck_out_tongue: - I read till the end and did exactly what the tutorial said:

(unless you meant put the BOW itself on a different layer, because right now it’s only the arrows that are on the ‘Projectile’ layer)

apart from the player, where I got forced to remove the Rigidbody off of him to be able to avoid issues with the horse (here we go again… I know, trust me I know. Now that I think of it, later on I shall place an ‘AnimalFinder’ and detect for the Animal Layer. If I find anything, I shall turn off the rigidbody when I’m close. That’ll make everyone happy). I placed one now on the projectile, it made no difference (and it had a box collider on it anyway)

Debugs come out positive. HOWEVER, damage is not being dealt (and now I’m questioning myself why on earth is the WaterFinder (I made that for the swimming state…!) collider working right now…?!)

The first collider that worked was the targeter though (AND… THAT’S THE PROBLEM. He is targeting himself is my wildest guess!), and somehow… every other finder out there followed suit and triggered itself too. I placed the debuggers at the end of ‘OnTriggerEnter’, as follows:

// New Code, for Third Person projectiles:
        private void OnTriggerEnter(Collider other) 
        {
            // 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 (other.gameObject == instigator) return;
            if (other.TryGetComponent(out Health health)) health.TakeDamage(instigator, damage);
            if (other.TryGetComponent(out ForceReceiver forceReceiver)) forceReceiver.AddForce(transform.forward * damage, true);

            speed = 0;

            onHit.Invoke();

            if (hitEffect != null) Instantiate(hitEffect, GetAimLocation(), 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}");

        }

LOL… I don’t know what in the world is going on here, but if you eliminate this line:

if (other.gameObject == instigator) return;

and then keep a distance from the enemy and try fire an arrow at him, you will quite literally hit yourself for some reason (I had a good laugh out of that one)… If you get really close enough to him though, you will hit him.

Now I’m curious as to where I went so wrong for that one (my guess is that it has something to do with the fact that the first collider he hits is the targeter, followed by every other collider I have on him, which is all literally himself. Now I see why he has to be incredibly close to strike a hit, but I don’t see a clear way to solve this)

and my genius idea of giving the enemy a capsule collider to hit didn’t work either…

Look at the debug logs you posted above. The collider you are hitting is the water finder, not the enemy. You should check how this is happening.

This should stop the water finder from being hit. Something else is not set up right.

the collider I’m hitting is, quite literally, every single RangeFinder there is on the player, and that was all before I turned the collision between projectiles and RangeFinders off. I still keep detecting the targeter though, and changing the layer of that to ‘RangeFinder’ somehow completely turns the entire targeting system off, so I know now not to change that specifically


Edit: A bit of an update, and the problems that come out of them:

  1. After changing all my layers to RangeFinders, the Targeter cannot be set as one unfortunately (that will make it stop working and be a headache in and of itself). In this case, what I’ve done is deactivate the Projectile/Default connection:

leaving me with Projectile/Animal, Projectile/Terrain and Projectile/Characters, with both the player and the enemies under the “Characters” Layer… pretty much the essentials for me

and now the arrow actually goes into a straight path, as it should (because it’s no longer attempting to strike the targeter, which is on the default layer)… However, regardless of whether it hits the enemy or not, it still instantiates the hit particle effect at the enemy, because of this line:

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

which leads to this ‘GetAimLocation()’ function:

        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>();
            
            if (targetCapsule == null) {
                
                return target.transform.position;

            }

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

        }

from what I understand, this one doesn’t care if it hits an enemy or not… it’ll just play the hit effect even if it misses, which is not how I expect it to work (I only want this called when the enemy is hit)

  1. Apart from that, sometimes it hits and sometimes it misses (which is expected, for a non-homing arrow), but because of the Terrain/Projectile connection, sometimes it doesn’t even touch the enemy. It just hits the terrain on the way :sweat_smile:.

[SOLVED Number 3, by getting the data off the instigator’s Fighter component. IGNORE Number 3 only]:

  1. There’s still no Ranged/Magic XP going for the player, only defence (so I know AwardExperience is working, but with missing information). This can be fixed by filling up the optional parameter I have in my own latest ‘AwardExperience()’ function, but the projectile doesn’t know yet whether it’s connected to a ranged or a magic weapon, and I can’t just automatically always ask it to award Ranged XP… One way I can get this, is by checking the Associated Skill on the weapon the player is currently holding… how do I get the ‘currentWeaponConfig’ in projectile? (The other solution is a simple boolean, but I desperately don’t want to tangle variables up right now. Is there any way we can get the currentWeaponConfig information from the player’s hands, through fighter or something…?!)

  2. Finally, I also noticed that whilst the enemy does not rotate after looking at the player, until the animation plays again, the arrow itself rotates, regardless of the enemy, to face the player. How do I get it to face with the enemy instead, or at least make the enemy constantly try to face the player? I don’t want things looking out of the ordinary :smiley:

  3. With all these modifications done, right now when you’re close to the enemy you can hit him, but the “first strike” (i.e: first hit that initiates the fight) still misses if you’re far. After the fight begins, it acts like a normal arrow. In other words, it can hit him when you’re both fighting in targeting state, and he will get impacted and move backwards and what not (that’s for the melee enemies. For the ranger enemy, I’m still struggling a bit)… the major problem now is getting that first hit right

[So for point 5, the accuracy for hitting the melee fighters with ranged is quite high when they’re hostile and the player has a clear target, but not when they’re patrolling, unless you’re insanely close. As for Rangers, after the first hit the accuracy falls really really low for some reason… When his arrow strikes me, it’s spot on accurate, but I can’t say the same about my arrow striking him… It struggles terribly]

This is why you should have gone through all of this on the RPG repo. You have too many other systems that have too many issues. If I were you, I would remove everything extra and just get the systems in the two courses and the tutorial working. Then add the new things one at a time. I have a lot of ideas for features for my game, but it will all wait until the basics are working completely. This is essential for any systems that are this complex. Do your self a huge favor, SLOW DOWN.

1 Like

it broke down literally because I accidentally deleted an important script (the cooking thingy), and I have no idea why I hurt myself that way… :sweat_smile: (there’s a reason why backups exist)

I do agree though, I was a bit too fast with my movement, albeit it all ended up being worthwhile though. Regardless, I shall slow down and just work on Construction in the meanwhile as Brian continues the project. This one is taking me an insanely long time :slight_smile: - it’s not like the game environment can be done overnight anyway

this worked quite well, and as it turns out… this is basically the third person version of ‘Aggrevate Others’, a fun little mechanic to make fighting a little bit harder :slight_smile:

I did modify the variable names and a few other things to customize it for myself though, as follows, in ‘EnemyStateMachine.Start()’:

        Health.OnDamageTaken += () => 
        {
            // TEST (if statement and its contents only - the else statement has nothing to do with this):
            if (AggrevateOthers) 
            {
            foreach (Collider collider in Physics.OverlapSphere(transform.position, PlayerChasingRange).Where(collider => collider.TryGetComponent(out EnemyStateMachine enemyStateMachine))) 
            {
                collider.GetComponent<EnemyStateMachine>().CooldownTokenManager.SetCooldown("Aggro", CooldownTimer, true);
            }
            }
            else CooldownTokenManager.SetCooldown("Aggro", CooldownTimer, true);
        };

BUT… I also have this hanging around:

    private void OnTakenHit(GameObject instigator)
    {
        CooldownTokenManager.SetCooldown("Aggro", CooldownTimer, true); // 'true' in the end basically allows the enemy to add up to his total anger time
    }

which one do I eliminate, the ‘OnTakenHit’ function or the else statement above?

On a side note, why does Unity sometimes ignore my commands to make changes in a prefab? For example, expanding the ‘PlayerChaseRange’ variable does not respond in my game for some reason… I tried restarting Unity, but that failed to fix it

and when I wrote a tooltip, it completely ignored it… it refuses to show it to me in the inspector in action, and I have absolutely no idea why that is the case

ALL RangeFinder based classes need to have their GameObject layer set to RangeFinder… that includes the Targeter, the WaterFinder, the ShopFinder, the ItemFinder, or any other class based on RangeFinder. It’s failing because WaterFinder is on the Default layer instead of on the RangeFinder layer.

This has nothing to do with the problem. Leave this line in the code or you’ll just hit yourself over and over and over again

Turn RangeFinder and Characters collisions on and you can put the targeter on the RangeFinder layer.
image

Outside this tutorial (you like to forget I wrote this tutorial for the straight courses).

You could always get the currentWeaponConfig of the instigator with

instigator.GetComponent<Fighter>().GetCurrentWeaponConfig();

Before you ask, I’m not sitting on the code base, but I’m sure there’s a method in the tutorial for getting the current WeaponConfig… If there isn’t, you can add one to Fighter.

Alternatively, you could pass the damage type to the health from the Projectile. The projectile’s experience type will either be ranged or magic, so a checkbox on the Projectile could be

[SerializeField] bool IsMagic;

and then if it’s magic, send the Magic to the Health, otherwise send Ranged to the health.

Could this be because the enemy is walking around and you missed? This is normal, unless it’s a homing arrow.

Privacy & Terms