Código de ordenação de voxels

Prometi, aqui está. O mestrado começou e percebi que ele vai destruir o que resta de minha alma =D

Em breve faço um post sobre colisão de polígonos convexos.

EDIT: Sugestões para otimização são bem-vindas!

import math

def trans(xa, ya, xb, yb, voxel):
    """
    Given a start and end point, returns the list of voxels
    that must be checked against collisions, in priority order,
    as a list of (voxel_x, voxel_y) tuples.

    A brief study about parametric line equations will help!

    Implements the method seem at: http://www.cs.yorku.ca/~amana/research/grid.pdf

    @param xa: The start x coord
    @param ya: The start y coord
    @param xb: The end x coord
    @param yb: The end y coord
    @param voxel: The voxel width and length (i.e. for 16x16 tiles it would be 16)

    @author: Paolo victor, paolovictor@gmail.com
    """
    # Getting start and end x and y voxels
    xa_g, ya_g = xa // voxel, ya // voxel
    xb_g, yb_g = xb // voxel, yb // voxel

    # Base cases: same y voxel, same x voxel
    if xa_g == xb_g or ya_g == yb_g:
        # Getting x and y increments
        if xa_g < xb_g: x_inc = 1
        else: x_inc = -1

        if ya_g < yb_g: y_inc = 1
        else: y_inc = -1

        # If it's in same horizontal voxel, test if it's in the same
        # vertical voxel. Return a straight vertical line otherwise
        if xa_g == xb_g:
            if ya_g == yb_g:
                return [(xa_g, ya_g)]

            return [(xa_g, i) for i in range(ya_g, yb_g + y_inc, y_inc)]
        elif ya == yb: # Similar for being in the same vertical voxel
            return [(i, ya_g) for i in range(xa_g, xb_g + x_inc, x_inc)]

    # Usual case: different start and end voxels
    xbxa = xb - xa
    ybya = yb - ya

    # Evaluating horizontal increment
    x_dir = float((xbxa) // abs(xbxa))
    y_dir = float((ybya) // abs(ybya))

    # Initializing and starting loop
    xp, yp = xa, ya

    result = []
    r = 0.0
    while r <= 1.0: # Goes through the whole vector
        # Add current voxel to results
        xp_g, yp_g = xp // voxel, yp // voxel
        result.append((xp_g, yp_g))

        # Evaluate horizontal and vertical increase
        delta_rx = ((xp_g + x_dir) * voxel - xp) / xbxa
        delta_ry = ((yp_g + y_dir) * voxel - yp) / ybya

        # Choose the smaler increment
        if delta_rx < delta_ry:
            xp += delta_rx * xbxa
            yp += delta_rx * ybya
            r += delta_rx
        else:
            xp += delta_ry * xbxa
            yp += delta_ry * ybya
            r += delta_ry

    return result

Deixe um comentário

Python, lento?

Primeiramente, uma breve paráfrase do nosso colega Dilbert:

GAAAH!

Em um acidente de percurso, perdi todo o código-fonte da interface gráfica do editor. Ele tinha botões, checkboxes, janelas com “drag-and-drop”, tudo OO e fácil de usar. Era lindo.

Era.

Voltando à programação normal, vamos conversar um pouco sobre engines de plataforma. Mais especificamente, engines baseadas em tiles. A questão é: como mover o personagem e detectar colisões entre ele e o cenário?

Uma abordagem simples seria, a cada atualização, mover o personagem D pixels na direção desejada. Se a posição for inválida, o personagem é reposicionado.

Movimento e colisão em engine de plataforma 1

Mas… e se o movimento foi tão acentuado que impossibilitou a detecção?

Movimento e colisão em engine de plataforma 2

Uma solução é, dada uma reta entre o ponto de origem do personagem e o ponto de destino, testar a colisão com todos os blocos cuja intersecção com a reta não seja nula. Note que, para esse método funcionar, os blocos devem ser ordenados por ordem de intersecção.

Esse cara resolveu esse problema. Ele diz que o algoritmo é simples, rápido, etc etc (e realmente é!). Depois de relembrar as aulas de geometria vetorial, parti para implementar o algoritmo em Python. Vocês já devem imaginar o resultado. Embora deveria, ainda não trabalhei em uma implementação em C para comparar a performance, mas creio que isso não seja realmente necessário :-)

Entretanto, é suficiente. No meu teste, executei o algoritmo para 1000 pares de pontos de origem em destino, contidos entre (0,0) e (1000,1000). Isso seria como ter 1000 objetos se movendo a uma velocidade de até 1000 pixels por atualização de lógica. Com 30 atualizações de lógica por segundo, daria uns 30000 pixels por segundo. É um bocado, e provavelmente não vai acontecer. O resultado?

0.127067825659 segundos

Mudando para um cenário menos megalomaníacio: pontos entre (0,0) e (16,16), 128 objetos:

0.00307580991439 segundos

E mesmo se acontecer de se tornar um gargalo de desempenho, posso partir para a ignorância e usar artifícios com o psyco e bibliotecas como o mpmath.

Em breve faço um post com o código-fonte.

Deixe um comentário

File selector em Python + Tkinter

Antes de partir para a violência e decidir programar uma biblioteca própria, decidi tentar usar duas bibliotecas conhecidas: PyGTK e Tkinter, que é uma abstração simples sob o Tcl/Tk para Python.

Não tenho opinião formada sobre o PyGTK, até porque não consegui sequer usá-lo. Apesar de seguir as instruções de instalação à risca, o Python sempre acusa um erro críptico ao tentar importar uma das bibliotecas necessárias.

O Tkinter, como já disse antes, é uma camada simples sob o Tcl/Tk. Desta forma, não espere nada que lembre a simplicidade de Python. Na verdade, há muita magia-negra envolvida. Até para abrir um file selector você tem que fazer o bind manual das ações “Ok” e “Cancel”. Está bem que algum cidadão poderia querer que o botão “Ok” fizesse mais alguma coisa além de confirmar a escolha, mas a falta de uma opção padrão me faz sentir como se ainda estivéssemos em 1997.

E não preciso nem falar sobre a “documentação”. Exemplos intuitivos de uso? Hah!

Apesar dos pesares, o Tkinter tem algumas funcionalidades que dariam um bom trabalho se eu decidisse implementá-las. Assim, o usarei para alguns itens específicos, como para selecionar um arquivo para edição.

Para ajudar a manter minha sanidade, implementei mais uma camada acima do Tkinter, que implementa um seletor de arquivos. Eu queria algo que permitisse que eu usasse apenas um método para selecionar um arquivo. Algo como:

caminho_do_arquivo = open_file_selector()

O resultado é:

# Wrapper for the Python's Tix ExFileSelector dialog. It handles
# the annoying handle assignment and TK black magic, offering
# a bare-bones OO file selector implementation.
#
# Author: Paolo Victor, paolovictor@gmail.com
import Tix
from Tkinter import *
from Tkconstants import *

class TKSelector:
    def __file_selected__(self, path):
        # Sets path, closes window
        self.__path__ = path
        self.__root__.destroy()

    def __cancel_button_clicked__(self, action):
        # Unsets path, closes window
        self.__path__ = None
        self.__root__.destroy()

    def open(self, title="Select a file", baseDir=None):
        # Resetting path
        self.__path__ = None

        # Initializing root panel
        self.__root__ = Tix.Tk()
        self.__root__.wm_resizable(0, 0)
        self.__root__.wm_title(title)

        # Initializing  selector
        self.__selector__ = Tix.ExFileSelectBox(self.__root__)
        self.__selector__.cancel.bind("", self.__cancel_button_clicked__)        

        configuration = {"command": self.__file_selected__}
        if baseDir: configuration["dir"] = baseDir
        self.__selector__.configure(configuration)

        self.__selector__.pack()

        # Displaying selector
        self.__selector__.mainloop(0)

        return self.__path__

# Convenience method =]
def open_file_selector(title="Select a file", baseDir=None):
    selector = TKSelector()
    return selector.open(title, baseDir)

É no mínimo irônico que eu tenha reclamado da documentação do Tkinter, para logo depois mostrar um código pouco documentado :) , mas entendo que os nomes das variáveis são auto-descritivos.

