Skip to content

Commit

Permalink
Scene stack and pause prototype
Browse files Browse the repository at this point in the history
- This introduces a shitton of new problems for which i shall proceed to write issues
- New tweens
- Fixed epic math fail in screen_center
  • Loading branch information
Square789 committed Oct 12, 2021
1 parent bfb4440 commit b177841
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 50 deletions.
1 change: 1 addition & 0 deletions pyday_night_funkin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class CONTROL(IntEnum):
DOWN = 1
UP = 2
RIGHT = 3
ENTER = 4


@dataclass
Expand Down
13 changes: 7 additions & 6 deletions pyday_night_funkin/graphics/pnf_sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
gl_Position = \\
window.projection * \\
window.view * \\
m_camera_trans_scale *\\
m_camera_pre_trans *\\
m_camera_trans_scale * \\
m_camera_pre_trans * \\
m_trans_scale * \\
m_rotation * \\
vec4(position, 0, 1) \\
Expand Down Expand Up @@ -332,8 +332,8 @@ def screen_center(self, screen_dims: t.Tuple[int, int]) -> None:
Sets the sprite's world position so that it is centered
on screen. (Ignoring camera and scroll factors)
"""
self.x = (screen_dims[0] // 2) - self.width
self.y = (screen_dims[1] // 2) - self.height
self.x = (screen_dims[0] - self.width) // 2
self.y = (screen_dims[1] - self.height) // 2

def get_midpoint(self) -> Vec2:
"""
Expand Down Expand Up @@ -425,8 +425,9 @@ def check_animation_controller(self):
# Unfortunately, the name `update` clashes with sprite, so have
# this as a certified code smell
def update_sprite(self, dt: float) -> None:
self.animation.update(dt)
self.check_animation_controller()
if self.animation.is_set:
self.animation.update(dt)
self.check_animation_controller()

if self.movement is not None:
dx, dy = self.movement.update(dt)
Expand Down
11 changes: 3 additions & 8 deletions pyday_night_funkin/levels/week1level.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def ready(self) -> None:
)

def process_input(self, dt: float) -> None:
super().process_input(dt)
pressed = {
type_: self.key_handler.just_pressed(control)
for type_, control in self.note_handler.NOTE_TO_CONTROL_MAP.items()
Expand Down Expand Up @@ -272,13 +273,8 @@ def countdown(self, dt: float) -> None:
sprite_idx = self._countdown_stage
tex = self.countdown_textures[sprite_idx]
if tex is not None:
sprite = self.create_sprite(
"ui0",
"ui",
x = (CNST.GAME_WIDTH - tex.width) // 2,
y = (CNST.GAME_HEIGHT - tex.height) // 2,
image = tex,
)
sprite = self.create_sprite("ui0", "ui", image = tex)
sprite.screen_center(CNST.GAME_DIMENSIONS)

sprite.start_tween(
in_out_cubic,
Expand All @@ -292,7 +288,6 @@ def countdown(self, dt: float) -> None:

self._countdown_stage += 1


class Bopeebo(Week1Level):
@staticmethod
def get_song() -> int:
Expand Down
78 changes: 59 additions & 19 deletions pyday_night_funkin/main_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ def __init__(self) -> None:
if ogg_decoder not in pyglet.media.get_decoders():
pyglet.media.add_decoders(ogg_decoder)

self.debug = True
logger.remove(0)

self.debug = True
if self.debug:
self._update_time = 0
self._fps = [time() * 1000, 0, "?"]
Expand All @@ -45,48 +46,84 @@ def __init__(self) -> None:
CONTROL.DOWN: [key.DOWN, key.S],
CONTROL.UP: [key.UP, key.W],
CONTROL.RIGHT: [key.RIGHT, key.D],
CONTROL.ENTER: key.ENTER,
},
)

self.pyglet_ksh = KeyStateHandler()
self.key_handler = KeyHandler(self.config.key_bindings)
self.window = PNFWindow(
width = GAME_WIDTH,
height = GAME_HEIGHT,
resizable = True,
vsync = False,
)

self.pyglet_ksh = KeyStateHandler()
self.key_handler = KeyHandler(self.config.key_bindings)

self.window.push_handlers(self.key_handler)
self.window.push_handlers(self.pyglet_ksh)
self.window.push_handlers(on_draw = self.draw)

self._scene_stack: t.List[BaseScene] = []
self._scenes_to_draw: t.List[BaseScene] = []
self._scenes_to_update: t.List[BaseScene] = []

