Foros DeCeMuLaDoReS

Retroceder   Foros DeCeMuLaDoReS > PC > Programación

Programación Escribe aquí todo lo relacionado sobre programación, ya sea C, C++, PHP, etc, etc...

Respuesta
 
LinkBack Herramientas
  #1 (permalink)  
Antiguo 17-May-2007, 05:41
ninio es como un diamante en bruto ninio es como un diamante en bruto ninio es como un diamante en bruto
Especialista DC
 
Fecha de Ingreso: 14-January-2007
Mensajes: 228
Posts agradecidos: 1
Agradecido 0 veces en 0 posts
Predeterminado [C][Tuto]Animación simple bajo windows

Hola, les traigo un nuevo tutorial, en esta ocasión se tocará el interesante tema de la animación, si bien será algo muy simple, servirá para que puedan adentrarse en el tema.

Requisitos:

Haber revisado el tutorial de gráficos 2D, por lo menos el de gráficos simples y preferentemente tanto el de gráficos simples como el de gráficos no tan simples.

Compilador wxDevCpp (Para más información sobre como obtenerlo por favor visiten mi tutorial de gráficos 2D simples) Pueden bajarlo de la página oficial: http://wxdsgn.sourceforge.net/ (DEBEN revisar el tutorial, porque se deben modificar los parámetros del compilador, como allí se explica)

Algunos conocimientos básicos en matemáticas (si tienen dudas pueden preguntar)

Pueden usar el proyecto inicial que coloqué en el tutorial de gráficos 2D (si no pueden bajarlo avisen para ver que se puede hacer) y solo rellenarlo con los códigos que aquí colocaré. El link es: http://www.megaupload.com/?d=5O2Y5720 y si no pueden bajarlo sigan las siguientes instrucciones:

Spoiler para Por si no pueden bajar el proyecto:
El proyecto se conforma de tres archivos, Graf.dev, main.cpp y main.h los tres son archivos de texto. Los archivos main.* son los del código de programa, y el archivo Graf.dev corresponde al archivo del proyecto, que contiene información respecto a los archivos del proyecto y tipo de proyecto. Si no pueden bajar el proyecto pueden crear los tres archivos con cualquier editor de texto y pegar el contenido, solo tengan la precaución que la extención de los archivos sea la correcta:

Graf.dev:
Código:
[Project]
FileName=Graf.dev
Name=Proyecto 1
UnitCount=2
Type=0
Ver=3
IsCpp=1
Folders=
CommandLine=
CompilerSettings=
PchHead=-1
PchSource=-1
ProfilesCount=1
ProfileIndex=0

[Unit1]
FileName=main.cpp
CompileCpp=1
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

[VersionInfo]
Major=0
Minor=1
Release=1
Build=1
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNrOnRebuild=0
AutoIncBuildNrOnCompile=0

[Unit3]
FileName=Menu.h
CompileCpp=1
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

[Profile1]
ProfileName=Default Profile
Type=0
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
PreprocDefines=
CompilerSettings=
Icon=
ExeOutput=Default Profile
ObjectOutput=Default Profile
OverrideOutput=0
OverrideOutputName=
HostApplication=
CommandLine=
UseCustomMakefile=0
CustomMakefile=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=0
compilerType=0

[Unit2]
FileName=main.h
CompileCpp=1
Folder=Proyecto 1
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
main.h
Código:
void insertarMenu(HWND hWnd);

#define CM_SALIR         9072
#define CM_ABRIR         9071
main.h
Código:
#include <windows.h>
#include <stdio.h>
#include "main.h"

