Got the engine working.
This commit is contained in:
commit
8a40f24d65
8 changed files with 297 additions and 0 deletions
BIN
assets/stacked_sprites/Building.png
Normal file
BIN
assets/stacked_sprites/Building.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/stacked_sprites/Robot.png
Normal file
BIN
assets/stacked_sprites/Robot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
66
sprite_stacking_engine/cache.py
Normal file
66
sprite_stacking_engine/cache.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import pygame.draw
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
def __init__(self):
|
||||||
|
self.stacked_sprite_cache = {}
|
||||||
|
self.viewing_angle = 360 // NUM_ANGLES
|
||||||
|
self.outline_thickness = 4
|
||||||
|
self.get_stacked_sprite_cache()
|
||||||
|
|
||||||
|
def get_stacked_sprite_cache(self):
|
||||||
|
for object_name in SPRITE_ATTRS:
|
||||||
|
self.stacked_sprite_cache[object_name] = {
|
||||||
|
"rotated_sprites": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = SPRITE_ATTRS[object_name]
|
||||||
|
layer_array = self.get_layer_array(attrs)
|
||||||
|
self.run_prerender(object_name, layer_array, attrs)
|
||||||
|
|
||||||
|
def run_prerender(self, object_name, layer_array, attrs):
|
||||||
|
outline = attrs.get("outline", True)
|
||||||
|
|
||||||
|
for angle in range(NUM_ANGLES):
|
||||||
|
surface = pygame.Surface(layer_array[0].get_size())
|
||||||
|
surface = pygame.transform.rotate(surface, angle * self.viewing_angle)
|
||||||
|
sprite_surface = pygame.Surface([
|
||||||
|
surface.get_width(),
|
||||||
|
surface.get_height() + attrs["num_layers"] * attrs["scale"]
|
||||||
|
])
|
||||||
|
|
||||||
|
sprite_surface.fill(TRANSP_COLOR)
|
||||||
|
sprite_surface.set_colorkey(TRANSP_COLOR)
|
||||||
|
|
||||||
|
for i, layer in enumerate(layer_array):
|
||||||
|
layer = pygame.transform.rotate(layer, angle * self.viewing_angle)
|
||||||
|
sprite_surface.blit(layer, (0, i * attrs["scale"]))
|
||||||
|
|
||||||
|
# get outline
|
||||||
|
if outline:
|
||||||
|
outline_coords = pygame.mask.from_surface(sprite_surface).outline()
|
||||||
|
pygame.draw.polygon(sprite_surface, "black", outline_coords, self.outline_thickness)
|
||||||
|
|
||||||
|
image = pygame.transform.flip(sprite_surface, True, True)
|
||||||
|
self.stacked_sprite_cache[object_name]["rotated_sprites"][angle] = image
|
||||||
|
|
||||||
|
def get_layer_array(self, attrs):
|
||||||
|
# load sprite sheet
|
||||||
|
sprite_sheet = pygame.image.load(attrs["path"]).convert_alpha()
|
||||||
|
# scaling
|
||||||
|
sprite_sheet = pygame.transform.scale(sprite_sheet, vec2(sprite_sheet.get_size()) * attrs["scale"])
|
||||||
|
sheet_width, sheet_height = sprite_sheet.get_size()
|
||||||
|
sprite_width = sheet_width // attrs["num_layers"]
|
||||||
|
# new width to prevent error
|
||||||
|
sheet_width = sprite_width * attrs["num_layers"]
|
||||||
|
|
||||||
|
# get sprites
|
||||||
|
layer_array = []
|
||||||
|
for x in range(0, sheet_width, sprite_width):
|
||||||
|
sprite = sprite_sheet.subsurface((x, 0, sprite_width, sheet_height))
|
||||||
|
layer_array.append(sprite)
|
||||||
|
|
||||||
|
return layer_array
|
53
sprite_stacking_engine/engine.py
Executable file
53
sprite_stacking_engine/engine.py
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from settings import *
|
||||||
|
from stacked_sprite import StackedSprite
|
||||||
|
from cache import Cache
|
||||||
|
from player import Player
|
||||||
|
from scene import Scene
|
||||||
|
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def __init__(self):
|
||||||
|
self.screen = pygame.display.set_mode(RES)
|
||||||
|
self.clock = pygame.time.Clock()
|
||||||
|
self.time = 0
|
||||||
|
self.delta_time = 0.01
|
||||||
|
# groups
|
||||||
|
self.main_group = pygame.sprite.LayeredUpdates()
|
||||||
|
# game objects
|
||||||
|
self.cache = Cache()
|
||||||
|
self.player = Player(self)
|
||||||
|
self.scene = Scene(self)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.main_group.update()
|
||||||
|
pygame.display.set_caption(f"{self.clock.get_fps(): .1f}")
|
||||||
|
self.delta_time = self.clock.tick(60)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.screen.fill(BG_COLOR)
|
||||||
|
self.main_group.draw(self.screen)
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
def check_events(self):
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
|
||||||
|
pygame.quit()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def get_time(self):
|
||||||
|
self.time = pygame.time.get_ticks() * 0.001
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
self.check_events()
|
||||||
|
self.get_time()
|
||||||
|
self.update()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = App()
|
||||||
|
app.run()
|
58
sprite_stacking_engine/player.py
Normal file
58
sprite_stacking_engine/player.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class Player(pygame.sprite.Sprite):
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.group = app.main_group
|
||||||
|
super().__init__(self.group)
|
||||||
|
|
||||||
|
self.group.change_layer(self, CENTER.y)
|
||||||
|
|
||||||
|
size = vec2([50, 50])
|
||||||
|
self.image = pygame.Surface(size, pygame.SRCALPHA)
|
||||||
|
pygame.draw.circle(self.image, "red", size / 2, size[0] / 2)
|
||||||
|
self.rect = self.image.get_rect(center=CENTER)
|
||||||
|
|
||||||
|
self.offset = vec2(0)
|
||||||
|
self.inc = vec2(0)
|
||||||
|
self.angle = 0
|
||||||
|
self.diag_move_corr = 1 / math.sqrt(2)
|
||||||
|
|
||||||
|
def control(self):
|
||||||
|
self.inc = vec2(0)
|
||||||
|
speed = PLAYER_SPEED * self.app.delta_time
|
||||||
|
rot_speed = PLAYER_ROT_SPEED * self.app.delta_time
|
||||||
|
|
||||||
|
key_state = pygame.key.get_pressed()
|
||||||
|
|
||||||
|
if key_state[pygame.K_LEFT]:
|
||||||
|
self.angle -= rot_speed
|
||||||
|
|
||||||
|
if key_state[pygame.K_RIGHT]:
|
||||||
|
self.angle += rot_speed
|
||||||
|
|
||||||
|
if key_state[pygame.K_w]:
|
||||||
|
self.inc += vec2(0, -speed).rotate_rad(-self.angle)
|
||||||
|
|
||||||
|
if key_state[pygame.K_s]:
|
||||||
|
self.inc += vec2(0, speed).rotate_rad(-self.angle)
|
||||||
|
|
||||||
|
if key_state[pygame.K_a]:
|
||||||
|
self.inc += vec2(-speed, 0).rotate_rad(-self.angle)
|
||||||
|
|
||||||
|
if key_state[pygame.K_d]:
|
||||||
|
self.inc += vec2(speed, 0).rotate_rad(-self.angle)
|
||||||
|
|
||||||
|
if self.inc.x and self.inc.y:
|
||||||
|
self.inc *= self.diag_move_corr
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.control()
|
||||||
|
self.move()
|
||||||
|
|
||||||
|
def move(self):
|
||||||
|
self.offset += self.inc
|
42
sprite_stacking_engine/scene.py
Normal file
42
sprite_stacking_engine/scene.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from stacked_sprite import *
|
||||||
|
from random import uniform
|
||||||
|
|
||||||
|
P = "player"
|
||||||
|
A, B = "Robot", "Building"
|
||||||
|
|
||||||
|
MAP = [
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, B, B, B, B],
|
||||||
|
[0, 0, A, A, 0, B, B, B, B],
|
||||||
|
[0, 0, A, A, P, B, B, B, B],
|
||||||
|
[0, 0, A, A, 0, B, B, B, B],
|
||||||
|
[0, 0, 0, 0, 0, B, B, B, B],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
]
|
||||||
|
|
||||||
|
MAP_SIZE = MAP_WIDTH, MAP_HEIGHT = vec2(len(MAP), len(MAP[0]))
|
||||||
|
MAP_CENTER = MAP_SIZE / 2
|
||||||
|
|
||||||
|
|
||||||
|
class Scene:
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.load_scene()
|
||||||
|
|
||||||
|
def load_scene(self):
|
||||||
|
rand_rot = lambda: uniform(0, 360)
|
||||||
|
rand_pos = lambda pos: pos + vec2(uniform(-0.25, 0.25))
|
||||||
|
|
||||||
|
for j, row in enumerate(MAP):
|
||||||
|
for i, name in enumerate(row):
|
||||||
|
pos = vec2(i, j) + vec2(0.5)
|
||||||
|
|
||||||
|
if name == "player":
|
||||||
|
self.app.player.offset = pos * TILE_SIZE
|
||||||
|
|
||||||
|
elif name:
|
||||||
|
StackedSprite(self.app, name=name, pos=rand_pos(pos), rot=rand_rot())
|
31
sprite_stacking_engine/settings.py
Normal file
31
sprite_stacking_engine/settings.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
vec2 = pygame.math.Vector2
|
||||||
|
|
||||||
|
RES = WIDTH, HEIGHT = vec2(1400, 800)
|
||||||
|
CENTER = H_WIDTH, H_HEIGHT = RES // 2
|
||||||
|
TILE_SIZE = 250
|
||||||
|
|
||||||
|
PLAYER_SPEED = 0.4
|
||||||
|
PLAYER_ROT_SPEED = 0.0015
|
||||||
|
|
||||||
|
BG_COLOR = "olivedrab"
|
||||||
|
NUM_ANGLES = 180 # multiple of 360
|
||||||
|
TRANSP_COLOR = (43, 64, 36)
|
||||||
|
|
||||||
|
SPRITE_ATTRS = {
|
||||||
|
"Robot": {
|
||||||
|
"path": "../assets/stacked_sprites/Robot.png",
|
||||||
|
"num_layers": 16,
|
||||||
|
"scale": 8,
|
||||||
|
"y_offset": -8
|
||||||
|
},
|
||||||
|
"Building": {
|
||||||
|
"path": "../assets/stacked_sprites/Building.png",
|
||||||
|
"num_layers": 80,
|
||||||
|
"scale": 4,
|
||||||
|
"y_offset": -40
|
||||||
|
}
|
||||||
|
}
|
47
sprite_stacking_engine/stacked_sprite.py
Normal file
47
sprite_stacking_engine/stacked_sprite.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
import math
|
||||||
|
from wobbl_tools import pg
|
||||||
|
|
||||||
|
|
||||||
|
class StackedSprite(pygame.sprite.Sprite):
|
||||||
|
def __init__(self, app, name, pos, rot=0):
|
||||||
|
self.app = app
|
||||||
|
self.name = name
|
||||||
|
self.pos = vec2(pos) * TILE_SIZE
|
||||||
|
self.player = self.app.player
|
||||||
|
self.group = self.app.main_group
|
||||||
|
super().__init__(self.group)
|
||||||
|
|
||||||
|
self.attrs = SPRITE_ATTRS[name]
|
||||||
|
self.y_offset = vec2(0, -self.attrs["num_layers"] / 2 * self.attrs["scale"])
|
||||||
|
self.cache = self.app.cache.stacked_sprite_cache
|
||||||
|
self.viewing_angle = app.cache.viewing_angle
|
||||||
|
self.rotated_sprites = self.cache[name]["rotated_sprites"]
|
||||||
|
self.angle = 0
|
||||||
|
self.screen_position = vec2(0)
|
||||||
|
self.rot = (rot % 360) // self.viewing_angle
|
||||||
|
|
||||||
|
def transform(self):
|
||||||
|
pos = self.pos - self.player.offset
|
||||||
|
pos = pos.rotate_rad(self.player.angle)
|
||||||
|
self.screen_position = pos + CENTER
|
||||||
|
|
||||||
|
def change_layer(self):
|
||||||
|
self.group.change_layer(self, self.screen_position.y)
|
||||||
|
|
||||||
|
def get_angle(self):
|
||||||
|
self.angle = -math.degrees(self.player.angle) // self.viewing_angle + self.rot
|
||||||
|
self.angle = int(self.angle % NUM_ANGLES)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.transform()
|
||||||
|
self.get_angle()
|
||||||
|
self.get_image()
|
||||||
|
self.change_layer()
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
self.image = self.rotated_sprites[self.angle]
|
||||||
|
self.rect = self.image.get_rect(center=self.screen_position + self.y_offset)
|
Loading…
Reference in a new issue