Beruflich Dokumente
Kultur Dokumente
Contenido
5. Convirtiendo las ideas en código — Parte 2 ........................................................ 2
Esto nos da una escena básica que podemos utilizar. Ahora para construir los
conceptos básicos de un controlador de cámara en primera personal, vamos a
añadir un scirpt llamado FPSAim.cs al objeto de la cámara principal.
Para mover la cámara, en primer lugar, vamos a querer comprobar los datos que la
clase Input nos dará. Comenzando con Input.mousePosition, donde podemos
ver que consta de un Vector3. Input tiene muchas variables y funciones
estáticas que podemos usar, pero vamos a ver las otras variables y funciones
disponibles más adelante.
Por lo tanto, una variable Vector3 mousePosition será útil aquí. Sin embargo, el
mouse es un dispositivo 2D, así que ¿como son los datos que arroja el mouse?
void Update ()
{
Vector3 mousePosition = Input.mousePosition;
Debug.Log(mousePosition);
}
void Update ()
{
Vector3 mousePosition = Input.mousePosition;
float mouseX = mousePosition.x;
float mouseY = mousePosition.y;
}
Debug.Log(transform.localRotation);
Debug.log (transform.eulerAngles);
Sin embargo, esto no tiene el efecto deseado. Estamos limitados por el tamaño de
nuestro monitor. Cuando el ratón llega a la parte superior del monitor, estamos
atascados y no podemos mirar hacia arriba o hacia abajo más allá de lo que nuestro
monitor nos permita. Tal vez tenemos los datos equivocados!
void Update ()
{
float mouseX = Input.GetAxis("Mouse X");
Debug.Log(mouseX);
transform.eulerAngles = new Vector3(0, mouseX, 0);
}
float mouseX;
void Update ()
{
mouseX += Input.GetAxis("Mouse X");
Debug.Log(mouseX);
transform.eulerAngles = new Vector3(0, mouseX,0);
}
Esto significa que tendremos una variable de clase que no se restablece cada vez
que la función Update se llama. Luego cambiamos el mouseX = input... por
mouseX += Input.GetAxis("MouseX");.
Probamos esto otra vez y nos da un comportamiento más agradable. Ahora que
tenemos esto, debemos hacer la misma configuración para mouseY.
float mouseX;
float mouseY;
void Update ()
{
mouseX + = Input.GetAxis("Mouse X");
mouseY + = Input.GetAxis("Mouse Y");
transform.eulerAngles = new Vector3(mouseY, mouseX,
0);
}
Hasta ahora, todo bien. Sin embargo, la mira invertida. Si esto sigue el diseño,
entonces no hay problema, pero es mejor añadir una opción para que el jugador
escoja si usar el mouse invertido o no.
float mouseX;
float mouseY;
public bool InvertedMouse;
void Update ()
{
mouseX + = Input.GetAxis("Mouse X");
if (InvertedMouse)
{
mouseY + = Input.GetAxis("Mouse Y");
} else
{
mouseY - = Input.GetAxis("Mouse Y");
}
Debug.Log(mouseX);
transform.eulerAngles = new Vector3(mouseY, mouseX,
0);
}
void Update ()
{
if(Input.GetKey(KeyCode.W))
{
transform.position + = transform.forward;
}
}
Este código nos permitirá desacelerar el movimiento a una tasa más controlable. Al
final el código completo de la clase FPSMove.cs Luciría así:
void Update ()
{
if (Input.GetKey(KeyCode.W))
{
transform.position + = transform.forward * speed;
}
if (Input.GetKey(KeyCode.S))
{
transform.position - = transform.forward * speed;
}
if (Input.GetKey(KeyCode.A))
{
transform.position - = transform.right * speed;
}
if (Input.GetKey(KeyCode.D))
{
transform.position + = transform.right * speed;
}
}
Nos daremos cuenta que también hay un up. Muchos otros valores estáticos se
encuentran aquí. Es importante aprender que Unity ha organizado los datos en cada
clase de tal manera que podamos hacer uso de ellos.
Bueno, ahora tenemos un par de scripts bastante útiles y sencillos, que podemos
adjuntar a cualquier objeto en una escena. Sin embargo, aún debemos evitar que la
cámara traspase el suelo. Si añadimos un Rigidbody y un Capsule Collider a
la cámara principal, el objeto chocará con la tierra y todo lo que posea un collider.
Los colisionadores de física son estructuras de datos complejas que solidifican los
objetos en un juego. Podemos utilizar estas estructuras para hacer que los objetos
se sientan y actúen pesados. Es posible que la cámara resbale un poco como si
estuviéramos caminando sobre el hielo. Esto se debe a que el Rigidbody tiene un
Drag (Fricción) de 0 por defecto. Cambiarlo a 1 evitará esto.
6. Instrucciones de salto
Supongamos que vamos a buscar un crayón de un color en específico en escritorio
de 10 cajones. Después de revisar en diferentes gavetas, encontramos el crayón
que queremos y dejamos de mirar a través del resto de los cajones. Hemos utilizado
una instrucción de salto y retomamos nuestro trabajo con el crayón en las manos.
Las palabras clave break , return y continue nos permiten dar saltos en el
código y así conrtrolar la ejecución del código. Miremos return por ahora.
6.1. Return
Esta palabra clave convierte una función en datos. Hay un par de condiciones que
deben cumplirse antes de que esta funcione. Hasta ahora, hemos estado utilizando
la palabra clave void para declarar el tipo de retorno de una función.
void MyFunction()
{
//código ...
}
void MyFunction()
{
//código ...
return;
}
Esta función no devuelve nada, pero eso tiene un significado más profundo. La
palabra clave void al inicio de la declaración de la función significa que esta función
no tiene un tipo de retorno. Si cambiamos la declaración, tenemos que asegurarnos
de que hay un valor de retorno que corresponda la declaración. Esto puede ser tan
simple como el fragmento de código siguiente.
int MyFunction()
{
//código ...
return 1;// 1 es un int
}
Esta función devuelve un int 1. Declarar una función con un valor de retorno
requiere que el tipo de retorno y el de la declaración concuerden.
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour
{
int MyNumber()
{
return 7;
}
void Start ()
{
int a = MyNumber();
print (a);
}
}
En este fragmento, tenemos int MyAdd (int a, int b), que luego le asignamos a
int a en la función Start. Al final se Imprime 13 en la consola. Podemos saltar un
paso para hacer que el código quede un poco más corto.
void Start ()
{
print (MyAdd(6, 7));
}
using UnityEngine;
using System.Collections;
public class ReturnZombie : MonoBehaviour
{
//retorna un Zombie
Zombie GetZombie()
{
return (Zombie) GameObject.FindObjectOfType
(typeof(Zombie));
}
}
void Update ()
{
Debug.DrawLine(transform.position,
GetZombie().transform.position);
}
void Update ()
{
Zombie target = GetZombie();
if (target != null)
{
Debug.DrawLine(transform.position,
target.transform.position);
}
}
Si añadimos una comprobación para ver si Zombie es nulo, vamos a ser capaces de
evitar dar a la función Debug.DrawLine datos inexistentes. Por lo tanto, creamos
una variable local a la función. Añadimos Zombie target = GetZombie(); y
ahora tenemos una variable que puede ser nulo o un Zombie.
void Start ()
{
float temp = 90f;
bool sunny = true;
if (temp > 60 && sunny)
{
print("Hora de nadar!");
}
}
void Start ()
{
if (true)
{
if (true)
{
print("Esto podría ser más simple.");
}
}
}
El uso de más de una sentencia if debe parecer un poco torpe. La razón de utilizar
operadores condicionales es simplificar el número de sentencias if utilizadas.
void Start ()
{
if (true && true)
{
print("Ambos lados del && son verdaderos");
}
}
void Start ()
{
if (false && true)
{
print("Esto no se escribirá jamas");
}
}
La declaración anterior tiene un valor falso y no será evaluado. Todas las
condiciones de los argumentos del caso de los estados tienen que ser verdaderos
para que las instrucciones encapsuladas se ejecuten. Esta lógica se puede ampliar
aún más con más de un operador condicional.
void Start ()
{
if (true && true && true)
{
print("Esto si se imprime!");
}
if (true && true && true && true && true && true &&
false)
{
print("pero esto no");//un false al final lo daña todo
}
}
void Start ()
{
if (true || false)
{
print("Esto se imprime");
}
}
void Start ()
{
if (false || false)
{
print("No se imprime");
}
if (false || false || true)
{
print("Se imprime");
}
if (false || false || false || false || false || false ||
true)
{
print("Y también imprime!");
// solo necesita de un true para funcionar!
}
}
void Start ()
{
if (false || true && true)
{
print("Esto se imprime");
}
}
Es difícil adivinar de inmediato lo que podría suceder. Para hacer las cosas más
claras, podemos usar paréntesis como lo hicimos con los números.
void Start ()
{
if ((false || true) && true)
{
print("Vuelve e imprime");
}
}
void Start ()
{
int enemyHealth = 10;
int myHealth = 1;
bool ImStronger = MyHealth > EnemyHealth;
if (ImStronger)
{
print("Yo le puedo ganar!");
}
}
Con el código anterior, el operador relacional nos dice que estamos claramente en
desventaja. Sin embargo, podemos añadir un poco de información adicional para
tomar una decisión mejor informada.
void Start ()
{
int enemyHealth = 10;
int myHealth = 1;
bool imStronger = myHealth > enemyHealth;
int enemyBullets = 0;
int myBullets = 11;
bool imArmed = myBullets > enemyBullets;
if (imStronger || imArmed)
{
print("Puedo ganar!");
}
}
Si estamos mejor armados que nuestro enemigo, entonces debemos tener una
mejor oportunidad de ganar. Por lo tanto, en este caso, si imStronger o imArmed
son verdaderas entonces es posible que "pueda ganar!".
Para empeorar las cosas, si necesitamos procesar cada valor, habría que ocuparse
de cada variable por su nombre. Para comprobar si score2 es superior score1,
habría que escribir una función específicamente para comprobar esas dos variables.
Benditas sean las matrices.
using UnityEngine;
using System.Collections;
La inclusión de los corchetes indica al compilador que se está creando una matriz
de enteros y se llama scores. Debido a que estamos trabajando con un tipo int,
cada valor almacenado en la matriz se inicializa en 0. Un nuevo array int[]se
debe crear antes de que ser asignada a la matriz scores.
La mejor parte es que la matriz scores se maneja como un solo objeto, vamos a
ver lo que eso significa en un momento. En lugar de escribir una función que
procesa un valor numérico a la vez, podemos escribir una función que utilice la
matriz y todos sus valores a la vez.
Copiar int [10] crea una matriz con 10 elementos. Como se pueden imaginar, se
pueden hacer matrices grandes o pequeñas, cambiando el número entre corchetes.
No hay limitaciones en el tamaño que se puede asignar a una matriz. Algo de
sentido común debería aplicarse, ya que una matriz con muchos miles de millones
de valores podría no ser tan útil.
Cada fragmento de datos de la matriz se encuentra en lo que se llama un índice, un
número entero. Las matrices se pueden crear a partir de algo que no sea int
también.
Los float, int y otros números en general tendrá 0 como valor predeterminado en
la matriz cuando se crean. Los string, por otro lado, se inicializan con null en la
matriz, o mejor dicho, no tienen ningún valor asignado. Hay que recordar que una
matriz puede contener un solo tipo en todas sus entradas. Por lo tanto, una matriz
de string sólo puede contener string, una matriz de int sólo puede contener
int y así sucesivamente.
La convención de utilizar un plural hace entender la variable con más facilidad, pero
de ninguna manera es necesario para la identificación de una matriz.
void Start ()
{
Debug.Log(myGameObjects.Length);
}
6
UnityEngine.Debug:Log(Object)
ArraysAFirstLook:Start () (at Assets/ArraysAFirstLook.cs:16)
Hasta aquí todo bien, pero ¿cómo podemos usar esto? Ahora que tenemos una
gran variedad de objetos en la escena, que seremos capaces de manipular en un
bucle for.
void Start ()
{
Debug.Log(myGameObjects.Length);
for (int i = 0; i < myGameObjects.Length; i++)
{
myGameObjects [i].name = i.ToString();
}
}
Este código cambia los nombres de los objetos en la matriz uno por uno. La
propiedad de la matriz .length devuelve un valor entero que podemos utilizar de
varias maneras, el uso más práctico es para establecer el número de iteraciones de
un bucle for.
8.2. Foreach
Este bucle también refleja muy bien cómo se organizan los objetos de la matriz.
Como habíamos experimentado antes con bucles, podemos usar la matriz en una
multitud de formas. Una vez que hemos cambiado los nombres de cada objeto,
podemos mostrar su nombre.
void Start ()
{
Debug.Log(myGameObjects.Length);
for (int i = 0; i < myGameObjects.Length; i++)
{
myGameObjects [i].name = i.ToString();
}
foreach (GameObject go in myGameObjects)
{
Debug.Log(go.name);
}
}
void Start ()
{
float[] dynamicFloats = new float[10];
}
También podemos inicializar un nuevo array en una función. Esto significa que la
matriz sólo existe en el ámbito de la función y no se puede acceder desde fuera de
esa función. Es importante saber que el tamaño de una matriz se determina antes
de su uso. Cuando la matriz se declara en esta forma, el número de objetos que la
matriz puede contener debe estar definido.
La inicialización se divide en dos declaraciones. La primera línea le dice a C# que
estamos creando una variable float[] identificada como dynamicFloats.
Despues, tenemos que llenar esta variable con una nueva matriz. A esta se le
asigna una nueva matriz tipo float con 10 índices.
No podemos asignarle a la variable una matriz de diferente tipo.
Float[] dynamicFloats;
dynamicFloats = new int[10]; // ERROR
Por otro lado, podemos rellenar una matriz con información predeterminada.
La declaración anterior establece una matriz que tiene siete miembros y asigna a
cada indice el valor de un número primo.
void Start ()
{
int[] scores = new int[10];
int i = 0;
while(i < 10)
{
print(scores[i]);
i++;
}
}
En este punto, todos los valores son efectivamente cero, por lo que vamos a obtener
10 ceros impresos en la consola. Sin embargo, hay algunas cosas interesantes que
señalar aquí. En primer lugar, int i se inicializa a 0 por delante del bucle while,
por lo que las matrices comienzan en cero. La siguiente cosa interesante es cómo
se accede a los números almacenados en scores[].
scores[0] = 10;
void Start ()
{
int[] scores = new int[10];
int i = 0;
while(i < 10)
{
scores[i] = Random.Range(0, 100);
print (scores[i]);
i++;
}
}
Con este código, estamos usando una clase llamada Random y el uso de su función
miembro Range, que retorna un valor aleatorio entre 0 y 100, valor asignado al
index equivalente a i. Al inicio i se establece en 0. El bucle comienza con
scores[0] se establece en un número aleatorio entre 0 y 100.
Al final del bloque, la i se incrementa en 1 y el bucle while comienza de nuevo. La
próxima vez, sin embargo, estamos estableciendo a scores[1] un número
aleatorio entre 0 y 100.
8.4.2. Obteniendo valores de una matriz
Al igual que cuando lo establecimos, podemos acceder al valor de cada espacio en
la matriz
void Start ()
{
int[] scores = new int[10];
int i = 0;
while(i < 10)
{
scores[i] = Random.Range(0, 100);
En la línea int score = scores [i];, estamos creando una variable entera a la
que se le asignará el valor en el índice actual.
Este documento es una traducción y modificación del libro “Learning C# with Unity
3D” de Alex Okita.