I have a very simple 2D game where the player is placed on the left, while the enemies are placed on the right and they move towards the player.
The player would attack the enemies with a sword, and I have this current function for it:
[SerializeField] int damage = 1;
[SerializeField] Transform attackPos;
[SerializeField] LayerMask whatIsEnemy;
[SerializeField] float attackRangeX;
[SerializeField] float attackRangeY;
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
DamageEnemies();
}
}
private void DamageEnemies()
{
Collider2D[] enemiesToDamage = Physics2D.OverlapBoxAll(attackPos.position, new Vector2(attackRangeX, attackRangeY), 0, whatIsEnemy);
for (int i = 0; i < enemiesToDamage.Length; i++)
{
enemiesToDamage[i].GetComponent<EnemyController>().TakeDamage(damage);
}
}
attackPos game object is just placed in front of the player. AttackRangeX and Y resemble a square in front of the player.
The problem with this setup is that when the attack key is pressed, it will interact with the enemies inside at that point in time only, which is a single frame.
How can I have the collision active for 5 seconds, but only damage the new enemies once. I wouldn't want it to keep damaging the enemies it already damaged.
You could store the already damaged enemies in a List and skip them if you already damaged them.
For the longer active damage some people prefer using Coroutines (IEnumerator
) but for keeping it simple for now I'ld use a simple timer.
[SerializeField] int damage = 1;
[SerializeField] Transform attackPos;
[SerializeField] LayerMask whatIsEnemy;
[SerializeField] float attackRangeX;
[SerializeField] float attackRangeY;
// Adjust here how long the damage should take on (in seconds)
[SerializeField] float damageDuration = 5;
// This is the countdown timer for the damage
private float damageTimer;
// Flag that controls the damage mode
private bool isDamaging;
// Here you will store the already damaged enemies to skip them later
private List<Collider2D> alreadyDamagedEnemies = new List<Collider2D>();
private void Start()
{
// Initialize the timer
damageTimer = damageDuration;
}
private void Update()
{
// only take keyboard input if not already damaging
// to prevent a peanant press
if(!isDamaging)
{
// Hint: If you want you could with a second timer at this point add
// a cooldown so you can not directly atack again right after the damage duration
// Just thought it might be interesting for you so I leave it as a homework ;)
if (Input.GetKeyDown(KeyCode.A))
{
// Only activate the damaging
isDamaging = true;
}
}
else // We are currently in damage mode
{
// If end of timer reset and leave damage mode
if(damageTimer <= 0)
{
// Reset the timet
damageTimer = damageDuration;
// Switch off damage mode
isDamaging = false;
// Reset the list
alreadyDamagedEnemies.Clear();
}
// else make damage and redue the timer
else
{
DamageEnemies();
// Reduce the timer by the time passed since last frame
damageTimer-= Time.deltaTime;
}
}
}
private void DamageEnemies()
{
Collider2D[] enemiesToDamage = Physics2D.OverlapBoxAll(attackPos.position, new Vector2(attackRangeX, attackRangeY), 0, whatIsEnemy);
foreach (var currentEnemy in enemiesToDamage)
{
// Skip if you already damaged this enemy
if(alreadyDamagedEnemies.Contains(currentEnemy) continue;
currentEnemy.GetComponent<EnemyController>().TakeDamage(damage);
// Add the damaged enemy to the list
alreadyDamagedEnemies.Add(currentEnemy);
}
}
This example assumes that you press A once but perform the damage for the full 5 seconds also if the key is released meanwhile.
If you rather want it to only take place during the key is pressed and with a maximum of 5 seconds instead, I'm sure you will be able to figure it out on your own using
if(Input.GetKey(KeyCode.A)){ ... }
.