Given a ScriptableObject class ‘Attack’, how do i properly apply composition?
(All code below)
I have a class ‘ProjectileAttack’ that fires a projectile and runs on a cooldown system. Also below is a MultiProjectileAttack class which does the same except fires multiple projectiles per shot. If i eventually have hundreds of these attacks, how do i properly apply composition?
For example, i want to add a ‘Charge’ system, where a weapon holds a number of charges that can be expended, and regenerated. I now have to write the charge ScriptableObject script for each attack type. So for single projectiles i have to write a charge script that inherits from ‘ProjectileAttack’ and again for ‘MultiProjectileAttack’. The charge script would have no difference for either ‘ProjectileAttack’ or MultiProjectileAttack’ but i would nonetheless have to create two scripts for each attack type. If i had more attack types i would have to do the same. How do i apply the principles of composition here, ie. one script for all the attack types.
Now granted i could have all the attacks work on the charge system and simply set the single charge weapons max charges to 1 (it would look exactly the same) but that’s not my point. I want to be able to cleaner apply composition here.
This isn’t just for the charge script either, i have plenty of other scripts i want to add.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Attack : ScriptableObject
{
public abstract bool CanAttack(GameObject attacker);
public abstract void OnAttack(GameObject attacker);
public float AS = 1f;
public bool offCD;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Projectile Attack", menuName = "Attack/Projectile")]
public class ProjectileAttack : Attack
{
public GameObject projectilePrefab;
public float force;
public override bool CanAttack(GameObject attacker)
{
return offCD;
}
public override void OnAttack(GameObject attacker){
if(offCD){
Cooldown(attacker);
Attack(attacker);
}
}
public void Cooldown(GameObject attacker){
offCD = false;
attacker.GetComponent<Cooldowns>().WaitAndDo(AS, () => offCD = true);
}
public void Attack(GameObject attacker)
{
Vector3 sp = Camera.main.WorldToScreenPoint(attacker.transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(projectilePrefab,
attacker.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * force);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Triple Projectile Attack", menuName = "Attack/MultiProjectile")]
public class MultiProjectileAttack : ProjectileAttack
{
public int shotsPerAttack;
public float waitTimeBetweenShots;
protected IEnumerator firingThreeShots;
public override void OnAttack(GameObject attacker)
{
if(offCD){
Cooldown(attacker);
firingThreeShots = FireShots(attacker);
CoroutineBypass.instance.StartCoroutine(firingThreeShots);
}
}
protected IEnumerator FireShots(GameObject attacker)
{
for(int i = 0; i < shotsPerAttack; i++)
{
Attack(attacker);
yield return new WaitForSeconds(waitTimeBetweenShots);
}
}
}