sábado, 17 de octubre de 2020

Scroll - Movimiento 2D

  En nuestro ejemplo anterior, el personaje se desplaza por un escenario que es mostrado por pantalla. Si este escenario tiene un tamaño mayor que el de la pantalla solo se estará mostrando una parte de este, concretamente la parte donde este situado nuestro personaje.

Cuando el personaje empieza a recorrer el escenario, puede llegar en algún momento en que llegue a un rincón que no se muestra por pantalla, para evitar esto se desplaza la imagen del escenario de manera que se muestre por pantalla por donde va nuestro personaje.

A este desplazamiento de imagen se le llama Scroll.

Para entender un poco mas esto, es como si la pantalla fuera una cámara que persigue a nuestro personaje.



En este ejemplo el control de dicha cámara será controlado desde la clase player.


class player
{
    // posicion del personaje con respecto imagen
    int x, y;
    // posicion del personaje con respecto pantalla
    int px, py;
    // posicion de la camara que sigue al personaje
    int camarax, camaray;
    int dir;
    int paso;
    int tam;
    int tiempoPaso;
    int tiempoCont;
    ALLEGRO_BITMAP* img;
public:
    int getCamX() { return camarax; }
    int getCamY() { return camaray; }
    void inicia()
    {
        img = al_load_bitmap("datos/per32s.png");
        tiempoPaso = int(sistema.FPS / mueve);
        tiempoCont = 0;
        x = 260;
        y = 260;
        px = x;
        py = y;
        camarax = 0;
        camaray = 0;
        dir = 0;
        paso = 0;
        tam = 32;
    }
    
    bool colisiona()
    {
        escena.bloquea();
        bool valor = false;
        for (int i = 1; i < tam-1; i++)
        {
            for (int j = tam/2; j < tam; j++)
            {
                int vx = x + i;
                int vy = y + j;
                if (escena.esRojo(vx, vy))
                {
                    valor = true;
                }
            }
        }    
        escena.desbloquea();
        return valor;
    }
    
    void actualiza(ALLEGRO_EVENT evento)
    {
        ALLEGRO_KEYBOARD_STATE teclado;
        // estado anterior
        int ax, ay;
        ax = x;
        ay = y;
    
        al_get_keyboard_state(&teclado);
        if (al_key_down(&teclado, ALLEGRO_KEY_UP))
        {
            y -= desplaza;
            dir = 3;            
            tiempoCont++;
        }
        if (al_key_down(&teclado, ALLEGRO_KEY_DOWN))
        {
            y += desplaza;
            dir = 0;
            tiempoCont++;
        }
        if (al_key_down(&teclado, ALLEGRO_KEY_LEFT))
        {
            x -= desplaza;
            dir = 1;
            tiempoCont++;
        }
        if (al_key_down(&teclado, ALLEGRO_KEY_RIGHT))
        {
            x += desplaza;
            dir = 2;
            tiempoCont++;
        }
        if ((x != ax || y != ay) &&  colisiona())
        {
            x = ax;
            y = ay;
        }
        // limitadores
        if (x < 0) x = 0;
        if (x > escenax - tam) x = escenax - tam;
        if (y < 0) y = 0;
        if (y > escenay - tam) y = escenay - tam;
        if (tiempoCont > tiempoPaso)
        {
            paso++;
            tiempoCont = 0;
        }
        if (paso > 2) paso = 0;
        if (x < pantallax / 2) camarax = 0;
        if (y < pantallay / 2) camaray = 0;
        if (x >= pantallax / 2 && x <= escenax - (pantallax / 2)) camarax = -x + (pantallax / 2);
        if (y >= pantallay / 2 && y <= escenay - (pantallay / 2)) camaray = -y + (pantallay / 2);
        if (x > escenax - (pantallax / 2)) camarax = pantallax - escenax;
        if (y > escenay - (pantallay / 2)) camaray = pantallay - escenay;
        px = x + camarax;
        py = y + camaray;
    }
    void pinta()
    {
        al_draw_bitmap_region(img, paso * tam, dir * tam, tam, tam, px, py, 0);
    }
} jugador;

Al principio de la clase se definen las variables privadas de la clase. Después de public tenemos todos los métodos públicos de la clase.

Se tienen getCamX(), getCamY() para obtener la posición de la cámara que sigue al personaje.

El método inicia, sirve para inicializar todas las variables de la clase.

El método colisiona, se encarga de comprobar si choca el personaje con algún objeto del escenario. Contiene dos bucles que se encargan de recorrer todo el espacio del cuerpo del personaje. 

