Hi everyone,
My name is Azim, and I’ve been working on a combo system (currently a 2-attack combo) for my game. I’ve been struggling for quite some time with getting it to work smoothly and organizing my code in a maintainable way. I’m planning to add more combos in the future, and I’m worried that my current approach might become unmanageable over time.
Here’s an excerpt of my current code:
using UnityEngine;
using System.Collections;
public class AttackScript : MonoBehaviour
{
[Header("Important References")]
[SerializeField] private AttackComboData attackComboDataRef;
[SerializeField] private PlayerAttackData currentWeaponData;
[SerializeField] private GameObject rightAttackPosition;
[SerializeField] private GameObject RotateSecondAttack;
[SerializeField] private GameObject leftAttackPosition;
[SerializeField] private GameObject upAttackPosition;
[SerializeField] private GameObject downAttackPosition;
[SerializeField] private LayerMask standardEnemy;
[SerializeField] private Camera_System cameraSystemReference;
private Coroutine ComboCoroutine = null;
[Header("Debug")]
public bool showHitboxes = false;
[Header("Runtime Variables")]
[HideInInspector] public float currentCooldown = 0f;
[HideInInspector] public bool isCurrentlyAttacking = false;
private GameObject activeAttackPosition;
private int originalAttackDamage;
private int runtimeAttackDamage;
private void Awake()
{
originalAttackDamage = currentWeaponData.attackDamage;
runtimeAttackDamage = originalAttackDamage;
}
private void Update()
{
if (currentCooldown > 0)
currentCooldown -= Time.deltaTime;
UpdateAttackDirection();
}
public void CheckoutForInputs()
{
if (Input.GetMouseButtonDown(0) && currentCooldown <= 0)
{
PerformAttack();
isCurrentlyAttacking = true;
currentCooldown = currentWeaponData.attackCooldown;
cameraSystemReference?.TriggerShake();
}
}
private void UpdateAttackDirection()
{
if (Input.GetKeyDown(KeyCode.W))
activeAttackPosition = upAttackPosition;
else if (Input.GetKeyDown(KeyCode.S))
activeAttackPosition = downAttackPosition;
else if (Input.GetKeyDown(KeyCode.A))
{
activeAttackPosition = leftAttackPosition;
RotateSecondAttack.transform.position = new Vector2(transform.position.x, leftAttackPosition.transform.position.y);
}
else if (Input.GetKeyDown(KeyCode.D))
{
activeAttackPosition = rightAttackPosition;
RotateSecondAttack.transform.position = new Vector2(transform.position.x, rightAttackPosition.transform.position.y);
}
}
public void PerformAttack()
{
if (activeAttackPosition == null || currentWeaponData == null)
return;
Collider2D[] enemiesInRange = Physics2D.OverlapBoxAll(
activeAttackPosition.transform.position,
currentWeaponData.boxSize,
0f,
standardEnemy
);
foreach (var enemy in enemiesInRange)
{
if (enemy.TryGetComponent(out EnemyHealth enemyHealth))
{
enemyHealth.TakeDamage(runtimeAttackDamage);
ApplyKnockBack(enemy);
}
}
}
private void ApplyKnockBack(Collider2D enemy)
{
if (enemy.TryGetComponent(out Rigidbody2D rb))
{
Vector2 direction = (enemy.transform.position - transform.position).normalized;
rb.AddForce(direction * currentWeaponData.knockBackForce, ForceMode2D.Impulse);
}
}
public void OnAttackAnimationEnd()
{
isCurrentlyAttacking = false;
}
private void OnDrawGizmos()
{
if (currentWeaponData == null)
return;
Gizmos.color = Color.red;
if (rightAttackPosition != null)
Gizmos.DrawWireCube(rightAttackPosition.transform.position, currentWeaponData.boxSize);
if (leftAttackPosition != null)
Gizmos.DrawWireCube(leftAttackPosition.transform.position, currentWeaponData.boxSize);
if (upAttackPosition != null)
Gizmos.DrawWireCube(upAttackPosition.transform.position, currentWeaponData.boxSize);
if (downAttackPosition != null)
Gizmos.DrawWireCube(downAttackPosition.transform.position, currentWeaponData.boxSize);
}
public void StartComboWindow()
{
if (ComboCoroutine == null)
{
ComboCoroutine = StartCoroutine(ReadSecondAttackInput());
}
}
private IEnumerator ReadSecondAttackInput()
{
float timer = attackComboDataRef.inputWindow;
while (timer > 0f)
{
if (Input.GetMouseButtonDown(0))
{
ComboCoroutine = null;
attackComboDataRef.startSecondAttack = true;
yield break;
}
timer -= Time.deltaTime;
yield return null;
}
ComboCoroutine = null;
}
public void AddDamageToSecondAttack()
{
runtimeAttackDamage = attackComboDataRef.comboDamage;
Debug.Log("Schaden für den zweiten Angriff gesetzt: " + runtimeAttackDamage);
}
public void ResetDamageToFirstAttack()
{
runtimeAttackDamage = originalAttackDamage;
Debug.Log("Schaden für den ersten Angriff zurückgesetzt: " + runtimeAttackDamage);
}
private void OnDrawGizmosSelected()
{
if (currentWeaponData == null)
return;
if (showHitboxes && activeAttackPosition != null)
{
Gizmos.color = Color.green;
Gizmos.DrawWireCube(activeAttackPosition.transform.position, currentWeaponData.boxSize);
}
if (showHitboxes && RotateSecondAttack != null)
{
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(RotateSecondAttack.transform.position, currentWeaponData.boxSize);
}
}
}
My Questions & Issues
- Code Organization & Extensibility:
My current implementation is a monolithic script, and I’m finding it increasingly difficult to manage. I plan to add more combos in the future.
- Do you separate the code into multiple classes for each combo or use a specific design pattern (like State or Command) to handle attacks?
- What strategies or best practices do you recommend for structuring a combo system to ensure it remains maintainable and extensible?
- Collider Rotation Issue:
I’m having an issue where the polygon collider for the second attack is always aligned to the right—even when the player is facing left.
- Do you have a mechanism to rotate the collider based on the player’s facing direction?
- What are the best practices to ensure that the collider adjusts dynamically when the player turns?
- General Best Practices:
- What are some good habits or best practices you follow regarding Animation Events, combo systems, and collision checks?
- How do you manage the interaction between animations and gameplay logic so that complex attacks don’t become overly intertwined and difficult to maintain?
Any advice, best practices, or experiences you could share would be greatly appreciated. I’ve been at this for a long time and keep hitting similar issues, and I’m eager to learn how to build a robust and scalable combo system.
Thanks in advance for your help!
Best regards,
Azim