Ir al contenido

publicidad
publicidad

Foto

Curso MM: 9 Manejando los sprites


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
No hay respuestas en este tema

#1

Escrito 11 agosto 2009 - 21:54

Manejando un mundo de sprites

El manejador de sprites estará integrado en el motor del juego como un conjunto de métodos en la clase GemeEngine. En la clase Sprite hay que añadir las acciones de sprite, que se usan para informar al manejador de sprites que se debe tomar una acción con un sprite en particular. En la clase Sprite también se añadirá un rectángulo más pequeño que el del sprite para calcular colisiones y métodos para calcular el rectángulo y comprobar si ha habido colisiones. En el manejador de sprites habrá un vector para contener los sprites.

El manejador de sprites debe hacer lo siguiente (lo llamo lista de sprites pero no es una lista en el sentido de estructura de datos, es un vector, vedlo como un array o tabla simple):
- añadir un sprite a la lista.
- dibujar todos los sprites de la lista.
- actualizar todos los sprites de la lista.
- limpiar todos los sprites de la lista.
- comprobar si un punto está dentro de un sprite de la lista.

Cuando sucede una colisión el manejador lanzará un mensaje que será tratado por el juego, así que habrá que incluir una función que se encargue de las colisiones.

Añadiendo una manejador de sprites al motor del juego

Primero empezamos por los cambios en la clase Sprite.

Primero hay que añadir el rectángulo de colisión. Se añade como una variable de miembro llamada m_rcCollision de tipo RECT. También añadimos un método de acceso:

[code:1]RECT& GetCollision() { return m_rcCollision; };[/code]

La siguiente función llamada CalcCollisionRect(); toma el rectángulo de posición del sprite y obtiene el de colisión restándole 1/6 de su tamaño.

[code:1]inline void Sprite::CalcCollisionRect()
{
int iXShrink = (m_rcPosition.left - m_rcPosition.right) / 12;
int iYShrink = (m_rcPosition.top - m_rcPosition.bottom) / 12;
CopyRect(&m_rcCollision, &m_rcPosition);
InflateRect(&m_rcCollision, iXShrink, iYShrink);
}[/code]

La clase Sprite tiene el método TestCollision() para comprobar si el sprite ha chocado con otro.

[code:1]inline BOOL Sprite::TestCollision(Sprite* pTestSprite)
{
RECT& rcTest = pTestSprite->GetCollision();
return m_rcCollision.left <= rcTest.right &&
rcTest.left <= m_rcCollision.right &&
m_rcCollision.top <= rcTest.bottom &&
rcTest.top <= m_rcCollision.bottom;
}[/code]

Comprueba si ha habido una colisión entre los dos sprites, el actual y el llamado "test". Si ha sucedido se devuelve TRUE.

El rectángulo de colisiones debe ser inicializado en el constructor. Así que se ha añadido en cada uno una llamada a CalcCollisionRect().

Otro cambio en la clase Sprite es el añadido de las acciones de sprites, así el manejador de sprites puede actuar sobre ellos en respuesta a eventos como por ejemplo colisiones. Se crea un tipo de datos para representar las acciones:

[code:1]typedef WORD SPRITEACTION;
const SPRITEACTION SA_NONE = 0x0000L,
SA_KILL = 0x0001L;[/code]

Sólo se han creado dos acciones, la primera indica que no hagamos nada, la segunda indica que el sprite debe ser eliminado de la lista y destruido. Se utilizan en la función Update() que ahora devuelve un valor SPRITEACTION.

Ahora Update() soporta la acción de bordes BA_DIE que hace que el sprite sea destruido cuando encuentra un borde. Esa acción es posible gracias al mensaje de sprite SA_KILL que devuelve la función Update() en respuesta al mensaje BA_DIE.

Ahora vamos con los cambios en el motor de juego

Para la lista de sprites vamos a usar un vector, así que hay que incluir en el archivo de cabecera lo siguiente:

[code:1]#include
using namespace std;[/code]

Para definir el vector: vector m_vSprites;

En el constructor de GameEngine se reserva espacio para 50 sprites en el vector. No es un límite, es un sistema para poder meter 50 sprites antes de tener que reservar más memoria, que es un proceso lento.

En el código del juego hay que añadir la función SpriteCollision() para responder a las colisiones de cada juego de forma específica:

[code:1]BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee);[/code]

A esta función la llama CheckSpriteCollision() del motor del juego que hace una pasada por todos los sprites y cuenta con SpriteCollision para las colisiones individuales:

[code:1]BOOL CheckSpriteCollision(Sprite* pTestSprite);[/code]

La devolución de TRUE hace que el sprite sea devuelto a sus coordenadas anteriores a la colisión.

Los métodos de GameEngine:

[code:1]void AddSprite(Sprite* pSprite);
void DrawSprites(HDC hDC);
void UpdateSprites();
void CleanupSprites();
Sprite* IsPointInSprite(int x, int y);[/code]

Eliminando el parpadeo con el buffer doble

Lo que se hace es dibujar todo fuera de la pantalla y luego volcarlo en la pantalla de golpe. Necesitamos un contexto de dispositivo y un bitmap:

[code:1]HDC _hoffscreenDC;
HBITMAP _hoffscreenBitmap;[/code]

Con éstas variables creamos el contexto de dispositivo fuera de pantalla (en memoria) y lo usamos para crear un bitmap del mismo tamaño que la pantalla:

[code:1]// Create the offscreen device context and bitmap
_hOffscreenDC = CreateCompatibleDC(GetDC(hWindow));
_hOffscreenBitmap = CreateCompatibleBitmap(GetDC(hWindow),
_pGame->GetWidth(), _pGame->GetHeight());
SelectObject(_hOffscreenDC, _hOffscreenBitmap);[/code]

Ahora vemos un ejemplo:

[code:1]// Obtain a device context for repainting the game
HWND hWindow = _pGame->GetWindow();
HDC hDC = GetDC(hWindow);

// Paint the game to the offscreen device context
GamePaint(_hOffscreenDC);

// Blit the offscreen bitmap to the game screen
BitBlt(hDC, 0, 0, _pGame->GetWidth(), _pGame->GetHeight(),
_hOffscreenDC, 0, 0, SRCCOPY);

// Cleanup
ReleaseDC(hWindow, hDC);[/code]

Este código va en GameCycle(). A GamePaint() le pasamos el contexto de dispositivo del buffer que está fuera de la pantalla. De ésta manera todo el dibujo se hace allí. Luego lo pasamos a la pantalla. Al final hacemos limpieza:

[code:1]// Cleanup the offscreen device context and bitmap
DeleteObject(_hOffscreenBitmap);
DeleteDC(_hOffscreenDC);[/code]

Programa de ejemplo:
Fore.h , Fore.cpp.

En GameStart() creamos el sistema de doble buffer y al crear los sprites los vamos metiendo en la lista con AddSprite(). En GameEnd() hacemos limpieza de todo (contextos, objetos, sprites, bitmaps, el juego mismo).

En GamePaint() sólo dibujamos el fondo y luego llamamos a la función DrawSprites() que coge la lista y los dibuja todos.

GameCycle() actualiza los sprites, los dibuja todos en el buffer y luego pasa el buffer a la pantalla. Lo primero es llamar a UpdateSprites(), luego con GamePaint(_hoffscreenDC) dibujamos en el buffer. Con BitBlt pasamos el buffer a la pantalla.

Las funciones del ratón se controlan con sus respectivas funciones. Es lo mismo que el ejemplo del capítulo anterior. Ahora para ver si hemos pinchado en un sprite usamos la función IsPointInSprite(x,y);

Por último vemos SpriteCollision() que intercambia las velocidades de los sprites que chocan creando el efecto rebote. Devuelve TRUE para que los sprites sean devueltos a la posición anterior al choque.

Fore.h creo que es bastante obvio, así que no lo explico a no ser que sea necesario.

Fore.cpp
[code:1]//-----------------------------------------------------------------
// Fore 2 Application
// C++ Source - Fore.cpp
//-----------------------------------------------------------------

//-----------------------------------------------------------------
// Include Files
//-----------------------------------------------------------------
#include "Fore.h"

//-----------------------------------------------------------------
// Game Engine Functions
//-----------------------------------------------------------------
BOOL GameInitialize(HINSTANCE hInstance)
{
// Create the game engine
_pGame = new GameEngine(hInstance, TEXT("Fore 2"),
TEXT("Fore 2"), IDI_FORE, IDI_FORE_SM, 600, 400);
if (_pGame == NULL)
return FALSE;

// Set the frame rate
_pGame->SetFrameRate(30);

// Store the instance handle
_hInstance = hInstance;

return TRUE;
}

void GameStart(HWND hWindow)
{
// Seed the random number generator
srand(GetTickCount());

// Creamos el contexto de dispositivo fuera de pantalla y el bitmap.
_hOffscreenDC = CreateCompatibleDC(GetDC(hWindow));
_hOffscreenBitmap = CreateCompatibleBitmap(GetDC(hWindow),
_pGame->GetWidth(), _pGame->GetHeight());
SelectObject(_hOffscreenDC, _hOffscreenBitmap);

// Crear y cargar los bitmaps.
HDC hDC = GetDC(hWindow);
_pForestBitmap = new Bitmap(hDC, IDB_FOREST, _hInstance);
_pGolfBallBitmap = new Bitmap(hDC, IDB_GOLFBALL, _hInstance);

// Crear los sprites de las bolas de golf.
RECT rcBounds = { 0, 0, 600, 400 };
Sprite* pSprite;
pSprite = new Sprite(_pGolfBallBitmap, rcBounds, BA_WRAP);
pSprite->SetVelocity(5, 3);
_pGame->AddSprite(pSprite); //lo metemos en la lista.
pSprite = new Sprite(_pGolfBallBitmap, rcBounds, BA_WRAP);
pSprite->SetVelocity(3, 2);
_pGame->AddSprite(pSprite);
rcBounds.left = 265; rcBounds.right = 500; rcBounds.bottom = 335;
pSprite = new Sprite(_pGolfBallBitmap, rcBounds, BA_BOUNCE);
pSprite->SetVelocity(-6, 5);
_pGame->AddSprite(pSprite);
rcBounds.right = 470;
pSprite = new Sprite(_pGolfBallBitmap, rcBounds, BA_BOUNCE);
pSprite->SetVelocity(7, -3);
_pGame->AddSprite(pSprite);

// Set the initial drag info
_pDragSprite = NULL; //no arrastramos una bola de golf.
}

void GameEnd()
{
// Cleanup the offscreen device context and bitmap
DeleteObject(_hOffscreenBitmap);
DeleteDC(_hOffscreenDC);

// Cleanup the bitmaps
delete _pForestBitmap;
delete _pGolfBallBitmap;

// Cleanup the sprites
_pGame->CleanupSprites();

// Cleanup the game engine
delete _pGame;
}

void GameActivate(HWND hWindow)
{
}

void GameDeactivate(HWND hWindow)
{
}

void GamePaint(HDC hDC)
{
// Draw the background forest
_pForestBitmap->Draw(hDC, 0, 0);

// Draw the sprites
_pGame->DrawSprites(hDC);
}

void GameCycle()
{
// Actualizar los sprites.
_pGame->UpdateSprites();

// Obtener un contexto de dispositivo para redibujar el juego.
HWND hWindow = _pGame->GetWindow();
HDC hDC = GetDC(hWindow);

// Dibujar el juego en el contexto de dispositivo para fuera de la pantalla.
GamePaint(_hOffscreenDC);

// Movemos lo que hay en _hOffscreenDC a la pantalla del juego.
BitBlt(hDC, 0, 0, _pGame->GetWidth(), _pGame->GetHeight(),
_hOffscreenDC, 0, 0, SRCCOPY);

// Cleanup
ReleaseDC(hWindow, hDC);
}

void HandleKeys()
{
}

void MouseButtonDown(int x, int y, BOOL bLeft)
{
// See if a ball was clicked with the left mouse button
if (bLeft && (_pDragSprite == NULL))
{
if ((_pDragSprite = _pGame->IsPointInSprite(x, y)) != NULL)
{
// Capture the mouse
SetCapture(_pGame->GetWindow());

// Simulate a mouse move to get started
MouseMove(x, y);
}
}
}

void MouseButtonUp(int x, int y, BOOL bLeft)
{
// Release the mouse
ReleaseCapture();

// Stop dragging
_pDragSprite = NULL;
}

void MouseMove(int x, int y)
{
if (_pDragSprite != NULL)
{
// Move the sprite to the mouse cursor position
_pDragSprite->SetPosition(x - (_pDragSprite->GetWidth() / 2),
y - (_pDragSprite->GetHeight() / 2));

// Force a repaint to redraw the sprites
InvalidateRect(_pGame->GetWindow(), NULL, FALSE);
}
}

void HandleJoystick(JOYSTATE jsJoystickState)
{
}

BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee)
{
// Si colisionan dos sprites intercambiamos sus velocidades para que parezca que rebotan.
POINT ptSwapVelocity = pSpriteHitter->GetVelocity();
pSpriteHitter->SetVelocity(pSpriteHittee->GetVelocity());
pSpriteHittee->SetVelocity(ptSwapVelocity);
return TRUE;
}[/code]

Código fuente


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
publicidad
publicidad