From 030f1a872234c2425bfd04cd2800c5d2b62201a0 Mon Sep 17 00:00:00 2001 From: cecilkorik Date: Sun, 27 Apr 2025 03:16:51 -0400 Subject: [PATCH] input and sprite handling, plus music! --- .gitignore | 4 ++- engine/binding.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++ engine/data.py | 31 +++++++++++++++-- engine/file.py | 3 ++ engine/input.py | 8 +++++ engine/screen.py | 5 ++- pyglet_demo.py | 58 ++++++++++++++++++++++++++++++-- 7 files changed, 187 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 2b85710..77f8fdc 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,6 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -conf/user*.json \ No newline at end of file +/conf/user*.json +/data/* +!/data/README.md \ No newline at end of file diff --git a/engine/binding.py b/engine/binding.py index e69de29..2eeb951 100644 --- a/engine/binding.py +++ b/engine/binding.py @@ -0,0 +1,84 @@ +from . import input +from pyglet.window import key +import pyglet.clock +from enum import Enum +from collections import defaultdict + +bindings = defaultdict(lambda: None) +firing = defaultdict(lambda: None) +actions = [] +fixed_rate = 0.003 + +class RepeatType(Enum): + No = 0 + Interval = 1 + Fixed = 2 + +class FiringTime(Enum): + Immediate = 0 + Lazy = 1 + +class KeyBinding(object): + def __init__(self, key, action, repeat=RepeatType.Fixed, interval=fixed_rate, delay=FiringTime.Immediate): + self.key = key + self.action = action + self.repeat = repeat + self.interval = interval + self.repeat_offset = None + self.delay = delay + self.firedonce = False + + def fire(self, clock): + global firing + self.repeatfire(clock) + firing[self.key] = True + + def unfire(self, clock): + if self.delay == FiringTime.Lazy and not self.firedonce: + self.trigger() + firing[self.key] = False + + def repeatfire(self, clock): + if self.repeat_offset == None: + self.repeat_offset = 0.0 + if self.delay == FiringTime.Immediate: + self.trigger() + + if self.repeat != RepeatType.No: + self.repeat_offset += clock + + while self.repeat_offset > self.interval: + # print(f"accumulated offset by {clock} to {self.repeat_offset}") + self.repeat_offset -= self.interval + self.trigger() + + def trigger(self): + global actions + # print(f"Repeat firing for {self.repeat_offset} x {self.repeat_offset / self.interval}") + actions.append(self.action) + self.firedonce = True + +def process_keys(clock): + global actions + + actions = [] + + #clock = pyglet.clock.get_default() + for keybind in bindings.keys(): + binding = bindings[keybind] + is_firing = firing[keybind] + is_pressed = input.state[keybind] + if is_pressed and not is_firing: + # print(f"Starting firing for key") + binding.fire(clock) + elif not is_pressed and is_firing: + # print(f"Stopping firing for key") + binding.unfire(clock) + elif is_firing: + binding.repeatfire(clock) + +def bind(key, action, repeat=RepeatType.Fixed, interval=fixed_rate, delay=FiringTime.Immediate): + global bindings + + kb = KeyBinding(key, action, repeat, interval, delay) + bindings[key] = kb diff --git a/engine/data.py b/engine/data.py index f1bad7b..666f903 100644 --- a/engine/data.py +++ b/engine/data.py @@ -2,9 +2,18 @@ import os import sys from . import config from .file import * +import pyglet.resource g_data = {} +class ModDependency(object): + def __init__(self: object, modname: str): + self.name = modname + self.deps = [] + + def add(self: object, modname: str): + self.deps.append(modname) + class ModInfo(object): def __init__(self: object, modname: str): self.name = modname @@ -44,13 +53,31 @@ def load_mods() -> None: # handle zip mods here somehow # full_modlist[mn][me] = mod pass - elif me == '' or os.path.isdir(os.path.join(program_path(), 'data', mn)): - moddata = ModInfoDir() + elif me == '' and os.path.isdir(os.path.join(program_path(), 'data', mn)): + # bare directory used for mod development/game development + moddata = ModInfoDir(mn) if moddata.has_modinfo(): full_modlist[mn][me] = moddata modlist = {} for mn in full_modlist.keys(): + print(f"Checking mod {full_modlist[mn]}") if '' in full_modlist[mn]: modlist[mn] = full_modlist[mn][''] + + modgraph = {} + + if not modlist: + """ we have no mods, which means we have no base game data """ + """ assume development mode, and try git! """ + import subprocess + projectname = project_name() + subprocess.check_output(["git", "clone", "ssh://git@git.eltanin.net/game_data/" + projectname, "maingame"], cwd=os.path.join(program_path(), "data")) + + pyglet.resource.path = [] + for mod in modlist: + pyglet.resource.path.append('data/' + mod) + + pyglet.resource.reindex() + diff --git a/engine/file.py b/engine/file.py index 6b1177f..8a2cd98 100644 --- a/engine/file.py +++ b/engine/file.py @@ -10,6 +10,9 @@ def program_path() -> str: return g_pp +def project_name() -> str: + return os.path.split(program_path())[1] + def exists(vault: str, path: str) -> bool: fp = os.path.join(program_path(), vault, path) return os.path.exists(fp) diff --git a/engine/input.py b/engine/input.py index e69de29..9b9763d 100644 --- a/engine/input.py +++ b/engine/input.py @@ -0,0 +1,8 @@ + +import pyglet.window.key + +state = pyglet.window.key.KeyStateHandler() + +def init_key_handler(window): + global state + window.push_handlers(state) diff --git a/engine/screen.py b/engine/screen.py index 2def9be..710ac27 100644 --- a/engine/screen.py +++ b/engine/screen.py @@ -1,5 +1,6 @@ import pyglet from . import config +from . import input def auto_res(res: list) -> list: return res @@ -51,4 +52,6 @@ def init_window() -> None: # print(f"creating window on screen {screen}") # windows.append(pyglet.window.Window(fullscreen=True, screen=screen)) window = pyglet.window.Window(fullscreen=fs, screen=screens[0]) - cfg.save() \ No newline at end of file + input.init_key_handler(window) + + cfg.save() diff --git a/pyglet_demo.py b/pyglet_demo.py index 554017a..3ac576d 100644 --- a/pyglet_demo.py +++ b/pyglet_demo.py @@ -1,23 +1,77 @@ import pyglet import engine import unittest +from pyglet.window import key +from enum import Enum +import time unittest.main(exit=False) print("Hello!!!") +engine.data.load_mods() + engine.screen.init_window() window = engine.screen.window +scene_batch = pyglet.graphics.Batch() + label = pyglet.text.Label('Hello, world', font_name='Times New Roman', font_size=window.height//8, x=window.width//2, y=window.height//2, - anchor_x='center', anchor_y='center') + anchor_x='center', anchor_y='center', + batch=scene_batch) + +def center_image(image): + image.anchor_x = image.width // 2 + image.anchor_y = image.height // 2 + return image + +player_image = center_image(pyglet.resource.image('tex/test1.png')) +player_sprite = pyglet.sprite.Sprite(player_image, x=200, y=200, batch=scene_batch) + +def move_player(x, y): + player_sprite.x += x + player_sprite.y += y + +music_player = pyglet.media.Player() +music_player.loop = True +music_player.queue(pyglet.resource.media('music/menu.mp3')) +music_player.play() + +class Actions(Enum): + Quit = 0 + Up = 1 + Down = 2 + Left = 3 + Right = 4 + +action_map = { + Actions.Quit: lambda: pyglet.app.exit(), + Actions.Up: lambda: move_player(0, 1), + Actions.Down: lambda: move_player(0, -1), + Actions.Left: lambda: move_player(-1, 0), + Actions.Right: lambda: move_player(1, 0) +} + +engine.binding.bind(key.Q, Actions.Quit) +engine.binding.bind(key.W, Actions.Up) +engine.binding.bind(key.S, Actions.Down) +engine.binding.bind(key.A, Actions.Left) +engine.binding.bind(key.D, Actions.Right) @window.event def on_draw(): engine.screen.window.clear() - label.draw() + scene_batch.draw() + +def handle_input(clock): + engine.binding.process_keys(clock) + for action in engine.binding.actions: + action_map[action]() + + +pyglet.clock.schedule_interval(handle_input, engine.binding.fixed_rate) pyglet.app.run() \ No newline at end of file