self.push_scene(WEEKS[1].levels[1], DIFFICULTY.HARD)
# self.push_scene(TestScene)

self.main_batch = pyglet.graphics.Batch()
self.active_scene = None
def _on_scene_stack_change(self, ignore: t.Optional[BaseScene] = None) -> None:
for self_attr, scene_attr, scene_callback in (
("_scenes_to_draw", "draw_passthrough", "on_regular_draw_change"),
("_scenes_to_update", "update_passthrough", "on_regular_update_change"),
):
start = len(self._scene_stack) - 1
while start >= 0 and getattr(self._scene_stack[start], scene_attr):
start -= 1

self.switch_scene(WEEKS[1].levels[1](self, DIFFICULTY.HARD))
# self.switch_scene(TestScene(self))
prev = getattr(self, self_attr)
new = self._scene_stack[start:]

for scene in prev:
if scene is not ignore and scene not in new:
getattr(scene, scene_callback)(False)

for scene in new:
if scene is not ignore and scene not in prev:
getattr(scene, scene_callback)(True)

setattr(self, self_attr, new)

def run(self) -> None:
logger.debug(f"Game started (v{__version__}), pyglet version {pyglet.version}")
pyglet.clock.schedule_interval(self.update, 1 / 80.0)
pyglet.app.run()

def switch_scene(self, new_scene: BaseScene) -> None:
def push_scene(self, new_scene_cls: t.Type[BaseScene], *args, **kwargs) -> None:
"""
Causes game to switch scene to the new scene.
Pushes a new scene onto the scene stack which will then
be the topmost scene.
The game instance will be passed as the first argument to the
scene class, with any args and kwargs following it.
"""
if self.active_scene is not None:
self.active_scene.on_leave()
self.window.pop_handlers()
self.active_scene = new_scene
self.window.push_handlers(
on_draw = self.draw,
on_resize = self.active_scene.on_window_resize,
)
new_scene = new_scene_cls(self, *args, **kwargs)
self._scene_stack.append(new_scene)
self._on_scene_stack_change(new_scene)

def remove_scene(self, scene: BaseScene) -> None:
"""
Removes the given scene from anywhere in the scene stack.
ValueError is raised if it is not present.
"""
self._scene_stack.remove(scene)
self._on_scene_stack_change()

def draw(self) -> None:
stime = time()
self.window.clear()
self.active_scene.draw()

for scene in self._scenes_to_draw:
scene.draw()

if self.debug:
self.debug_batch.draw()
self._fps_bump()
Expand All @@ -96,7 +133,10 @@ def draw(self) -> None:

def update(self, dt: float) -> None:
stime = time()
self.active_scene.update(dt)

for scene in self._scenes_to_update:
scene.update(dt)

self._update_time = (time() - stime) * 1000

def _fps_bump(self):
Expand Down
65 changes: 50 additions & 15 deletions pyday_night_funkin/scenes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import typing as t

from loguru import logger
from pyglet.graphics import Group
from pyglet.graphics import Batch, Group
from pyglet.window.key import B, R

import pyday_night_funkin.constants as CNST
Expand Down Expand Up @@ -66,7 +66,10 @@ def __init__(self, game: "Game") -> None:
:param game: The `Game` the scene belongs to.
"""
self.game = game
self.batch = game.main_batch
self.batch = Batch()

self.draw_passthrough = True
self.update_passthrough = False

self.layers = OrderedDict(
(name, Layer(Group(order = i), force_order))
Expand Down Expand Up @@ -107,6 +110,33 @@ def get_layer_names() -> t.Sequence[t.Union[str, t.Tuple[str, bool]]]:
"""
return ()

def on_regular_update_change(self, new: bool) -> None:
"""
Called when the game's scene stack changes and the scene will
now either be updated each game tick when it wasn't before
(`new` is `True`) or the scene will now not be updated
anymore when it was before (`new` is `False`).
Should be used to stop/pause/start/play things that are not
handled in the scene's `update` function.
! WARNING ! Due to poor programming, the scene may not delete
itself here and should not interact with the game at all.
"""
pass

def on_regular_draw_change(self, new: bool) -> None:
"""
Called when the game's scene stack changes and the scene will
now either be drawn each game tick when it wasn't before
(`new` is `True`) or the scene will now not be drawn
anymore when it was before (`new` is `False`).
! WARNING ! Due to poor programming, the scene may not delete
itself here and should not interact with the game at all.
"""
pass

def create_sprite(
self,
layer: str,
Expand Down Expand Up @@ -147,18 +177,6 @@ def remove_sprite(self, sprite: PNFSprite) -> None:
self._sprites.remove(sprite)
sprite.delete()

def on_leave(self) -> None:
"""
Called when scene is about to be switched away from.
"""
pass

def on_window_resize(self, new_w: int, new_h: int) -> None:
"""
Called when the game window is resized.
"""
pass

def update(self, dt: float) -> None:
if self.game.debug:
if self.game.pyglet_ksh[R]:
Expand All @@ -171,8 +189,25 @@ def update(self, dt: float) -> None:
for c in self.cameras.values():
c.update(dt)

for sprite in set(self._sprites):
for sprite in self._sprites.copy():
sprite.update_sprite(dt)

def draw(self) -> None:
"""
Draw the scene.
There should be no reason to override this.
"""
self.batch.draw()

def destroy(self) -> None:
"""
Destroy the scene by removing it from its game, deleting its
sprites and deleting its graphics batch.
"""
self.game.remove_scene(self)

# Copy in case __del__ or delete does weird things
for spr in self._sprites.copy():
spr.delete()

del self.batch
16 changes: 14 additions & 2 deletions pyday_night_funkin/scenes/in_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from pyglet.media.player import PlayerGroup

from pyday_night_funkin.asset_system import load_asset
from pyday_night_funkin.config import CONTROL
from pyday_night_funkin.enums import DIFFICULTY, GAME_STATE
from pyday_night_funkin.scenes.music_beat import MusicBeatScene
from pyday_night_funkin.scenes.pause import PauseScene

if t.TYPE_CHECKING:
from pyday_night_funkin.asset_system import OggVorbisSong
from pyday_night_funkin.main_game import Game
from pyday_night_funkin.note_handler import AbstractNoteHandler

Expand All @@ -27,6 +28,8 @@ class InGameScene(MusicBeatScene):
def __init__(self, game: "Game", difficulty: DIFFICULTY) -> None:
super().__init__(game)

self.draw_passthrough = False

self.difficulty = difficulty

self.key_handler = game.key_handler
Expand Down Expand Up @@ -56,6 +59,12 @@ def get_song() -> int:
def create_note_handler(self) -> "AbstractNoteHandler":
raise NotImplementedError("Subclass this!")

def on_regular_update_change(self, new: bool) -> None:
if new:
self.song_players.play()
else:
self.song_players.pause()

def load_resources(self) -> None:
"""
This function will be called by the game scene in an early
Expand Down Expand Up @@ -113,10 +122,13 @@ def update(self, dt: float) -> None:
self.conductor.song_position += dt * 1000
discrepancy = self.inst_player.time * 1000 - self.conductor.song_position
if abs(discrepancy) > 20 and self._updates_since_desync_warn > 100:
logger.warning(f"Conductor out of sync with player by {discrepancy:.4f} ms.")
logger.warning(f"Player ahead of conductor by {discrepancy:.4f} ms.")
self._updates_since_desync_warn = 0
self._updates_since_desync_warn += 1

if self.key_handler.just_pressed(CONTROL.ENTER):
self.game.push_scene(PauseScene)

self.process_input(dt)

def process_input(self, dt: float) -> None:
Expand Down
38 changes: 38 additions & 0 deletions pyday_night_funkin/scenes/pause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import typing as t

from pyglet.image import ImageData

from pyday_night_funkin.config import CONTROL
from pyday_night_funkin import constants as CNST
from pyday_night_funkin.graphics.pnf_sprite import PNFSprite
from pyday_night_funkin.scenes._base import BaseScene
from pyday_night_funkin.tweens import TWEEN_ATTR, in_out_quart


class PauseScene(BaseScene):
"""
Cheap menu scene that destroys itself when button is pressed.
"""

def __init__(self, game) -> None:
super().__init__(game)

pixel = ImageData(1, 1, "RGBA", b"\x00\x00\x00\xFF").get_texture()
self.background = self.create_sprite(
"main", image = pixel,
)
self.background.scale_x = CNST.GAME_WIDTH
self.background.scale_y = CNST.GAME_HEIGHT
self.background.opacity = 0
self.background.start_tween(in_out_quart, {TWEEN_ATTR.OPACITY: 153}, 0.4)

@staticmethod
def get_layer_names() -> t.Sequence[t.Union[str, t.Tuple[str, bool]]]:
return ("main", )

def update(self, dt: float) -> None:
super().update(dt)

if self.game.key_handler.just_pressed(CONTROL.ENTER):
self.destroy()
Loading

0 comments on commit b177841

Please sign in to comment.