Child Arcana
About the Game
Child Arcana is 2D platformer remake of the classic game: Kid Icarus. Ascend from the underworld and fight your way through several ravenous monsters. Shooting enemies with your bow will get you points or health, along with point pickups in the level as you rise up to the overworld. The game only remakes the first level of Kid Icarus, but we did our best to recreate as much of the level as we could.
Developed in Retro Remake Jam 2017
Play the game here!
Role in Development
- Manager: Hector Piñeiro II
- Designer: Hector Piñeiro II,
- Programmers: Hector Piñeiro II, David Liu
- Artist: Leaf Mautrec
This was the very first game jam I took a part of. My friend, Hector, was very kind in assisting me through the game jam. He taught me about basic platformer mechanics, basic AI movement, and lots of Unity tricks and practices. Since I still wasn't knowledgeable with much programming for games nor with using Unity as an engine at the time, I helped contribute to simple scripts such as player movement and attributes.
Script Examples
PlayerMovement.csStats.csInvincibility.csSpecknoseAI.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovement : MonoBehaviour { public float jumpHeight = 3 * 1.28f; public float totalJumpTime = 1.0f; public float walkSpeed = 3 * 1.28f; public bool isCrouch = false; public bool isUp = false; public bool isLeft = false; public Vector2 displacement; private Rigidbody2D rigidBody; private BoxCollider2D collider; private bool canJump = false; private bool isGrounded = false; private bool isTouchingWall = false; private int layerIndex; private float jumpTimer = 0; private HashSetcollidedGroundObjects; private AudioManager audioManager; private RaycastHit2D hit; private Vector3 startPosition; void Awake() { startPosition = transform.position; } // Use this for initialization void Start () { rigidBody = gameObject.GetComponent (); collider = gameObject.GetComponent (); collidedGroundObjects = new HashSet (); audioManager = GameObject.Find("Audio").GetComponent (); layerIndex = 1 << LayerMask.NameToLayer("Block"); // Reset player on Start so it doesn't fly through walls at the beginning of the scene displacement = Vector3.zero; rigidBody.velocity *= 0; transform.position = startPosition; } void OnCollisionEnter2D(Collision2D c) { OnCollisionStay2D(c); } void OnCollisionStay2D(Collision2D c) { if (c.contacts.Length > 0) { ContactPoint2D contact = c.contacts[0]; // If the collision was below the player, the player is grounded if (rigidBody.velocity.y <= 0 && displacement.y <= 0 && Vector3.Dot(contact.normal, Vector2.up) > 0.75f) { ResetJump(); isGrounded = true; collidedGroundObjects.Add(c.gameObject); } // If player hits its head, end the jump else if (Vector3.Dot(contact.normal, Vector2.up) < -0.75f) { if (canJump) { audioManager.PlayDenySound(); } jumpTimer = totalJumpTime; canJump = false; } } } void OnCollisionExit2D(Collision2D c) { collidedGroundObjects.Remove(c.gameObject); // If there are no game objects directly under the player, it's no longer grounded if (collidedGroundObjects.Count == 0) { isGrounded = false; } } // Update is called once per frame void Update() { float verticalInput = Input.GetAxis("Vertical"); // Pit crouches if (verticalInput < 0) { isCrouch = true; isUp = false; collider.size = new Vector2(1, 1.92f * (2f / 3f)); collider.offset = new Vector2(0, 0.95f * (2f / 3f)); } else if (verticalInput > 0) // Pit aims upward { isCrouch = false; isUp = true; collider.size = new Vector2(1, 1.92f); collider.offset = new Vector2(0, 0.95f); } else // Pit is standing/jumping/falling { isCrouch = false; isUp = false; collider.size = new Vector2(1, 1.92f); collider.offset = new Vector2(0, 0.95f); } // Reset movement for this tick displacement = Vector2.zero; // Shoots the raycast in the proper direction if (isLeft) { hit = Physics2D.Raycast(transform.position, Vector3.left, 0.64f, layerIndex); } else { hit = Physics2D.Raycast(transform.position, Vector3.right, 0.64f, layerIndex); } // Hits a block and stops the player if (hit.collider != null && hit.collider.tag == "Block") { // Makes sure the player can still collide with the door to finish the game if(hit.collider.name == "FinishDoor") { isTouchingWall = false; } else { isTouchingWall = true; } } else { isTouchingWall = false; } UpdateMovement(); UpdateJump(); rigidBody.position += new Vector2(displacement.x, displacement.y); } private void UpdateMovement() { float walkDirection = Input.GetAxis("Horizontal"); // Determines the direction the player is facing if(walkDirection < 0) { isLeft = true; } else if(walkDirection > 0) { isLeft = false; } // Add movement from walking around if(!((isUp || isCrouch) && isGrounded) && !isTouchingWall) // Prevents Pit from moving along the ground while aiming up or crouching or touching a wall { displacement.x += walkDirection * walkSpeed * Time.deltaTime; } } private void UpdateJump() { bool jumpKeyDown = Input.GetButtonDown("Jump") || (jumpTimer > 0 && Input.GetButton("Jump")); float prevJumpTimer = jumpTimer; float deltaTimeSinceLastJump = Time.deltaTime; // If no jump key is down, or the jump has finished, the player can no longer jump if (!jumpKeyDown || jumpTimer >= totalJumpTime) { canJump = false; } // Otherwise, if the player can jump, jump! else if (canJump) { // Play the jump sound if this is the beginning of the jump if (jumpTimer == 0) { audioManager.PlayJumpSound(); } if (jumpTimer < totalJumpTime) { jumpTimer += deltaTimeSinceLastJump; // Do not allow the jump timer to exceed the total time allow for holding a jump if (jumpTimer > totalJumpTime) { deltaTimeSinceLastJump = jumpTimer - totalJumpTime; jumpTimer = totalJumpTime; } } } // If the player is jumping, prepare to move this game object up in space if (canJump && jumpTimer > 0) { displacement += Vector2.up * CalculateJumpIncrement(prevJumpTimer, jumpTimer); // Undo the gravity that was applied since the jump calculation will already be parabolic Vector2 currentFrameGravity = Physics2D.gravity * (jumpTimer - prevJumpTimer) * rigidBody.mass; rigidBody.AddForce(-currentFrameGravity, ForceMode2D.Impulse); } // Reset jump variables if grounded and can jump if (isGrounded && !canJump) { ResetJump(); } } /** * Returns the distance the player should rise from t0 seconds to t1 seconds in the jump cycle to abide by a parabolic motion */ private float CalculateJumpIncrement(float t0, float t1) { float offset0 = t0 - totalJumpTime; float offset1 = t1 - totalJumpTime; return (jumpHeight / (totalJumpTime * totalJumpTime * totalJumpTime)) * (offset1 * offset1 * offset1 - offset0 * offset0 * offset0); } private void ResetJump() { jumpTimer = 0; canJump = true; } }