XNA - AIDA

Anuncio
Introducción a la Programación de
Videojuegos y Gráficos
GRADO EN INGENIERÍA
INFORMÁTICA
Curso 2012/2013
T3: VIDEOJUEGOS 2D Y 3D
3.1. Estructura de un videojuego.
3.2. Motores gráficos(perspectivas, estructuras de datos y
algoritmos de visualización, navegación).
3.3. Física (conceptos, colisiones, proyectiles, motores de físicas).
3.4. Programación gráfica 2D (APIs gráficas).
3.5. Estructura de videojuegos 3D.
3.6. Programación gráfica 3D.
Estructura de los videojuegos.
• La estructura del código de un videojuego sigue un esquema
básico que en algunos casos difiere del de otros tipos de software.
• La estructura del código de un videojuego tiene la siguente
estructurá básica.
Inicialización
Bucle principal
Comprobar entrada
de usuario
Actualización de datos
Finalización
Sí
¿Fin del juego?
No
Salida
Inicialización.
• La inicialización de un videojuego usualmente tiene los
siguientes pasos:
• Comprobaciones de hardware.
• Inicialización de los datos del motor gráfico.
• Carga del estado del videojuego.
• Carga de los recursos iniciales (texturas, sonidos, etc).
• Posibles cinemáticas. (Nota: Se recomienda que este paso
pueda ser saltado mediante una entrada del usuario, ver el
mismo video de 10 minutos cada vez que se desea jugar es
bastante frustrante).
El bucle principal, entrada del usuario
• Una vez el usuario ha recibido una imagen del estado del juego, éste
puede producir (o no) algunos eventos adicionales.
• Los usuarios producen eventos de juego a través de los dispositivos
de salida.
•Teclado.
•Ratón.
•Mando de juegos.
•Micrófono.
• Estos eventos de usuario se combinan con la lógica del juego para
producir modificicaciones en el estado del juego.
La mayoría de estas tareas son gestionadas usualmente por el entorno
de trabajo (framework o herramienta) que estemos usando. Si
decidimos no usar un entorno de trabajo es necesario que nosotros
mismos implementemos todo este código.
El bucle principal, modificación de los datos
de estado.
• El videojuego tiene que mantener un conjunto de estructuras de
datos que representan el estado del juego (Nota: estos datos son
independientes de los datos que tiene que mantener el motor gráfico).
• Estos datos se modifican por muchos eventos producidos por la
lógica del juego. Algunos ejemplos de tales eventos son:
• Acciones del usuario.
• Entrada de datos por la red.
• Acciones de la Inteligencia Artificial del juego.
• Eventos en el tiempo.
• Eventos relacionados con la física (colisiones, explosiones,
etc).
• La modificación de los datos puede producir el final del juego al
alcanzar un estado en el cual el juego debe terminar.
El bucle principal. Salida
• Una vez que el estado del juego se ha modificado
necesitamos mostrar al usuario una representación
de dicho estado.
• Esta representación se muestra mediante
dispositivos de salida:
• Dispositivos gráficos (2D y 3D).
• Sonidos.
• Mensajes por la red.
• Mandos de juego (vibración).
Motores gráficos
• Los motores gráficos generan la representación gráfica del juego
mediante el uso de elementos gráficos más pequeños.
• El motor gráfico suele ser parte de un marco (framework) que suele
proporcionar la estructura del programa y métodos adicionales para
la gestión del teclado, reproducción de sonidos, actualización de los
datos.
• En este curso usaremos un marco de trabajo que ya incluye (entre
otras muchas cosas) un motor gráfico.
Motor gráfico y framework
• En este curso usaremos como marco de trabajo el XNA Game Studio
4.0.
• XNA (X Not Acronym) 4.0 is a una herramienta de programación
que permite usar Visual Studio para desarrollar juegos para
Windows (para PC o Smartphone) y XBOX 360.
• XNA 4.0 Game Studio incluye XNA Framework, un conjunto de
librerías diseñadas para el desarrollo de videojuegos y basadas en
.NET Framework 2.0.
http://msdn.microsoft.com/es-es/library/dd430289
• XNA Framework también incluye un motor gráfico, la mayoría
de los conceptos de los motores gráficos (estructuras de datos,
colisiones, partículas, perspectiva, etc.) se irán explicando a
medida que aparezcan.
Programación gráfica en 2D
• Para comenzar a desarrollar XNA 4.0. Necesitáis:
• Visual Studio 2010.
• .NET Framework (si no lo tenéis instalado
previamente, Visual Studio lo instalará).
• XNA Game Studio 4.0.
• Un vez tengáis el software necesario, simplemente
ejecutad Visual Studio 2010 en el menú Inicio.
La primera aplicación: Nuevo proyecto XNA 4.0
1.
En la barra de menú seleccionad File (Fichero) → New (Nuevo) → Project
(Proyecto). Aparecerá un diálogo. Seleccionad Windows Game (4.0) en las
plantillas de Visual C#. Elegid la carpeta y el nombre del juego (de momento los
valores por defecto nos valdrán) y Aceptar.
Nuestra primera aplicación: Nuevo proyecto XNA
2. Visual Studio generará un montón de plantillas de fichero y abrirá el fichero
principal. Deberíais obtener la siguiente pantalla. Ahora, en el menú, elegid Debug
(depurar) → Start Debugging (Iniciar depuración).
Nuestra primera aplicación: Nuevo proyecto XNA
3. Después de compilar el proyecto obtendreis el siguiente “videojuego”.
! Eso no es un videojuego!.
• Bueno, en realidad sí que lo es. Tiene todos los elementos que describimos en la
sección anterior, Inicialización, Bucle Principal (entrada de usuario, gráficos,
actualización de los datos) y terminación.
• Ahora se debe programar la lógica del juego.
Las clases principales de un proyecto XNA
•
•
El proyecto tiene dos ficheros fuente:
•
Program.cs
•
•
Bastante simple, crea un objeto de tipo Game1 y llama a su método run.
Game1.cs
•
Contiene la definición de la clase principal Game1 del tipo
Microsoft.Xna.Framework.Game
•
Proporciona dos variables de clase
•
graphics (GraphicsDeviceManager): Permite el acceso a los recursos
gráficos, por ejemplo a la GPU.
•
spriteBatch (SpriteBatch): Gestiona los sprites.
Las clases principales de un proyecto XNA
Un sprite es una imagen 2D o 3D que se puede integrar en una escena.
Y cinco métodos derivados.
•
Initialize: Inicializa los datos del juego.
•
Load Content: Carga las imágenes, sonidos y cualquier otro
recurso.
•
UnloadContent: Libera los recursos.
•
Update: Actualiza los datos del juego usando la lógica del jiego,
tambien comprueba la entrada del usuario.
•
Draw: Realiza la salida gráfica.
Sprites y entrada.
Para dibujar algo e intentar moverlo:
• Abrid el proyecto que generasteis en la última
clase
• Probablemente todavía está en la lista de proyectos recientes.
• Si no está, en la barra de menú seleccionad select File (Archivo)
→ Open (Abrir) → Project or Solution (Proyecto o
Solucion).
Sprites y entrada.
•
•
Mirad al panel llamado Solution Explorer (explorador de soluciones) situado
normalmente a la derecha de la ventana de Visual Studio window). Vereis dos
proyectos dentro de la solución, WindowsGame1 and WindowsGame1Content.
De momento, es una buena idea crear subcarpetas dentro de WindowsGame1Content
para diferentes tipos de contenidos (imagenes, sonidos etc). FileAdd (Agregar) → New
Folder (Nueva Carpeta). Añadid una carpeta Images.
Añadir nuevo contenido
• Ahora, descargad el fichero “XNALogo.png” de la plataforma
Moodle y guardadlo en algún lugar del ordenador. Un buen
lugar es la carpeta WindowsGame1Content/Images dentro de
vuestro proyecto.
• Añadid la imagen como contenido
seleccionando Add (Agregar) →
Existing Item (Elemento Existente) y
seleccionad el fichero
XNALogo.png en el diálogo que
aparece.
• Ahora la imagen deberia aparecer
como nuevo contenido del
proyecto.
Cargar el sprite
• Antes de dibujar un Sprite es necesario cargarlo en variables que se
puedan usar para gestionarlo.
• El objeto por defecto usado para gestionar una imagen es Texture2D.
• Añadid un objeto Texture2D al fichero Game1.cs.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D sprite;
Cargar el sprite
• Ahora necesitamos cargar el recurso. Recordad que toda la carga de
recursos de realiza en el método sobrecargado LoadContent.
• Añadid la siguiente línea en el método LoadContent.
sprite = Content.Load<Texture2D>(@"Images/XNALogo");
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
sprite = Content.Load<Texture2D>(@"Images/XNALogo");
Dibujar el sprite.
• Todas las tareas de dibujo se realizan dentro del método sobrecargado
Draw.
• Ahora añadid las siguientes líneas al método Draw y ejecutad la
solución.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(sprite, Vector2.Zero, Color.White);
spriteBatch.End();
// TODO: Add your drawing code here
base.Draw(gameTime);
• Deberíais obtener el siguiente “juego”.
Dibujo básico de sprites.
• El procedimiento Begin del objeto SpriteBatch indica que vamos a
enviar un conjunto de sprites al dispositivo gráfico. Siempre
debemos llamar a Begin antes de dibujar cualquier sprite.
• El procedimiento Draw muestra el Sprite en el dispositivo gráfico.
• Texture2D sprite: El objeto a dibujar.
• Vector2 pos: La posición donde se dibuja el sprite.
• Color color: El color del sprite.
Dibujo básico de sprites.
• Cambiemos un poco el procedimiento Draw. Cambiad la llamada
por (por ejemplo) la siguiente línea y ejecutad el juego.
spriteBatch.Begin();
spriteBatch.Draw(sprite, new Vector2((Window.ClientBounds.Width / 2) (sprite.Width / 2),
(Window.ClientBounds.Height / 2) - (sprite.Height / 2)), Color.White);
spriteBatch.End();
• El objeto Window representa la ventana del videojuego.
Draw
• El procedimiento Draw es un procedimiento sobrecargado que
puede tener muchos argumentos:
public void Draw ( Texture2D texture, Vector2 position, Nullable<Rectangle>
sourceRectangle, Color color, float rotation, Vector2 origin, Vector2
scale, SpriteEffects effects, float layerDepth );
• texture Texture2D: El sprite.
• position Vector2: La posición donde se dibuja el Sprite.
• sourceRectangle Nullable<Rectangle>: Un rectángulo que indica la parte del Sprite que vamos a
dibujar (usar null para dibujar todo el sprite).
• color Color: El color del Sprite. Usar Color.White para usar los colores del Sprite.
• rotation Single: Ángulo de rotación con el que se pinta el Sprite respecto a su origen (origin).
• origin Vector2: Punto origen (origin) del Sprite, por defecto (0,0).
• scale Vector2: Factor de escala.
• effects SpriteEffects: Efectos a aplicar (invertir horizontal o verticalmente).
• layerDepth Single: Profundidad de la capa de dibujo, 0 es la capa frontal y 1 la trasera.Es necesario
usar SpriteSortMode si se desea ordenar los sprites durante el dibujo.
Texto
• Los textos se pintan en pantalla de una forma muy similar a los
Sprites.
• Antes de pintar un texto es necesario definir una fuente. Las
definiciones de fuentes de texto en XNA son ficheros XML que se
tratan como cualquier otro recurso y que contienen el nombre de
la fuente, el tamaño, los efectos (negrita, cursiva, etc), el rango de
caracteres que admitimos.
• Descargad de Moodle el fichero
text.spritefont
• Cread en vuestro una nueva categoría de contenido denominada
fonts y añadid el fichero como nuevo recurso.
• Podéis abrir el recurso para ver los campos que contiene.
Texto (II)
• Añadid como variable de clase, una variable de tipo SpriteFont
SpriteFont fuente;
• En el procedimiento Load() añadid la línea
fuente = Content.Load<SpriteFont>(@"fonts/text");
• Finalmente para dibujar el texto añadid la siguiente línea en el
procedimiento Draw, entre las llamadas a spriteBatch.begin y
spriteBatch.end;
spriteBatch.DrawString(fuente, "Puntuacion: ",
new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height - 30),
Color.Black, 0, Vector2.Zero,1, SpriteEffects.None, 1);
Transparencias.
• XNA permite establecer diferentes niveles de transparencia por
pixel. Para ello es necesario usar un editor y un formato que
fichero que permita establecer un canal alpha por pixel (RGBA),
por ejemplo .png . Si la imagan no tiene canal alpha, XNA toma el
color magenta (255,0,255) como transparente.
• Cambiad ahora el logo que usamos en el ejemplo anterior por el
fichero logo_trans.png. Para ello:
• Descargad el fichero logo_trans.png de Moodle
• Añadid el fichero como nuevo recurso tipo imagen.
• Cambiad la linea:
sprite = Content.Load<Texture2D>(@"Images/XNALogo");
por
sprite = Content.Load<Texture2D>(@"Images/logo_trans");
• Compilad y ejecutad.
Movimiento (I)
•
Para mover un objeto en la pantalla es necesario modificar la posición del
objeto entre llamadas al procedimiento Draw. Declaremos dos variables
de Sprite, dos variables de posición y dos variables de velocidad:
SpriteBatch spriteBatch;
Texture2D sprite;
Texture2D sprite2;
Vector2 pos1 = Vector2.Zero;
Vector2 pos2 = Vector2.Zero;
float speed1 = 2f;
float speed2 = 3f;
• Cargad ahora los dos sprites (transparente y no transparente),
para ello dejad en LoadContent() el siguiente código:
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
sprite = Content.Load<Texture2D>(@"Images/XNALogo");
sprite2 = Content.Load<Texture2D>(@"Images/logo_trans");
// TODO: use this.Content to load your game content here
}
Movimiento (II)
•
Ahora necesitamos modificar la posición de los sprites. Recordad que en
XNA toda la actualización de los datos se debe realizar en el
procedimiento Update. Añadid el siguiente código al procedimiento
Update.
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
pos1.X += speed1;
if (pos1.X > Window.ClientBounds.Width - sprite.Width || pos1.X < 0)
speed1 *= -1;
pos2.Y += speed2;
if (pos2.Y > Window.ClientBounds.Height - sprite.Height || pos2.Y < 0)
speed2 *= -1;
•
Vamos a pintar el logo transparente en color negro. Añadid las siguientes
líneas al procedimiento Draw. Compilad y ejecutad.
spriteBatch.Draw(sprite,pos1,Color.White);
spriteBatch.Draw(sprite2,pos2,Color.Black);
Tamaño y efectos.
• XNA permite modificar el tamaño de un Sprite, así como pintarlo
reflejado horizontal o verticalmente, o incluso girarlo.
• Para dibujar el logo transparente a un mayor tamaño (150% de
su tamaño original) y reflejado horizontalmente, probad a
cambiar la línea:
spriteBatch.Draw(sprite2,pos2,Color.Black);
por
spriteBatch.Draw(sprite2,pos2,null,Color.Black,0,Vector2.Zero,1.5f,
SpriteEffects.FlipHorizontally,0);
si además deseais girar el Sprite.
spriteBatch.Draw(sprite2,pos2,null,Color.Black,MathHelper.ToRadians(45.0f),
Vector2.Zero,1.5f, SpriteEffects.FlipHorizontally,0);
En XNA todos los ángulos se especifican en Radianes. Podéis usar la
clase MathHelper, para convertir de grados a radianes.
Profundidad.
•
XNA permite definir una profundidad de dibujo para los objetos 2D
(Orden Z), que sobrepase el orden de dibujo que se indica en la función
Draw. De tal forma que un Sprite siempre se dibuje adelante o atrás.
•
Para usar el Orden Z es necesario modificar la llamada a
spriteBatch.Begin(); dentro de Draw, añadiendo un argumento de tipo
SpriteSortMode (en este caso, tambíen es necesario añadir un argumento
de tipo BlendState)
• Deferred: Los sprites no se dibujan hasta que se llama al procedimiento SpriteBatch.End. En ese
momento todas las llamadas a Draw se envian al dispositivo gráfico en el mismo orden que se
realizaron (este es el modo por defecto). Se pueden tener varios objetos SpriteBatch Activos.
• Immediate: Los sprites se pintan tan pronto como se realiza la llamada a Draw. Este es el modo
más rápido, pero solo permite un objeto SpriteBatch activo
• Texture: Lo mismo que Deferred pero los sprites se ordenan por textura antes de ser pintados.
• BackToFront: Lo mismo que Deferred, pero los sprites se ordenan de adelante hacia atrás basado
en el orden indicado por el parámetro de profundidad de la llamada a Draw (pinta mas atrás los
valores más cercanos al 1).
• FrontToBack: Lo mismo que Deferred, pero los sprites se ordenan de atrás hacia adelante basado
en el orden indicado por el parámetro de profundidad de la llamada a Draw. (pinta más atras los
valores mas cercanos al 0)
Mezclas (Blending).
• XNA permite definir como se van a mezclar los Sprites con el
fondo de la escena (Blending).
• Para modificar el Blending es necesario modificar la llamada a
spriteBatch.Begin(); dentro de Draw, añadiendo un argumento de
tipo BlendState.
• AlphaBlend: Fuente y destino se mezclan usando los valores alpha del sprite. Este
es el comportamiento por defecto y permite usar transparencias.
• Additive: Suma fuente y desino ignorando los valores alpha.
• NonPremultiplied: Mezcla fuente y destino usando alpha pero suponiendo que los
datos de color no contienen información alpha.
• Opaque: Mezcla opaca, sobreescribe la fuente con los datos de destino.
Ejemplos de Blending.
• Para usar el Z orden y el Blending es necesario modificar la
llamada a la función spriteBatch.Begin(), añadid los siguientes
argumentos:
spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
• Para probar el Z orden, modificad spriteBatch.Begin() y cambiad
las llamadas a Draw en ambos Sprites,
spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
spriteBatch.Draw(sprite, pos1,null,Color.White,0,Vector2.Zero,1,SpriteEffects.None,1);
spriteBatch.Draw(sprite2, pos2,null,Color.Black,0,Vector2.Zero,1,SpriteEffects.None,0);
• Probad a cambiar también algunos de los valores del argumento
BlendState.
Animación I.
•
XNA permite usar Sprites con varias imágenes (frame sheets).
•
Descargad de Moodle los ficheros threerings.png y plus.png y añadidlos
como recursos de imagen del proyecto.
•
Cambiad las texturas antiguas (XNALogo y logo_trans) por las nuevas
que acabamos de añadir al proyecto.
sprite = Content.Load<Texture2D>(@"Images/threerings");
sprite2 = Content.Load<Texture2D>(@"Images/plus");
•
Las nuevas texturas contienen muchas imágenes (frames), tenemos que
seleccionar el trozo de imagen vamos a pintar en cada momento.
•
Antes de pintar la animación es necesario saber:
•
El ancho y el alto de cada frame dentro del frame sheet.
•
El número de filas y columas del frame sheet.
•
Un índice que específica la fila y la columna dentro del frame sheet
del frame que vamos a dibujar
Animación II.
• threerings.png tiene seis columnas y ocho filas, cada frame ocupa
75x75 pixels.
• plus tiene seis columnas y cuatro filas, cada frame ocupa 75x75
pixels.
• Añadid esta información como variables de la clase.
Point frameSize = new Point(75, 75);
Point currentFrame1 = new Point(0, 0);
Point sheetSize1 = new Point(6, 8);
Point currentFrame2 = new Point(0, 0);
Point sheetSize2 = new Point(6, 4);
• Cambiad las llamadas a draw para ambos sprites.
spriteBatch.Draw(sprite, pos1,new Rectangle(currentFrame1.X * frameSize.X,
currentFrame1.Y * frameSize.Y,frameSize.X,frameSize.Y),Color.White, 0, Vector2.Zero,
1, SpriteEffects.None, 0);
spriteBatch.Draw(sprite2, pos2, new Rectangle(currentFrame2.X * frameSize.X,
currentFrame2.Y * frameSize.Y, frameSize.X, frameSize.Y), Color.White, 0, Vector2.Zero,
1, SpriteEffects.None, 0);
•
En la función Update, cambiad los valores sprite.Width y sprite.Height
por los valores frameSize.X y frameSize.Y respectivamente.
Animación III.
• Ahora añadid el siguiente código en la función Update:
++currentFrame1.X;
if (currentFrame1.X >= sheetSize1.X)
{
currentFrame1.X = 0;
++currentFrame1.Y;
if (currentFrame1.Y >= sheetSize1.Y)
currentFrame1.Y = 0;
}
++currentFrame2.X;
if (currentFrame2.X >= sheetSize2.X)
{
currentFrame2.X = 0;
++currentFrame2.Y;
if (currentFrame2.Y >= sheetSize2.Y)
currentFrame2.Y = 0;
}
•
Compilad y ejecutad.
Animación IV, framerates.
•
XNA permite modificar el número de frames por segundo (fps) a los que
se ejecuta un juego (por defecto 60fps en Windows y 360, y 30fps en
Windows phone).
•
Para modificar el número de frames, añadid la siguiente línea al final del
constructor de Game1;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 50);
}
•
•
Esta línea le indica a XNA que se debe llamar al prodedimiento
Game.Update una vez cada 50 milisegundos, obteniendo por tanto, una
tasa de 20fps.
El objeto GameTime que Draw pasa como parámetro, contiene el campo
IsRunningSlowly, que si toma el valor true indica que XNA no es capaz de
alcanzar la tasa de refresco especificada. En este caso, usualmente XNA
realiza un menor número de llamadas a la función Draw.
Animación V, framerates.
Bien, pero me gustaría modificar el framerate de un Sprite, no el de todo el
juego.
• Para modificar el framerate de un Sprite concreto, añadid las dos
siguientes variables como variables de la clase
int timeSinceLastFrame = 0;
int millisecondsPerFrame = 50;
•
Cambiad el siguiente código dentro de la funcion Update()
++currentFrame1.X;
if (currentFrame1.X >= sheetSize1.X)
{
currentFrame1.X = 0;
++currentFrame1.Y;
if (currentFrame1.Y >= sheetSize1.Y)
currentFrame1.Y = 0;
}
por
timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
if (timeSinceLastFrame > millisecondsPerFrame)
{
timeSinceLastFrame -= millisecondsPerFrame;
++currentFrame.X;
if (currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
++currentFrame.Y;
if (currentFrame.Y >= sheetSize.Y)
currentFrame.Y = 0;
}
}
Descargar