Atualizando a tela com Allegro e com Java
Como disse no post passado, eu ia implementar rotinas para atualização de tela para servir de exemplo. Implementei um programa de exemplo em C que utiliza a biblioteca Allegro, e um em Java utilizando o Java 2D, que já vem no JSE.
C e Allegro
Allegro, para quem ainda não conhece, é uma biblioteca para jogos para a linguagem C e C++ e roda nas plataformas DOS, Unix (Linux, FreeBSD, Irix, Solaris, Darwin), Windows, QNX, BeOS and MacOS X que fornece funções para gráficos, sons, entrada, temporizador, GUI, entre outras coisas. Mais informações na página oficial do Allegro.
O código é muito simples, e além do Allegro usei uma biblioteca de atualização de tela que encontrei na GameDev um tempo atrás que se mostrou muito útil e resolvi aproveitá-la no exemplo. Ela implementa double buffering, page flipping e triple buffering, e estes dois últimos podem ser também com um buffer em memória RAM, caso seu aplicativo use muitas imagens translúcidas. O double buffering também pode ser usando memória RAM ou de sistema. Informações sobre os tipos de memória para a estrutura BITMAP você encontra no manual no inÃcio da parte que fala de Bitmap.
O programa fica movendo um quadrado na parte de cima de um lado para o outro, enquanto apresenta informações do modo de atualização, do vsync e instruções para interagir com o programa. O usuário pode desligar o v-sync em alguns modos com a tecla “V” e trocar de modo com as telas de “0″ a “6″. É claramente possÃvel ver os efeitos de flickering e tearing quando o vsync é desligado também. Aparentemente há algum bug que quando troca para triple buffering, ele fica alguns segundos muito lento, mas depois de um tempo ele volta ao normal. Segue o código:
#include <allegro.h> // Screen Update API for Allegro // http://www.gamedev.net/reference/articles/article2158.asp #include "al_screen.h" // count the fps volatile int fps = 0, frames = 0; void my_timer_handler() { fps = frames; frames = 0; } END_OF_FUNCTION(my_timer_handler) // init allegro int init(); // create bitmap for the moving square BITMAP *create_square(); // draw text information on buffer void draw_text(BITMAP *buffer); // check user input void check_input(); // names of update modes char update_modes[][80] = { { "UPDATE_NONE" }, { "UPDATE_TRIPLE_BUFFER" }, { "UPDATE_PAGE_FLIP" }, { "UPDATE_SYSTEM_BUFFER" }, { "UPDATE_DOUBLE_BUFFER" }, { "UPDATE_TRIPLE_WMB" }, { "UPDATE_PAGEFLIP_WMB" } }; int main() { BITMAP *buffer; BITMAP *square; int x, y, vx; if (init()) { return 1; } square = create_square(); // initial position and velocity x = y = 0; vx = 5; while (!key[KEY_ESC]) { // check user input check_input(); // update the square's position x += vx; if (x > SCREEN_W - square->w) { x = SCREEN_W - square->w; vx *= -1; } if (x < 0) { x = 0; vx *= -1; } // get the buffer if (get_update_method()) { buffer = get_buffer(); } else { buffer = screen; if (vsync_is_enabled()) { vsync(); } } // draw acquire_bitmap(buffer); clear(buffer); draw_sprite(buffer, square, x, y); draw_text(buffer); release_bitmap(buffer); // show in screen if (get_update_method()) { update_screen(); } else { // já está na tela } // increment frame count, to show current fps frames++; } if (get_update_method()) { shutdown_screen_updating(); } return 0; } END_OF_MAIN(); // init allegro and screen updating int init() { allegro_init(); set_uformat(U_ASCII); install_keyboard(); install_timer(); set_color_depth(32); if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0)) { allegro_message("Falhou"); return 1; } if (!initialize_screen_updating(UPDATE_DOUBLE_BUFFER)) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message("Falha no initialize_screen_updating"); return 1; } clear_keybuf(); LOCK_VARIABLE(fps); LOCK_VARIABLE(frames); LOCK_FUNCTION(my_timer_handler); // call my_timer_handler every 1000 ms install_int(my_timer_handler, 1000); return 0; } // create and paint the square bitmap BITMAP *create_square() { BITMAP *square; square = create_bitmap(200, 200); clear_to_color(square, makecol(200, 200, 200)); return square; } // draw some info in the buffer void draw_text(BITMAP *buffer) { int mode, vsync; mode = get_update_method(); vsync = vsync_is_enabled(); if (mode == UPDATE_SYSTEM_BUFFER || mode == UPDATE_DOUBLE_BUFFER || mode == UPDATE_NONE) { textprintf_ex(buffer, font, 10, SCREEN_H - 60, makecol(230, 230, 230), -1, "Vsync - %s", (vsync ? "on" : "off")); } textprintf_ex(buffer, font, 10, SCREEN_H - 50, makecol(230, 230, 230), -1, "Mode: %d - %s", mode, update_modes[mode]); textout_ex(buffer, font, " V - Toggle vsync on/off", 10, SCREEN_H - 40, makecol(230, 230, 230), -1); textout_ex(buffer, font, "0-6 - Change update mode", 10, SCREEN_H - 30, makecol(230, 230, 230), -1); textprintf_ex(buffer, font, 10, SCREEN_H - 20, makecol(230, 230, 230), -1, "Fps: %d", fps); } // check user input void check_input() { int ch; int mode; if (keypressed()) { ch = readkey(); // vsync on/off if (ch >> 8 == KEY_V) { toggle_vsync(); } else { if ((ch & 0xFF) >= '0' && (ch & 0xFF) <= '6') { // update modes mode = (ch & 0xFF) - '0'; if (mode) { initialize_screen_updating(mode); } else { // this is for UPDATE_NONE shutdown_screen_updating(); } } } } }
Java e Java2D
A partir do Java 1.4, apareceu no JSE uma api chamada Full-Screen Exclusive Mode API (inclusive seguindo o link tem vários exemplos melhores do que o meu :P) que permite você trocar de resolução e deixar uma janela em tela cheia. Usarei isso para criar meu exemplo.
Outra coisa que usarei aqui é o chamado Active Rendering, que nada mais é do que você tomar conta da rotina de desenho. Em aplicações tradicionais de GUI, quem decide quando desenhar é o Sistema Operacional.
Nesse código, eu entro em modo de tela cheia com o primeiro modo possÃvel, retornado pelo método getDisplayModes() da classe GraphicsDevice. Logo depois da inicialização, o método run() que tem o loop principal é chamado, criando a estratégia de atualização e movendo um quadrado pela tela. O programa ainda apresenta na tela a taxa de quadros.
Uma coisa importante para se observar aqui é o uso do método contentsLost() da classe BufferStrategy. Isso é necessário pois o usuário pode dar um Alt+TAB e depois voltar, e caso o programa tente desenhar em um buffer que perdeu, causará uma exceção e a interrupção do programa.
Segue então o código:
package com.dudaskank.screenupdate; import java.awt.Color; import java.awt.DisplayMode; import java.awt.Graphics; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.image.BufferStrategy; import java.util.Timer; import java.util.TimerTask; import javax.swing.JFrame; public class Main extends JFrame { private static final long serialVersionUID = 1L; private int frames = 0, fps = 0; public Main() { // iniciando a tela cheia super("Screen Update Test"); setResizable(false); setUndecorated(true); // para usar active rendering setIgnoreRepaint(true); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); GraphicsEnvironment env = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice device = env.getDefaultScreenDevice(); DisplayMode[] modes = device.getDisplayModes(); device.setFullScreenWindow(this); // pega o primeiro modo disponÃvel em ela cheia device.setDisplayMode(modes[0]); // timer para zerar a quantidade atual de frames a atualizar o fps Timer timer = new Timer(true); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { fps = frames; frames = 0; } }, 0, 1000); } public void run() { // cria 2 bitmaps, um para a tela sendo exibida e outro para desenhar // fora da tela (double buffering ou page flipping) createBufferStrategy(2); BufferStrategy strategy = getBufferStrategy(); Graphics g; // variáveis usadas na movimentação do quadrado int x, y, vx, vy; x = y = 0; vx = vy = 5; // loop até fechar a janela while (true) { // atualiza a posição do quadrado x += vx; y += vy; if (x < 0) { x = 0; vx *= -1; } else if (x + 100 > getWidth()) { x = getWidth() - 100; vx *= -1; } if (y < 0) { y = 0; vy *= -1; } else if (y + 100 > getHeight()) { y = getHeight() - 100; vy *= -1; } // desenha um quadrado g = strategy.getDrawGraphics(); if (!strategy.contentsLost()) { g.setColor(Color.BLACK); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.GRAY); g.fillRect(x, y, 100, 100); // escreve o fps na tela g.setColor(Color.BLUE); g.drawString("Fps: " + fps, 10, 50); g.drawString("Alt+F4 para sair", 10, 60); g.dispose(); strategy.show(); // incrementa número de frames exibidos frames++; } } } public static void main(String[] args) { Main main = new Main(); main.setVisible(true); main.run(); } }
Conclusão
Vendo um exemplo de implementação agora tenho certeza que ficou mais fácil de entender o último post aqui do blog. Espero que tenham gostado, crÃticas e dúvidas por favor mandem os comentários, assim que possÃvel irei respondendo :).
22 de setembro de 2008 Ã s 21:43
Adoroo esse tipo de coisaaaa
27 de maio de 2009 Ã s 13:25
Como faço para exibir uma imagem que a resolução´é maior que 800 X 600? Comsiguo definir esta resolução no set_gfx_mode(GFX_AUTODETECT, ???, ???, 0, 0))
9 de junho de 2009 Ã s 15:50
Rafael, resolução 800×600 você consegue no set_gfx_mode() sim.
Para você colocar uma maior dentro da tela, você deve usar a função stretch_blit().
A documentação destas e de outras funções você encontra no seu diretório docs do allegro ou em http://www.allegro.cc/manual/