{"id":18842112,"url":"https://github.com/luizh-queiroz/foxadventures","last_synced_at":"2025-10-03T13:57:56.875Z","repository":{"id":60197059,"uuid":"525928717","full_name":"LuizH-Queiroz/FoxAdventures","owner":"LuizH-Queiroz","description":null,"archived":false,"fork":false,"pushed_at":"2022-11-24T04:14:23.000Z","size":48729,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-30T10:53:05.298Z","etag":null,"topics":["2d","2d-game","game","game-development","game-engine","gamedev","games","platform","sunnyland","unity","unity2d"],"latest_commit_sha":null,"homepage":"","language":"ASP.NET","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LuizH-Queiroz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-17T19:31:39.000Z","updated_at":"2022-11-18T20:17:41.000Z","dependencies_parsed_at":"2022-09-26T15:50:23.235Z","dependency_job_id":null,"html_url":"https://github.com/LuizH-Queiroz/FoxAdventures","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuizH-Queiroz%2FFoxAdventures","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuizH-Queiroz%2FFoxAdventures/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuizH-Queiroz%2FFoxAdventures/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuizH-Queiroz%2FFoxAdventures/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LuizH-Queiroz","download_url":"https://codeload.github.com/LuizH-Queiroz/FoxAdventures/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239780120,"owners_count":19695734,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["2d","2d-game","game","game-development","game-engine","gamedev","games","platform","sunnyland","unity","unity2d"],"created_at":"2024-11-08T02:53:43.358Z","updated_at":"2025-10-03T13:57:51.830Z","avatar_url":"https://github.com/LuizH-Queiroz.png","language":"ASP.NET","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FoxAdventures\n\n_This is my final project for [CS50's Introduction to Game Development](https://cs50.harvard.edu/games/2018/)._\n\nFoxAdventures is a 2D, platform game build in [Unity](https://unity.com/) Game Engine, which has one playable scene/level called Play and three other simple scenes/screens: Start, GameOver and Victory.\n\nOn the Play scene the player wins if he manages to go through the map and reach the house at the end without being killed by the enemies, and loses otherwise.\n\nThe player mechanics is mainly walking or jumping. Jumping is important to win the game, as the player can only kill enemies by falling on top of them.\n\n\u003cbr\u003e\n\n\n## Detailing the Game\n\n### **Game Flow**\n\nThe game is divided in basically three main sections (in order of appearance):\n- Start: A simple Scene presenting the game\n- Play: The playable level, in which the player can either win or lose\n- End: There are two Scenes that may be presented:\n    - Victory: if the player won in the Play Scene\n    - GameOver: if the player lost in the Play Scene\n\n\n### **Scenes**\n\nNote: the game is a 2D platformer\n\n- Start Scene\n\n    A simple screen with a SunnyLand (main asset) background, a music and texts to welmcome the player and inform how to start playing the game.\n\n- Play Scene\n\n    The main part of the game. The scenario, collidable objects and triggers were created using tilemaps, so there are multiple tilemaps in the scene.\n\n    The player must cross the map to reach the house at the end. To do this, he'll have to dodge or kill the enemies that will appear on the way.\n\n    The player has 50 life points and the damage caused by a enemy is 10, but there are collectables in the map where the player can pick up and heal. A life bar is shown on the top left corner for the player to know approximately how much life he has.\n\n    To kill a enemy the player must fall on top of them, either by falling from a higher place or after jumping.\n\n    The player will only take damage when colliding with an enemy but not falling.\n\n    When the player reaches the house the Play Scene ends and the game goes to the Victory Scene. If the player runs out of life, the game goes to the GameOver Scene.\n\n- Victory Scene\n\n    A simple screen that plays a victory sound effect and congrats the player for completing the game.\n\n- GameOver Scene\n\n    A simple screen that plays a defeat sound effect and tells the player to try again by pressing Enter.\n\n\u003cbr\u003e\n\n\n### **Player**\n\nThe player has 5 States: IDLE, WALK, JUMP, FALL and ON_HIT.\n\nThe main file for controlling the player is called PlayerStateManager.cs, in which the State calls are made (like Enter and Update) and where we can find the methods that may be used by any State the player is in, like these:\n\n```cs\npublic void ChangeState(PlayerBaseState state)\n{\n    currentState = state;\n    currentState.EnterState(this);\n}\n\npublic void SetSpriteDirection(float moveDirection)\n{\n    if (moveDirection == -1)\n    {\n        sprite.flipX = true;\n    }\n    else if (moveDirection == 1)\n    {\n        sprite.flipX = false;\n    }\n}\n\npublic void TakeDamage(int amount)\n{\n    currentHealth -= amount;\n\n    if (currentHealth \u003e 0)\n    {\n        ChangeState(OnHitState);\n    }\n    else\n    {\n        SceneManager.LoadScene(\"GameOver\");\n    }\n}\n\npublic void Heal(int amount)\n{\n    currentHealth = Mathf.Min(currentHealth + amount, maxHealth);\n}\n```\n\n\u003cbr\u003e\n\nFollows detailed explanations of how every State works (if the Enter method doesn't have any important features it will not be covered):\n\n- IDLE\n    - Enter: the animation is changed by setting the *Animator* 'State' variable value:\n        ```cs\n        public override void EnterState(PlayerStateManager player)\n        {\n            player.animator.SetInteger(\"State\", (int) PlayerStateManager.STATES.IDLE);\n        }\n        ```\n        This is made in every single State in the whole game. In every State's Enter method (even in the enemies States) a *Animator* value is set to a new value (so it won't be said again in order not to be repetitive).\n\n    - Update: it is checked if the player wants to move the character by walking or jumping.\n\n\u003cbr\u003e\n\n- WALK\n    - Update: the character moves horizontally, changing the Sprite direction if needed, and may change the State to IDLE (if the player stops moving), FALL (if the *Rigidbody*'s \"y\" velocity is minor than zero) or JUMP (if the player hits Space Key).\n\n\u003cbr\u003e\n\n- JUMP\n    - Enter: the *Rigidbody*'s \"y\" velocity is set\n        ```cs\n        rigidbody.velocity = new Vector2(0, jumpForce);\n        ```\n    \n    - Update: the player may move to left or right. The State is changed to FALL when the *Rigidbody*'s velocity gets minor then zero.\n\n\u003cbr\u003e\n\n- FALL\n    - Update: the player may move to left or right when falling. The State is changed when the character reaches the ground, going to IDLE if the player is not moving or WALK, otherwise.\n\n\u003cbr\u003e\n\n- ON_HIT\n    - Enter: the character is thrown to the opposite side in wich he's loking at\n        ```cs\n        player.rigidbody.velocity = new Vector2(6 * (player.sprite.flipX ? 1 : -1), 12);\n        ```\n    \n    - Update: change to IDLE State after 0.8 seconds\n        ```cs\n        if (Time.time - startTime \u003e= 0.8)\n        {\n            player.ChangeState(player.IdleState);\n        }\n        ```\n\u003cbr\u003e\n\nA very important method on PlayerStateManager.cs is the OnCollisionEnter2D. It is responsible for making damage to the enemies or taking damage from them.\n\nIf in FALL state it will do the damage (and automatically jump again), or take it, otherwise.\n\n```cs\nprivate void OnCollisionEnter2D(Collision2D collision)\n{\n    if (collision.gameObject.tag.Equals(\"Enemy\"))\n    {\n        if (currentState.GetType() == typeof(PlayerFallState))\n        {\n            ChangeState(JumpState);\n\n            if (collision.gameObject.GetComponent\u003cEagle\u003e() != null)\n            {\n                StartCoroutine(collision.gameObject.GetComponent\u003cEagle\u003e().Die());\n            }\n            else if (collision.gameObject.GetComponent\u003cFrogStateManager\u003e() != null)\n            {\n                StartCoroutine(collision.gameObject.GetComponent\u003cFrogStateManager\u003e().Die());\n            }\n            else\n            {\n                StartCoroutine(collision.gameObject.GetComponent\u003cOpossum\u003e().Die());\n            }\n        }\n        else\n        {\n            TakeDamage(10);\n        }\n    }\n}\n```\n\n\u003cbr\u003e\n\n\n## Health Bar\n\nThe Health Bar has a single file which controls the life's rectangle width through a *Slider* depending on the player's amount of life.\n```cs\npublic class HealthBar : MonoBehaviour\n{\n    public Slider slider;\n    public PlayerStateManager player;\n\n\n    // Start is called before the first frame update\n    void Start()\n    {\n        slider.maxValue = player.maxHealth;\n        slider.value = player.currentHealth;\n    }\n\n    // Update is called once per frame\n    void Update()\n    {\n        slider.value = player.currentHealth;\n    }\n}\n```\n\n\u003cbr\u003e\n\n\n## Enemies\n\nThere are three enemies in the game: Eagle, Grog and Opossum.\n\nA description of each of them follows.\n\n\u003cbr\u003e\n\n### **Eagle**\n\nAn Eagle may be of two types: STATIC or VERTICAL_MOVE.\n\nA STATIC Eagle is the simpler enemy in the game, as it just stays in the exact same place all the time, so it should be easy to kill them.\n\nA VERTICAL_MOVE Eagle is a little different. The \"x\" position of this type of Eagle is always the same, but the \"y\" coordinate keeps continually changing as it flies up and down.\n\nThe Eagle type may be selected in its inspector, since the variable that determines its type is public and *enum*.\n\n\u003cbr\u003e\n\n### **Frog**\n\nFrogs are also designed with State Machines. A Frog has three States: IDLE, JUMP and FALL.\n\n- IDLE\n    - Enter: it is defined a time in seconds, in the range [2, 7], that the Frog will continue IDLE. The side on which the Frog must look is also randomly defined.\n\n    - Update: after the time defined on the Enter method, the Frog will go to the Jump State.\n\n\u003cbr\u003e\n\n- JUMP\n    - Enter: the Frog's *Rigidbody* velocity is changed on both \"x\" and \"y\" axis.\n        ```cs\n        frog.rigidbody.velocity = new Vector2(frog.jumpForceLateral * (frog.sprite.flipX? 1 : -1), frog.jumpForceUp);\n        ```\n\n    - Update: when the Frog starts falling (*Rigidbody*'s \"y\" velocity is minor than zero) the State changes to FALL.\n\n\u003cbr\u003e\n\n- FALL\n    - Update: when the Frog reaches the ground a timer starts to be increased and the State changes back to IDLE when the timer gets equal to or higher than two.\n        ```cs\n        public override void UpdateState(FrogStateManager frog)\n        {\n            if (frog.rigidbody.velocity.y == 0)\n            {\n                frog.animator.SetInteger(\"State\", 0); // although we're not in IdleState, we want\n                                                    // the Idle animation to be played\n                timer += Time.deltaTime;\n\n                if (timer \u003e= 2)\n                {\n                    frog.ChangeState(frog.IdleState);\n                }\n            }\n        }\n        ```\n\n\u003cbr\u003e\n\n### **Opossum**\n\nOpossums are enemies that are walking all the time. They may walk to right or left.\n\nFirst it is defined in which direcion it'll go and how much time it'll walk. After that determined time passes, this cycle restart.\n\n```cs\nvoid Start()\n{\n    SetMovementVariables();\n}\n\n// Update is called once per frame\nvoid Update()\n{\n    transform.Translate(moveDirection * moveSpeed * Time.deltaTime, 0, 0);\n\n    if (Time.time - timer \u003e= moveTime)\n    {\n        SetMovementVariables();\n    }\n}\n\nvoid SetMovementVariables()\n{\n    moveDirection = Random.Range(-1, 2) \u003c= 0 ? -1 : 1;\n    if (moveDirection == 1)\n    {\n        sprite.flipX = true;\n    }\n    else\n    {\n        sprite.flipX = false;\n    }\n\n    moveTime = Random.Range(3, 6);\n    timer = Time.time;\n}\n```\n\n\u003cbr\u003e\n\nThe enemies death (**Die()** method) consists basically in deactivating its *Colliders* and *Rigidbody* and playing the death animation.\n\nThe enemies **Die()** method are quite similar because the differences are just to make sure their position will not change while playing the death animation.\n\nFollows the Frog's **Die()** method:\n\n```cs\npublic IEnumerator Die()\n{\n    foreach (var collider in gameObject.GetComponents\u003cCollider2D\u003e())\n    {\n        collider.enabled = false;\n    }\n    rigidbody.Sleep();\n    ChangeState(IdleState); // for the death animation not to move\n    animator.SetTrigger(\"Death\");\n\n    yield return new WaitForSeconds(1);\n\n    Destroy(gameObject);\n}\n```\n\n\u003cbr\u003e\n\n\n## Consumables\n\nConsumables are simply game objects meant to heal the player, having no movement at all and no conditions to worry about except checking if collided with the player, so its code is pretty simple:\n\n```cs\npublic class Consumable : MonoBehaviour\n{\n    public int healAmount;\n    public Animator animator;\n\n\n    private void OnTriggerEnter2D(Collider2D collision)\n    {\n        if (collision.tag.Equals(\"Player\"))\n        {\n            collision.gameObject.GetComponent\u003cPlayerStateManager\u003e().Heal(healAmount);\n            StartCoroutine(OnConsume());\n        }\n    }\n\n    IEnumerator OnConsume()\n    {\n        animator.SetTrigger(\"OnConsume\");\n        GetComponent\u003cCollider2D\u003e().enabled = false;\n\n        yield return new WaitForSeconds(1);\n\n        Destroy(gameObject);\n    }\n}\n```\n\n\u003cbr\u003e\n\n\n## Credits\n\n- Development:\n    - [Luiz Henrique Queiroz de Albuquerque Silva](https://github.com/LuizH-Queiroz)\n    \n    \u003cbr\u003e\n\n- Art:\n    - [Ansimuz](https://assetstore.unity.com/packages/2d/characters/sunny-land-103349) - [LICENSE](https://unity.com/legal/as-terms)\n    - [PaperHatLizard](https://paperhatlizard.itch.io/cryos-mini-gui) - [LICENSE](https://creativecommons.org/licenses/by/4.0/)\n    - [Tiny Worlds](https://assetstore.unity.com/packages/2d/fonts/free-pixel-font-thaleah-140059) - [LICENSE](https://unity.com/legal/as-terms)\n\n    \u003cbr\u003e\n\n- Music:\n    - [rezoner](https://opengameart.org/content/happy-arcade-tune) - [LICENSE](https://creativecommons.org/licenses/by/3.0/)\n    - [Not Jam](https://not-jam.itch.io/not-jam-music-pack) - [LICENSE](https://creativecommons.org/licenses/by/4.0/)\n\n    \u003cbr\u003e\n\n- Sound Effects:\n    - [Pixabay](https://pixabay.com/sound-effects/?utm_source=link-attribution\u0026amp;utm_medium=referral\u0026amp;utm_campaign=music\u0026amp;utm_content=14800) - [LICENSE](https://pixabay.com/pt/service/license/)\n    - [den_yes](https://opengameart.org/content/game-over-soundold-school) - [LICENSE](https://creativecommons.org/publicdomain/zero/1.0/)\n\n    \u003cbr\u003e\n\n- Important Notes\n    - **I'm not the author/creator of any asset (art, music or sound effect) used in this project**\n    - No changes were made in any asset used in this project\n    - You can check any asset by clicking its author's name/identifier. Please note that each asset's license is provided along with the author's name/identifier\n    - You are free to use, share, modify and build upon this project as long as you give the credits to all of the authors listed in this section (_Credits_), respecting each license's\n\n    \u003cbr\u003e\n\n    - The sound effect from [Pixabay](https://pixabay.com/sound-effects/?utm_source=link-attribution\u0026amp;utm_medium=referral\u0026amp;utm_campaign=music\u0026amp;utm_content=14800) is named \"piglevelwin2.mp3\"","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluizh-queiroz%2Ffoxadventures","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluizh-queiroz%2Ffoxadventures","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluizh-queiroz%2Ffoxadventures/lists"}