¿Qué son las Code Katas?
«Code Kata» es un término acuñado por Dave Thomas, co-autor del libro «The Pragmatic Programmer», con referencia al concepto japonés de kata en las artes marciales. Una kata de código, más vulgarmente conocida, es un ejercicio de programación que ayuda a un programador a perfeccionar sus habilidades a través de la práctica y la repetición.
Nuestra Primera Kata
En nuestro caso, nos juntamos todos los miembros del equipo y decidimos hacer una kata de Unity, en la que @maktub82 y un servidor @CarlosKlsOne guiamos al equipo para realizar un pequeño ejemplo de juego. El día en concreto, al iniciar la kata, propusimos varias ideas de juegos a elegir, como un plataformas, un runner, un top down shooter y varias opciones más. Con una votación y consenso salió ganador hacer una especie de shooter con plataformas en 2d, al estilo «Metal Slug».
Con esto decidido nos pusimos en marcha.
Desarrollo de la Kata
Os recomiendo leer mi primer Post sobre Unity, dónde se ven conceptos básicos sobre este motor para iniciados, ya que aquí me limitaré a contar como fue el desarrollo de la Kata y no el paso por paso.
Tras una pequeña introducción de lo que ofrece Unity y unas nociones básicas de GameObjects, Components, etc. para los iniciados, empezamos creando el PlayerController, clase con la que controlaremos las acciones del personaje principal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
using UnityEngine; public class PlayerController : MonoBehaviour { public float Speed = 10; public float JumpForce = 10; public int MaxJump = 2; private Rigidbody2D _rigidBody2D; private int _jumpCount = 0; void Start() { _rigidBody2D = GetComponent<Rigidbody2D>(); } void FixedUpdate() { var horizontalVelocity = Input.GetAxis("Horizontal"); _rigidBody2D.velocity = new Vector2(horizontalVelocity * Speed, _rigidBody2D.velocity.y); if (horizontalVelocity < 0) gameObject.transform.localScale = new Vector3(-1, 1, 1); if(horizontalVelocity > 0) gameObject.transform.localScale = new Vector3(1, 1, 1); if (Input.GetButtonDown("Jump") && _jumpCount < MaxJump) { _rigidBody2D.AddForce(new Vector2(0, JumpForce), ForceMode2D.Impulse); _jumpCount++; } if (Input.GetButtonDown("Fire1")) { var target = transform.FindChild("Target"); var bullet = Instantiate(Resources.Load("Bullet"), target.position, Quaternion.identity) as GameObject; bullet.GetComponent<Bullet>().Shot((int)gameObject.transform.localScale.x); Physics2D.IgnoreCollision(gameObject.GetComponent<Collider2D>(), bullet.GetComponent<Collider2D>()); } } private void OnCollisionEnter2D(Collision2D collision) { if(collision.gameObject.tag == "Ground") { _jumpCount = 0; } else if(collision.gameObject.tag == "Enemy") { GameController.GetInstance().GameOver(); DestroyObject(gameObject); } } } |
Como vemos, analizando la clase por partes, es muy sencilla, en ella damos movimiento al personaje mediante las teclas horizontales, que por defecto son la ‘flecha derecha’, ‘flecha izquierda’, ‘A’ y ‘D’:
1 2 |
var horizontalVelocity = Input.GetAxis("Horizontal"); _rigidBody2D.velocity = new Vector2(horizontalVelocity * Speed, _rigidBody2D.velocity.y); |
con esto nuestro personaje se moverá en el eje de las x a la velocidad especificada en la variable ‘Speed’.
Por otro lado controlamos también las teclas de salto y disparo, para que nuestro personaje salte y dispare cuando se pulse las teclas mapeadas, que por defecto son la barra espaciadora y el botón izquierdo del ratón respectivamente.
1 2 3 4 5 6 7 8 9 10 11 12 |
if (Input.GetButtonDown("Jump") && _jumpCount < MaxJump) { _rigidBody2D.AddForce(new Vector2(0, JumpForce), ForceMode2D.Impulse); _jumpCount++; } if (Input.GetButtonDown("Fire1")) { var target = transform.FindChild("Target"); var bullet = Instantiate(Resources.Load("Bullet"), target.position, Quaternion.identity) as GameObject; bullet.GetComponent<Bullet>().Shot((int)gameObject.transform.localScale.x); Physics2D.IgnoreCollision(gameObject.GetComponent<Collider2D>(), bullet.GetComponent<Collider2D>()); } |
Para el salto controlamos que no se pueda saltar más de dos veces y para el disparo utilizamos un objeto target para indicar de donde sale la bala en el sprite. Y también llamamos al método Shot(), que hace que la bala se mueva, lo veremos en la clase Bullet.
En esta clase también controlamos las colisiones con otros objetos.
1 2 3 4 5 6 7 8 9 10 11 12 |
private void OnCollisionEnter2D(Collision2D collision) { if(collision.gameObject.tag == "Ground") { _jumpCount = 0; } else if(collision.gameObject.tag == "Enemy") { GameController.GetInstance().GameOver(); DestroyObject(gameObject); } } |
Cuando el personaje colisiona con el suelo reiniciamos el contador de saltos y si colisionamos con un enemigo lanzamos el Game Over y eliminamos al jugador de la escena.
Lo siguiente que hicimos es crear la clase para controlar la bala:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using UnityEngine; public class Bullet : MonoBehaviour { public float Speed = 50; private void OnBecameInvisible() { DestroyObject(gameObject); } private void OnCollisionEnter2D(Collision2D collision) { DestroyObject(gameObject); if (collision.gameObject.tag == "Enemy") { DestroyObject(collision.gameObject); GameController.GetInstance().AddScore(1); } } public void Shot(int direction) { transform.GetComponent<Rigidbody2D>().velocity = new Vector2(Speed * direction, 0); } } |
Aquí vemos como eliminar la bala de la escena si sale de la vista de la cámara:
1 2 3 4 |
private void OnBecameInvisible() { DestroyObject(gameObject); } |
Como en la anterior clase, también controlamos las colisiones de la bala con el entorno, para eliminarla si colisiona, y si lo hace con un enemigo aumentar la puntuación, que veremos más adelante. Y también vemos el método Shot() del que hablé antes, se llama cuando el personaje pulsa el botón de disparo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private void OnCollisionEnter2D(Collision2D collision) { DestroyObject(gameObject); if (collision.gameObject.tag == "Enemy") { DestroyObject(collision.gameObject); GameController.GetInstance().AddScore(1); } } public void Shot(int direction) { transform.GetComponent<Rigidbody2D>().velocity = new Vector2(Speed * direction, 0); } |
Hasta aquí teníamos un personaje que se movía por las plataformas, saltando y disparando.
Después añadimos una clase muy simple para que la cámara siguiese al personaje:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraController : MonoBehaviour { public GameObject Player; void Update () { transform.position = new Vector3(Player.transform.position.x, Player.transform.position.y, transform.position.z); } } |
Accedemos al objeto Player para saber su posición y se la asignamos a la cámara.
Para que las balas tuvieran sentido y no disparar a la nada, lo siguiente que hicimos fue crear un enemigo con un movimiento simple que se mueve entre dos puntos en vertical.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using UnityEngine; public class Enemy : MonoBehaviour { public float Speed = 5; public Transform UpPoint; public Transform DownPoint; private bool _isUpMovement = false; void Update() { if (_isUpMovement) { transform.position = Vector2.MoveTowards(transform.position, UpPoint.position, Speed * Time.deltaTime); } else { transform.position = Vector2.MoveTowards(transform.position, DownPoint.position, Speed * Time.deltaTime); } if(Vector2.Distance(transform.position, UpPoint.position) < 1) _isUpMovement = false; if (Vector2.Distance(transform.position, DownPoint.position) < 1) _isUpMovement = true; } } |
No tiene mucho misterio, este componente también sirve para hacer plataformas móviles o ascensores si se trabaja un poco más, para que el personaje se desplace encima de él. Pero en este caso si lo tocas te mata, y si lo toca una bala ocurre al contrario.
Por último, para controlar la escena o el juego en general se suelen crear clases GameController o GameManager, nosotros la hicimos para controlar los objetos del interfaz, la puntuación, el game over y en este último caso para restablecer la escena si acaba el juego.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class GameController : MonoBehaviour { private static GameController _gameController; public GameObject GameOverLayer; public Text Text; private int _score = 0; private bool _isDead = false; private void Start() { _gameController = GetComponent<GameController>(); UpdateScore(); } private void Update() { if(_isDead && Input.GetKeyDown(KeyCode.R)) { Restart(); } } public static GameController GetInstance() { return _gameController; } public void AddScore(int score) { _score += score; UpdateScore(); } public void GameOver() { GameOverLayer.SetActive(true); Time.timeScale = 0.3f; _isDead = true; } public void Restart() { SceneManager.LoadScene("MainScene"); Time.timeScale = 1.0f; } private void UpdateScore() { Text.text = string.Format("Score: {0}", _score); } } |
Y hasta aquí pudimos llegar en esta primera Kata.
El código completo está en este enlace de github.
Experiencia
Tras nuestra primera Kata en equipo, la experiencia ha sido muy positiva, a parte de aprender mucho durante el proceso, resolver dudas, debatir mejores formas de código, etc. nos divertimos mucho y compartimos buenos momentos. Y de esto han salido más ideas de Kata que se irán produciendo a lo largo del tiempo, sobre más tecnologías, y ahondar más en Unity. Quién sabe si en un futuro no sacamos un Triple A de aquí? estad atentos a los avances.
Resultados
Aunque suele haber un guía en las katas, todo el mundo puede aportar ideas y hacer cambios, en este caso tratamos de seguir todos al mismo paso el desarrollo del juego, pero cada uno a su manera, así que aunque todos teníamos un personaje que saltaba y disparaba a un enemigo, los resultados fueron muy diferentes. He aquí las diferentes versiones de algunos de los participantes:
Deja un comentario