Dentro se va comprobando pixel a pixel, si el pixel de la imagen de choque es rojo, en ese caso la variable valor es true, es decir, que ha colisionado. 

Para un correcto funcionamiento se añade las funciones escena.bloquea(), y escena.desbloquea(). Que es necesario para usar repetidamente la función escena.esRojo() que comprueba el pixel seleccionado.

El método actualiza, se encarga de comprobar las teclas que manejan a nuestro personaje, y con la nueva posición de x,y se obtiene la nueva posición de la pantalla.

El método pinta, como su nombre indica pinta el personaje.

void dibuja()
{    
    al_clear_to_color(sistema.fondo);
    escena.pinta(jugador.getCamX(), jugador.getCamY());
    jugador.pinta();
    escena.pinta(jugador.getCamX(), jugador.getCamY(), true);
   // al_draw_textf(font, al_map_rgb(60, 60, 60), 50, 330, 0, "%d, %d", px,py);
    al_draw_text(font, al_map_rgb(0, 0, 0), 50, 380, 0, "KodayRPG 2020");
    // muestra por pantalla
    al_flip_display();
}


Dibuja(), pinta la escena, al jugador, y el texto y lo muestra por pantalla.

void juego()
{
    font = al_load_ttf_font("datos/neuropol.ttf", 64, 0);
    ALLEGRO_EVENT evento;
    bool repetir = true;
    bool dibujar = true;
    jugador.inicia();
    escena.carga();
    while (repetir)
    {
        // Pinta si es dibuja y esta vacia la lista de eventos
        if (dibujar && al_event_queue_is_empty(sistema.Mis_eventos))
        {                       
            dibuja();
            dibujar = false;
        }
        // esperamos a que ocurra un evento
        al_wait_for_event(sistema.Mis_eventos, &evento);
        // se ha cerrado la ventana
        if (evento.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
        {
            repetir = false;
        }
        // se ha pulsado ESC
        if (evento.type == ALLEGRO_EVENT_KEY_DOWN)
        {
            if (evento.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
            {
                repetir = false;
            }
        }
        // pasa un tiempo determinado
        if (evento.type == ALLEGRO_EVENT_TIMER)
        {
            dibujar = true;
            jugador.actualiza(evento);
        }
    }
    al_destroy_font(font);
}


juego() contiene el bucle principal que se repite mientras que repetir sea verdadero, este valor cambia cuando se pulsa la tecla ESC o se hace click en la X de la ventana.


int main(void)
{
    // inicializamos las librerías utilizadas
    al_init();
    al_init_primitives_addon();
    al_init_font_addon();
    al_init_ttf_addon();
    al_init_image_addon();
    al_install_keyboard();
    ALLEGRO_DISPLAY* display = al_create_display(800, 450);
    ALLEGRO_TIMER* timer = NULL;
    al_set_window_title(display, "KodayRPG");
    sistema.img = al_load_bitmap("datos/escenario.png");
    sistema.fondo = al_map_rgb(255, 255, 255);
    sistema.FPS = 60;    
    timer = al_create_timer(1.0 / sistema.FPS);
    // creo lista de eventos
    sistema.Mis_eventos = al_create_event_queue();
    // asigno eventos a la lista de eventos
    al_register_event_source(sistema.Mis_eventos, al_get_keyboard_event_source());
    al_register_event_source(sistema.Mis_eventos, al_get_display_event_source(display));
    al_register_event_source(sistema.Mis_eventos, al_get_timer_event_source(timer));
    al_start_timer(timer);
    juego();
    al_destroy_display(display);      
}


Aquí tienen el código completo para descargar, con las imágenes incluidas.

Descargar

Nota:  Para los que descargaron el archivo antes del 19 de nov, el ejemplo viene con un error. El nombre de la imagen que se utiliza es incorrecto, debes cambiarlo a per32s.png. Si no se hace este cambio da un error de ejecución al no encontrar la imagen.

2 comentarios:

  1. bro,me sale un error, es este:

    [Warning] anonymous type with no linkage used to declare variable ' sistema' with linkage

    espero me puedas responder, gracias

    ResponderEliminar
    Respuestas
    1. Esa advertencia supongo que se debe a que se a declarado una variable con un tipo anónimo y te da problemas. Prueba a darle un nombre por ejemplo datos quedando de la siguiente forma:
      struct datos{
      int FPS;
      ALLEGRO_EVENT_QUEUE* Mis_eventos;
      ALLEGRO_COLOR fondo;
      ALLEGRO_BITMAP* img;
      } sistema;

      Saludos.

      Eliminar