Hello.
I have a problem with bow attack.
It doesn’t instantiate any Arrow.
Here is my bow
I’ve a projectile associated to it:
It tag is Projectiles
here are my Fighter, weaponConfig and Projectile script:
Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;
using GameDevTV.Saving;
using RPG.Attributes;
using RPG.Stats;
using System.Collections.Generic;
using GameDevTV.Utils;
using GameDevTV.Inventories;
using System;
using Newtonsoft.Json.Linq;
namespace RPG.Combat
{
public class Fighter : MonoBehaviour, IAction
{
[SerializeField] private float timeBetweenAttacks = 1f;
[SerializeField] private Transform rightHandTransform = null;
[SerializeField] private Transform leftHandTransform = null;
[SerializeField] private WeaponConfig defaultWeapon = null;
[SerializeField] float autoAttackRange = 4f;
public event System.Action<WeaponConfig> OnWeaponChanged;
Health target;//On accède au script Health du porteur
private Equipment equipment;
private ForceReceiver forceReceiver;//Ajout 3rd person chap 20
private BaseStats baseStats;
private bool isPlayer;
float timeSinceLastAttack = Mathf.Infinity;
WeaponConfig currentWeaponConfig;
LazyValue<Weapon> currentWeapon;
private Attack currentAttack;//Ajout 3RDPerson
/// <summary>
/// Obtenir l'animation spécifiée par index et définir l'attaque actuelle.
/// </summary>
/// <param name="attack"></param>
/// <returns></returns>
public Attack GetCurrentAttack(int attack)
{
if (currentWeaponConfig == null || currentWeaponConfig.Attacks.Length==0) return null;
if (attack < 0 || attack >= currentWeaponConfig.Attacks.Length)
{
attack = 0;
}
currentAttack = currentWeaponConfig.Attacks[attack];
return currentAttack;
}
/// <summary>
/// Définit l'attaque actuelle. Utile lorsque l'attaque contient une attaque combo.
/// </summary>
/// <param name="attack"></param>
public void SetCurrentAttack(Attack attack)
{
currentAttack = attack;
}
private void Awake()
{
baseStats = GetComponent<BaseStats>();//Ajout 3rd Person chap 21
currentWeaponConfig = defaultWeapon;
currentWeapon = new LazyValue<Weapon>(SetupDefaultWeapon);
equipment = GetComponent<Equipment>();
if (equipment)
{
equipment.equipmentUpdated += UpdateWeapon;
}
forceReceiver = GetComponent<ForceReceiver>();//Ajout 3rd Person
isPlayer = gameObject.CompareTag("Player");//Ajout 3rd Person chap 21
}
private Weapon SetupDefaultWeapon()
{
return AttachWeapon(defaultWeapon);
}
private void Start()
{
currentWeapon.ForceInit();
}
private void Update()
{
if (isPlayer) return;//Ajout 3rd Person chap 21
timeSinceLastAttack += Time.deltaTime; //Le temps qui s'écoule depuis la dernière attaque
if (target == null) return;//S'il n'y a pas de cible cliquée on ne lit pas la suite de l'update
if (target.IsDead())
{
target = FindNewTargetInRange();
if(target == null) return;
}
if(!GetIsInRange(target.transform))//S'il n'est pas à portée c'est qu'on a quand même cliqué sur un enemi dont target n'est pas null, donc on avance vers l'ennemi
{
GetComponent<Mover>().MoveTo(target.transform.position, 1f);//On utilise la méthode MoveTo de Mover avec comme destination la target à la vitesse max.
}
else//sinon on arrète le mouvement et on attaque.
{
GetComponent<Mover>().Cancel();
AttackBehavior();
}
}
public void EquipWeapon(WeaponConfig weapon)
{//if(defaultWeapon == null) return;//Si on n'a pas d'arme équipée on ne fait rien d'autre.
currentWeaponConfig = weapon;
currentWeapon.value = AttachWeapon(weapon);
}
private void UpdateWeapon()//Méthode pour mettre à jour l'arme depuis l'inventaire
{
//Récuperer l'arme depuis l'équipement
var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
if (weapon == null)//Lancer le WeaponConfig
{
EquipWeapon(defaultWeapon);
}
else
{
EquipWeapon(weapon);
}
//Equiper l'arme
//Que faire s'il n'y a pas d'arme équipée?
}
private Weapon AttachWeapon(WeaponConfig weapon)//Méthode pour récupérer l'arme équipée
{
Animator animator = GetComponent<Animator>();//On récupère l'animator de l'objet qui porte le script.
return weapon.Spawn(rightHandTransform, leftHandTransform, animator);//On fait apparaitre l'arme sélectionnée et l'animation associée.
}
public Health GetTarget()//Ajout 3RD Person
{
return target;
}
public Transform GetHandTransform(bool isRightHand)
{
if (isRightHand)
{
return rightHandTransform;
}
else
{
return leftHandTransform;
}
}
private void AttackBehavior()
{
transform.LookAt(target.transform);
if(timeSinceLastAttack > timeBetweenAttacks)
{
// Cela déclenchera l'événement Hit().
TriggerAttack();
timeSinceLastAttack = 0;
}
}
private Health FindNewTargetInRange()
{
Health best = null;
float bestDistance = Mathf.Infinity;//Tout ce qui est infèrieur à l'infini
foreach(var candidate in FindAllTargetsInRange())
{
float candidateDistance = Vector3.Distance(transform.position, candidate.transform.position);//On mesure la distance entre la cible et le joeuur
if(candidateDistance < bestDistance)
{
best = candidate;
bestDistance = candidateDistance;
}
}
return best;
}
private IEnumerable<Health> FindAllTargetsInRange()
{
RaycastHit[] raycastHits = Physics.SphereCastAll(transform.position,autoAttackRange, Vector3.up );
foreach(var hit in raycastHits)
{
Health health = hit.transform.GetComponent<Health>();
if(health == null) continue;//Si l'objet dans la sphere de cast n'a ps de script Health, on continue la recherche
if(health.IsDead()) continue;//Si le PNJ est mort, on continue la recherche
if(health.gameObject == gameObject) continue;//On s'exclu de la recherche.
yield return health;
}
}
private void TriggerAttack()
{
GetComponent<Animator>().ResetTrigger("stopAttack");//Pour éviter le bug lorsqu'on sort du combat où le trigget stopAttack reste actif et qui fait en sorte que lors de la prochaine attaque le joueur semble faire une pause avant de frapper.
GetComponent<Animator>().SetTrigger("attack");
}
//méthode uniquement pour event de l'animator
private void Hit()
{
//Si on sort du combat avant la frame qui déclenche l'event hit, alors on ne lit pas la suite du code. Evite le message d'erreur NullReferenceException
if(target == null) {return;}
float damage = GetComponent<BaseStats>().GetStat(Stat.Damage);
BaseStats targetBaseStats = target.GetComponent<BaseStats>();
if(targetBaseStats != null)
{
float defence = targetBaseStats.GetStat(Stat.Defence);
Debug.Log($"{name} hit {target.name} with {damage} attack. Target's defense = {defence}");
Debug.Log($"{damage} /= 1 + {defence}/{damage} = {damage / 1 + defence / damage}");
damage /= 1 + defence / damage;
}
if(currentWeapon.value != null)
{
currentWeapon.value.OnHit();
}
if(currentWeaponConfig.HasProjectile())//Si l'arme actuelle est une arme à munitions
{
currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, target, gameObject, damage);
}else{
target.TakeDamage(gameObject, damage);//target étant relié directement au script Healt, on lance la méthode TakeDamage à chaque event hit.
}//gameObject represente la personne qui inflige les dégats.
}
/*private void TryApplyHitForce(Collider other, Vector3 position)
{
other.GetComponent<ForceReceiver>().AddForce((other.transform.position - position).normalized * currentAttack.HitForce);
}*/
void Shoot()//Ajout 3rd person chap 40 projectiles, remplace méthode du dessous du même nom
{
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);
}
}
}
/*private void Shoot()
{
Hit();
}*/
void TryHit(int slot)//Ajout 3rdPerson chap 19
{
if (currentAttack == null) return;//Ajout chap 21
Vector3 transformPoint;
float damageRadius = .5f;//Ajout Chap 21
switch (slot)
{
case 0:
transformPoint = currentWeapon.value.DamagePoint;
damageRadius = currentWeapon.value.DamageRadius;
break;
case 1:
transformPoint = rightHandTransform.position;
break;
case 2:
transformPoint = leftHandTransform.position;
break;
default:
transformPoint = rightHandTransform.position;
break;
}
Debug.Log($"Attacking with slot {slot}, position {transformPoint}");
foreach (Collider other in Physics.OverlapSphere(transformPoint, damageRadius))//Ajout boucle complète Chap 21
{
if (other.gameObject == gameObject) continue;
if (other.TryGetComponent(out Health otherHealth) && !otherHealth.IsDead())//Ajout 3RD Person Chap 34 de la condition !otherHealth.IsDead()
{
Debug.Log($"Hitting {otherHealth.gameObject}");
float damage = baseStats.GetStat(Stat.Damage);
damage *= currentAttack.DamageModifier; //Allows for varying damage based on attack style.
if (other.TryGetComponent(out BaseStats otherBaseStats))
{
float defence = otherBaseStats.GetStat(Stat.Defence);
damage /= 1 + defence / damage;
}
otherHealth.TakeDamage(gameObject, damage);
TryApplyHitForce(other, transform.position);//Ajout Chap 35 Damage knockback
}
}
}
private void TryApplyHitForce(Collider other, Vector3 transformPoint)//Ajout chap 35
{
other.GetComponent<ForceReceiver>().AddForce((other.transform.position - transformPoint).normalized * currentAttack.HitForce, true);
}
void ApplyAttackForce()
{
if (!forceReceiver) return;
forceReceiver.AddForce(transform.forward * currentAttack.AttackForce);
}
private bool GetIsInRange(Transform targetTransform)//On défini la cible et non plus la distance via le navMesh
{
return Vector3.Distance(transform.position, targetTransform.position) < currentWeaponConfig.GetRange();
}
public bool CanAttack(GameObject combatTarget)
{
if (combatTarget == null) {return false;}
if (!GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) &&
!GetIsInRange(combatTarget.transform))
{
return false;
}//Pour empecher le deplacement vers une cible au dela de la portée de mouvement max definie.
Health targetToTest = combatTarget.GetComponent<Health>();
return targetToTest != null && !targetToTest.IsDead();//REnvoyer l'info qu'il y a bien une cible et qu'elle n'est pas morte
}
public void Attack(GameObject combatTarget)
{
if (isPlayer) return;
GetComponent<ActionScheduler>().StartAction(this);//Lorsqu'on attaque on informe le script ActionScheduler que l'action en cours est attaque.
target = combatTarget.GetComponent<Health>();//La cible est le transform de la combatTarget.
}
public void Cancel()
{
if (isPlayer) return;
StopAttack();//La méthode Cancel désactive l'attaque.
target = null;
GetComponent<Mover>().Cancel();//La méthode Cancel déscative aussi le mouvement.
}
private void StopAttack()
{
GetComponent<Animator>().ResetTrigger("attack");//Pour remettre à 0 le trigger attaque lors de l'annulation de l'attaque.
GetComponent<Animator>().SetTrigger("stopAttack");
}
public float GetAttackingRange()
{
return currentWeaponConfig.GetRange();
}
/*public object CaptureState()
{
return currentWeaponConfig.name;
}
public void RestoreState(object state)
{
string weaponName = (string)state;
WeaponConfig weapon = UnityEngine.Resources.Load<WeaponConfig>(weaponName);
EquipWeapon(weapon);
}
public JToken CaptureAsJToken()
{
return JToken.FromObject(currentWeaponConfig.name);
}
public void RestoreFromJToken(JToken state)
{
string weaponName = state.ToObject<string>();
WeaponConfig weapon = UnityEngine.Resources.Load<WeaponConfig>(weaponName);
EquipWeapon(weapon);
}*/
}
}
WeaponConfig
using System;
using UnityEngine;
using RPG.Attributes;
using GameDevTV.Inventories;
using RPG.Stats;
using System.Collections.Generic;
using UnityEditor;
namespace RPG.Combat
{
[CreateAssetMenu(fileName = "weapon", menuName = "RPG Weapons/Make new Weapon", order = 0)]
public class WeaponConfig:EquipableItem, IModifierProvider
{
[SerializeField] private int animatorWeaponType;//Ajout chap 31
//[SerializeField] private AnimatorOverrideController animatorOverride = null;//Suppression chap 31 RPG=>3RD
[SerializeField] private Weapon equippedPrefab = null;
[SerializeField] private float weaponDamage = 5f;
[SerializeField] private float percentageBonus = 0f;
[SerializeField] private float weaponRange = 2f;
[SerializeField] private float targetingRange = 10f;//Ajout Oublie dans le github
[SerializeField] private bool isRightHanded = true;
[SerializeField] private Projectile projectile = null;
[field: SerializeField] public Attack[] Attacks { get; private set; }//Ajout pour les 3rd Person settings
const string weaponName = "Weapon"; //cette constante est créée en string car on va utiliser FindObject qui cherche des strings.
public int AnimatorWeaponType => animatorWeaponType;//Ajout chap 31
public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
{
DestroyOldWeapon(rightHand, leftHand);
Weapon weapon = null;
if (equippedPrefab != null)
{
Transform handTransform = GetTransform(rightHand, leftHand);
weapon = Instantiate(equippedPrefab, handTransform);//On instancie l'arme dans la main adéquate
weapon.gameObject.name = weaponName;
}
//Retrait Chap 31
/*if (!animator.gameObject.CompareTag("Player"))//Ajout 3rdPerson
{
var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController;//Ajout 3rdPerson
if (animatorOverride != null)
{
animator.runtimeAnimatorController = animatorOverride;//On remplace l'animation d'avant par celle correspondant à l'arme équipée
}
else if (overrideController != null)//Ajout 3rdPerson
{
animator.runtimeAnimatorController = overrideController.runtimeAnimatorController;
}
}*/
animator.SetFloat("WeaponType", animatorWeaponType);
return weapon;
}
private void DestroyOldWeapon(Transform rightHand, Transform leftHand)
{
Transform oldWeapon = rightHand.Find(weaponName);
if (oldWeapon == null)
{
oldWeapon = leftHand.Find(weaponName);
}
if (oldWeapon == null) return;
oldWeapon.name = "DESTROYING";
Destroy(oldWeapon.gameObject);
}
private Transform GetTransform(Transform rightHand, Transform leftHand)//Méthode pour récupérer la main d'origine de l'arme ou du projectile
{
Transform handTransform;
if (isRightHanded) handTransform = rightHand;
else handTransform = leftHand;
return handTransform;
}
public bool HasProjectile()
{
return projectile != null;//La booléenne sert à vérifier que le porteur du script a encore des projectiles.
}
public void LaunchProjectile(Transform rightHand, Transform leftHand, Health target, GameObject instigator, float calculatedDamage)//la méthode de projection d'objet à besoin de connaitre son point d'origine et d'arrivée (le porteur du script Health cliqué)
{
Debug.Log($"Launch Projectile ({target}), instigator = {instigator}");
//On fait apparaitre un prefab (instance) du projectile dans la bonne main du personnage
Projectile projectileInstance = Instantiate(projectile, GetTransform(rightHand, leftHand).position, Quaternion.identity);
//On lui assigne la cible:
projectileInstance.SetTarget(target,instigator, calculatedDamage);
}
//Ajout GitHub chap 40 sur les projectiles
public void LaunchProjectile(Transform rightHandTransform, Transform leftHandTransform, GameObject instigator, float damage)
{
Debug.Log($"LaunchProjectile(noTarget), instigator = {instigator}");
Transform correctTransform = GetTransform(rightHandTransform, leftHandTransform);
Projectile projectileInstance = Instantiate(projectile, correctTransform.position, Quaternion.identity);
projectileInstance.transform.forward = instigator.transform.forward;
projectileInstance.SetupNoTarget(instigator, damage);
}
public float GetDamage()
{
return weaponDamage;
}
public float GetPercentageBonus()
{
return percentageBonus;
}
public float GetRange()
{
return weaponRange;
}
public IEnumerable<float> GetAdditiveModifiers(Stat stat)
{
if(stat == Stat.Damage)
{
yield return weaponDamage;
}
}
public IEnumerable<float> GetPercentageModifiers(Stat stat)
{
if(stat == Stat.Damage)
{
yield return percentageBonus;
}
}
public float GetTargetingRange()
{
return targetingRange;
}
#region InventoryItemEditor Additions
public override string GetDescription()
{
string result = projectile ? "Ranged Weapon" : "Melee Weapon";
result += $"\n\n{GetRawDescription()}\n";
result += $"\nRange {weaponRange} meters";
result += $"\nBase Damage {weaponDamage} points";
if ((int)percentageBonus != 0)
{
string bonus = percentageBonus > 0 ? "<color=#8888ff>bonus</color>" : "<color=#ff8888>penalty</color>";
result += $"\n{(int)percentageBonus} percent {bonus} to attack.";
}
return result;
}
#if UNITY_EDITOR
void OnValidate()
{
if (GetAllowedEquipLocation() != EquipLocation.Weapon)
{
SetAllowedEquipLocation(EquipLocation.Weapon);
}
}
void SetWeaponRange(float newWeaponRange)
{
if (FloatEquals(weaponRange, newWeaponRange)) return;
SetUndo("Set Weapon Range");
weaponRange = newWeaponRange;
Dirty();
}
void SetWeaponDamage(float newWeaponDamage)
{
if (FloatEquals(weaponDamage, newWeaponDamage)) return;
SetUndo("Set Weapon Damage");
weaponDamage = newWeaponDamage;
Dirty();
}
void SetPercentageBonus(float newPercentageBonus)
{
if (FloatEquals(percentageBonus, newPercentageBonus)) return;
SetUndo("Set Percentage Bonus");
percentageBonus = newPercentageBonus;
Dirty();
}
void SetIsRightHanded(bool newRightHanded)
{
if (isRightHanded == newRightHanded) return;
SetUndo(newRightHanded?"Set as Right Handed":"Set as Left Handed");
isRightHanded = newRightHanded;
Dirty();
}
/*void SetAnimatorOverride(AnimatorOverrideController newOverride)
{
if (newOverride == animatorOverride) return;
SetUndo("Change AnimatorOverride");
animatorOverride = newOverride;
Dirty();
}*/
void SetEquippedPrefab(GameObject potentialnewWeapon)
{
if (!potentialnewWeapon)
{
SetUndo("No Equipped Prefab");
equippedPrefab = null;
Dirty();
return;
}
if (!potentialnewWeapon.TryGetComponent(out Weapon newWeapon)) return;
if (newWeapon == equippedPrefab) return;
SetUndo("Set Equipped Prefab");
equippedPrefab = newWeapon;
Dirty();
}
void SetProjectile(GameObject potentialNewProjectile)
{
if (!potentialNewProjectile)
{
SetUndo("No Projectile");
projectile = null;
Dirty();
return;
}
if (!potentialNewProjectile.TryGetComponent(out Projectile newProjectile))
{
return;
}
if (newProjectile == projectile) return;
SetUndo("Set Projectile");
projectile = newProjectile;
Dirty();
}
public override bool IsLocationSelectable(Enum location)
{
EquipLocation candidate = (EquipLocation) location;
return candidate == EquipLocation.Weapon;
}
bool drawWeaponConfigData = true;
public override void DrawCustomInspector()
{
base.DrawCustomInspector();
drawWeaponConfigData = EditorGUILayout.Foldout(drawWeaponConfigData, "Données de configuration de l'arme",foldoutStyle);
if (!drawWeaponConfigData) return;
EditorGUILayout.BeginVertical(contentStyle);
//Trick to allow searching for the prefab using the . button instead of having to drag it in
GameObject potentialPrefab = equippedPrefab?equippedPrefab.gameObject:null;
SetEquippedPrefab((GameObject)EditorGUILayout.ObjectField("Prefab Équipé", potentialPrefab,typeof(GameObject), false));
SetWeaponDamage(EditorGUILayout.Slider("Dégats de l'arme", weaponDamage, 0, 1000));
SetWeaponRange(EditorGUILayout.Slider("Portée de l'arme", weaponRange, 1,40));
SetPercentageBonus(EditorGUILayout.IntSlider("Pourcentage de Bonus", (int)percentageBonus, -10, 100));
SetIsRightHanded(EditorGUILayout.Toggle("Main droite", isRightHanded));
//SetAnimatorOverride((AnimatorOverrideController)EditorGUILayout.ObjectField("Outrepasser l'Animator", animatorOverride, typeof(AnimatorOverrideController), false));
GameObject potentialProjectile = projectile ? projectile.gameObject : null;
SetProjectile((GameObject)EditorGUILayout.ObjectField("Projectile", potentialProjectile, typeof(GameObject), false));
EditorGUILayout.EndVertical();
}
#endif
#endregion
}
}
And the Projectile script
using RPG.Attributes;
using RPG.Movement;
using UnityEngine;
using UnityEngine.Events;
namespace RPG.Combat
{
public class Projectile : MonoBehaviour
{
[SerializeField] private float speed = 1;
[SerializeField] private float projectileDamage = 0;
[SerializeField] private bool isHoming = true;//Tête chercheuse.
[SerializeField] private GameObject hitEffect = null;
[SerializeField] private float MaxLifeTime = 5;//Pour détruire les missiles qui ont manqués leur cible.
[SerializeField] private GameObject[] destroyOnHit = null;//Création d'une liste d'objet qu'on souhaite détruire à l'impact.
[SerializeField] private float lifeAfterImpact = 2f;
[SerializeField] private UnityEvent onHit;
Health target = null;
Vector3 targetPoint;
GameObject instigator = null;
float damage = 0;
void Start()//Modif 3rd person chap 40 ajout if(target)
{
if (target) transform.LookAt(GetAimLocation());//Dés le départ on vise la cible.Annule l'effet missile chercheur.
}
void Update()
{
if (target == null) return;
if(target != null && isHoming && !target.IsDead())//Si le missile est à tête chercheuse, il va suivre la cible. sauf s'il est mort, il continuera son chemin
{
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)
{
Debug.Log($"Arrow fired for {projectileDamage} damage");
this.target = target; // La santé de la cible est celle du porteur de ce script ciblé.
this.targetPoint = targetPoint;
this.damage += damage; //Les dommages infligés par le porteur du script (le projectile) sont les dommages du projectile.
this.instigator = instigator;
Destroy(gameObject, MaxLifeTime);
}
private Vector3 GetAimLocation()
{
if(target ==null)//target cherchant un script Heal, si l'endroit pointé n'en a pas, l'endroit visé devient l'endroit pointé.
{
return targetPoint;
}
//BoxCollider targetBox =target.GetComponent<BoxCollider>();
var targetCapsule = target.GetComponent<CharacterController>();
if (targetCapsule == null)//était targetBox
{
return target.transform.position;
}
return target.transform.position + Vector3.up * targetCapsule.height /2;
}
private void OnTriggerEnter(Collider other)
{
//Retrait des 3 lignes suivantes suite à 3RD Person Chap 40 projectiles.
//Health health= other.GetComponent<Health>();
//if(target != null && health != target) return;//Si l'objet touché n'a pas de composant Health, on ne lit pas la suite du code.
//if (health == null || health.IsDead()) return;//Si la cible est morte ou s'il n'y a pas de cible avec le script Health, la fleche continue son chemin.
if (other.gameObject == instigator) return;
if (other.TryGetComponent(out Health health))//On vérifie si la cible a un composant Health, si oui on lui inflige les dégâts
{
health.TakeDamage(instigator, damage);
}
//Ensuite on vérifies'il la cible à un composant ForceReceiver puis on applique la force dans la direction du projectile
if (other.TryGetComponent(out ForceReceiver forceReceiver))//Ajout 3RD Person Chap 40 Projectiles
{
forceReceiver.AddForce(transform.forward * damage, true);
}
speed = 0;
onHit.Invoke();
//Création de l'efffet d'impact
if(hitEffect != null)
{
Instantiate(hitEffect, GetAimLocation(), transform.rotation);
}
/*target.TakeDamage(instigator,projectileDamage);
speed = 0.5f;//On arrete le projectile*/
//Destruction à l'impact de la liste d'objet qu'on a choisi.
foreach (GameObject toDestroy in destroyOnHit)
{
Destroy(toDestroy);
}
Destroy(gameObject , lifeAfterImpact);
}
//Ajout Github chapitre 40 projectiles
public void SetupNoTarget(GameObject instigator, float damage)
{
this.instigator = instigator;
this.damage = damage;
this.target = null;
isHoming = false;
}
}
}
The bow spawn correctly and the shooting animation is the good one but no arrow spawns at all.
Any idea?
Do you need other information?
Here is my layer collision matrix
Thank you.
François
(and thank you for your patience
)


















