A la hora de trabajar con nuestros datos necesitaremos organizarlos de algún modo. Para ello usaremos collections, que nos permitirán almacenar, ordenar y gestionar esos datos.
Supongamos que tenemos varias abejas obreras, representadas por la clase Worker
. Ahora deseamos definir los trabajos permitidos en esta clase. Para esto usaremos un enum, un tipo de datos que permite ciertos valores para esa parte de datos. Lo definiremos del siguiente modo:
enum Job
{
NectarCollector,
StingPatrol,
HiveMaintenance,
BabyBeeTutoring,
EggCare,
HoneyManufacturing,
}
El nombre del enum será Job. Cada uno de sus elementos (enumerators) será uno de los valores permitidos para Job y estarán dentro de unas llaves y separados entre sí por comas. La coma del último elemento podría eliminarse.
Ahora, siempre y cuando el constructor de Worker acepte Workers.Jobs como su parámetro type, podemos hacer referencia con types como este:
Worker nanny = new Worker (Job.EggCare);
Con Job accedemos al Enum y con .EggCare accedemos al valor que deseamos dentro del enum Job. No podremos crear un nuevo valor para el enum.
Podemos asignar números a cada uno de los valores dentro del enum. A continuación se muestra con el enum TrickScore que almacenará las puntuaciones para los trucos de una competición de perros:
enum TrickScore
{
Sit = 7,
Beg = 25,
RollOver = 50,
Fetch = 10,
ComeHere = 5,
Speak = 30,
}
No tiene que haber un orden específico e incluso pueden asignarse varios nombres a un mismo número.
Aquí está un extracto de un método para usar TrickScore enum invocándolo a y desde un int.
int value = (int)TrickScore.Fetch * 3;
Debug.Log (value.ToString());
TrickScore score = (TrickScore)value;
Debug.Log (score.ToString ());
Puesto que Fetch tiene un valor de 10, se asignará 30 a int value. Luego se muestra en la Console (dentro de Unity) el valor de int value (30). A continuación invocamos un int de vuelta a TrickScore. Puesto que value es 30, TrickScore score toma el valor TrickScore.Speak. De modo que mostrando su valor en Console aparecerá «Speak».
Podemos invocar el enum como un número y hacer cálculos con él, o podemos usar el método ToString( ) para tratar el nombre como un string. Si no asignamos un número a los nombres, los elementos de la lista tomarán valores por defecto: primer elemento asignado a 0, segundo elemento asignado a 1, tercer elemento asignado a 2, etc.
En el siguiente ejemplo crearemos la clase Card. Esta clase Card tendrá dos propiedades públicas: Suit (el palo de la baraja: picas, tréboles, diamantes o corazones) y Value (Ace, two, three…ten, Jack, Queen, King). También tendrá una propiedad de sólo lectura Name (ejemplos de cómo será: Ace of Spades, Five of Diamonds). Para definir los valores de Suit y Value crearemos dos enum (Suits y Values). Por último se creará un objeto de la clase Card (card) pasándole como parámetros su Suit y Value correspondientes (tal como aparece en el constructor) y se mostrará en Console su Name. Para estos parámetros se usará la función Range de la clase Random, que tomará un valor aleatorio dentro del rango que le pasemos en los argumentos.
enum Suits
{
Spades,
Clubs,
Diamonds,
Hearts
}
enum Values
{
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13
}
class Card
{
public Suits Suit { get; set; }
public Values Value { get; set; }
public Card(Suits suit, Values value)
{
this.Suit = suit;
this.Value = value;
}
public string Name
{
get { return Value.ToString () + " of " + Suit.ToString (); }
}
}
void Start()
{
Card card = new Card ((Suits)Random.Range (0, 4), (Values)Random.Range (1, 14));
Debug.Log (card.Name);
}
Si quisiésemos crear una clase para representar la baraja de cartas, necesitaríamos que hiciese un seguimiento de todas las cartas y conocer el orden en el que están. Podría usarse un array de Card, donde la carta de arriba estaría en la posición 0 del array, la siguiente en el 1, etc.
class Deck
{
private Card[] cards =
{
new Card (Suits.Spades, Values.Ace),
new Card (Suits.Spades, Values.Two),
new Card (Suits.Spades, Values.Three),
// ...
new Card (Suits.Diamonds, Values.Queen),
new Card (Suits.Diamonds, Values.King),
};
public void PrintCards()
{
for (int i = 0; i < cards.Length; i++)
{
Debug.Log (cards[i].Name);
}
}
}
Sin embargo, con este array no podemos cambiar el orden o añadir/eliminar cartas de la baraja fácilmente. Un array está bien para almacenar una lista fija de valores o referencias. Pero una vez que necesitas cambiar de posición o añadir más elementos de los que el array puede almacenar, empiezan los problemas.
- Todo array tiene una longitud y necesitamos conocer dicha longitud para trabajar con él. Podríamos usar refenrencias null para mantener elementos del array vacíos. En el siguiente ejemplo se muestra un array de longitud 7 que almacena 3 cartas e indexa las posiciones 3,4,5 y 6 a null (por lo que no almacenarán nada).
- Necesitaríamos hacer un seguimiento de cuántas cartas están siendo almacenadas. Por lo que necesitaríamos un int topCard que indicase el índice de la última carta en el array. Siguiendo el ejemplo anterior la longintud sería 7, mientras que topCard sería igual a 3. Todo índice por encima de topCard tendrá como referencia null.
- Ahora las cosas se complican. Es fácil añadir un método Peek() para devolver la referencia a la última carta, de manera que podamos mirar en el tope de la baraja. Si queremos añadir una carta y topCard es menor que la longitud del array, podemos simplemente almacenar la carta en el índice de topCard y añadirle 1 a topCard. Pero si el array está lleno necesitaremos crear un nuevo array de mayor tamaño o redimensionar el que ya tenemos (método Array.Resize( ) en .NETFramework). Eliminar una carta es fácil si se trata de la última carta, sólo tendremos que eliminarla del array, poner una referencia a null en su posición y decrementar topCard. Sin embargo, si la carta que deseamos eliminar está en medio del array tendremos que reordenar todo el array corriendo los valores de la derecha a la izquierda.
Para manejar estos inconvenientes de añadir y eliminar elementos de un array tenemos .NET Framewortk, que tiene clases con colecciones para este fin. La colección más común es List<T>
. Una vez creado un objeto List<T>, es fácil añadir, eliminar, observar o mover un elemento de la lista. En Unity deberemos incluir using System.Collections.Generic;
. La lista funcionará del siguiente modo:
- Primero crearemos una nueva instancia de List<T> (T será sustituido por el tipo de la lista).
Todo array tiene un tipo (int, Card, etc.). Lo mismo con las listas. Deberemos especificar su tipo entre los símbolos < > .
List<Card> cards = new List<Card> ();
- Ahora podemos añadir a nuestra List<T>.
Una vez que tenemos un objeto List<T>, podemos añadir tantos elementos como queramos (siempre que sean asignables a su tipo). Para ello se usará el método Add( ). La lista mantendrá sus elementos en orden, como un array.
cards.Add (new Card (Suits.Diamonds, Values.King));
cards.Add (new Card (Suits.Clubs, Values.Three));
cards.Add (new Card (Suits.Hearts, Values.Ace));
Podríamos mostrar por Console esta list mediante:
for(int i = 0; i < 3; i++)
{
Debug.Log (cards[i].Name);
}
Una lista será más flexible que un array. Ahora se mostrarán algunas de sus funcionalidades, para más información pulsa aquí:
List<Egg> myCarton = new List<Egg>();
Una nueva lista no contendrá nada.
Egg x = new Egg();
myCarton.Add(x);
Ahora la lista se expande para almacenar el objeto Egg.
Egg y = new Egg();
myCarton.Add(y);
Se expandirá de nuevo para almacenar este segundo objeto Egg.
- Averiguar cuántas cosas contiene.
int theSize = myCarton.Count;
- Averiguar si contiene algo en particular.
bool isIn = myCarton.Contains(x);
Devolverá true si encuentra el objeto x dentro de myCarton.
- Descubrir dónde está una cosa en concreto.
int idx = myCarton.IndexOf(y);
El índice para x sería 0 y para y sería 1.
- Eliminar algo concreto dentro de ella.
myCarton.Remove(y);
Se eliminará y de la lista, por lo que myCarton ya sólo contendrá x.
Las listas encogen y crecen dinámicamente, por lo que no necesitarás saber su tamaño exacto a la hora de crearlas. A continuación se mostrará un ejemplo con unos cuantos métodos empleados para trabajar con listas.
class Shoe
{
public Style Style;
public string Color;
}
enum Style
{
Sneakers,
Loafers,
Sandals,
Flipflops,
Wingtips,
Clogs,
}
Esta será la clase Shoe y el enum Style que se usarán en el ejemplo.
void Start ()
{
List<Shoe> shoeCloset = new List<Shoe> ();
// Declaración de la lista.
shoeCloset.Add (new Shoe ()
{ Style = Style.Sneakers, Color = "Black" });
// Se pueden usar nuevas sentencias dentro del método List.Add( ).
shoeCloset.Add (new Shoe ()
{ Style = Style.Clogs, Color = "Brown" });
shoeCloset.Add (new Shoe ()
{ Style = Style.Wingtips, Color = "Black" });
shoeCloset.Add (new Shoe ()
{ Style = Style.Loafers, Color = "White" });
shoeCloset.Add (new Shoe ()
{ Style = Style.Loafers, Color = "Red" });
shoeCloset.Add (new Shoe ()
{ Style = Style.Sneakers, Color = "Green" });
int numberOfShoes = shoeCloset.Count;
// Devuelve el número total de objetos en la lista
foreach (Shoe shoe in shoeCloset)
// El bucle foreach pasa por cada uno de los elementos Shoe de la lista shoeCloset.
{
shoe.Style = Style.Flipflops;
shoe.Color = "Orange";
}
shoeCloset.RemoveAt (4);
// El método Remove( ) eliminará el objeto; RemoveAt( ) eliminará el objeto del índice que le pasemos.
Shoe thirdShoe = shoeCloset [2];
Shoe secondShoe = shoeCloset [1];
shoeCloset.Clear();
// El método Clear( ) elimina todos los objetos de la lista.
shoeCloset.Add (thirdShoe);
if (shoeCloset.Contains (secondShoe))
// El método Contains( ) devuelve true si el objeto está contenido en la lista y false de no ser así.
{
Debug.Log ("That's surprising.");
}
}
Capturas del código escrito usando MonoDevelop (recordad incluir System.Collections.Generics en vuestro código para poder trabajar con la clase List<T>):
Debemos recordar que los enums son types (tipos), mientras que las Lists (listas) son objetos. Una lista puede almacenar cualquier cosa (dependiendo del tipo de datos que le marquemos que va a almacenar) y cada elemento tendrá sus propiedades y métodos. Los enums tienen que ser asignados a uno de los tipos de valores de C#. Por otra parte, si tenemos un número fijo de elementos con los que vamos a trabajar que se mantendrá constante, podremos trabajar con arrays en vez de listas sin ningún problema. Usando ToArray( ) podemos pasar una lista a un array. Existe también un constructor para crear una lista a partir de un array.
Al inicializar una lista podemos pasarle unos objetos iniciales que serán añadidos a la lista con su creación.
List<Shoe> shoeCloset = new List<Shoe> ();
shoeCloset.Add (new Shoe () { Style = Style.Sneakers, Color = "Black" });
shoeCloset.Add (new Shoe () { Style = Style.Clogs, Color = "Brown" });
shoeCloset.Add (new Shoe () { Style = Style.Wingtips, Color = "Black" });
shoeCloset.Add (new Shoe () { Style = Style.Loafers, Color = "White" });
shoeCloset.Add (new Shoe () { Style = Style.Loafers, Color = "Red" });
shoeCloset.Add (new Shoe () { Style = Style.Sneakers, Color = "Green" });
En este código se crea la lista shoeCloset y posteriormente se le añaden mediante el método Add( ) varios objetos. Para inicializar la lista directamente con esos objetos haremos lo siguiente:
List<Shoe> shoeCloset = new List<Shoe> ()
{
new Shoe () { Style = Style.Sneakers, Color = "Black" },
new Shoe () { Style = Style.Clogs, Color = "Brown" },
new Shoe () { Style = Style.Wingtips, Color = "Black" },
new Shoe () { Style = Style.Loafers, Color = "White" },
new Shoe () { Style = Style.Loafers, Color = "Red" },
new Shoe () { Style = Style.Sneakers, Color = "Green" },
};
Creamos unas llaves donde incluiremos los elementos que se inicializarán con la lista. Seguirá el mismo esquema que en el anterior código pero sin la necesidad del método Add( ). Además, los elementos estarán separados por comas y al final de las llaves se finalizará con punto y coma. No estaremos limitados a usar únicamente new en el inicializador, podrán ser usadas variables también. Posteriormente se le podrán seguir añadiendo nuevos objetos a la lista.
En resumen, la clase List<T> representa una lista de objetos fuertemente tipados a la que se puede obtener acceso por índice. Proporciona métodos para buscar, ordenar y manipular listas. En el lugar de la T escribiremos el tipo de datos que contendrán los objetos de la lista (int, string, etc). Los métodos explicados anteriormente son los siguientes:
- Add(T): añade un objeto al final de la lista.
- Clear( ): elimina todos los objetos de la lista.
- Count: devuelve el número de elementos que contiene la lista.
- Contains(T): averigua si un objeto está en la lista.
- IndexOf(T): busca un objeto y devuelve el índice del objeto en la lista.
- Remove(T): se eliminará el objeto de la lista.
- RemoveAt(int): se eliminará el objeto de la posición indicada.