{"id":652,"date":"2023-12-10T12:43:53","date_gmt":"2023-12-10T12:43:53","guid":{"rendered":"https:\/\/timohonig.nl\/?p=652"},"modified":"2024-10-09T06:55:23","modified_gmt":"2024-10-09T06:55:23","slug":"game-3","status":"publish","type":"post","link":"https:\/\/timohonig.nl\/index.php\/2023\/12\/10\/game-3\/","title":{"rendered":"Roman Revenant"},"content":{"rendered":"\n<p><strong>About<\/strong><br>Roman Revenant is a top down singleplayer game where you play as a Roman warrior defending the city of Rome from invading monsters. As you progress, you unlock a variety of weapons, each with unique attributes and combat styles, allowing you to customize your strategy. Additionally, you gain special abilities tailored to each weapon.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Roman Revenant Trailer\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/rvz6166Kvz4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><strong>Project Info<\/strong><br>Group Size: 7<br>Project Length: 8 Weeks<br>Project Date: April 2024<br>Engine &amp; Tools: Unity, Visual Studio Code &amp; Trello<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-text-align-center\"><strong>Summary<\/strong><br><a href=\"#PlayerCombat\" data-type=\"internal\" data-id=\"#PlayerCombat\">Player Combat<\/a><br><a href=\"#PlayerAbilities\" data-type=\"internal\" data-id=\"#PlayerAbilities\">Player Abilities<\/a><br><a href=\"#PlayerMovement\" data-type=\"internal\" data-id=\"#PlayerMovement\">Player Movement<\/a><br><a href=\"#WaveSystem\" data-type=\"internal\" data-id=\"#WaveSystem\">Enemy Wave Spawning<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Player Combat &amp; Ability System<\/strong>                                                                           In this project, I was responsible for designing all aspects related to the player, including movement and combat. One of the key components of the player was the combat system, which I divided into two separate parts: Player Combat and Player Abilities.<\/p>\n\n\n\n<p id=\"PlayerCombat\">For the combat, I chose a simple yet effective approach. I created a system where the player has linked box colliders used as hitboxes. Different weapons have different sizes and corresponding hitboxes. The sizes are based on the appearance of the weapon; for example, the spear has a long but slim hitbox, while the battleaxe has a short but wide hitbox.<\/p>\n\n\n\n<p>In this system, I used references to hitboxes for light and heavy attacks, each with its own hitbox and animation. When an attack button is pressed, the hitboxes for that attack are enabled, allowing enemies to take damage.<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Partial Player Combat Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class PlayerCombat : MonoBehaviour\n{\n    &#91;Header(\"References\")]\n    private PlayerController playerController;\n    private WeaponCollision weaponCollision;\n    private Animator animator;\n\n    &#91;Header(\"Animations\")]\n    &#91;SerializeField] private float animationDuration;\n\n    &#91;Header(\"Input\")]\n    &#91;SerializeField] private KeyCode lightAttack;\n    &#91;SerializeField] private KeyCode heavyAttack;\n\n    private void Start()\n    {\n        animator = GetComponent&lt;Animator&gt;();\n        playerController = GetComponent&lt;PlayerController&gt;();\n\n        weaponCollision = GetComponentInChildren&lt;WeaponCollision&gt;();\n    }\n\n    private void Update()\n    {\n        LightAttack();\n        HeavyAttack();\n    }\n\n    private void LightAttack()\n    {\n        if (Input.GetKeyDown(lightAttack))\n        {\n            AnimatorClipInfo&#91;] clipInfo = animator.GetCurrentAnimatorClipInfo(0);\n\n            if (clipInfo.Length &gt; 0)\n            {\n                StartAttack(0, \"LightAttack\");\n            }\n        }\n    }\n\n    private void HeavyAttack()\n    {\n        if (Input.GetKeyDown(heavyAttack))\n        {\n            AnimatorClipInfo&#91;] clipInfo = animator.GetCurrentAnimatorClipInfo(0);\n\n            if (clipInfo.Length &gt; 0)\n            {\n                StartAttack(20, \"HeavyAttack\");\n            }\n        }\n    }\n\n    private void StartAttack(float staminaCostForAttack, string attackType)\n    {\n        animator.SetTrigger(attackType);\n        playerController.DrainStaminaOnAttack(staminaCostForAttack);\n\n        if (attackType == \"LightAttack\")\n        {\n            weaponCollision.EnableLightHitbox();\n        }\n        else if (attackType == \"HeavyAttack\")\n        {\n            weaponCollision.EnableHeavyHitbox();\n        }\n\n        StartCoroutine(WaitForAttack());\n    }\n\n    private IEnumerator WaitForAttack()\n    {\n        playerController.CanMove = false;\n        yield return new WaitForSeconds(animationDuration);\n        ResetAttack();\n    }\n\n    private void ResetAttack()\n    {\n        animator.ResetTrigger(\"HeavyAttack\");\n        animator.ResetTrigger(\"LightAttack\");\n        weaponCollision.DisableHitboxes();\n        playerController.CanMove = true;\n    }\n}\n<\/code><\/pre>\n<\/details>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6a07a0661bdcc&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6a07a0661bdcc\" class=\"wp-block-image size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1436\" height=\"896\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerCombat.gif\" alt=\"\" class=\"wp-image-726\"\/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p id=\"PlayerAbilities\"><br>For abilities, I decided to base them of the players current weapon. The <code>PlayerUseAbilities<\/code> class contains a dictionary of abilities linked to weapon types and tracks cooldowns to manage ability usage. The InitializeAbilityDictionary method sets up abilities like healing for the sword, special attacks for the axe, defensive moves for the shield, and ranged attacks for the spear. The <code>UseAbility<\/code> method checks for cooldowns and stamina before activating an ability. The script includes an abstract <code>Ability<\/code> class and specific implementations such as SwordAbility, AxeAbility, ShieldAbility, and SpearAbility, each with unique functions and animations. <\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Partial Player Ability Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class PlayerUseAbilities : MonoBehaviour\n{\n    private PlayerController playerController;\n    private ActivateWeapon activateWeapon;\n\n    private Dictionary&lt;string, Ability&gt; abilityDictionary;\n    private Dictionary&lt;string, float&gt; abilityCooldowns;\n\n    private string currentWeapon;\n\n    &#91;SerializeField] private GameObject spearThrowPrefab;\n    &#91;SerializeField] private Transform spearThrowOrigin;\n\n    private void Start()\n    {\n        InitializeAbilityDictionary();\n        playerController = GetComponent&lt;PlayerController&gt;();\n\n        activateWeapon = GetComponent&lt;ActivateWeapon&gt;();\n\n        abilityCooldowns = new Dictionary&lt;string, float&gt;();\n        currentWeapon = activateWeapon.HoldingWeapon.ToString();\n    }\n\n    private void InitializeAbilityDictionary()\n    {\n        abilityDictionary = new Dictionary&lt;string, Ability&gt;\n        {\n            { \"Sword\", new SwordAbility(50, 15f, 30f) }, \/\/ HealAmount Cooldown Cost\n            { \"Axe\", new AxeAbility(12f, 30f) }, \/\/ Cooldown Cost\n            { \"Shield\", new ShieldAbility(16f, 30f) }, \/\/ Cooldown Cost\n            { \"Spear\", new SpearAbility(7f, 15f, spearThrowPrefab, spearThrowOrigin) } \/\/ Cooldown Cost WeaponPrefab Origin\n        };\n    }\n\n    private void Update()\n    {\n        if (Input.GetKeyDown(KeyCode.Q))\n        {\n            UseAbility(currentWeapon);\n        }\n    }\n\n    private void UseAbility(string weaponType)\n    {\n        if (abilityDictionary.ContainsKey(weaponType))\n        {\n            Ability ability = abilityDictionary&#91;weaponType];\n            float lastUsedTime;\n\n            if (abilityCooldowns.TryGetValue(weaponType, out lastUsedTime))\n            {\n                if (Time.time &lt; lastUsedTime + ability.Cooldown)\n                {\n                    Debug.Log(\"Ability on Cooldown\");\n                    return;\n                }\n            }\n\n            if (playerController.Stamina &lt; ability.StaminaCost)\n            {\n                Debug.Log(\"Not enough stamina to use Ability\");\n                return;\n            }\n\n            ability.Use(gameObject);\n\n            abilityCooldowns&#91;weaponType] = Time.time;\n            playerController.DrainStamina(ability.StaminaCost);\n\n            if (ability is AxeAbility axeAbility)\n            {\n                StartCoroutine(axeAbility.WaitForAbilityCoroutine(gameObject));\n            }\n            else if (ability is ShieldAbility shieldAbility)\n            {\n                StartCoroutine(shieldAbility.WaitForAbilityCoroutine(gameObject));\n                StartCoroutine(shieldAbility.RollCoroutine());\n            }\n            else if (ability is SpearAbility spearAbility)\n            {\n                StartCoroutine(spearAbility.WaitForSpearAbility(gameObject));\n            }\n        }\n        else\n        {\n            Debug.LogWarning(\"No Ability found for weapon type: \" + weaponType);\n        }\n    }\n\n    private abstract class Ability\n    {\n        public float Cooldown { get; private set; }\n        public float StaminaCost { get; private set; }\n\n        protected Ability(float cooldown, float staminaCost)\n        {\n            Cooldown = cooldown;\n            StaminaCost = staminaCost;\n        }\n\n        public abstract void Use(GameObject user);\n    }\n\n    private class SwordAbility : Ability\n    {\n        private int healAmount;\n\n        public SwordAbility(int healAmount, float cooldown, float staminaCost)\n            : base(cooldown, staminaCost)\n        {\n            this.healAmount = healAmount;\n        }\n\n        public override void Use(GameObject user)\n        {\n            PlayerHealth playerHealth = user.GetComponent&lt;PlayerHealth&gt;();\n            if (playerHealth != null)\n            {\n                playerHealth.Heal(healAmount);\n            }\n        }\n    }\n\n    private class AxeAbility : Ability\n    {\n        public AxeAbility(float cooldown, float staminaCost)\n            : base(cooldown, staminaCost) { }\n\n        private WeaponCollision weaponCollision;\n        private Animator animator;\n\n        public override void Use(GameObject user)\n        {\n            weaponCollision = user.GetComponentInChildren&lt;WeaponCollision&gt;();\n            animator = user.GetComponent&lt;Animator&gt;();\n            animator.SetTrigger(\"AbilityAttack\");\n            weaponCollision.EnableAbilityHitbox();\n        }\n\n        public IEnumerator WaitForAbilityCoroutine(GameObject user)\n        {\n            yield return new WaitForSeconds(1.6f);\n            weaponCollision.DisableHitboxes();\n        }\n    }\n\n    private class ShieldAbility : Ability\n    {\n        private CharacterController characterController;\n        private WeaponCollision weaponCollision;\n        private Animator animator;\n        private float rollDistance = 8;\n        private float rollSpeed = 20;\n        private Vector3 movement;\n\n        public ShieldAbility(float cooldown, float staminaCost)\n            : base(cooldown, staminaCost) { }\n\n        public override void Use(GameObject user)\n        {\n            weaponCollision = user.GetComponentInChildren&lt;WeaponCollision&gt;();\n            characterController = user.GetComponent&lt;CharacterController&gt;();\n            animator = user.GetComponent&lt;Animator&gt;();\n\n            animator.SetTrigger(\"AbilityAttack\");\n\n            weaponCollision.EnableAbilityHitbox();\n\n            float horizontal = Input.GetAxis(\"Horizontal\");\n            float vertical = Input.GetAxis(\"Vertical\");\n\n            movement.Set(horizontal, 0f, vertical);\n        }\n\n        public IEnumerator RollCoroutine()\n        {\n            Vector3 rollDirection = movement.normalized * rollDistance;\n            float rollTime = rollDistance \/ rollSpeed;\n\n            float elapsedTime = 0f;\n            while (elapsedTime &lt; rollTime)\n            {\n                characterController.Move(rollDirection * Time.deltaTime \/ rollTime);\n                elapsedTime += Time.deltaTime;\n                yield return null;\n            }\n\n            characterController.Move(rollDirection * (rollTime - elapsedTime) \/ rollTime);\n        }\n\n        public IEnumerator WaitForAbilityCoroutine(GameObject user)\n        {\n            yield return new WaitForSeconds(0.8f);\n            weaponCollision.DisableHitboxes();\n        }\n    }\n\n\n    private class SpearAbility : Ability\n    {\n        private GameObject spearThrowPrefab;\n        private Transform spearThrowOrigin;\n        private Animator animator;\n\n        public SpearAbility(float cooldown, float staminaCost, GameObject spearThrowPrefab, Transform spearThrowOrigin)\n            : base(cooldown, staminaCost)\n        {\n            this.spearThrowPrefab = spearThrowPrefab;\n            this.spearThrowOrigin = spearThrowOrigin;\n        }\n\n        public override void Use(GameObject user)\n        {\n            if (spearThrowPrefab == null || spearThrowOrigin == null)\n            {\n                Debug.LogError(\"Spear throw prefab or origin is not set\");\n                return;\n            }\n\n            animator = user.GetComponent&lt;Animator&gt;();\n\n            animator.SetTrigger(\"AbilityAttack\");\n        }\n\n        public IEnumerator WaitForSpearAbility(GameObject user)\n        {\n            yield return new WaitForSeconds(0.45f);\n\n            Quaternion rotation = Quaternion.Euler(0, user.transform.rotation.eulerAngles.y, 0);\n            Instantiate(spearThrowPrefab, spearThrowOrigin.transform.position, rotation);\n\n            Quaternion rotation1 = Quaternion.Euler(0, user.transform.rotation.eulerAngles.y + 15, 0);\n            Instantiate(spearThrowPrefab, spearThrowOrigin.transform.position, rotation1);\n\n            Quaternion rotation2 = Quaternion.Euler(0, user.transform.rotation.eulerAngles.y - 15, 0);\n            Instantiate(spearThrowPrefab, spearThrowOrigin.transform.position, rotation2);\n        }\n    }\n}\n<\/code><\/pre>\n<\/details>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6a07a0661c1bd&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6a07a0661c1bd\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"639\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerAbility_Sword-1024x639.gif\" alt=\"\" class=\"wp-image-728\" srcset=\"https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerAbility_Sword-1024x639.gif 1024w, https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerAbility_Sword-300x187.gif 300w, https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerAbility_Sword-768x479.gif 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p id=\"PlayerMovement\"><strong>Player Movement<\/strong><br>For the player movement i decided on making a simple but effective charachter controller using the inbuild Character Controller component. I made a simple script around it which i could change later into a more complex controller featuring dashes and stamina. <\/p>\n\n\n\n<p>We wanted to leave physics out of this project so i move the player using the inbuild Move method that comes with the Character Controller component. <\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Partial Character Controller Code<\/summary>\n<pre class=\"wp-block-code\" style=\"border-style:none;border-width:0px\"><code>private void MoveCharacter()\n{\n    float horizontal = Input.GetAxis(\"Horizontal\");\n    float vertical = Input.GetAxis(\"Vertical\");\n\n    movement.Set(horizontal, 0f, vertical);\n\n    if (movement.magnitude &gt; 1f)\n    {\n        movement.Normalize();\n    }\n\n    animator.SetBool(\"IsMoving\", true);\n    characterController.Move(movement * moveSpeed * Time.deltaTime);\n}\n\npublic void DrainStamina(float staminaAmount)\n    {\n        if (Stamina &lt;= staminaAmount)\n        {\n            Debug.Log(\"Not enough stamina to perform this action\");\n            return;\n        }\n        else\n        {\n            Stamina -= staminaAmount;\n            float currentStamina = Stamina \/ 100;\n            staminaBar.fillAmount = currentStamina;\n            lastStaminaUsedTime = Time.time;\n        }\n    }<\/code><\/pre>\n<\/details>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"566\" height=\"338\" src=\"https:\/\/timohonig.nl\/wp-content\/uploads\/2024\/06\/RomanRevenant_PlayerDash.gif\" alt=\"\" class=\"wp-image-764\" style=\"width:620px;height:auto\"\/><\/figure>\n\n\n\n<p id=\"WaveSystem\"><strong>Wave System<\/strong><br>One of my other developers was running late on making the enemies so i stepped in making a system to spawn enemies based on a weight system per wave. First i had calculate how much weight each wave had, how much weight every enemy has and how to increase the weight per wave.<\/p>\n\n\n\n<p>My first step was making sure there could only be one Instance of the wave manager using an Singleton Reference. Then i decided to use a exponential graph to calculate the weight for each wave. Using that i made sure that when i spawned them i did not use the standard Random method that is inbuild in unity but rather make something that ensures there is a higher chance of getting higher weight enemies in higher waves. <\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Partial Wave System Code<\/summary>\n<pre class=\"wp-block-code\"><code>    private int CalculateWaveWeight(int waveNumber)\n    {\n        return currentWaveWeight = Mathf.RoundToInt(10 * Mathf.Pow(1.2f, waveNumber - 1));\n    }\n    \nprivate IEnumerator SpawnNextWave()\n    {\n        isSpawningWave = true;\n        CalculateWaveWeight(CurrentWave);\n\n        enemyManager.buffedEnemyCount = 0;\n\n        int totalWeightLeft = currentWaveWeight;\n\n        while (totalWeightLeft &gt; 0)\n        {\n            EnemyTypes.Data selectedEnemy = SelectRandomEnemyType();\n\n            while (selectedEnemy.EnemyWeight &gt; totalWeightLeft)\n            {\n                List&lt;EnemyTypes.Data&gt; suitableEnemies = new List&lt;EnemyTypes.Data&gt;();\n                foreach (EnemyTypes.Data enemyData in enemyTypesData.Enemies)\n                {\n                    if (enemyData.EnemyWeight &lt;= totalWeightLeft)\n                    {\n                        suitableEnemies.Add(enemyData);\n                    }\n                }\n\n                if (suitableEnemies.Count &gt; 0)\n                {\n                    selectedEnemy = suitableEnemies&#91;Random.Range(0, suitableEnemies.Count)];\n                }\n                else\n                {\n                    break;\n                }\n            }\n\n            if (selectedEnemy.EnemyWeight &gt; totalWeightLeft)\n            {\n                break;\n            }\n\n            Transform spawnPoint = spawnLocations&#91;Random.Range(0, spawnLocations.Count)];\n            Instantiate(selectedEnemy.EnemyPrefab, spawnPoint.position, Quaternion.identity);\n            yield return new WaitForEndOfFrame();\n\n            uglyfix.Instance.UpdateNumber(CurrentWave);\n\n            totalWeightLeft -= selectedEnemy.EnemyWeight;\n        }\n\n        yield return new WaitUntil(() =&gt; CheckEnemyAmount());\n\n        CurrentWave++;\n\n        isSpawningWave = false;\n    }\n\n\n\n\n    private EnemyTypes.Data SelectRandomEnemyType()\n    {\n        int totalWeight = 0;\n        foreach (EnemyTypes.Data enemyData in enemyTypesData.Enemies)\n        {\n            totalWeight += enemyData.EnemyWeight;\n        }\n\n        int randomValue = Random.Range(0, totalWeight);\n\n        int cumulativeWeight = 0;\n        foreach (EnemyTypes.Data enemyData in enemyTypesData.Enemies)\n        {\n            cumulativeWeight += enemyData.EnemyWeight;\n            if (randomValue &lt; cumulativeWeight)\n            {\n                return enemyData;\n            }\n        }\n\n        return enemyTypesData.Enemies&#91;0];\n    }<\/code><\/pre>\n<\/details>\n","protected":false},"excerpt":{"rendered":"<p>Roman Revenant is a top down singleplayer game where you play as a Roman warrior defending the city of Rome from invading monsters. As you progress, you unlock a variety of weapons, each with unique attributes and combat styles, allowing you to customize your strategy. Additionally, you gain special abilities tailored to each weapon.<\/p>\n","protected":false},"author":1,"featured_media":732,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,8],"tags":[],"class_list":["post-652","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-all-projects","category-favorite"],"_links":{"self":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/652","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/comments?post=652"}],"version-history":[{"count":25,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/652\/revisions"}],"predecessor-version":[{"id":768,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/652\/revisions\/768"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/media\/732"}],"wp:attachment":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/media?parent=652"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/categories?post=652"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/tags?post=652"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}