Exemplo de uso, se o código estiver em um módulo chamado “utils”:

import utils

caminho_do_arquivo = utils.open_file_selector("Selecione um arquivo")

Este código abre um seletor de arquivos com o título “Selecione um arquivo”, cujo caminho inicial é o diretório de execução do processo e que retorna o nome do arquivo, se este for selecionado, ou “None”, se a seleção foi cancelada.

Comentários (1)

Um homem e suas ferramentas

EDIT: Pelo que me parece, o link para imagem está quebrado porque um dos servidores do ImageShack pode estar fora do ar. 

Comecei a escrever o editor de mapas. Após mais ou menos 200 linhas de código, tinha algo ligeiramente funcional, mas com alguns problemas:

  1. A interface estava altamente acoplada com a lógica do mapa;
  2. Novas funcionalidades (como escolher quais camadas estavam visíveis, por exemplo) eram abordadas de formas diferentes, o que resultou numa interface “Frankenstein”;
  3. As diversas ferramentas eram acionadas apenas por atalhos de teclado\mouse. Até mesmo eu precisaria de um manual no futuro.

Para se ter uma idéia, eis uma imagem da versão atual:

Free Image Hosting at www.ImageShack.us

Deixo como um exercício de interpretação descobrir o quê significa essa bagunça toda :)

