Source

2Dengine / engine2d.py

Full commit
import math
import os
import pickle
import pygame
import sys
import time

try:
    import psyco
    psyco.full()
except:
    print "No psyco."

Resources = {}

def load_resource(filename):
    if filename in Resources:
        return Resources[filename]

    key = filename
    if isinstance(filename, tuple):
        extra_args = filename[1:]
        filename = filename[0]
    _, extension = os.path.splitext(filename)
    extension = extension.lower()
    if extension in ('.png', '.gif'):
        Resources[key] = pygame.image.load(filename).convert_alpha()
    elif extension in ('.ttf', ):
        Resources[key] = pygame.font.Font(filename, *extra_args)
    else:
        raise ValueError(filename)
    return Resources[key]

class Sprite(object):
    x, y = None, None
    width, height = None, None
    @property
    def rect(self):
        return pygame.Rect((self.x - self.width/16., self.y - self.height/16.),
                (self.width, self.height))
    def collide(self, other):
        pass
    def see(self, other):
        pass
    def tick(self):
        pass
    def render(self, surface, posx, posy):
        return True

class Plane(object):
    def __init__(self):
        self.grid = {}
        self.tiles = []
        self.sprites = []
        self.sublayers = []
        self.x, self.y = 0., 0.
        self._filename = None
    def render(self, surface=None, interact=True):
        if not surface:
            surface = pygame.display.get_surface()
        for index, layer in enumerate(self.sublayers):
            layer.x = self.x * 1./(2. + index)
            layer.y = self.y * 1./(2. + index)
            layer.render(surface, interact)
        width, height = surface.get_width(), surface.get_height()
        # Draw tiles
        tilewidth = (width/32)
        tileheight = (height/32)
        for x in xrange(int(self.x - tilewidth/2 - 2),
                        int(self.x + tilewidth/2 + 2)):
            for y in xrange(int(self.y - tileheight/2 - 2),
                            int(self.y + tileheight/2 + 2)):
                tileval = self.grid.get((int(x), int(y)), None)
                if tileval is not None:
                    resource = load_resource(self.tiles[tileval])
                    sx, sy = self.screenpos(surface, x, y)
                    sx -= 16
                    sy -= 16
                    surface.blit(resource, (sx, sy))
        view_rect = pygame.Rect(self.x - tilewidth/2 - 2, self.y - tileheight/2 - 2,
                                self.x + tilewidth/2 + 2, self.y + tileheight/2 + 2)
        # Draw sprites
        visible_sprites = []
        pop_indexes = []
        if interact: # Only do the "I see you/I touch you" stuff out of the editor.
            for index, sprite in enumerate(self.sprites):
                if view_rect.colliderect(sprite.rect):
                    visible_sprites.append((index, sprite))
                    sprite.tick()
            for index, sprite in visible_sprites:
                for otherindex, othersprite in visible_sprites:
                    sprite.see(othersprite)
                    if sprite.rect.collidrect(othersprite.recs):
                        sprite.collide(othersprite)
        for index, sprite in visible_sprites:
            x, y = self.screenpos(surface, sprite.x, sprite.y)
            if not sprite.render(surface, x, y):
                pop_indexes.append(index)
        if interact and pop_indexes:
            for index in reversed(sorted(pop_indexes)):
                self.sprites.pop(index)

    def screenpos(self, display, x, y):
        x_difference = (x - self.x) * 32
        y_difference = (y - self.y) * 32
        width = display.get_width()
        height = display.get_height()
        center = (width/2 + x_difference, height/2 + y_difference)
        return center
    def editorloop(self, display=None):
        if not self.tiles:
            load_resource('res/brick.png')
            self.tiles.append('res/brick.png')
        font = load_resource(('res/manaspc.ttf', 12))
        pygame.mouse.set_visible(True)
        quit = False
        mousepos = (0, 0)

        xvel, yvel = (0., 0.)

        if not display:
            display = pygame.display.get_surface()

        display_width, display_height = (display.get_width(),
                                         display.get_height())

        looking, drawing, erasing = range(3)
        mode = looking

        while not quit:
            run = time.time()
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.QUIT:
                    quit = True
                    yield event
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        quit = True
                        yield event
                    elif event.key == pygame.K_RIGHT:
                        xvel = 1.0
                    elif event.key == pygame.K_LEFT:
                        xvel = -1.0
                    elif event.key == pygame.K_UP:
                        yvel = -1.0
                    elif event.key == pygame.K_DOWN:
                        yvel = 1.0
                elif event.type == pygame.KEYUP:
                    if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
                        xvel = 0.0
                    elif event.key == pygame.K_UP or event.key == pygame.K_DOWN:
                        yvel = 0.0
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        mode = drawing
                    elif event.button == 3:
                        mode = erasing
                elif event.type == pygame.MOUSEBUTTONUP:
                    mode = looking
                elif event.type == pygame.MOUSEMOTION:
                    mousepos = event.pos
                else:
                    print event

            if mode == drawing:
                self.grid[grid_x, grid_y] = 0
            elif mode == erasing:
                if (grid_x, grid_y) in self.grid:
                    del self.grid[grid_x, grid_y]
            if xvel:
                self.x += xvel * (time.time() - run + 0.125)
            if yvel:
                self.y += yvel * (time.time() - run + 0.125)

            display.fill((100, 100, 100))
            self.render(display, False)

            grid_x = int(self.x+(mousepos[0] - display_width/2)/32.)
            grid_y = int(self.y+(mousepos[1] - display_height/2)/32.)

            mouse_grid_pos = (grid_x, grid_y)
            screenposx, screenposy = self.screenpos(display, grid_x, grid_y)
            pygame.draw.rect(display, (255, 0, 0), ((screenposx-16, screenposy-16),
                (32, 32)), 2)

            top_status = font.render("%r [%04.4f %04.4f] [%04.4f %04.4f] %03iFPS" % 
                                      (self._filename, self.x, self.y, grid_x,
                                          grid_y, int(1./(time.time() - run)))
                                     , False, (255, 255, 255))
            display.blit(top_status, (0, 0))
            pygame.display.flip()
    @classmethod
    def load(cls, filename):
        if os.path.exists(filename):
            plane = pickle.load(open(filename, 'rb'))
        else:
            plane = cls()
        plane._filename = filename
        return plane
    def save(self, filename=None):
        if filename:
            self._filename = filename
        pickle.dump(self, open(self._filename, 'w'))

if __name__ == "__main__":
    filename = None
    if len(sys.argv) > 1:
        filename = sys.argv[1]
    if filename:
        plane = Plane.load(filename)
    else:
        plane = Plane()
    pygame.init()
    display = pygame.display.init()
    pygame.display.set_mode((1024, 768))
    for x in plane.editorloop():
        pass
    if filename:
        print "Saving."
        pickle.dump(plane, open(filename, 'wb'))