import pyxel
# ==============================================================================
# --- GLOBAL CONFIGURATION CONSTANTS (ENGINE SETTINGS) ---
# ==============================================================================
# Display Options
WIDTH, HEIGHT = 160, 120
GAME_TITLE = "Workshop: Low Level OO Engine"
RESOURCE_FILE = "my_resource.pyxres"
# Player Physics & Mechanics
PLAYER_START_X = 80
PLAYER_START_Y = 0
PLAYER_WALK_SPEED = 2
PLAYER_GRAVITY = 0.3
PLAYER_JUMP_FORCE = -5
PLAYER_SIDE_PIXELS = 8
# Weapon & Combat Settings
LASER_DURATION_FRAMES = 5
LASER_Y_OFFSET = 4
ENEMY_SIDE_PIXELS = 8
# Input Mapping (Low-Level Keys)
KEY_MOVE_LEFT = pyxel.KEY_LEFT
KEY_MOVE_RIGHT = pyxel.KEY_RIGHT
KEY_ACTION_JUMP = pyxel.KEY_SPACE
KEY_ACTION_FIRE = pyxel.KEY_X
# Audio Settings
JUMP_SOUND_INDEX = 0
LANDING_SOUND_INDEX = 1
AUDIO_CHANNEL = 0
# Color Palette (Pyxel 16-Color Index)
COLOR_BACKGROUND = 0
COLOR_PLATFORM = 11
COLOR_ENEMY = 8
COLOR_SPRITE_TRANSPARENCY = 2
# ==============================================================================
# --- LEVEL DESIGN DATA (CAN BE MIGRATED TO A JSON FILE LATER) ---
# ==============================================================================
# Common variables reused across the raw layout data
FLOOR_HEIGHT = 10
ALTURA_PLATAFORMA = 2
FLOOR_RECT = [0, HEIGHT - FLOOR_HEIGHT, WIDTH, FLOOR_HEIGHT]
LEVEL_DATA = {
"door": {
"room_index": 0,
"x": 150,
"y": 7
},
"rooms": [
{
"platforms": [
FLOOR_RECT,
[40, 80, 40, ALTURA_PLATAFORMA],
[100, 60, 40, ALTURA_PLATAFORMA],
[150, 15, 10, ALTURA_PLATAFORMA]
],
"enemies": [
{"x": 60, "y": 72},
{"x": 120, "y": 52}
]
},
{
"platforms": [
FLOOR_RECT,
[50, 90, 80, ALTURA_PLATAFORMA],
[40, 50, 20, ALTURA_PLATAFORMA]
],
"enemies": [
{"x": 80, "y": 82}
]
}
]
}
# ==============================================================================
class Platform:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def to_rect_args(self):
return (self.x, self.y, self.width, self.height)
def check_player_landing(self, px, py, p_width, p_height, next_py):
if px + p_width > self.x and px < self.x + self.width:
if py + p_height <= self.y and next_py + p_height >= self.y:
return True
return False
class Enemy:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = ENEMY_SIDE_PIXELS
self.height = ENEMY_SIDE_PIXELS
self.is_alive = True
def check_hitscan(self, player_x, player_y, facing_sign):
if not self.is_alive:
return False
laser_y = player_y + LASER_Y_OFFSET
if self.y <= laser_y <= self.y + self.height:
if facing_sign == 1 and self.x > player_x:
return True
if facing_sign == -1 and self.x < player_x:
return True
return False
def draw(self):
if self.is_alive:
pyxel.rect(self.x, self.y, self.width, self.height, COLOR_ENEMY)
class Door:
def __init__(self, room_index, x, y):
self.room_index = room_index
self.x = x
self.y = y
self.has_collided = False
def update_collision(self, player_x, player_y, active_room, is_jumping):
if active_room != self.room_index or is_jumping:
self.has_collided = False
return
bounding_box_side = 4
x_collision = player_x < self.x + bounding_box_side and player_x > self.x - bounding_box_side
y_collision = player_y < self.y + bounding_box_side and player_y > self.y - bounding_box_side
self.has_collided = x_collision and y_collision
def draw(self):
pyxel.blt(self.x, self.y, 0, 8, 8, 8, 8, COLOR_SPRITE_TRANSPARENCY)
class Room:
def __init__(self, room_dict):
# Dynamically build internal engine entities from the data dictionaries
self.platforms = []
for p in room_dict["platforms"]:
self.platforms.append(Platform(x=p[0], y=p[1], width=p[2], height=p[3]))
self.enemies = []
for e in room_dict["enemies"]:
self.enemies.append(Enemy(x=e["x"], y=e["y"]))
class Level:
def __init__(self, raw_data):
# Configure Goal Door
door_info = raw_data["door"]
self.door = Door(room_index=door_info["room_index"], x=door_info["x"], y=door_info["y"])
# Parse all rooms from raw dictionary lists
self.rooms = []
for room_cfg in raw_data["rooms"]:
self.rooms.append(Room(room_cfg))
self.active_room_idx = 0
@property
def current_room(self):
return self.rooms[self.active_room_idx]
def change_room(self, direction):
self.active_room_idx = (self.active_room_idx + direction) % len(self.rooms)
def draw(self):
for plat in self.current_room.platforms:
pyxel.rect(*plat.to_rect_args(), COLOR_PLATFORM)
for enemy in self.current_room.enemies:
enemy.draw()
if self.active_room_idx == self.door.room_index:
self.door.draw()
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.dy = 0
self.facing_sign = 1
self.is_jumping = True
self.on_platform = False
self.laser_timer = 0
self.side = PLAYER_SIDE_PIXELS
def update(self, level):
# 1. Horizontal Movement & Room Switching
if pyxel.btn(KEY_MOVE_LEFT):
self.x -= PLAYER_WALK_SPEED
self.facing_sign = -1
if pyxel.btn(KEY_MOVE_RIGHT):
self.x += PLAYER_WALK_SPEED
self.facing_sign = 1
if self.x < 0:
level.change_room(-1)
self.x %= WIDTH
elif self.x >= WIDTH:
level.change_room(1)
self.x %= WIDTH
# 2. Apply Gravity Intent
self.dy += PLAYER_GRAVITY
self.on_platform = False
next_y = self.y + self.dy
# 3. Environment Collision Handling
for plat in level.current_room.platforms:
if plat.check_player_landing(self.x, self.y, self.side, self.side, next_y):
if self.is_jumping:
pyxel.play(AUDIO_CHANNEL, LANDING_SOUND_INDEX)
self.y = plat.y - self.side
self.dy = 0
self.is_jumping = False
self.on_platform = True
break
if not self.on_platform:
self.y = next_y
self.is_jumping = True
# 4. Jump Action Input
if pyxel.btnp(KEY_ACTION_JUMP) and not self.is_jumping:
self.dy = PLAYER_JUMP_FORCE
self.is_jumping = True
pyxel.play(AUDIO_CHANNEL, JUMP_SOUND_INDEX)
# 5. Laser Hitscan Combat Logic
if self.laser_timer > 0:
self.laser_timer -= 1
if pyxel.btnp(KEY_ACTION_FIRE):
self.laser_timer = LASER_DURATION_FRAMES
for enemy in level.current_room.enemies:
if enemy.check_hitscan(self.x, self.y, self.facing_sign):
enemy.is_alive = False
def draw(self):
if self.laser_timer > 0:
color_tick = pyxel.frame_count % 16
lx = self.x + (self.side // 2)
ly = self.y + LASER_Y_OFFSET
target_x = WIDTH if self.facing_sign == 1 else 0
pyxel.line(lx, ly, target_x, ly, 10 if color_tick < 8 else 7)
if self.is_jumping:
u, v = (0, self.side) if self.dy < 0 else (self.side, 0)
else:
u, v = 0, 0
pyxel.blt(self.x, self.y, 0, u, v, self.facing_sign * self.side, self.side, COLOR_SPRITE_TRANSPARENCY)
class SimplePlatformer:
def __init__(self):
pyxel.init(WIDTH, HEIGHT, title=GAME_TITLE)
pyxel.load(RESOURCE_FILE)
pyxel.sounds[JUMP_SOUND_INDEX].set("c3c4", "S", "7", "N", 5)
pyxel.sounds[LANDING_SOUND_INDEX].set("g1", "N", "5", "F", 10)
# Dependency Injection: Pass the data configuration into the system loader
self.level = Level(LEVEL_DATA)
self.player = Player(x=PLAYER_START_X, y=PLAYER_START_Y)
pyxel.run(self.update, self.draw)
def update(self):
self.player.update(self.level)
self.level.door.update_collision(
self.player.x,
self.player.y,
self.level.active_room_idx,
self.player.is_jumping
)
def draw(self):
pyxel.cls(COLOR_BACKGROUND)
self.level.draw()
self.player.draw()
if self.level.door.has_collided:
pyxel.text(20, 20, "Wiiii!!!", pyxel.rndi(1, 15))
SimplePlatformer()