Para resolver esses problemas, imaginei que o ideal seria utilizar alguma biblioteca de GUI python, compatível com pygame. Como não consegui fazer o PyGTK funcionar, o TK é um samba-do-crioulo-doido e as bibliotecas feitas em pygame ora eram pouco documentadas ora simplesmente não funcionavam, resolvi tratar o problema com minhas próprias mãos.

Claro que escrever uma biblioteca para GUI pode se tornar um projeto tão grande quanto o jogo em si, mas eu pretendo focar no básico, tendo como objetivos a simplicidade e reusabilidade. Já comecei a pensar em problemas que podem me morder no futuro, como a ordem de exibição dos itens (o negócio pode ficar feio se você não se preocupar com isso) e assim que tiver algumas idéias maduras, posto por aqui :)

Deixe um comentário

Toc Toc

Ora, há quanto tempo!

O hiato tem uma série de bons motivos. Aconteceu um  bocado de coisas nesses dias, entre trabalho e aprovações no mestrado (vou para BH!), minhas “férias”, assim por dizer, começaram =) Além disso, estou com uma máquina “de trabalho” nova: um Acer Aspire 5720. Ele é bem legal e vai dar muitíssimo bem para o gasto durante o mestrado.

Status atual? Implementei um renderizador simples para os mapas, usando o pygame. Comecei a implementar um editor de mapas. Assim que tiver algo usável, coloco uma screenshot. Também estou pensando em colocar o código no Google project hosting, só tenho receio em relação à licença

Deixe um comentário

Renderização e OO – Código

Como prometi, o código que ilustra a parte de renderização. Python não implementa interfaces explícitas, como em Java, então fiz uma ligeira gambiarra com uma classe abstrata. E sim, eu programo em inglês. Me processem :P

# Object renderer abstract class
class Renderer:
    # Default constructor
    #
    # Raises an exception because this class can't
    # be instantiated. It's used as a base class
    # for other renderer classes
    def __init__(self):
        raise "Can't instantiate this abstract class"

    # Renders an object
    #
    # Renders the object based on its class. It uses
    # an ugly chain of "if else isinstance", because
    # Python can't do method overloading (at least
    # not out of the box). Thanks, guys.
    def render(self, object):
        if isinstance(object, Map):
            self.__renderMap__(object)

# Renderer that uses pygame's routines
class PygameRenderer(Renderer):
    # Default constructor
    #
    # Overrides the base class' constructor
    # to prevent an instantiation exception
    def __init__(self):
        pass

    # Renders a map object
    def __renderMap__(self, map):
        print "Rendering map using PyGame!"

# Renderer that uses OpenGL routines
class OpenGLRenderer(Renderer):
    # Default constructor
    #
    # Overrides the base class' constructor
    # to prevent an instantiation exception
    def __init__(self):
        pass

    # Renders a map object
    def __renderMap__(self, map):
        print "Rendering map using OpenGL!"

Comentários (1)

Casa de ferreiro…

Para quem está curioso sobre o que pretendo usar:

Deixe um comentário

Renderização e OO

Independente da definição dos mapas, uma coisa importante é definir a forma com a qual eles serão renderizados, assim como para todos os outros objetos de jogo (jogador, inimigos, etc). Uma abordagem ingênua seria acrescentar um método “renderiza” para cada classe, assim cada classe teria conhecimento de como suas representações devem ser mostradas.