LRESULT CALLBACK ProcedimientoDventana(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   PSTR szCmdLine, int iCmdShow)
{
   static char   szAppName[] = "Graficacion";
   HWND          hwnd;
   MSG           msg;
   WNDCLASSEX    clase_de_ventana;

   clase_de_ventana.cbSize        = sizeof (clase_de_ventana);
   clase_de_ventana.style         = CS_HREDRAW | CS_VREDRAW;
   clase_de_ventana.lpfnWndProc   = ProcedimientoDventana;
   clase_de_ventana.cbClsExtra    = 0;
   clase_de_ventana.cbWndExtra    = 0;
   clase_de_ventana.hInstance     = hInstance;
   clase_de_ventana.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
   clase_de_ventana.hCursor       = LoadCursor (NULL, IDC_ARROW);
   clase_de_ventana.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
   clase_de_ventana.lpszMenuName  = "MenuPrincipal";
   clase_de_ventana.lpszClassName = szAppName;
   clase_de_ventana.hIconSm       = LoadIcon (NULL, IDI_APPLICATION);

   if(!RegisterClassEx (&clase_de_ventana)) return 0;
        
   hwnd = CreateWindowEx(
                         0,
                         szAppName,
                         "Graficaci�n",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         HWND_DESKTOP,
                         NULL,
                         hInstance,
                         NULL);
   insertarMenu(hwnd);

   ShowWindow (hwnd, iCmdShow);

   while (GetMessage (&msg, NULL, 0, 0))
   {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
   return msg.wParam;
}

LRESULT CALLBACK ProcedimientoDventana (HWND hwnd, UINT iMsg, WPARAM wParam, 
                                        LPARAM lParam)
{
   HDC              hdc;
   PAINTSTRUCT      ps;
   RECT             rect;
   POINT            pt;
   HPEN             hLapiz;
   
   static int xMax, yMax;

   switch (iMsg)
   {
      case WM_COMMAND:
         switch(LOWORD(wParam))
         { 
            case CM_SALIR:              
              PostQuitMessage(0); // Envia un mensaje WM_QUIT a la cola de mensajes 
              break;
         }
         return 0;   
    
      case WM_SIZE:
         xMax = LOWORD(lParam);
         yMax = HIWORD(lParam);
         printf("<%d,%d>",xMax,yMax);
         return 0;

      case WM_PAINT:
         hdc = BeginPaint (hwnd, &ps);
         hLapiz = CreatePen(PS_SOLID, 1, RGB(255,0,0));
         SelectObject(hdc, hLapiz);
         Rectangle(hdc,xMax*0.25,yMax*0.25,xMax*0.75,yMax*0.75);
         hLapiz = CreatePen(PS_SOLID, 1, RGB(0,0,0));
         /*INSERTAR AQUI OTRAS FUNCIONES DE DIBUJO*/
         DeleteObject(hLapiz);
         EndPaint (hwnd, &ps);
         return 0;

      case WM_DESTROY:
         PostQuitMessage (0);
         return 0;
    }
    return DefWindowProc (hwnd, iMsg, wParam, lParam);
}

void insertarMenu(HWND hWnd)
{
   HMENU hMenu1, hMenu2;

   hMenu1 = CreateMenu();
   hMenu2 = CreateMenu();

   AppendMenu(hMenu1, MF_STRING | MF_POPUP, (UINT)hMenu2, "&Archivo");

   AppendMenu(hMenu2, MF_STRING, CM_ABRIR, "&Abrir");
   AppendMenu(hMenu2, MF_SEPARATOR, 0, NULL);
   AppendMenu(hMenu2, MF_STRING, CM_SALIR, "&Salir");

   SetMenu (hWnd, hMenu1);
}
Los tres archivos deben estar en la misma carpeta


Lección 1. Introducción

La animación de gráficos es la parte medular en la creación de videojuegos. Una animación consiste en presentar una secuencia de gráficos a través del tiempo, los gráficos van cambiando de modo que dan un efecto de moverse.

Dada la naturaleza de las computadoras es imposible presentar un movimiento continuo, solo puede presentarse pantallas en secuencia cada cierto tiempo, de modo que aunque veamos que un objeto se mueve suavemente desde una posición a otra, en realidad se mueve en pasos, como se muestra en la imagen siguiente:


http://img248.imageshack.us/img248/8995/marcosnn5.png

Para mostrar que la pelota se mueve de la posición 1 a la posición 2 a través de la trayectoria en azul, la animación mostraría una secuencia, que serían las pelotas que se dibujan semitransparentes y con línea discontinua. Mientras más pelotas intermedias se muestren, la animación se verá más suave, sin embargo si se muestran muchas imágenes la animación sería lenta. A cada imagen de la animación se le llama marco (del ingles frame)

En realidad en una animación como un videojuego lo que se controla no es el número de marcos que se muestran, si no el tiempo que se deja pasar entre cada marco. De modo que para un objeto que se mueve rápido el movimiento será más salteado, en cambio para un objeto lento el movimiento será suave.

El tiempo entre dos marcos de una animación es un factor importante puesto que si el refresco es lento, el usuario verá los cambios, lo cual da un aspecto indeseable y molesto, y si el refresco es demasiado alto, se requieren demasiados cálculos (en cada marco deben calcularse las posiciones de todos los objetos en base a las características que estos tengan, como su velocidad, aceleración, etc.) y sistemas adecuados (como monitores que tengan tasas de refresco muy altas).

Considerando que el ojo humano es capaz de percibir una frecuencia de alrededor de 20Hz máximo, es decir, que si al ojo humano le presentamos una lámpara que prende y apaga a más de 20 Hz, el ojo verá como si la lámpara estuviera siempre encendida, cualquier frecuencia mayor a esta pasará como un movimiento continuo. Por ejemplo, en el caso del cine se utilizan 24 marcos por segundo.

Lección 2. Timers (Temporizadores)

Un temporizador (en la literatura se les llama timers pero odio usar palabras en ingles por lo que yo les llamaré temporizadores que sería la traducción al español) es un proceso que dispara alguna acción a espacios regulares de tiempo. El temporizador se ajusta para que se genere alguna acción que deseamos en intervalos que nos interese.

Los temporizadores son controlados por el sistema por lo que una vez activados el programa puede dedicarse a realizar otras tareas y será interrumpido cuando el temporizador alcance el tiempo de espera que le ha sido indicado.

Windows proporciona funciones para manejo de temporizadores.
Para la creación de un temporizador:

Código:
UINT_PTR SetTimer(      
    HWND hWnd,
    UINT_PTR nIDEvent,
    UINT uElapse,
    TIMERPROC lpTimerFunc
);
Parámetros:

hWnd
Manejador de la ventana que capturará los mensajes del temporizador.

nIDEvent
Un valor no nulo que identifica al temporizador

uElapse
Especifica el valor en milisegundos que transcurre entre cada evento.

lpTimerFunc
Apuntador a una función que se llamará cuando el timer alcance el tiempo de espera. Si es un valor NULL, el sistema enviará un mensaje WM_TIMER.



Y para la eliminación de un temporizador:

Código:
BOOL KillTimer(      
    HWND hWnd,
    UINT_PTR uIDEvent
);

Ahora veamos un ejemplo bastante simple del uso de un temporizador.

Código de main.cpp:
Código:
#include <windows.h>
#include <stdio.h>
#include "main.h"

LRESULT CALLBACK ProcedimientoDventana(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   static char   szAppName[] = "Graficación";
   HWND          hwnd;
   MSG           msg;
   WNDCLASSEX    clase_de_ventana;

   clase_de_ventana.cbSize        = sizeof (clase_de_ventana);
   clase_de_ventana.style         = CS_HREDRAW | CS_VREDRAW;
   clase_de_ventana.lpfnWndProc   = ProcedimientoDventana;
   clase_de_ventana.cbClsExtra    = 0;
   clase_de_ventana.cbWndExtra    = 0;
   clase_de_ventana.hInstance     = hInstance;
   clase_de_ventana.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
   clase_de_ventana.hCursor       = LoadCursor (NULL, IDC_ARROW);
   clase_de_ventana.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
   clase_de_ventana.lpszMenuName  = "MenuPrincipal";
   clase_de_ventana.lpszClassName = szAppName;
   clase_de_ventana.hIconSm       = LoadIcon (NULL, IDI_APPLICATION);

   if(!RegisterClassEx (&clase_de_ventana)) return 0;

   hwnd = CreateWindowEx(
                         0,
                         szAppName,
                         "Graficación",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         HWND_DESKTOP,
                         NULL,
                         hInstance,
                         NULL);
   insertarMenu(hwnd);

   SetTimer(hwnd,ID_TEMPORIZADOR,500,(TIMERPROC) NULL);

   ShowWindow (hwnd, iCmdShow);

   while (GetMessage (&msg, NULL, 0, 0))
   {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
   return msg.wParam;
}

LRESULT CALLBACK ProcedimientoDventana (HWND hwnd, UINT iMsg, WPARAM wParam,
                                        LPARAM lParam)
{
   HDC              hdc;
   PAINTSTRUCT      ps;
   RECT             rect;
   POINT            pt;
   HPEN             hLapiz;
   static COLORREF  colorActual=RGB(0,0,0);
   static bool      bandera=FALSE;


   static int xMax, yMax;

   switch (iMsg)
   {
      case WM_COMMAND:
         switch(LOWORD(wParam))
         {
            case CM_SALIR:
              PostQuitMessage(0); // Envía un mensaje WM_QUIT a la cola de mensajes
              break;
         }
         return 0;

      case WM_SIZE:
         xMax = LOWORD(lParam);
         yMax = HIWORD(lParam);
         printf("<%d,%d>",xMax,yMax);
         return 0;

      case WM_PAINT:
         hdc = BeginPaint (hwnd, &ps);
         hLapiz = CreatePen(PS_SOLID, 10, colorActual);
         SelectObject(hdc, hLapiz);
         Rectangle(hdc,xMax*0.25,yMax*0.25,xMax*0.75,yMax*0.75);
         /*INSERTAR AQUI OTRAS FUNCIONES DE DIBUJO*/
         DeleteObject(hLapiz);
         EndPaint (hwnd, &ps);
         return 0;

      case WM_DESTROY:
         KillTimer(hwnd, ID_TEMPORIZADOR);
         PostQuitMessage (0);
         return 0;
      case WM_TIMER:
         switch (wParam)
         {
            case ID_TEMPORIZADOR:
               bandera=!bandera;
               if(bandera)
                  colorActual=RGB(255,0,0);
               else
                  colorActual=RGB(0,0,0);
               InvalidateRect(hwnd, NULL, TRUE);
         }
   }
   return DefWindowProc (hwnd, iMsg, wParam, lParam);
}

void insertarMenu(HWND hWnd)
{
   HMENU hMenu1, hMenu2;

   hMenu1 = CreateMenu();
   hMenu2 = CreateMenu();

   AppendMenu(hMenu1, MF_STRING | MF_POPUP, (UINT)hMenu2, "&Archivo");

   AppendMenu(hMenu2, MF_STRING, CM_ABRIR, "&Abrir");
   AppendMenu(hMenu2, MF_SEPARATOR, 0, NULL);
   AppendMenu(hMenu2, MF_STRING, CM_SALIR, "&Salir");

   SetMenu (hWnd, hMenu1);
}
Código de main.h:
Código:
void insertarMenu(HWND hWnd);

#define CM_SALIR         9072
#define CM_ABRIR         9071
#define ID_TEMPORIZADOR  9070
Ejecútenlo y vean el resultado. El cuadrado del ejemplo principal (el proyecto esqueletoWin) pero con unos cambios que hacen cambie de color cada cierto tiempo, del código deben destacarse las siguientes partes:

Código:
   SetTimer(hwnd,ID_TEMPORIZADOR,500,(TIMERPROC) NULL);
Se crea el temporizador para que se dispare cada 500 milisegundos (medio segundo)

Código:
      case WM_TIMER:
         switch (wParam)
         {
            case ID_TEMPORIZADOR:
               bandera=!bandera;
               if(bandera)
                  colorActual=RGB(255,0,0);
               else
                  colorActual=RGB(0,0,0);
               InvalidateRect(hwnd, NULL, TRUE);
         }
Se captura la interrupción del temporizador, dado que pueden existir muchos temporizadores, un bloque switch permite elegir la acción a realizar por cada temporizador, en este caso lo que se hace es cambiar el color de dibujado actual y se solicita que se repinte la pantalla.

Código:
      case WM_DESTROY:
         KillTimer(hwnd, ID_TEMPORIZADOR);
         PostQuitMessage (0);
         return 0;
Se destruye el temporizador y se termina la aplicación.

Lección 3. Un ejemplo más avanzado
Con el ejemplo presentado en la lección anterior podemos ya realizar animaciones, sin embargo una animación más compleja requiere que se añadan otras etapas.

Se verá el caso de una pelota rebotando dentro de una caja bidimensional. Las características de la ventana, la caja y la pelota, tienen las características que se muestran en la imagen siguiente:


http://img252.imageshack.us/img252/9263/cajadp9.png

El rectángulo mayor corresponde a la totalidad de la ventana y el rectángulo en azul que se encuentra dentro de la ventana será la caja dentro de la cual se haya confinada la pelota que se encuentra rebotando. La pelota tendrá un radio r y una posición (posX, posY), dicha posición irá cambiando con el tiempo.

La aplicación debe dibujar todos estos elementos y además ir moviendo la pelota, hasta que haga contacto con una de las caras de la caja y entonces se debe cambiar la dirección de la pelota.

La pelota se moverá cierta cantidad de píxeles en dirección x (velX) y otra cantidad en dirección y (velY) cada vez que el temporizador emita un mensaje.


http://img263.imageshack.us/img263/6...vpelotasz1.png

Lección 3.1. El choque de la pelota

Cuando la pelota choca con alguna de las paredes de la caja que la contiene, debe cambiar su dirección de movimiento.

En este caso simplemente se cambiará la dirección de la pelota, de modo que si choca contra una pared vertical, se cambiará el signo del incremento en la dirección x y si choca con alguna pared horizontal se cambia el signo del incremento en y.

El caso de que se choque contra una esquina, la pelota debe cambiar ambos signos.

Para determinar si la pelota ha chocado con alguna de las paredes simplemente hay que verificar la distancia de la pelota a las paredes, y si esta distancia es menor o igual a cero, significa que existe un contacto.

Este ejemplo representa un caso muy simple ya que las paredes de la caja se colocaron de manera paralela a los ejes cartesianos imaginarios asociados a la pantalla, de modo que determinar la distancia entre la pared y la pelota consiste en realizar una resta.
Para dar un margen de error puede considerarse que si la distancia es demasiado pequeña se ha dado un contacto.

Pero considero que la mejor manera de entender todos estos conceptos es revisando el código del programa.
Lección 3.2. Código de ejemplo

Código del archivo main.cpp
Código:
 #include <windows.h>
#include <stdio.h>
#include <math.h>
#include "main.h"

LRESULT CALLBACK ProcedimientoDventana(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   static char   szAppName[] = "Graficación";
   HWND          hwnd;
   MSG           msg;
   WNDCLASSEX    clase_de_ventana;

   clase_de_ventana.cbSize        = sizeof (clase_de_ventana);
   clase_de_ventana.style         = CS_HREDRAW | CS_VREDRAW;
   clase_de_ventana.lpfnWndProc   = ProcedimientoDventana;
   clase_de_ventana.cbClsExtra    = 0;
   clase_de_ventana.cbWndExtra    = 0;
   clase_de_ventana.hInstance     = hInstance;
   clase_de_ventana.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
   clase_de_ventana.hCursor       = LoadCursor (NULL, IDC_ARROW);
   clase_de_ventana.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
   clase_de_ventana.lpszMenuName  = "MenuPrincipal";
   clase_de_ventana.lpszClassName = szAppName;
   clase_de_ventana.hIconSm       = LoadIcon (NULL, IDI_APPLICATION);

   if(!RegisterClassEx (&clase_de_ventana)) return 0;

   hwnd = CreateWindowEx(
                         0,
                         szAppName,
                         "Graficación",
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         HWND_DESKTOP,
                         NULL,
                         hInstance,
                         NULL);
   insertarMenu(hwnd);

   //   Se crea el temporizador para la animacion, se eligieron 40 milisegundos
   //para tener una frecuencia de 25 marcos por segundo
   SetTimer(hwnd,ID_TEMPORIZADOR,40,(TIMERPROC) NULL);

   ShowWindow (hwnd, iCmdShow);

   while (GetMessage (&msg, NULL, 0, 0))
   {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
   return msg.wParam;
}

LRESULT CALLBACK ProcedimientoDventana (HWND hwnd, UINT iMsg, WPARAM wParam,
                                        LPARAM lParam)
{
   HDC              hdc;
   PAINTSTRUCT      ps;
   RECT             rect;
   POINT            pt;
   HPEN             hLapiz;
   static bool      bandera=FALSE;
   static caja      c1;
   static pelota    p1;


   static int xMax, yMax;

   switch (iMsg)
   {
      case WM_COMMAND:
         switch(LOWORD(wParam))
         {
            case CM_SALIR:
              PostQuitMessage(0); // Envía un mensaje WM_QUIT a la cola de mensajes
              break;
         }
         return 0;

      case WM_SIZE:
         xMax = LOWORD(lParam);
         yMax = HIWORD(lParam);
         printf("<%d,%d>",xMax,yMax);

         //   Se inicializan las dimendiones de la caja que dependen del tamaño
         //de la ventana
         c1.izq=xMax*0.1;
         c1.arriba=yMax*0.1;
         c1.der=xMax*0.9;
         c1.abajo=yMax*0.9;

         // Se inicializan los datos correspondientes a la pelota
         // La pelota se coloca inicialmente al centro de la caja
         p1.posX=0.5*(c1.der+c1.izq);
         p1.posY=0.5*(c1.arriba+c1.abajo);
         //
         p1.velX=7;
         p1.velY=5;
         //Tamaño de la pelota
         p1.r=30;
         return 0;

      case WM_PAINT:
         hdc = BeginPaint (hwnd, &ps);
         hLapiz = CreatePen(PS_SOLID, 1, RGB(0,0,255));
         SelectObject(hdc, hLapiz);

         //Se dibuja la caja que contiene la pelota
         Rectangle(hdc,c1.izq,c1.arriba,c1.der,c1.abajo);
         //Se dibuja la pelota
         circulo(hdc,p1.posX,p1.posY,p1.r);

         DeleteObject(hLapiz);
         EndPaint (hwnd, &ps);
         return 0;

      case WM_DESTROY:
         KillTimer(hwnd, ID_TEMPORIZADOR);
         PostQuitMessage (0);
         return 0;
      case WM_TIMER:
         switch (wParam)
         {
            case ID_TEMPORIZADOR:
               //Se mueve la pelota
               p1.posX+=p1.velX;
               p1.posY+=p1.velY;

               //   Si choco con alguna pared vertical se invierte la
               //direccion de movimiento de la pelota en el eje x
               if(((c1.der-(p1.posX+p1.r))<0)||(((p1.posX-p1.r)-c1.izq)<0))
                  p1.velX*=-1;
               //   Si choco con alguna pared horizontal Se invierte la
               //direccion de movimiento de la pelota en el eje y
               if(((c1.abajo-(p1.posY+p1.r))<0)||(((p1.posY-p1.r)-c1.arriba)<0))
                  p1.velY*=-1;

               //Se manda redibujar la ventana
               InvalidateRect(hwnd, NULL, TRUE);
         }
   }
   return DefWindowProc (hwnd, iMsg, wParam, lParam);
}

void insertarMenu(HWND hWnd)
{
   HMENU hMenu1, hMenu2;

   hMenu1 = CreateMenu();
   hMenu2 = CreateMenu();

   AppendMenu(hMenu1, MF_STRING | MF_POPUP, (UINT)hMenu2, "&Archivo");

   AppendMenu(hMenu2, MF_STRING, CM_ABRIR, "&Abrir");
   AppendMenu(hMenu2, MF_SEPARATOR, 0, NULL);
   AppendMenu(hMenu2, MF_STRING, CM_SALIR, "&Salir");

   SetMenu (hWnd, hMenu1);
}

void circulo(HDC hdc, int cx, int cy, int radio)
{
   Ellipse(hdc, cx-radio, cy-radio, cx+radio, cy+radio);
}

Código del archivo main.h

Código:
 void insertarMenu(HWND hWnd);
void circulo(HDC hdc, int cx, int cy, int radio);

#define CM_SALIR         9072
#define CM_ABRIR         9071
#define ID_TEMPORIZADOR  9070

struct pelota
{
   float posX, posY;
   float velX, velY;
   float r;
};

struct caja
{
   int izq, arriba, der, abajo;
};

Del código existen las siguientes cuestiones a destacar.

Primero, la caja y la pelota se crean como estructuras, para manejar más ordenadamente los datos. Estas estructuras se rellenan en la parte donde se captura el mensaje de redimensionamiento de la pantalla WM_SIZE, este mensaje se genera cada vez que el usuario cambia el tamaño de la pantalla pero también se genera un mensaje en el momento en el que se crea la pantalla, de este modo, es posible conocer el tamaño de la pantalla y crear la caja acorde a esta situación, así como también la pelota se sitúa en medio de la caja. En realidad no es necesario que la pelota este en un principio en medio sin embargo si debe estar dentro de la caja.
Si el usuario cambia el tamaño de la ventana, a animación se reinicia con los nuevos valores, esto permite experimentar con cajas de diferentes tamaños sin tener que modificar el código, ya que al variar el tamaño de la ventana, también se modifica el tamaño de la caja.

La pelota usa variables del tipo float para almacenar sus datos, esto para así poder manejar fracciones de píxel de desplazamiento.

También podemos ver que para crear la pelota se hace uso de la función circulo que se mencionó en el tutorial de gráficos simples.

La parte más importante del código está en la captura del mensaje WM_TIMER en la cual la pelota se mueve y además se verifica si esta ha impactado alguna de las paredes. La pelota se mueve simplemente sumando los valores correspondientes de desplazamiento a la coordenada adecuada de la pelota:

Código:
               //Se mueve la pelota
               p1.posX+=p1.velX;
               p1.posY+=p1.velY;
E inmediatamente después se verifica si la pelota ha hecho contacto con alguna pared, se tienen dos casos, si hizo contacto con alguna pared vertical o con alguna pared horizontal. Dependiendo del contacto se invierte el signo del incremento correspondiente (el signo se invierte mediante la operación vel*=-1;).
Como se había comentado, también se tiene el caso de que la pelota impacte una esquina. En esas circunstancias, ambas condiciones (los bloques if) se cumplen y por lo tanto ambas direcciones de movimiento se invierten.

Para verificar la distancia se emplea una simple resta y una comparación:

Código:
               //   Si choco con alguna pared vertical se invierte la
               //direccion de movimiento de la pelota en el eje x
               if(((c1.der-(p1.posX+p1.r))<0)||(((p1.posX-p1.r)-c1.izq)<0))
                  p1.velX*=-1;
               //   Si choco con alguna pared horizontal Se invierte la
               //direccion de movimiento de la pelota en el eje y
               if(((c1.abajo-(p1.posY+p1.r))<0)||(((p1.posY-p1.r)-c1.arriba)<0))
                  p1.velY*=-1;
Las restas, tienen como fin medir la distancia perpendicular de la superficie de la pelota a la pared de la caja, si el valor es menor que cero, significa que la pelota ha rebasado la pared y entonces la ha impactado.

Si se quiere aumentar la velocidad de la pelota existen dos maneras de hacerlo, una es aumentando los incrementos velX y velY y otra es disminuyendo el tiempo del temporizador para que así la animación tenga más marcos por segundo. Esta última opción es válida para experimentar sin embargo no es una opción práctica porque significaría aumentar la velocidad de todo el sistema.

Ahora solo falta que experimenten cambiando los desplazamientos y vean los resultados.

Lección 3.3. Algunas consideraciones especiales

Si bien todo parece correcto existe un problema relacionado con el hecho de tratarse de una animación discreta (discreta se refiere a que se realiza a pasos) y es que la pelota puede atravesar la caja:


http://img511.imageshack.us/img511/9...sepasa2qs7.png

Esta situación no tiene una forma simple de evitarse. Sin embargo, una manera de solucionar este problema y que da buenos resultados consiste en calcular la posición de la pelota en el contacto, es decir, que si la pelota atravesará la caja en su próximo movimiento se ajusta la posición para colocarla (la pelota) sobre la superficie de la caja. Claro que esto lleva la complejidad extra de averiguar si la pelota hará contacto y de corregir su posición si atraviesa la caja.

Otra situación anómala es que podrán ver que la animación realiza ciertos centelleos, esto aunque se esté empleando una frecuencia alta. Lo que sucede en ese caso es que la interfaz gráfica que estamos empleando (que se conoce como GDI) no es muy eficiente y por lo tanto no es capaz de mostrar animaciones que varíen de manera veloz. Para mejorar el desempeño existen otras bibliotecas de gráficos más eficientes. Pero para animaciones simples el GDI es aceptable.


Bueno, pues hasta aquí le dejo hasta que haya alguna pregunta.
__________________
I was just a shadow...

Última edición por ninio; 18-May-2007 a las 04:28
Responder Citando
Respuesta


(0 miembros y 1 visitantes)
 
Herramientas

Normas de Publicación
No puedes crear nuevos temas
No puedes responder mensajes
No puedes subir archivos adjuntos
No puedes editar tus mensajes

Los Códigos BB están Activado
Las Caritas están Activado
[IMG] está Activado
El Código HTML está Desactivado
Trackbacks are Activado
Pingbacks are Activado
Refbacks are Activado

Ir al Foro


Torneo DC 2012
Torneo DC 2012

La franja horaria es GMT +1. Ahora son las 21:47.


Desarrollado por: vBulletin® Versión 3.8.2
Derechos de Autor ©2000 - 2012, Jelsoft Enterprises Ltd.
Traducido por mcloud de vBhispano.com
 

Content Relevant URLs by vBSEO 3.2.0