# HACS: Unity Track Homepage ## October 18th, 2023 ### Learning Goals: * Create our first GameObjects and familiarize ourselves with the basic Physics components. * Introduce scripting with our first C# script, and begin to create player movement. ### What We Achieved: * We created our first GameObjects, the Player and the Ground. * We used the `Rigidbody2D` and `Box/CircleCollider2D` components to add physics and collisions to our objects. * We wrote `PlayerMovementScript.cs`, our first C# script. which introduces jumping movements on key press, only when the player is touching the ground. > PlayerMovementScript.cs ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovementScript : MonoBehaviour { private Rigidbody2D rig; public LayerMask ground; // Start is called before the first frame update void Start() { rig = gameObject.GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { if (Input.GetKeyDown(KeyCode.UpArrow) && OnGrounded()) { rig.velocity += Vector2.up * 10; } } public bool OnGrounded() { return Physics2D.OverlapCircle(transform.position + 0.5f * Vector3.down, 0.05f, ground); } } ``` ## October 20th, 2023 ### Learning Goals: * Extend `PlayerMovementScript.cs` to introduce horizontal motion and velocity smoothing. * Create and spawn obstacles... that fall from the sky?! ### What We Achieved: * On `PlayerMovementScript.cs`, we used `Input.GetAxisRaw()` to add horizontal motion to our player while leaving existing y-velocity intact. * We created a new `GameObject` as our enemy, * with a `Rigidbody2D` to make it fall from the sky, * and a `PolygonCollider2D` to control collisions. * We wrote `EnemyScript.cs`, a script controlling the enemy's spawning behavior. * We used `Random.Range()` to generate an x-position between our screen bounds. * We experimented with the editor window to determine the y and z-values for our `Transform` position. > PlayerMovementScript.cs (updated) ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovementScript : MonoBehaviour { private Rigidbody2D rig; private float horizontal; public LayerMask ground; public float speed = 5f; // Start is called before the first frame update void Start() { rig = gameObject.GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { horizontal = Input.GetAxisRaw("Horizontal"); if (Input.GetKeyDown(KeyCode.UpArrow) && OnGrounded()) { rig.velocity += Vector2.up * 10; } } void FixedUpdate() { rig.velocity = new Vector2(horizontal * speed, rig.velocity.y); } public bool OnGrounded() { return Physics2D.OverlapCircle(transform.position + 0.5f * Vector3.down, 0.05f, ground); } private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.CompareTag("Enemy")) { Destroy(gameObject); } } } ``` > EnemyScript.cs ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyScript : MonoBehaviour { // Start is called before the first frame update void Start() { transform.position = new Vector3(Random.Range(-8.1f, 8.1f), 7f, 0); } } ``` ## November 17th, 2023 ### Learning Goals: * Implement a score counting system * Create a pause menu after the player loses, with a restart button to reload the scene! * IF TIME PERMITS: Tips and tricks to beautify your game :) ### What We Achieved: * We deviated from the original plan, and instead we created an enemy follow system to track the player! * We created a Spawner `GameObject` and accompanying it, a `SpawnScript` in order to > EnemyScript.cs (updated) ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyScript : MonoBehaviour { public Transform player; // purpose: to fall, follow the player // Start is called before the first frame update void Start() { player = GameObject.FindGameObjectWithTag("Player").transform; transform.position = new Vector3(Random.Range(-8.1f, 8.1f), 7f, 0); } void Update() { if (player) transform.position = Vector3.Lerp(transform.position, player.position, 0.001f); } } ``` > SpawnScript.cs ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpawnScript : MonoBehaviour { public GameObject enemy; float timer; // Start is called before the first frame update void Start() { timer = Time.time; } // Update is called once per frame void Update() { if (Time.time > (timer + 3)) { Instantiate(enemy); timer = Time.time; } } } ``` ## November 24th, 2023 ### Learning Goals: * Implement a score counting system that increments each second. * Design and show the pause/restart menu upon the death of the player character. * Finish our game and prepare for build/export (moved to next session!) ### What We Achieved: * We imported the `TextMeshPro` package into our project, enabling us to create scalable text across multiple screen resolutions. * We created text using the `TextMeshProUGUI` component, as a child of the `GameObject` with the UI `Canvas` component. * We scripted the beahvior of the menu by creating a new script, `PauseMenuScript.cs`. * In this script, we set `public void` functions that could be used as callbacks for our restart button and our player's death. * We learned to use `SceneManager` to load the scene at index 0 – the default scene in our Build Settings. * We ensured enemies wouldn't infinitely pile up (and weaken performance) by applying the `Destroy(GameObject obj, int delay)` function to `EnemyScript.cs`. > PlayerMovementScript.cs (updated) ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; public class PlayerMovementScript : MonoBehaviour { private Rigidbody2D rig; private float horizontal; public LayerMask ground; public float speed = 5f; public TextMeshProUGUI scoreText; private float startTime = 0; private PauseMenuScript script; // Start is called before the first frame update void Start() { rig = gameObject.GetComponent<Rigidbody2D>(); scoreText.text = "0"; startTime = Time.time; script = FindObjectOfType<PauseMenuScript>(); } // Update is called once per frame void Update() { horizontal = Input.GetAxisRaw("Horizontal"); if (Input.GetKeyDown(KeyCode.UpArrow) && OnGrounded()) { rig.velocity += Vector2.up * 10; } int score = (int) (Time.time - startTime); if (score.ToString() != scoreText.text) { scoreText.text = score + ""; } } void FixedUpdate() { rig.velocity = new Vector2(horizontal * speed, rig.velocity.y); } public bool OnGrounded() { return Physics2D.OverlapCircle(transform.position + 0.5f * Vector3.down, 0.05f, ground); } private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.CompareTag("Enemy")) { script.openPauseMenu(); Destroy(FindObjectOfType<SpawnScript>().gameObject); Destroy(gameObject); } } } ``` > EnemyScript.cs (updated) ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyScript : MonoBehaviour { public Transform player; // Start is called before the first frame update void Start() { player = GameObject.FindGameObjectWithTag("Player").transform; transform.position = new Vector3(Random.Range(-8.1f, 8.1f), 7f, 0); Destroy(gameObject, 20); } void Update() { if (player) transform.position = Vector3.Lerp(transform.position, player.position, 0.001f); } } ``` > PauseMenuScript.cs ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class PauseMenuScript : MonoBehaviour { public Button restart; public GameObject pauseMenu; // Start is called before the first frame update void Start() { pauseMenu.SetActive(false); } public void openPauseMenu() { pauseMenu.SetActive(true); } public void restartLevel() { SceneManager.LoadScene(0); } } ``` ## December 8th, 2024 ### Learning Goals - Continue developing player movement - Design bullet. ## January 12th-19th 2024 ### Learning Goals - Finalize character movement by adding flexible jump functionality no matter the collider height (setting pivot to the botttom of the collider). - Design bullet spawning using `Input.GetKeyDown()` when the player presses`KeyCode.Space` or an equivalent 'fire' button. - Set up equations for calculating bullet direction/magnitude of fire speed, and gain intuition visually for why it works. ### What We Achieved: - Modelled the bullet direction calculations on the whiteboard. - Used `Vector3.normalized` to output the unit vector in any given direction, then scale it by `bulletSpeed`. - (Due next session) Challenge: can you implement the bullet movement yourself in `PlayerMovementScript.cs`, using the equations we derived? **For code, please refer to the February 2nd, 2024 post** ## February 2nd, 2024 (Please follow these instructions! I will be absent today, unfortunately, due to a counselor meeting. So please email/dm me if you have questions!!!) ### Learning Goals / Plan For Today 1. Check your bullet spawning code against my solution; explain why it works to the person next to you. Remember, our goal was to move at unit speed in the direction of of our mouse click, with an initial offset of radius 1 emanating from the player `GameObject` such that the bullet is spawned outside the player's `Collider2D`. * Notice how I've created an additional public variable `bulletOffset`. We spawn the bullet at `transform.position + a * bulletOffset`, originating at our player position with`bulletOffset` units in the direction of our cursor (scaling up the unit vector `a`). > PlayerMovementScript.cs (including the solution from my challenge to you) ```csharp= using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovementScript : MonoBehaviour { private Rigidbody2D rig; private float horizontal; private bool grounded = false; public float speed = 3f; public float jumpSpeed = 8f; public LayerMask groundLayer; public GameObject bulletPrefab; public float bulletSpeed = 40f; public float bulletOffset = 1f; // Start is called before the first frame update void Start() { rig = gameObject.GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { horizontal = Mathf.Lerp(horizontal, Input.GetAxisRaw("Horizontal") * speed, 0.3f); grounded = isGrounded(); if (grounded && Input.GetKeyDown(KeyCode.Space)) { rig.velocity = new Vector2(rig.velocity.x, jumpSpeed); } if (Input.GetKeyDown(KeyCode.Mouse0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector3 a = new Vector2(mousePosition.x - transform.position.x, mousePosition.y - transform.position.y).normalized; GameObject bullet = Instantiate(bulletPrefab, transform.position + a * bulletOffset, Quaternion.identity); bullet.GetComponent<Rigidbody2D>().velocity = a * bulletSpeed; Destroy(bullet, 5); } } private void FixedUpdate() { rig.velocity = new Vector2(horizontal, rig.velocity.y); } private bool isGrounded() { return Physics2D.OverlapCircle(transform.position, 0.05f, groundLayer); } } ``` 2. Learn how to implement 1 of 3 optional features according to your preference (if you'd like to do multiple of them, go ahead). * [Collectible Coins (play at 0.5x speed!)](https://www.youtube.com/watch?v=GG0NYcOQd0k) * A good feature to try if you want something quick and easy. * [Homing missiles (for funsies!)](https://www.youtube.com/watch?v=0v_H3oOR0aU&pp=ygUQYnJhY2tleXMgZW5lbWllcw%3D%3D) * Yes, I'm looking at you Devank! * [Enemy Patrol (introduction!)](https://www.youtube.com/watch?v=MPnN9i1SD6g) * You'll need to create a GameObject to serve as your enemy. * We'll delve into more advanced enemy behavior together, but for now this was the most basic kind of enemy behavior I could find. You'll want to spawn your enemy on a flat surface at the moment. 3. Begin designing YOUR enemy system. Brainstorm and take notes on the following: * What would you like your enemy to look like? * Will your enemy stay on the ground? In the air? * What modes of attack do you want your enemy to have? * How will the player be able to kill the enemy? Please note down all of the following on a document for future reference. ## February 16rd, 2024 ### Learning Goals: - Debug any remaining errors in our code from our last independent work session. - Begin work on Idle/Run sprite animations (for Evan): - Introduce the `Animation` asset, placing animations within the `Animator` component and modelling the state flow. - Learn how to use spritesheets and change the speed of an animation. ### What We Achieved: - Fixed: bullets spawning inside the player collider when shooting upwards. - Modified the origin from which we spawn bullets to our collider's y-height divided by 2, effectively moving the center of our offset radius to the center of our player. - Fixed: Devank's bullets failed to collide with neighboring geometry and despawn. - Implemented `BulletScript.cs` for Devank's project, and set the bullet's `Rigidbody2D` to Dynamic mode. - Created an Idle animation for Evan's player, and introduced the various states of the Animator component. > PlayerMovementScript.cs ```csharp= using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovementScript : MonoBehaviour { private Rigidbody2D rig; private CapsuleCollider2D col; private float horizontal; private bool grounded = false; public float speed = 3f; public float jumpSpeed = 8f; public LayerMask groundLayer; public GameObject bulletPrefab; public float bulletSpeed = 40f; public float bulletOffset = 1f; // Start is called before the first frame update void Start() { rig = gameObject.GetComponent<Rigidbody2D>(); col = gameObject.GetComponent<CapsuleCollider2D>(); } // Update is called once per frame void Update() { horizontal = Mathf.Lerp(horizontal, Input.GetAxisRaw("Horizontal") * speed, 0.3f); grounded = isGrounded(); if (grounded && Input.GetKeyDown(KeyCode.Space)) { rig.velocity = new Vector2(rig.velocity.x, jumpSpeed); } if (Input.GetKeyDown(KeyCode.Mouse0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector3 a = new Vector2(mousePosition.x - transform.position.x, mousePosition.y - transform.position.y).normalized; GameObject bullet = Instantiate(bulletPrefab, transform.position + col.offset.y * Vector3.up + a * bulletOffset, Quaternion.identity); bullet.GetComponent<Rigidbody2D>().velocity = a * bulletSpeed; Destroy(bullet, 5); } } private void FixedUpdate() { rig.velocity = new Vector2(horizontal, rig.velocity.y); } private bool isGrounded() { return Physics2D.OverlapCircle(transform.position, 0.05f, groundLayer); } } ``` > BulletScript.cs (somehow I never attached this before!) ```csharp= using System.Collections; using System.Collections.Generic; using UnityEngine; public class BulletScript : MonoBehaviour { private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.CompareTag("Enemy")) { Destroy(collision.collider.gameObject); } Destroy(gameObject); } } ``` ## February 23rd, 2024 ### Learning Goals: - Complete basic Idle/Run animations and transition between them. - Discuss the components/behavior required to create a functional enemy. - Begin scripting a enemy with a child `SpriteRenderer` and a new `EnemyScript.cs` (different than our last project!) for subtractable health based on shots made by player. - Bonus, if time permits: create sound effects that play upon bullet fires and enemy deaths. ### What We Achieved: - ## xx,xx 2024 ### Learning Goals: - Design enemy AI and discuss the `enum` type. Defining an`enum` (placeholder `EnemyState`), we will design enemy states of `EnemyState.Idle`, `EnemyState.Chase` and `EnemyState.Death` - If time permits, `EnemyState.Attack` can be implemented and trigger periodic bullet firing aimed at the player. ### What We Achieved: - ## xx,xx 2024 ### Learning Goals: - Revisit `class` hierarchy and dissect the attributes/methods of `MonoBehaviour` using Unity documentation. - Add varying bullet/enemy types (this could easily extend to skins, characters, etc.) using a `class` without inheriting from `MonoBehaviour` - Classes that do not inherit from `MonoBehaviour` will not show in the editor as a selectable component. - Design customizations and code switching between options ### What We Achieved: -