Seria algo como:

class Mapa    /* uma porção de métodos relacionados ao mapa */

    renderizaMapa( ... ) 

A idéia parece legal, mas começa a vazar quando se considera a possibilidade de múltiplos renderizadores. Digamos que além de um renderizador padrão feito com as rotinas do pygame, eu quisesse adicionar um novo renderizador baseado em OpenGL. Fácil! É só adicionar um novo método, “renderizaOpenGL”!

class Mapa

    /* uma porção de métodos relacionados ao mapa */


    renderizaMapaPython( ... )


    renderizaMapaGL( ... )

Mas e se eu quiser adicionar uma outra forma de renderização, por DirectX? Adicionaria um novo método para cada tipo de objeto? Obviamente esta abordagem não é a ideal.Uma solução, que é a que vou seguir, será criar um renderizador de cada tipo, que recebe os objetos que serão renderizados. Assim, para mudar o renderizador para um que usa OpenGL, DirectX ou sinais de fumaça, basta implementar o novo renderizador e substituí-lo, sem modificar o código dos objetos de jogo.

Exemplo para o mapa:

class Mapa

    /* métodos do mapa */ 

interface Renderizador 

    renderizaMapa( Mapa mapa, ... )

class RenderizadorPython implements Renderizador

class RenderizadorOpenGL implements Renderizador

class RenderizadorDirectX implements Renderizador 

Em breve mostro o código Python que ilustra essa idéia. Primeiro preciso estudar como (se) Python trata tipos abstratos de dados :)

Deixe um comentário

Mapa de jogo, inimigos

Mapas

Os mapas do jogo serão baseados em tiles, com camadas. Assim posso definir camadas de “fundo”, “frente”, “máscara”, etc. Dessa forma, um dos metadados de um mapa é uma matriz [l][a][c], onde:

  • l é a largura do mapa, em tiles;
  • a é a altura do mapa, também em tiles;
  • c é o número de camadas do mapa.

Assim, a posição [12][10][1] daria o objeto na camada 1 e posição (12 * t,10 * t), sendo t o tamanho do tile.

Ainda devem ser definidos como devem ser detectadas as colisões do jogador com o mapa, como lidar com gatilhos de eventos de script e como posicionar os inimigos, o que nos leva a…

Inimigos

Uma coisa que deve ser levada em consideração é o posicionamento dos inimigos, mas isso depende de uma decisão de design: Os inimigos “reaparecem” quando o jogador passa por uma certa posição (pense Megaman), ou só reaparecem ao sair/entrar de um mapa (Castlevania SOTN e outros recentes)?

A primeira opção seria mais interessante com a implementação de um sistema de níveis, no qual o jogador pode acumular experiência ao eliminar inimigos. O problema é que esse sistema, se não balanceado corretamente, pode forçar o jogador a parar o progresso para alcançar um certo nível, o que considero uma forma artificial de aumentar o tempo de jogo, sem contar que é chato pacas. Sendo assim, creio que seja adequada a segunda opção.

Deixe um comentário

World 1-1 Paolo Start!

Olá.

Eu não gosto de enrolação, então vamos aos fatos:

  • Quem é você?
    • Paolo Victor, 22.
  • Pra quê esse blog?
    • Este blog será usado como blog de desenvolvimento de um jogo, que pretendo desenvolver usando Python.
  • Que jogo?
    • Definidos estão apenas o tipo (híbrido metroid/castlevania) e parte da estória.
  • Por que Python?
    • Quero aprender Python;
    • As bibliotecas de Python podem ajudar/agilizar as coisas;
    • Python é cool.
  • Quando fica pronto?
    • Boa pergunta :)
  • E o que que eu tenho a ver com isso?
    • Estou criando este blog para compartilhar minha jornada (poético?) com quem estiver interessado. Pretendo postar não apenas informações sobre o progresso, mas também falar sobre o funcionamento interno, dificuldades, erros cometidos, etc. Assim, esta pode ser uma ferramenta de aprendizado tanto para mim, quanto para outros que pretendem ou já estão desenvolvendo algum jogo em Python.

E é isso. Nas próximas horas pretendo começar a por a mão na massa, começando pelo planejamento da engine de plataforma. Sigam-me os bons!

Deixe um comentário