Last active
November 22, 2023 06:09
-
-
Save f0ursqu4r3/d96c79bc6f1c75c1d846433efb6ceda9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"stacks": [ | |
{ | |
"name": "house", | |
"dimentions": { | |
"width": 32, | |
"height": 32 | |
}, | |
"slices": 16, | |
"path": "/Users/kyle/Documents/resources/house_ss.png" | |
}, | |
{ | |
"name": "rock", | |
"dimentions": { | |
"width": 16, | |
"height": 16 | |
}, | |
"slices": 4, | |
"path": "/Users/kyle/Documents/resources/rock_ss.png" | |
}, | |
{ | |
"name": "tree", | |
"dimentions": { | |
"width": 48, | |
"height": 48 | |
}, | |
"slices": 48, | |
"path": "/Users/kyle/Documents/resources/tree_ss.png" | |
}, | |
{ | |
"name": "cube", | |
"dimentions": { | |
"width": 32, | |
"height": 32 | |
}, | |
"slices": 32, | |
"path": "/Users/kyle/Documents/resources/cube_ss.png" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
import itertools | |
import random | |
import json | |
import math | |
from dataclasses import dataclass | |
import pygame as pg | |
from pygame import Vector2 as vec2 | |
class Game: | |
def __init__(self, width:int, height:int): | |
pg.init() | |
self.window = pg.display.set_mode((width, height)) | |
self.win_size = vec2(width, height) | |
self.disp_scale = 2.0 | |
self.clock = pg.time.Clock() | |
self.running = False | |
self.dt = 0.0 | |
self.y_offset = 1 | |
self.camera = Camera( | |
frame=self.win_size/self.disp_scale, | |
position=-self.center, | |
angle=0.0 | |
) | |
self.spritestacks = self.load_spritestacks() | |
self.objs = [ | |
Object( | |
kind='cube', | |
position=vec2(), | |
angle=0.0 | |
) | |
] | |
self.selected = None | |
@property | |
def screen_size(self): | |
return self.win_size/self.disp_scale | |
@property | |
def center(self): | |
return self.screen_size/2 | |
def run(self): | |
self.running = True | |
while self.running: | |
self.handle_events() | |
self.update() | |
self.draw() | |
def handle_events(self): | |
for event in pg.event.get(): | |
if (event.type == pg.QUIT or | |
(event.type == pg.KEYDOWN and | |
event.key == pg.K_ESCAPE)): | |
self.running = False | |
elif event.type == pg.KEYDOWN: | |
if event.key == pg.K_UP: | |
self.obj.angle += 5 | |
elif event.key == pg.K_DOWN: | |
self.obj.angle -= 5 | |
elif event.type == pg.MOUSEMOTION: | |
if event.buttons[0]: | |
if pg.key.get_pressed()[pg.K_SPACE]: | |
screen_movement = vec2(event.rel)/self.disp_scale | |
world_movement = screen_movement.rotate(self.camera.angle) | |
self.camera.position -= world_movement | |
if pg.key.get_pressed()[pg.K_LSHIFT]: | |
rel = vec2(event.rel)/self.disp_scale | |
self.y_offset -= rel.y*0.05 | |
self.y_offset = min(1, max(self.y_offset, 0)) | |
self.camera.angle += rel.x | |
elif event.type == pg.MOUSEBUTTONDOWN: | |
if event.button == 1: | |
mpos = vec2(event.pos)/self.disp_scale | |
self.selected = None | |
objs = sorted( | |
[ | |
[obj, apply_camera_transform(obj.position, self.camera, self.y_offset)] | |
for obj in self.objs | |
], | |
key=lambda o: o[1].y, | |
) | |
for obj, pos in reversed(objs): | |
if (pos - mpos).length() < 16: | |
self.selected = obj | |
break | |
def update(self): | |
self.dt = self.clock.tick(60) * 0.001 | |
fps = self.clock.get_fps() | |
pg.display.set_caption(f'{fps:0.2f}') | |
# for i, obj in enumerate(self.objs): | |
# obj.angle += ((i+1)*10)*self.dt | |
def draw(self): | |
self.window.fill((200,200,200)) | |
screen_surf = pg.Surface(self.screen_size, pg.SRCALPHA) | |
objs = sorted( | |
[ | |
[obj, apply_camera_transform(obj.position, self.camera, self.y_offset)] | |
for obj in self.objs | |
], | |
key=lambda o: o[1].y, | |
) | |
for obj, pos in objs: | |
if ( | |
pos.x < -48 or | |
pos.x > self.screen_size.x + 48 or | |
pos.y < -48 or | |
pos.y > self.screen_size.y + 48 | |
): | |
continue | |
if self.selected == obj: | |
pg.draw.circle(screen_surf, (0,200,0), pos, 16, 1) | |
slices = self.spritestacks[obj.kind]['slices'] | |
scale = map_value(pos.y, 0, self.screen_size.y, .75, 1.25) | |
for y, surf in enumerate(slices): | |
apos = pos + vec2(0, (-y * self.y_offset)) | |
layer = pg.transform.rotozoom( | |
surf, | |
obj.angle + self.camera.angle, | |
scale | |
) | |
rect = layer.get_rect() | |
rect.center = apos | |
screen_surf.blit(layer, rect.topleft) | |
self.window.blit(pg.transform.scale(screen_surf,self.win_size), vec2()) | |
pg.display.update() | |
def load_spritestacks(self): | |
with open('/Users/kyle/Documents/resources/spritestacks.json') as f: | |
stacks = { | |
item['name']: item | |
for item in | |
json.load(f)['stacks'] | |
} | |
for name, stack in stacks.items(): | |
width = stack['dimentions']['width'] | |
height = stack['dimentions']['height'] | |
sheet = pg.image.load(stack['path']).convert_alpha() | |
stack['slices'] = [ | |
sheet.subsurface((x*width, 0, width, height)) | |
for x in range(stack['slices']) | |
] | |
return stacks | |
def world_to_screen(pos: vec2, camera: Camera) -> vec2: | |
translated_pos = pos - camera.position | |
screen_pos = translated_pos.rotate(-camera.angle) | |
return screen_pos | |
def screen_to_world(pos: vec2, camera: Camera) -> vec2: | |
world_pos = pos.rotate(camera.angle) | |
world_pos += camera.position | |
return world_pos | |
def apply_camera_transform(object_pos, camera, y_offset=0): | |
rotated_pos = rotate_around_pivot(object_pos, camera.position+camera.frame*0.5, -camera.angle) | |
translated_pos = rotated_pos - camera.position | |
mapped_y = map_value(translated_pos.y, 0, camera.frame.y, -1, 1) | |
return translated_pos - vec2(0, mapped_y * 32 * y_offset) | |
def rotate_around_pivot(object_pos:vec2, pivot_pos:vec2, angle:float): | |
offset = object_pos - pivot_pos | |
rotated_offset = offset.rotate(angle) | |
new_pos = pivot_pos + rotated_offset | |
return new_pos | |
def map_value(old_value, old_min, old_max, new_min, new_max): | |
return (((old_value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min | |
@dataclass | |
class Camera: | |
frame: vec2 | |
position: vec2 | |
angle: float | |
zoom: float = 1.0 | |
@dataclass | |
class Object: | |
kind: str | |
position: vec2 | |
angle: float | |
Game(512, 512).run() |
Author
f0ursqu4r3
commented
Nov 18, 2023
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment