10 октября 2011

PyGame: Целочисленные координаты

Привет. Если вы уже успели познакомиться с PyGame, то смогли заметить, что все спрайты рисуются исключительно с целочисленными координатами. А знаете почему?

Потому что пиксели дискретны. Если вспомнить змейку, то там, что бы линии выгладили четко, мне приходилось их рисовать внутри пикселя. Для наглядности покажу разницу в линиях которые попадает в один пиксель и оказывается на его границе.

HTML5 canvas

Эти линии толщиной в 1px и только одна из них выглядит таковой. Все дело в том что правая и средняя линии рисуются между пикселями. Эффект размытости получается из-за попытки монитора нарисовать все как есть. Если мы не можем нарисовать пиксель в точке между первым и вторым пикселем, то вполне можем нарисовать чуть чуть на первом и самую малость на втором. Все логично. Но оно нам надо?

Render

В 2d играх, когда на экран выводиться спрайт, это совершенно лишнее. Особенно на небольших разрешениях экрана, размытость будет выглядеть, мягко говоря, плохо. Именно поэтому координаты для спрайтов должны быть целочисленными, что бы вывести пиксели там где им положено быть.

Можно конечно сказать что с целочисленными координатами движения спрайтов будут не такими плавными, но из-за замыленного изображения, картинка будет выглядеть грязной. Да и эффекта плавного движения все равно не создастся, просто иногда вместо обычного спрайта, будет виден размытый.

Физика

Целочисленные значение хороши для спрайтов, но если дело касается симуляции физики, тут никак не обойтись без чисел с плавающей точкой. Возьмем в качестве примера перемещение зависящее от времени, а не от частоты кадров. Координаты спрайта в этом случае достаточно округлять. Важно делать это в одну сторону, то есть округлять к большему или меньшему. Округляя к ближайшему целому можно получить подергивание спрайта при передвижении (а вот и нет, а вот и нельзя). Округлять в одну из сторон дешевле чем к ближайшему целому.


Зеленые квадраты на сером фоне. Двигаются.

Первый квадрат движется с округлением к ближайшему целому, средний к меньшему, а последний к большему. Вот пример кода рисующий тоже что и выше:

# -*- coding: utf-8 -*-

import pygame
import math

running = True

WIDTH = 400
HEIGHT = 113

pygame.display.set_mode((WIDTH,HEIGHT), pygame.DOUBLEBUF)
screen = pygame.display.get_surface()

class SimpleSprite(pygame.sprite.Sprite):
    def __init__(self, rect, color):
        pygame.sprite.Sprite.__init__(self)
        self.x = 0
        self.image = pygame.Surface((30,30))
        self.image.fill(color)
        self.rect = rect

    def update(self, dt):
        self.x = self.x + dt / 15.0
        if self.x > WIDTH:
            self.x = 0
        if math.modf(self.x)[0] > 0.5:
            self.rect = (math.ceil(self.x), self.rect[1])
        else:
            self.rect = (math.floor(self.x), self.rect[1])

class FloorSprite(SimpleSprite):
    def __init__(self, *argv, **kargv):
        SimpleSprite.__init__(self, *argv, **kargv)

    def update(self, dt):
        self.x = self.x + dt / 15.0
        if self.x > WIDTH:
            self.x = 0
        self.rect = (math.floor(self.x), self.rect[1])

class CeilSprite(SimpleSprite):
    def __init__(self, *argv, **kargv):
        SimpleSprite.__init__(self, *argv, **kargv)

    def update(self, dt):
        self.x = self.x + dt / 15.0
        if self.x > WIDTH:
            self.x = 0
        self.rect = (math.ceil(self.x), self.rect[1])

sprite_one = SimpleSprite((0,10), (0,255,0))
sprite_two = FloorSprite((0,41), (0,255,0))
sprite_three = CeilSprite((0,72), (0,255,0))

group = pygame.sprite.RenderUpdates(sprite_one, sprite_two, sprite_three)

clock = pygame.time.Clock()
dt = 0

screen.fill((155,155,155))
pygame.display.flip()

while running:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            running = False

    screen.fill((155,155,155))
    group.update(dt);
    rects = group.draw(screen)
    pygame.display.update(rects)
    dt = clock.tick(60)
Вместо заключения

Как видите целочисленные координаты это круто.