Hello good people,
I am attempting to create an RTS style melee system. Currently I have a “MeleeSystem” script attached to the player and an “EnemyHealth” script attached to the enemy. The player does damage and kills the enemy when in range. It just happens straight away, even though damage is set for 50 and health to 1000. I simply would like to add a time delay between each melee attack. I’ve been reading up on Coroutines and the WaitForSeconds/Time.time scripting, but can’t seem to implement it. Do I need to use a coroutine or can I simply add a delay to each attack with the script as is? If so, how?
Assistance is VERY much appreciated.
using UnityEngine;
using System.Collections;
public class MeleeSystem : MonoBehaviour
{
public int Damage = 50;
public float Distance;
public float WeaponRange = 1.5F;
void Start()
{
}
void Update()
// {
// if (Input.GetButtonDown("Fire2"))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit))
{
Distance = hit.distance;
if (Distance < WeaponRange)
{
hit.transform.SendMessage("ApplyDamage", Damage, SendMessageOptions.DontRequireReceiver);
}
}
}
// }
}
using UnityEngine;
using System.Collections;
public class EnemyHealth : MonoBehaviour
{
public int health = 1000;
private void ApplyDamage(int damage)
{
health -= damage;
if (health <= 0)
{
Dead();
}
}
private void Dead()
{
gameObject.SetActive(false); // (this.gameObject);
}
}
To clarify, I was using “if (Input.GetButtonDown(“Fire2”))” to trigger the attacks to test it, but would like it to be automatic. As in, player comes into range of enemy and does ** attacks per ** seconds.
For attack delays, I find it’s usually best to use Time.time in Update rather than coroutines:
public float attackDelay = 1f; //seconds
private float lastAttackAt = -999f;
void Attack(EnemyHealth target) {
if (Time.time > lastAttackAt + attackDelay) {
lastAttackAt = Time.time;
target.ApplyDamage(Damage);
}
}
void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit))
{
Distance = hit.distance;
if (Distance < WeaponRange)
{
EnemyHealth eh = hit.GetComponent<EnemyHealth>();
if (eh != null) Attack(eh);
}
}
}
I’ve also replaced .SendMessage with a direct call to the function. In addition to being inefficient, SendMessage can get false positives that happen to have the same name, while GetComponent will find exactly the components you need. If you need to be able to find multiple types of components, you can use inheritance or interfaces. (SendMessage also limits you to a single parameter)
1 Like
Thank you so much! I think I’m nearly there. Just such a n00b still.
Getting a “Assets/Scripts/MeleeSystem.cs(13,18): error CS0118: EnemyHealth' is a namespace’ but a `type’ was expected” error.
I added a name space to the EnemyHealth script to try and rectify this but no luck. Think I’m doing it correctly but unsure. Updated scripts:
using UnityEngine;
using System.Collections;
public class MeleeSystem : MonoBehaviour
{
public int Damage = 50;
public float Distance;
public float WeaponRange = 1.5F;
public float attackDelay = 1f; //seconds
private float lastAttackAt = -999f;
void Attack (EnemyHealth target)
{
if (Time.time > lastAttackAt + attackDelay)
{
lastAttackAt = Time.time;
target.ApplyDamage(Damage);
}
}
void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit))
{
Distance = hit.distance;
if (Distance < WeaponRange)
{
EnemyHealth eh = hit.GetComponent<EnemyHealth>();
if (eh != null) Attack(eh);
}
}
}
}
namespace EnemyHealth
{
using UnityEngine;
using System.Collections;
public class EnemyHealth : MonoBehaviour
{
public int health = 100;
private void Attack(int damage)
{
health -= damage;
if (health <= 0)
{
Dead();
}
}
private void Dead()
{
gameObject.SetActive(false); // (this.gameObject);
}
}
}
Unless you have something else called EnemyHealth somewhere, that doesn’t make sense - you original code didn’t have a namespace.
For clarity: the script in your most recent comment is after the modifications to attempt to fix that error?
1 Like
Yeah, I was attempting to fix before just asking. No namespace.
So, I’m not entirely sure what happened but I restarted Unity and VisStudio and no longer have that error. Got these two instead:
Got red squiggly lines under “ApplyDamage” & “GetComponent”.
Updated scripts:
using UnityEngine;
using System.Collections;
public class MeleeSystem : MonoBehaviour
{
public int Damage = 50;
public float Distance;
public float WeaponRange = 1.5F;
public float attackDelay = 1f; //seconds
private float lastAttackAt = -999f;
void Attack (EnemyHealth target)
{
if (Time.time > lastAttackAt + attackDelay)
{
lastAttackAt = Time.time;
target.ApplyDamage(Damage);
}
}
void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit))
{
Distance = hit.distance;
if (Distance < WeaponRange)
{
EnemyHealth eh = hit.GetComponent<EnemyHealth>();
if (eh != null) Attack(eh);
}
}
}
}
using UnityEngine;
using System.Collections;
public class EnemyHealth : MonoBehaviour
{
public int health = 100;
private void ApplyDamage (int damage)
{
health -= damage;
if (health <= 0)
{
Dead();
}
}
private void Dead()
{
gameObject.SetActive(false); // (this.gameObject);
}
}
Oh, that should be hit.gameObejct.GetComponent. Been a while since I’ve written raycast code.
ApplyDamage needs to be public rather than private.
1 Like
You, sir, are a legend!
I ended up putting “EnemyHealth eh = hit.transform.GetComponent();” It seems to be “transform” rather than “gameObject”. Well, it’s doing what I want at least.
Updated scripts:
using UnityEngine;
using System.Collections;
public class MeleeSystem : MonoBehaviour
{
public int Damage = 50;
public float Distance;
public float WeaponRange = 1.5F;
public float attackDelay = 1f; //seconds
private float lastAttackAt = -999f;
void Attack (EnemyHealth target)
{
if (Time.time > lastAttackAt + attackDelay)
{
lastAttackAt = Time.time;
target.ApplyDamage(Damage);
}
}
void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit))
{
Distance = hit.distance;
if (Distance < WeaponRange)
{
EnemyHealth eh = hit.transform.GetComponent<EnemyHealth>();
if (eh != null) Attack(eh);
}
}
}
}
using UnityEngine;
using System.Collections;
public class EnemyHealth : MonoBehaviour
{
public int health = 100;
public void ApplyDamage (int damage)
{
health -= damage;
if (health <= 0)
{
Dead();
}
}
private void Dead()
{
gameObject.SetActive(false); // (this.gameObject);
}
}