Forum begins after the advertisement:

 


[Part 15] Need help making a Santa Water clone

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [Part 15] Need help making a Santa Water clone

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #17922
    Jukers
    Level 9
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    Ive been trying for about two hours now, reading the scripts and trying to understand it with AI help, but i cant seem to understand how to implement a Santa Water-like weapon.

    Apparently doesn’t work the same way as Whip Weapon for example, because the Aura weapons work as some other way Whip Weapons inherits from Projectile but there is no GarlicWeapon, by my understand its split between Aura and AuraWeapon, which use functions from Weapon… Im currently in part 23. If you could help me, that would be massive!

    #17929
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi Jukers, for Santa Water, you can think of it as a weapon that constantly spawns new prefabs that hold an Aura component. This Aura will be configured to deal damage equivalent to what you want your Santa Water to deal.

    You will then need a custom weapon script that constantly spawns these prefabs around you. You can create a projectile weapon that homes in on targets, kind of like the Fire Wand, but when colliding with an enemy, instead of dealing damage, it will create the aura prefab for the Santa Water.

    Let me know if this helps.

    #17939
    Jukers
    Level 9
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    my idea is to use something similar to the Lighning weapon which picks a random enemy the problem is that when i instantiate the prefab (dont know if im doing the right way) it always stay in the player following it, tried to remove it as a child of the player but didnt succeed.

    I managed to do this with some AI help, SantaWaterWeapon.cs

    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// Dispara uma aura (efeito de Holy Water) sobre inimigos selecionados,
    /// instanciando o prefab de Aura que aplica dano ao longo do tempo.
    /// Controla o lifespan e o cooldown antes de permitir nova instância.
    /// </summary>
    public class SantaWaterWeapon : AuraWeapon
    {
        private List<EnemyStats> allSelectedEnemies = new List<EnemyStats>();
        private int currentAttackCount;
        private float currentAttackInterval;
        private Aura activeAura;
    
        void Awake()
        {
            // Número de ataques base vindo de WeaponData
            currentAttackCount = ((WeaponData)data).baseStats.number;
        }
    
        protected override bool Attack(int attackCount = 1)
        {
            // Usa currentStats (Weapon.Stats) para acessar prefab e parâmetros
            var stats = currentStats;
    
            // 1) Verifica se o prefab de Aura está definido
            if (stats.auraPrefab == null)
            {
                Debug.LogWarning($"Aura prefab não definido para {name}");
                ActivateCooldown(true);
                return false;
            }
    
            // 2) Verifica cooldown geral e se já existe uma aura ativa
            if (!CanAttack() || activeAura != null)
                return false;
    
            // 3) Na primeira ativação do ciclo, popula lista e reseta contagem
            if (currentCooldown <= 0)
            {
                allSelectedEnemies = new List<EnemyStats>(FindObjectsOfType<EnemyStats>());
                ActivateCooldown();
                currentAttackCount = attackCount;
            }
    
            // 4) Seleciona um inimigo aleatório para aplicar a aura
            EnemyStats target = PickEnemy();
            if (target)
            {
                // Calcula offset vertical para spawn acima da cabeça
                SpriteRenderer sr = target.GetComponent<SpriteRenderer>();
                float offsetY = sr ? sr.bounds.extents.y : 0.5f;
                Vector3 spawnPos = target.transform.position + Vector3.up * offsetY;
    
                Debug.Log($"Instanciando aura em {spawnPos} para {target.name}");
    
                // Instancia o componente Aura (prefab) em spawnPos
                Aura auraComp = Instantiate(stats.auraPrefab, spawnPos, Quaternion.identity);
                auraComp.transform.SetParent(target.transform);
    
                // Configura o script Aura para usar esta arma
                auraComp.SetWeapon(this);
    
                // Ajusta o raio do Collider de acordo com a área da arma
                CircleCollider2D cc = auraComp.GetComponent<CircleCollider2D>();
                if (cc)
                    cc.radius = GetArea();
    
                // Guarda referência e controla duração
                activeAura = auraComp;
                float duration = stats.lifespan;
                // Destrói o GameObject associado à aura após duração
                Destroy(auraComp.gameObject, duration);
                StartCoroutine(ClearActiveAura(duration));
            }
    
            // Efeito proc no jogador, se existir
            if (stats.procEffect)
                Destroy(Instantiate(stats.procEffect, owner.transform), 5f);
    
            // Ajusta contagem de ataques remanescentes
            if (attackCount > 0)
            {
                currentAttackCount = attackCount - 1;
                currentAttackInterval = stats.projectileInterval;
            }
    
            return true;
        }
    
        private IEnumerator ClearActiveAura(float delay)
        {
            yield return new WaitForSeconds(delay);
            activeAura = null;
        }
    
        /// <summary>
        /// Seleciona aleatoriamente um EnemyStats visível, removendo-o da lista.
        /// </summary>
        private EnemyStats PickEnemy()
        {
            EnemyStats target = null;
            while (!target && allSelectedEnemies.Count > 0)
            {
                int idx = Random.Range(0, allSelectedEnemies.Count);
                target = allSelectedEnemies[idx];
                if (!target)
                {
                    allSelectedEnemies.RemoveAt(idx);
                    continue;
                }
                Renderer r = target.GetComponent<Renderer>();
                if (!r || !r.isVisible)
                {
                    allSelectedEnemies.Remove(target);
                    target = null;
                    continue;
                }
            }
            if (target)
                allSelectedEnemies.Remove(target);
            return target;
        }
    }
    

    Current beheaviour: spawns around the player just like the default Aura weapon, doesn’t disappear when lifespan expires and dont re-appear when

    #17956
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    Let me have one of the guys working on the Vampire Survivors series help you with this next week, after the Easter weekend.

    We’ll write a guide and post it on this forum. Keep you updated. We should be able to get it up around Tuesday next week.

    #17957
    Jukers
    Level 9
    Bronze Supporter (Patron)
    Helpful?
    Up
    0
    ::

    Don’t worry about it! I would love some help but i get that you are busy and all that stuff! thanks for replying to me.

    I’m doing some work myself, i managed to assemble this “monster”

    
    
    // SantaWaterWeapon.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor.Analytics;
    using UnityEngine;
    
    /// <summary>
    /// Dispara uma aura (efeito de Holy Water) sobre inimigos selecionados,
    /// instanciando o prefab de Aura que aplica dano ao longo do tempo.
    /// Controla o lifespan e o cooldown antes de permitir nova instância.
    /// </summary>
    public class SantaWaterWeapon : AuraWeapon
    {
        private List<EnemyStats> allSelectedEnemies = new List<EnemyStats>();
        private int currentAttackCount;
        private float currentAttackInterval;
        private Aura activeAura;
    
        void Awake()
        {
            // Número de ataques base vindo de WeaponData
            currentAttackCount = ((WeaponData)data).baseStats.number;
        }
    
        protected override bool Attack(int attackCount = 1)
        {
            // Usa currentStats (Weapon.Stats) para acessar prefab e parâmetros
            var stats = currentStats;
    
            // 1) Verifica se o prefab de Aura está definido
            if (stats.auraPrefab == null)
            {
                Debug.LogWarning($"Aura prefab não definido para {name}");
                ActivateCooldown(true);
                return false;
            }
    
            // 2) Verifica cooldown geral e se já existe uma aura ativa
            if (!CanAttack() || activeAura != null)
                return false;
    
            // 3) Na primeira ativação do ciclo, popula lista e reseta contagem
            if (currentCooldown <= 0)
            {
                allSelectedEnemies = new List<EnemyStats>(FindObjectsOfType<EnemyStats>());
                ActivateCooldown();
                currentAttackCount = attackCount;
            }
    
            // 4) Seleciona um inimigo aleatório para aplicar a aura
            EnemyStats target = PickEnemy();
            if (target)
            {
                // Calcula offset vertical para spawn acima da cabeça
                SpriteRenderer sr = target.GetComponent<SpriteRenderer>();
                float offsetY = sr ? sr.bounds.extents.y : 0.5f;
                Vector3 spawnPos = target.transform.position + Vector3.up * offsetY;
    
                Debug.Log($"Instanciando aura em {spawnPos} para {target.name}");
    
                // Instancia o componente Aura (prefab) em spawnPos
                Aura auraComp = Instantiate(stats.auraPrefab, spawnPos, Quaternion.identity);
                auraComp.transform.SetParent(target.transform);
    
                // Configura o script Aura para usar esta arma
                auraComp.SetWeapon(this);
    
                // Ajusta o raio do Collider de acordo com a área da arma
                CircleCollider2D cc = auraComp.GetComponent<CircleCollider2D>();
                if (cc)
                    cc.radius = GetArea();
    
                // Guarda referência e controla duração
                activeAura = auraComp;
                Debug.Log($"Lifespan do objeto: {stats.lifespan}");
                float lifespan = stats.lifespan;
    
                // Destrói o GameObject associado à aura após duração
                Destroy(auraComp.gameObject, lifespan);
                StartCoroutine(ClearActiveAura(lifespan));
            }
    
            // Efeito proc no jogador, se existir
            if (stats.procEffect)
                Destroy(Instantiate(stats.procEffect, owner.transform), 5f);
    
            // Ajusta contagem de ataques remanescentes
            if (attackCount > 0)
            {
                currentAttackCount = attackCount - 1;
                currentAttackInterval = stats.projectileInterval;
            }
    
            return true;
        }
    
        private IEnumerator ClearActiveAura(float delay)
        {
            yield return new WaitForSeconds(delay);
            activeAura = null;
        }
    
        /// <summary>
        /// Seleciona aleatoriamente um EnemyStats visível, removendo-o da lista.
        /// </summary>
        private EnemyStats PickEnemy()
        {
            EnemyStats target = null;
            while (!target && allSelectedEnemies.Count > 0)
            {
                int idx = Random.Range(0, allSelectedEnemies.Count);
                target = allSelectedEnemies[idx];
                if (!target)
                {
                    allSelectedEnemies.RemoveAt(idx);
                    continue;
                }
                Renderer r = target.GetComponent<Renderer>();
                if (!r || !r.isVisible)
                {
                    allSelectedEnemies.Remove(target);
                    target = null;
                    continue;
                }
            }
            if (target)
                allSelectedEnemies.Remove(target);
            return target;
        }
    }
    
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class AuraWeapon : Weapon
    {
        protected Aura currentAura;
        protected bool isSpawning = false;
        protected bool SantaWaterBeheaviour = true;
    
        List<EnemyStats> allSelectedEnemies = new List<EnemyStats>();
    
        protected override void Update()
        {
            if (SantaWaterBeheaviour && !isSpawning)
            {
                StartCoroutine(SpawnAurasCoroutine());
                //Debug.Log("Tentando iniciar Coroutine");
            }
        }
    
        public override void OnEquip()
        {
            if (!SantaWaterBeheaviour)
            {
                // Garlic-style aura
                if (currentStats.auraPrefab)
                {
                    if (currentAura) Destroy(currentAura);
                    currentAura = Instantiate(currentStats.auraPrefab, transform);
                    currentAura.weapon = this;
                    currentAura.owner = owner;
    
                    float area = GetArea();
                    currentAura.transform.localScale = new Vector3(area, area, area);
                }
            }
        }
    
        public override void OnUnequip()
        {
            if (!SantaWaterBeheaviour)
            {
                if (currentAura) Destroy(currentAura);
            }
        }
    
        public override bool DoLevelUp()
        {
            if (!base.DoLevelUp()) return false;
    
            if (!SantaWaterBeheaviour)
            {
                if (currentAura)
                {
                    float area = GetArea();
                    currentAura.transform.localScale = new Vector3(area, area, area);
                }
            }
    
            return true;
        }
    
        private IEnumerator SpawnAurasCoroutine()
        {
            //Debug.Log("Coroutine iniciada");
            isSpawning = true;
    
            int count = currentStats.number;
            float interval = currentStats.projectileInterval;
    
            // Refresh enemy list
            allSelectedEnemies = new List<EnemyStats>(FindObjectsOfType<EnemyStats>());
    
            for (int i = 0; i < count; i++)
            {
                EnemyStats target = PickEnemy();
    
                if (target)
                {
                    SpawnAuraOnTarget(target);
                }
    
                if (i < count - 1)
                    yield return new WaitForSeconds(interval);
            }
    
            yield return new WaitForSeconds(currentStats.cooldown);
            isSpawning = false;
        }
    
        private void SpawnAuraOnTarget(EnemyStats target)
        {
            if (!currentStats.auraPrefab || !target) return;
    
            Vector2 spawnPosition = target.transform.position;
    
            Aura aura = Instantiate(currentStats.auraPrefab, spawnPosition, Quaternion.identity);
            aura.weapon = this;
            aura.owner = owner;
    
    
            float area = GetArea();
            aura.transform.localScale = new Vector3(area, area, area);
            Destroy(aura.gameObject, currentStats.lifespan);
        }
    
        // Randomly picks a visible enemy on screen
        private EnemyStats PickEnemy()
        {
            EnemyStats target = null;
    
            while (!target && allSelectedEnemies.Count > 0)
            {
                int idx = Random.Range(0, allSelectedEnemies.Count);
                target = allSelectedEnemies[idx];
    
                if (!target)
                {
                    allSelectedEnemies.RemoveAt(idx);
                    continue;
                }
    
                Renderer r = target.GetComponent<Renderer>();
                if (!r || !r.isVisible)
                {
                    allSelectedEnemies.Remove(target);
                    target = null;
                    continue;
                }
            }
    
            if (target) allSelectedEnemies.Remove(target);
    
            return target;
        }
    }
    

    Im manually switching up via script the beheaviour on the variable SantaWaterBeheaviour on AuraWeapon, it is currently working as intended but i need to understand it fully, another thing that i dont know why is that the script santawaterweapon starts by being disabled, so i did some tweaks on playerinventory to make sure that the scripts that spawn via weapon controller are active

    
    //REST OF THE CODE ABOVE
    
     if (weaponType != null)
            {
                // Spawn the weapon controller game object
                GameObject go = new GameObject(data.baseStats.name + " Controller");
                Weapon spawnedWeapon = (Weapon)go.AddComponent(weaponType);
                <strong>spawnedWeapon.enabled = true; //Make sure its script is enabled (santawater error)</strong>
                spawnedWeapon.transform.SetParent(transform); 
                spawnedWeapon.transform.localPosition = Vector2.zero;
                spawnedWeapon.Initialise(data);
                spawnedWeapon.OnEquip();
    
    //REST OF THE CODE BELOW
    

    Theres a lot of stuff remaining to change, like aplying buffs and stats, but thats what i managed to do in about 4-5 hours Helped me understand the script developed by the team a lot better, there are still a lot of things that i dont completely understand, but thats part of the process!

    #17960
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @Jukers no problem at all. One tip about the code if you don’t mind: you shouldn’t be modifying the AuraWeapon script if you can help it. Instead of introducing the SantaWeaponBehaviour variable, you should instead do this (using Update() as an example).

    public class SantaWaterWeapon : AuraWeapon
    {
         ...
         public override void Update() {
             base.Update(); // Calls Update() on AuraWeapon().
             StartCoroutine(SpawnAurasCoroutine()); // Your additional functionality specific to SantaWaterWeapon.
         }
    }

    Otherwise, when you make more weapons, you will find yourself needing to add more and more specific behaviours to the parent classes, which will not be sustainable over the long term.

    As for the actual functionality, instead of subclassing AuraWeapon, an easy way to do it will be to subclass LightningWeapon instead. I would then override the Attack() function, and remove the DamageArea().

    protected override bool Attack(int attackCount = 1)
    {
        // If no projectile prefab is assigned, leave a warning message.
        if (!currentStats.hitEffect)
        {
            Debug.LogWarning(string.Format("Hit effect prefab has not been set for {0}", name));
            currentCooldown = currentStats.cooldown;
            return false;
        }
    
        // If there is no projectile assigned, set the weapon on cooldown.
        if (!CanAttack()) return false;
    
        // If the cooldown is less than 0, this is the first firing of the weapon.
        // Refresh the array of selected enemies.
        if (currentCooldown <= 0)
        {
            allSelectedEnemies = new List<EnemyStats>(FindObjectsOfType<EnemyStats>());
            currentCooldown += currentStats.cooldown;
            currentAttackCount = attackCount;
        }
    
        // Find an enemy in the map to strike with lightning.
        EnemyStats target = PickEnemy();
        if(target)
        {
            DamageArea(target.transform.position, currentStats.area, GetDamage());
    
            Instantiate(currentStats.hitEffect, target.transform.position, Quaternion.identity);
        }
    
        // If we have more than 1 attack count.
        if(attackCount > 0)
        {
            currentAttackCount = attackCount - 1;
            currentAttackInterval = currentStats.projectileInterval;
        }
    
        return true;
    }

    For the damage, I will make a new hit effect for the Santa Water, and attach an Aura component (not AuraWeapon!) to do the area damage over time. You also have to make sure the hit effect stays for a longer duration (instead of expiring after a short amount of time).

    I hope this makes sense!

    #17962
    Jukers
    Level 9
    Bronze Supporter (Patron)
    Helpful?
    Up
    1
    ::

    I didnt exactly followed up that path because im not that experieced… but i managed to do great, i think

    AuraWeapon.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class AuraWeapon : Weapon
    {
        protected Aura currentAura;
        protected bool isSpawning = false;
        private CharacterData.Stats actualStats;
        protected bool SantaWaterBeheaviour;
    
        private bool isInitialized = false;
        List<EnemyStats> allSelectedEnemies = new List<EnemyStats>();
    
        private void Init()
        {
            if (isInitialized) return;
    
            SantaWaterBeheaviour = currentStats.isSantaWater;
            Debug.Log(currentStats.isSantaWater);
    
            isInitialized = true;
        }
    
        protected override void Update()
        {
            if (!isInitialized) 
             Init(); 
    
            if(!isInitialized) return;
    
            if (SantaWaterBeheaviour && !isSpawning)
            {
                StartCoroutine(SpawnAurasCoroutine());
            }
        }
    
        public override void OnEquip()
        {
            Init();
            if (!SantaWaterBeheaviour)
            {
                // Garlic-style aura
                if (currentStats.auraPrefab)
                {
                    if (currentAura) Destroy(currentAura);
                    currentAura = Instantiate(currentStats.auraPrefab, transform);
                    currentAura.weapon = this;
                    currentAura.owner = owner;
    
                    float area = GetArea();
                    currentAura.transform.localScale = new Vector3(area, area, area);
                }
            }
        }
    
        public override void OnUnequip()
        {
            if (!SantaWaterBeheaviour)
            {
                if (currentAura) Destroy(currentAura);
            }
        }
    
        public override bool DoLevelUp()
        {
            if (!base.DoLevelUp()) return false;
    
            if (!SantaWaterBeheaviour)
            {
                if (currentAura)
                {
                    float area = GetArea();
                    currentAura.transform.localScale = new Vector3(area, area, area);
                }
            }
    
            return true;
        }
    
        private IEnumerator SpawnAurasCoroutine()
        {
            isSpawning = true;
    
            int count = currentStats.number + Owner.Stats.amount;
            float interval = currentStats.projectileInterval;
    
            allSelectedEnemies = new List<EnemyStats>(FindObjectsOfType<EnemyStats>());
    
            for (int i = 0; i < count; i++)
            {
                EnemyStats target = PickClosestFreeEnemy();
    
                if (target)
                {
                    SpawnAuraOnTarget(target);
                }
    
                if (i < count - 1)
                    yield return new WaitForSeconds(interval);
            }
    
            yield return new WaitForSeconds(currentStats.cooldown * Owner.Stats.cooldown);
            isSpawning = false;
        }
    
        private void SpawnAuraOnTarget(EnemyStats target)
        {
            if (!currentStats.auraPrefab || !target) return;
    
            Vector2 spawnPosition = target.transform.position;
    
            Aura aura = Instantiate(currentStats.auraPrefab, spawnPosition, Quaternion.identity);
            aura.weapon = this;
            aura.owner = owner;
    
            float area = GetArea();
            aura.transform.localScale = new Vector3(area, area, area);
            Destroy(aura.gameObject, currentStats.lifespan * Owner.Stats.duration);
        }
    
        private EnemyStats PickClosestFreeEnemy()
        {
            Vector2 playerPosition = owner.transform.position;
            float minDistanceBetweenAuras = 1.5f; // Ajuste conforme o tamanho da aura
    
            List<EnemyStats> sortedEnemies = new List<EnemyStats>(allSelectedEnemies);
            sortedEnemies.Sort((a, b) =>
                Vector2.Distance(a.transform.position, playerPosition)
                .CompareTo(Vector2.Distance(b.transform.position, playerPosition))
            );
    
            foreach (var enemy in sortedEnemies)
            {
                if (!enemy) continue;
    
                Renderer r = enemy.GetComponent<Renderer>();
                if (!r || !r.isVisible) continue;
    
                Vector2 pos = enemy.transform.position;
                if (!IsAuraNear(pos, minDistanceBetweenAuras))
                {
                    allSelectedEnemies.Remove(enemy);
                    return enemy;
                }
            }
    
            return null;
        }
    
        private bool IsAuraNear(Vector2 position, float minDistance)
        {
            Aura[] existingAuras = FindObjectsOfType<Aura>();
            foreach (var aura in existingAuras)
            {
                if (Vector2.Distance(aura.transform.position, position) < minDistance)
                {
                    return true;
                }
            }
            return false;
        }
    }
    

    Changed the beheaviour to instantiate on the closest enemie and verify if there is an aura at that place, if there is, it will try to get another enemie. Made like this because i think makes more sense, the player will have a feeling that the item that it haves is being used

    in order to make those changes you need to have a empty SantaWaterWeapon Script

    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor.Analytics;
    using UnityEngine;
    using UnityEngine.Rendering.Universal;
    
    public class SantaWaterWeapon : AuraWeapon
    {
    
    }

    maybe in the future im going to port the functionality to there, to makes things clear, but for know, this works.

    has upvoted this post.
Viewing 7 posts - 1 through 7 (of 7 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: