RPG Combat Course - How to cast an ability in front of the caster

Hello everyone I need help with casting the ability in front of the caster. Right now I created a FrontDirectionTargeting.cs that handles this effect.

using System;
using UnityEngine;

namespace RPG.Abilities.Targeting
{
    [CreateAssetMenu(fileName = "Front Direction Targeting", menuName = "Abilities/Targeting/Front Direction", order = 0)]
    public class FrontDirectionTargeting : TargetingStrategy
    {
        [SerializeField] float groundOffset = 1;
        // Hardcoded direction y value
        [SerializeField] float directionalY = 0.01f;
        public override void StartTargeting(AbilityData data, Action finished)
        {
            Vector3 targetPosition = data.GetUser().transform.position + data.GetUser().transform.forward;
            Vector3 sourcePosition = data.GetUser().transform.position;
            Vector3 targetDirection = (targetPosition - sourcePosition).normalized;

            // This code is not working as intended
            // When I Debug.Log the targetDirection, The z are as near as I wanted.
            data.SetTargetedPoint(targetPosition * groundOffset / targetDirection.z);

            // This code is slightly working as intended but the directionalY is hardcoded
            data.SetTargetedPoint(targetPosition * groundOffset / directionalY);
            finished();
        }
    }
}


In the code above, I’m not happy with my two solutions. I seek the advice of masters and experts in the community.

The added division shouldn’t really be necessary… this was to fix things so that the target point was above the ground based on the raycast from the camera… in this case, we’re simply projecting forward from the character’s position…

I would try this:

public override void StartTargeting(AbilityData data, Action finished)
{
    Vector3 targetPosition = data.GetUser().transform.position + data.GetUser().transform.forward;
    data.SetTargetedPoint(targetPosition + (Vector3.up * groundOffset));
    finished.Invoke();
}

Thank you for helping me, I tried testing the code. There are a couple of issues that I see that will show in the video below.

  1. When I’m not moving then I cast the skill, it will throw the projectile in the front but the angle is downward, what do you think I should do to adjust the angle of that one?
  2. When I move then I use the skill, it will launch the projectile in the opposite direction from which I move.

Here’s the link of the video: Game dev log- rpg casting ability bug - YouTube

Hmmm… the first part (the angle towards the ground) actually makes sense, as it looks like the projectile is being released from a point higher than 0,1,0… An adjustment when the fireball is actually instantiated may help there… changing the targetpoint.y to the spawn transform.y would ensure that the fireball moves along the x/z plane.

For the second, I’m actually not sure what’s going on… paste in your complete targeting class as well as your SpawnProjectile and Projectile scripts.

Here’s my code for targeting, SpawnProjectile and Projectile scripts.
FrontDirectionTargeting.cs

using System;
using UnityEngine;

namespace RPG.Abilities.Targeting
{
    [CreateAssetMenu(fileName = "Front Direction Targeting", menuName = "Abilities/Targeting/Front Direction", order = 0)]
    public class FrontDirectionTargeting : TargetingStrategy
    {
        [SerializeField] float groundOffset = 1;
        public override void StartTargeting(AbilityData data, Action finished)
        {
            Vector3 targetPosition = data.GetUser().transform.position + data.GetUser().transform.forward;
            data.SetTargetedPoint(targetPosition + (Vector3.up * groundOffset));
            finished.Invoke();
        }
    }
}

SpawnProjectileEffect.cs

using System;
using RPG.Attributes;
using RPG.Combat;
using UnityEngine;

namespace RPG.Abilities.Effects
{
    [CreateAssetMenu(fileName = "Spawn Projectile Effect", menuName = "Abilities/Effects/Spawn Projectile", order = 0)]
    public class SpawnProjectileEffect : EffectStrategy
    {
        [SerializeField] Projectile projectileToSpawn;
        [SerializeField] float damage;
        [SerializeField] bool isRightHand = true;
        [SerializeField] bool useTargetPoint = true;

        public override void StartEffect(AbilityData data, Action finished)
        {
            Fighter fighter = data.GetUser().GetComponent<Fighter>();
            Vector3 spawnPosition = fighter.GetHandTransform(isRightHand).position;
            if (useTargetPoint)
            {
                SpawnProjectileForTargetPoint(data, spawnPosition);
            }
            else
            {
                SpawnProjectilesForTargets(data, spawnPosition);
            }
            finished();
        }

        private void SpawnProjectileForTargetPoint(AbilityData data, Vector3 spawnPosition)
        {
            Projectile projectile = Instantiate(projectileToSpawn);
            projectile.transform.position = spawnPosition;
            projectile.SetTarget(data.GetTargetedPoint(), data.GetUser(), damage);
        }

        private void SpawnProjectilesForTargets(AbilityData data, Vector3 spawnPosition)
        {
            foreach (var target in data.GetTargets())
            {
                Health health = target.GetComponent<Health>();
                if (health)
                {
                    Projectile projectile = Instantiate(projectileToSpawn);
                    projectile.transform.position = spawnPosition;
                    projectile.SetTarget(health, data.GetUser(), damage);
                }
            }
        }
    }
}

Projectile.cs

using UnityEngine;
using RPG.Attributes;
using UnityEngine.Events;

namespace RPG.Combat
{
    public class Projectile : MonoBehaviour
    {
        [SerializeField] float speed = 1;
        [SerializeField] bool isHoming = true;
        [SerializeField] GameObject hitEffect = null;
        [SerializeField] float maxLifeTime = 10;
        [SerializeField] GameObject[] destroyOnHit = null;
        [SerializeField] float lifeAfterImpact = 2;
        [SerializeField] UnityEvent onHit;

        Health target = null;
        Vector3 targetPoint;
        GameObject instigator = null;
        float damage = 0;

        private void Start()
        {
            transform.LookAt(GetAimLocation());
        }

        void Update()
        {
            if (target != null && isHoming && !target.IsDead())
            {
                transform.LookAt(GetAimLocation());
            }
            transform.Translate(Vector3.forward * speed * Time.deltaTime);
        }

        public void SetTarget(Health target, GameObject instigator, float damage)
        {
            SetTarget(instigator, damage, target);
        }

        public void SetTarget(Vector3 targetPoint, GameObject instigator, float damage)
        {
            SetTarget(instigator, damage, null, targetPoint);
        }

        public void SetTarget(GameObject instigator, float damage, Health target=null, Vector3 targetPoint=default)
        {
            this.target = target;
            this.targetPoint = targetPoint;
            this.damage = damage;
            this.instigator = instigator;

            Destroy(gameObject, maxLifeTime);
        }

        private Vector3 GetAimLocation()
        {
            if (target == null)
            {
                return targetPoint;
            }
            CapsuleCollider targetCapsule = target.GetComponent<CapsuleCollider>();
            if (targetCapsule == null)
            {
                return target.transform.position;
            }
            return target.transform.position + Vector3.up * targetCapsule.height / 2;
        }

        private void OnTriggerEnter(Collider other)
        {
            Health health = other.GetComponent<Health>();
            if (target != null && health != target) return;
            if (health == null || health.IsDead()) return;
            if (other.gameObject == instigator) return;
            health.TakeDamage(instigator, damage);

            speed = 0;

            onHit.Invoke();

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

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

            Destroy(gameObject, lifeAfterImpact);

        }

    }

}

DelayCompositeEffect.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Abilities.Effects
{
    [CreateAssetMenu(fileName = "Delay Composite Effect", menuName = "Abilities/Effects/Delay Composite", order = 0)]
    public class DelayCompositeEffect : EffectStrategy
    {
        [SerializeField] float delay = 0;
        [SerializeField] EffectStrategy[] delayedEffects;
        [SerializeField] bool abortIfCancelled = false;

        public override void StartEffect(AbilityData data, Action finished)
        {
            data.StartCoroutine(DelayedEffect(data, finished));
        }

        private IEnumerator DelayedEffect(AbilityData data, Action finished)
        {
            yield return new WaitForSeconds(delay);
            if (abortIfCancelled && data.IsCancelled()) yield break;
            foreach (var effect in delayedEffects)
            {
                effect.StartEffect(data, finished);
            }
        }
    }
}

By the way I’m using the Delay launch fireball

I think I get why it’s firing backwards when moving… because the target is selected, then the spawn actually happens several frames later, after the delay, at which point my simple + transform.forward target is now BEHIND the player…

Proposed changes:

This should select a point sufficiently forward so that the target point will still be in front of the character.

This should eliminate the football spiking effect.

1 Like

Thank you very much Brian, it’s working as intended, but there’s a new bug that pops up that I will show on one of the videos. When I hit an enemy with the projectile. The hit effect spawns in a weird location.
Here’s the video on the result of your solutions:

And this video shows the bug on hit fx:

I think it’s okay now, I found the solution on hit fx problem on this post: Correct position for hit effect with directional targeting

1 Like

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

Privacy & Terms