Reconstructed
About the Game
Reconstructed is a 2D metroidvania platformer where you play as a broken-down robot that is scavenging the world to reassemble itself. Surveillance robots are patrolling all over and will stop the robot at any cost, shooting off its parts to prevent its reconstruction. Certain parts will offer different abilities, such as new ways to attack, giving the player a variety of playstyles to take down any threats.
Play the game here!
Role in Development
- Manager: David Liu
- Designer: David Liu
- Programmer: David Liu
- Artist: Samuel Keir
- Audio Design: Samuel Keir
With this game jam, I wanted to make a game that was a way of showing everything that I had learned up to this point. With this ambitious goal, I decided to work on the project's design and development alone. My friend, Sam, was generously willing to create art, music, and sound assets for the game during the jam. The intial concept of the game was to have multiple different enemies that would drop their parts upon dying for the player to equip. Depending on what's equipped, the player would move and interact differently with the controls. However, I quickly spotted during the jam that Sam would not be able to produce that much art within 48 hours, and I was struggling to fix all the bugs at the moment already. I cut down the scope into having one enemy type with two different programmed behaviors: passive and aggressive. There are also only two different part types: arms for shooting bullets and a head to shoot lasers. Though the game didn't manage to reach its initial scope, our replanned scope managed to work out pretty well in the end. I was quite satsified to find that I was able to make a completed platformer with enemy AI and the unique mechanics implemented.
Script Examples
PlayerController.csPlayerStats.csPlayerAttack.csBodyPart.csEnemyBasic.csEnemyStats.csusing System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class PlayerController : MonoBehaviour { #region Inspector Variables public float alphaSpeed; public float betaSpeed; public float speedCap; public float rollForce; public float alphaJumpForce; public float omegaJumpForce; public float downForce; public ListanimStates; public AudioSource walkSound1; public AudioSource walkSound2; public AudioSource landSound; public AudioSource clickSound; public AudioSource laserSound; public AudioSource shootSound; #endregion public enum BodyState { Full, OneArm, NoArms, NoHead }; public enum AnimState { Idle, Walk, Jump }; // Animations private List animIdle; private List animWalk; private List animJump; public AnimState anim; private Rigidbody2D rigidbody; public BodyState bodyState; private float airTimer; [HideInInspector] public bool grounded = true; public bool hasCharge; public bool isCharging; // Start is called before the first frame update void Start() { hasCharge = false; isCharging = false; // Set the rigidbody rigidbody = GetComponent (); if (rigidbody == null) Debug.LogError($"Rigidbody2D is not set in {gameObject.name}"); // Get Animations animIdle = new List (); animWalk = new List (); animJump = new List (); if (animStates.Count == 0) animStates = GetComponentsInChildren ().ToList(); foreach (AnimateSprite anim in animStates) { switch (anim.name) { // Idle case "FullIdle": animIdle.Insert(0, anim); break; case "OneArmIdle": animIdle.Insert(1, anim); break; case "NoArmsIdle": animIdle.Insert(2, anim); break; case "NoHeadIdle": animIdle.Insert(3, anim); break; // Walk case "FullWalk": animWalk.Insert(0, anim); break; case "OneArmWalk": animWalk.Insert(1, anim); break; case "NoArmsWalk": animWalk.Insert(2, anim); break; case "NoHeadWalk": animWalk.Insert(3, anim); break; // Jump case "FullJump": animJump.Insert(0, anim); break; case "OneArmJump": animJump.Insert(1, anim); break; case "NoArmsJump": animJump.Insert(2, anim); break; case "NoHeadJump": animJump.Insert(3, anim); break; default: break; } } ChangeAnimState(AnimState.Idle); // Set Body State //bodyState = BodyState.Alpha; airTimer = 0; } private void FixedUpdate() { //transform.Translate(new Vector3(Input.GetAxis("Horizontal") * alphaSpeed * Time.deltaTime, 0)); if (!grounded) { airTimer += 0.02f; // Own check for grounded if (airTimer > 0.025f) { RaycastHit2D hit = Physics2D.Raycast(transform.position - new Vector3(0, 0.25f), new Vector2(0, -1), 0.75f); Debug.DrawLine(transform.position - new Vector3(0, 0.25f), transform.position - new Vector3(0, 0.25f) + (new Vector3(0, -1) * 0.75f)); if (hit.collider != null && (hit.collider.tag == "Ground" || hit.collider.tag == "Enemy")) { landSound.volume = -rigidbody.velocity.y / 250f; landSound.Play(); grounded = true; airTimer = 0; } } } if (Input.GetAxis("Horizontal") == 0 && anim != AnimState.Idle && grounded) { ChangeAnimState(AnimState.Idle); } else if (Input.GetAxis("Horizontal") != 0) { // Check for run animation if (grounded && anim != AnimState.Walk) { ChangeAnimState(AnimState.Walk); } // Left/Right Movement transform.Translate(new Vector3(Input.GetAxis("Horizontal") * alphaSpeed, 0)); // Sprite change if (Input.GetAxis("Horizontal") < 0) GetComponent ().flipX = true; else if (Input.GetAxis("Horizontal") > 0) GetComponent ().flipX = false; } if (Input.GetAxis("Vertical") < 0) rigidbody.AddForce(new Vector2(0, Input.GetAxis("Vertical") * downForce)); // Jumping if (Input.GetButton("Jump") && !grounded && rigidbody.velocity.y > 0) { rigidbody.AddForce(new Vector2(0, alphaJumpForce / (Mathf.Pow(airTimer, 2) + (alphaJumpForce / 5)))); } if (hasCharge && Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { alphaSpeed = 0.25f; GetComponent ().canBeAttacked = false; GetComponent ().color = Color.red; isCharging = true; } if (hasCharge && Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { alphaSpeed = 0.075f; GetComponent ().canBeAttacked = true; GetComponent ().color = Color.white; isCharging = false; } } // Update is called once per frame void Update() { if (Input.GetButtonDown("Jump") && grounded) { rigidbody.AddForce(new Vector2(0, alphaJumpForce)); grounded = false; ChangeAnimState(AnimState.Jump); } /*if (!grounded) { airTimer += Time.deltaTime; // Own check for grounded if (airTimer > 0.025f) { RaycastHit2D hit = Physics2D.Raycast(transform.position - new Vector3(0, 0.25f), new Vector2(0, -1), 0.75f); Debug.DrawLine(transform.position - new Vector3(0, 0.25f), transform.position - new Vector3(0, 0.25f) + (new Vector3(0, -1) * 0.75f)); if (hit.collider != null && (hit.collider.tag == "Ground" || hit.collider.tag == "Enemy")) { landSound.volume = -rigidbody.velocity.y / 250f; landSound.Play(); grounded = true; airTimer = 0; } } } // Full Body Movement //if (bodyState == BodyState.Full) //{ // Check for idle animation if (Input.GetAxis("Horizontal") == 0 && anim != AnimState.Idle && grounded) { ChangeAnimState(AnimState.Idle); } else if (Input.GetAxis("Horizontal") != 0) { // Check for run animation if (grounded && anim != AnimState.Walk) { ChangeAnimState(AnimState.Walk); } // Left/Right Movement transform.Translate(new Vector3(Input.GetAxis("Horizontal") * alphaSpeed * Time.deltaTime, 0)); // Sprite change if (Input.GetAxis("Horizontal") < 0) GetComponent ().flipX = true; else if (Input.GetAxis("Horizontal") > 0) GetComponent ().flipX = false; } // Fastfall if (Input.GetAxis("Vertical") < 0) rigidbody.AddForce(new Vector2(0, Input.GetAxis("Vertical") * downForce * Time.deltaTime)); // Jumping if (Input.GetButtonDown("Jump") && grounded) { rigidbody.AddForce(new Vector2(0, alphaJumpForce)); grounded = false; ChangeAnimState(AnimState.Jump); } // Higher jumping else if (Input.GetButton("Jump") && !grounded && rigidbody.velocity.y > 0) { rigidbody.AddForce(new Vector2(0, alphaJumpForce / (Mathf.Pow(airTimer, 2) + (alphaJumpForce / 5)))); }*/ //} // Broken Body Movement /*else if (bodyState == BodyState.Beta) { // Left/Right Movement transform.Translate(new Vector3(Input.GetAxis("Horizontal") * betaSpeed * Mathf.Abs(Mathf.Sin(Time.time * 4)), 0)); // Falling if (Input.GetAxis("Vertical") < 0) rigidbody.AddForce(new Vector2(0, Input.GetAxis("Vertical") * downForce)); // Jumping if (Input.GetButtonDown("Jump") && grounded) { rigidbody.AddForce(new Vector2(0, alphaJumpForce)); grounded = false; } // Higher jumping else if (Input.GetButton("Jump") && !grounded && rigidbody.velocity.y > 0) { rigidbody.AddForce(new Vector2(0, alphaJumpForce / (Mathf.Pow(airTimer, 2) + (alphaJumpForce / 5)))); } }*/ // Just Head Movement /*else if (bodyState == BodyState.Omega) { // Left/Right Movement if (Mathf.Abs(rigidbody.velocity.x) < speedCap) rigidbody.AddForce(new Vector2(Input.GetAxis("Horizontal") * rollForce, 0)); // Jumping if (Input.GetButtonDown("Jump") && grounded) { rigidbody.AddForce(new Vector2(0, omegaJumpForce)); grounded = false; } // Higher jumping else if (Input.GetButton("Jump") && !grounded && rigidbody.velocity.y > 0) { rigidbody.AddForce(new Vector2(0, omegaJumpForce / (Mathf.Pow(airTimer, 2) + (omegaJumpForce / 5)))); } } if (hasCharge && Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { alphaSpeed = 0.2f; GetComponent ().canBeAttacked = false; GetComponent ().color = Color.red; isCharging = true; } if (hasCharge && Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { alphaSpeed = 0.05f; GetComponent ().canBeAttacked = true; GetComponent ().color = Color.white; isCharging = false; }*/ } private void OnCollisionEnter2D(Collision2D collision) { // Hits what's labeled as the ground layer /*if (collision.gameObject.layer == 8) { grounded = true; airTimer = 0; }*/ } #region Helper Methods public void ChangeAnimState(AnimState state) { switch(state) { case AnimState.Idle: anim = AnimState.Idle; DisableAnimations(); animIdle[(int)bodyState].enabled = true; walkSound1.Stop(); walkSound2.Stop(); break; case AnimState.Walk: anim = AnimState.Walk; DisableAnimations(); animWalk[(int)bodyState].enabled = true; if (bodyState == BodyState.Full) walkSound1.Play(); walkSound2.Play(); break; case AnimState.Jump: //anim = AnimState.Jump; //DisableAnimations(); //animJump[(int)bodyState].enabled = true; break; default: break; } } public void DisableAnimations() { foreach (AnimateSprite sprite in animStates) sprite.enabled = false; } #endregion }