pyxora.scene
1from .display import Display 2from .assets import Assets 3from .camera import Camera as SceneCamera 4from .utils import engine,asyncio 5 6from time import perf_counter as time 7from typing import Dict,Tuple,Any,Type 8 9import pygame 10 11class classproperty(property): 12 """@private class to create @classproperties""" 13 def __get__(self, obj, cls): 14 return self.fget(cls) 15 16class SceneManager: 17 """The Main Manager of the Scenes.""" 18 scenes: Dict[str, Tuple[str, "Scene", Any]] = {} 19 """A mapping of scene keys to (name, Scene object, additional data) tuples.""" 20 21 selected: str 22 """The currently selected scene""" 23 24 @classproperty 25 def scene(cls) -> Tuple[str, "Scene", Any]: 26 """Class Property to get the active scene.""" 27 return cls.scenes.get(cls.selected,(None,None,None)) 28 29 # --- Scene Control --- 30 @classmethod 31 async def start(cls) -> None: 32 """ 33 Start the main game loop. 34 """ 35 engine.print_versions() 36 while True: 37 scene = cls.scene 38 if not scene: 39 engine.error(Exception("No scene selected")) 40 engine.quit() 41 scene_object = scene[1] 42 await scene_object._Scene__run() 43 44 @classmethod 45 def create(cls, name: str, **kwargs: Any) -> None: 46 """ 47 Create a new scene instance. 48 49 Args: 50 scene_class (str): The class of the scene. 51 kwargs: Additional arguments passed to the scene's constructor. 52 """ 53 scene = Assets.get("data","scenes",name) 54 if not scene: 55 engine.error(RuntimeError(f"Scene: {name}, not found in data/scenes folder")) 56 engine.quit() 57 cls.scene = (name, scene(**kwargs), kwargs) 58 59 @classmethod 60 def change(cls, name: str, **kwargs) -> None: 61 """ 62 Exit and change to a different scene. 63 64 Args: 65 name (str): The name of the scene to switch to. 66 """ 67 scene_obj = cls.scene[1] 68 scene_obj._on_change() 69 scene_obj._Scene__running = False 70 71 cls.create(name,**kwargs) 72 73 @classmethod 74 def pause(cls) -> None: 75 """ 76 Pause the current scene. 77 """ 78 scene_obj = cls.scene[1] 79 scene_obj._on_pause() 80 scene_obj._Scene__paused = True 81 scene_obj._Scene__pause_last_time = time() 82 83 @classmethod 84 def resume(cls) -> None: 85 """Resumes the current scene.""" 86 scene_obj = cls.scene[1] 87 scene_obj._on_resume() 88 scene_obj._Scene__paused = False 89 scene_obj._Scene__dt = 0 90 91 @classmethod 92 def restart(cls) -> None: 93 """ 94 Restart the current scene. 95 """ 96 scene_obj = cls.scene[1] 97 scene_obj._on_restart() 98 scene_obj._Scene__running = False 99 100 @classmethod 101 def reset(cls) -> None: 102 """ 103 Reset the current scene. 104 """ 105 scene_obj = cls.scene[1] 106 scene_obj._on_reset() 107 scene_obj._Scene__running = False 108 109 # manual create a new scene 110 name, obj, kwargs = cls.scene 111 cls.create(name,**kwargs) 112 113 @classmethod 114 def quit(cls) -> None: 115 """ 116 Quit the application through the current scene. 117 """ 118 scene_obj = cls.scene[1] 119 scene_obj._on_quit() 120 engine.quit() 121 122class SceneEvent: 123 """ 124 A helper class to create custom events for the Scenes. 125 """ 126 def __init__(self,scene: "Scene") -> None: 127 """ 128 @private 129 (Not include in the docs as it should be call only inside scene itself) 130 Updates all the custom events and it's properties at the scene state. 131 132 Initializes a Scene Event. 133 134 Args: 135 scene (SceneManager): The reference to the current Scene. 136 """ 137 self.__data = {} 138 self.__scene = scene 139 140 def create(self,name: str,state: int = 0,**kwargs: Any) -> int: 141 """ 142 Create and store a new custom event. 143 144 Args: 145 name (str): The name of the event. 146 state (int): The event state, where: 147 - 1: Runtime 148 - -1: Pause time 149 - 0: Both (default = 0) 150 **kwargs: Additional arguments passed to the event's info. Can be any extra data needed for the event. 151 152 Returns: 153 int: The ID of the new event. 154 """ 155 event_id = pygame.event.custom_type() 156 create_time = self._now(state) 157 last_time = create_time 158 basic_argv = { 159 "name":name,"custom":True, 160 "create_time":create_time,"calls":0, 161 "last_time":last_time,"timer":None, 162 "loops":None,"state":state 163 } 164 165 kwargs.update(basic_argv) 166 event = pygame.event.Event( 167 event_id, 168 kwargs 169 ) 170 171 self.__data[name] = event 172 return event_id 173 174 def get(self,name: str) -> pygame.event.Event: 175 """ 176 Get a custom event by its name. 177 178 Args: 179 name (str): The name of the event. 180 181 Returns: 182 pygame.event.Event or None: The event with the specified name, or None if not found. 183 """ 184 return self.__data.get(name,None) 185 186 def remove(self,name: str) -> "SceneEvent": 187 """ 188 Remove a custom event by its name. 189 190 Args: 191 name (str): The name of the event. 192 193 Returns: 194 SceneEvent: The event that was removed 195 """ 196 return self.__data.pop(event_name) 197 198 def post(self,name: str) -> bool: 199 """ 200 Post a custom event by its name. 201 202 Args: 203 name (str): The name of the event. 204 205 Returns: 206 bool: returns a boolean on whether the event was posted or not 207 """ 208 event = self.get(name) 209 return pygame.event.post(event) 210 211 def match(self,name: str,other_event: "SceneEvent") -> bool: 212 """ 213 Check if a custom event matches by its name. 214 215 Args: 216 name (str): The name of the event. 217 other_event (SceneEvent): The event to compare against. 218 219 Returns: 220 bool: True if the events match, False otherwise. 221 """ 222 event = self.get(name) 223 return event.type == other_event.type 224 225 # Handling events manually seems more flexible and easier for this use case. 226 def schedule(self,name: str,timer: int,loops: int = -1) -> None: 227 """ 228 Shedule a custom event by it's name, for ms timer and loop times. 229 230 Args: 231 name (str): The name of the event. 232 timer (int): The time of the shedule in ms. 233 loops (int): The amount of loop times (default = -1, forever). 234 """ 235 event = self.get(name) 236 event.timer = timer 237 event.loops = loops 238 239 # Update all the scene events and it properties 240 def update(self,state: int): 241 """ 242 @private 243 244 (Not include in the doc as it should be call only inside scene itself) 245 Updates all the custom events and it's properties at the scene state. 246 247 Args: 248 state (int): The scene state. (default = -1, forever). 249 """ 250 for event_name in self.__data: 251 event = self.get(event_name) 252 253 if not(self._is_state(state,event.state)): 254 continue 255 256 if not event.timer: 257 continue 258 259 if event.calls == event.loops: 260 continue 261 262 self._update_time(event) 263 264 def _now(self, state: int) -> float: 265 """Returns the current time based on the state. 266 267 Args: 268 state (int): Determines which time value to return. 269 - If 0: Returns runtime + pausetime. 270 - If >0: Returns runtime. 271 - If <0: Returns pausetime. 272 273 Returns: 274 float: The calculated current time value. 275 """ 276 runtime = self.__scene.runtime 277 pausetime = self.__scene.pausetime 278 if state == 0: 279 return runtime + pausetime 280 281 now = runtime if state > 0 else pausetime 282 return now 283 284 285 def _update_time(self, event:"Event") -> None: 286 """Checks if an event should be triggered based on elapsed time. 287 288 If the time since the last event trigger exceeds the timer threshold, 289 the event is posted and its last_time is updated. 290 291 Args: 292 event (object): An object with the following attributes: 293 - state (int): State used to determine which time value to use. 294 - last_time (float): Timestamp of the last event trigger. 295 - timer (float): Timer threshold in milliseconds. 296 - name (str): Name of the event to post. 297 298 Side Effects: 299 Posts the event via self.post() if the condition is met. 300 Updates event.last_time. 301 """ 302 now = self._now(event.state) 303 diff = (now - event.last_time) * 1000 304 is_time = diff >= event.timer 305 if not is_time: 306 return 307 308 self.post(event.name) 309 event.last_time = now 310 event.calls += 1 311 312 313 @staticmethod 314 def _is_state(state: int, event_state: int)-> bool: 315 """Checks if the event state matches the given state or is neutral (0). 316 317 Args: 318 state (int): Target state to compare against. 319 event_state (int): Current state of the event. 320 321 Returns: 322 bool: True if the event_state equals the state or is 0, False otherwise. 323 """ 324 same = event_state == state 325 is_zero = event_state == 0 326 return (same or is_zero) 327 328class Scene: 329 """Represents a scene in the game.""" 330 _global_runtime = _global_pausetime = 0 331 __global_start_time = time() 332 def __init__(self,**kwargs: Any) -> None: 333 """ 334 Initializes a Scene object. 335 336 Args: 337 **kwargs: Additional arguments passed to the scene. Can be any extra data needed for the scene. 338 339 Raises: 340 RuntimeError: If the Display has not been initialized. Call Display.init() first. 341 """ 342 if not self.display.window: 343 engine.error(RuntimeError("Display has not been initialized! Call Display.init first.")) 344 self.quit() 345 346 self.__initialize(kwargs) 347 348 @classproperty 349 def manager(cls) -> Type[SceneManager]: 350 """Class Property to get the scene manager class""" 351 return SceneManager 352 353 @classproperty 354 def display(cls) -> Type[Display]: 355 """Class Property to get a direct reference to the engine Display class.""" 356 return Display 357 358 @classproperty 359 def assets(cls) -> Type[Assets]: 360 """Class Property to get a direct reference to the engine Assets class.""" 361 return Assets 362 363 @classproperty 364 def global_runtime(cls) -> float: 365 """Class Property to get the total run time across all scenes.""" 366 return cls._global_runtime 367 368 @classproperty 369 def global_pausetime(cls) -> float: 370 """Class Property to get the total pause time across all scenes.""" 371 return cls._global_pausetime 372 373 @property 374 def camera(self) -> SceneCamera: 375 """Property to get the event camera instance of the current scene.""" 376 return self._camera 377 378 @property 379 def event(self) -> SceneEvent: 380 """Property to get the event handler instance of the current scene.""" 381 return self._event 382 383 @property 384 def events(self) -> set: 385 """Property to get all the events of the current frame.""" 386 return self._events 387 388 @property 389 def custom_events(self) -> set: 390 """Property to get all the custom events of the current frame.""" 391 return self._custom_events 392 393 @property 394 def keys_pressed(self) -> set: 395 """Property to get the keys currently pressed of the current frame.""" 396 return self._keys_pressed 397 398 @property 399 def dt(self) -> float: 400 """Property to get the time elapsed since the last frame.""" 401 return self._dt 402 403 @property 404 def fps(self) -> float: 405 """Property to get the current frames per second.""" 406 return self._fps 407 408 @property 409 def max_fps(self) -> int: 410 """Property to get the maximum frames per second limit.""" 411 return self._max_fps 412 413 @max_fps.setter 414 def max_fps(self, value: int): 415 """Setter to set the maximum frames per second limit.""" 416 self._max_fps = value 417 418 @property 419 def background_color(self) -> str | Tuple[int, int, int]: 420 """Property to get the background color""" 421 return self._background_color 422 423 @background_color.setter 424 def background_color(self, value: str | Tuple[int, int, int]): 425 """Setter to set the background color""" 426 self._background_color = value 427 428 @property 429 def runtime(self) -> float: 430 """Property to get the run time""" 431 return self._runtime 432 433 @property 434 def pausetime(self) -> float: 435 """Property to get the pause time""" 436 return self._pausetime 437 438 # Utils 439 def is_time(self,ms): 440 """Checks if a specified time interval has elapsed since the last frame.""" 441 multiplier = 1/ms*1000 442 return int(self._runtime * multiplier) != int((self._runtime - self._dt) * multiplier) 443 444 def is_event(self,event_id): 445 """Checks if an event is happening during the frame. """ 446 return event_id in self._events 447 448 def is_custom_event(self,event_name): 449 """Checks if a custom event is happening during the frame. """ 450 return event_name in self._custom_events 451 452 def is_paused(self): 453 """Returns if the scene is paused.""" 454 return self.__paused 455 456 # Lifecycle Methods 457 def _start(self) -> None: 458 """ 459 @public 460 Called once at the start of the scene. You must Override this func in your subclass. 461 462 Raises: 463 NotImplementedError: If not overridden. 464 """ 465 raise NotImplementedError("start() must be overridden in subclass.") 466 467 def _update(self) -> None: 468 """ 469 @public 470 Called every frame to update scene logic. You must Override this func in your subclass. 471 472 Raises: 473 NotImplementedError: If not overridden. 474 """ 475 raise NotImplementedError("update() must be overridden in subclass.") 476 477 def _draw(self) -> None: 478 """ 479 @public 480 Called every frame to draw elements to the screen. You must Override this func in your subclass. 481 482 Raises: 483 NotImplementedError: If not overridden. 484 """ 485 raise NotImplementedError("draw() must be overridden in subclass.") 486 487 # Paused Lifecycle Methods 488 def _paused_update(self) -> None: 489 """@public Called every paused frame to update scene logic. Override this func in your subclass.""" 490 pass 491 def _paused_draw(self) -> None: 492 """@public Called every paused frame to draw elements to the screen. Override this func in your subclass.""" 493 pass 494 495 # Scene State Change Methods 496 def _on_create(self) -> None: 497 """@public Called once at the scene creation "manager.create()". Override this func in your subclass to add code.""" 498 pass 499 def _on_quit(self) -> None: 500 """@public Called once at every scene quit "manager.quit()". Override this func in your subclass to add code.""" 501 pass 502 def _on_restart(self) -> None: 503 """@public Called once at every scene restart "manager.restart()". Override this func in your subclass to add code.""" 504 pass 505 def _on_reset(self) -> None: 506 """@public Called once at the scene reset "manager.reset()". Override this func in your subclass to add code.""" 507 pass 508 def _on_change(self) -> None: 509 """@public Called once at the scene change "manager.change()". Override this func in your subclass to add code.""" 510 pass 511 def _on_resume(self) -> None: 512 """@public Called once at the scene resume "manager.resume()". Override this func in your subclass to add code.""" 513 pass 514 def _on_pause(self) -> None: 515 """@public Called once at the scene pause "manager.pause()". Override this func in your subclass to add code.""" 516 pass 517 def _on_error(self,error: BaseException) -> None: 518 """ 519 @public 520 Called once at engine error "Scene.__handle_error()". Override this func in your subclass to add code. 521 522 Args: 523 error (BaseException): The engine error that occurred. 524 """ 525 pass 526 527 # Scene Event/Input Methods 528 def _on_event(self, event: pygame.Event) -> None: 529 """ 530 @public 531 Called every pygame event. Override this func in your subclass to add code. 532 533 Args: 534 event (pygame.Event): The pygame event that occurred. 535 """ 536 pass 537 def _on_keydown(self,key: str) -> None: 538 """ 539 @public 540 Called every keyboard keydown. Override this func in your subclass to add code. 541 542 Args: 543 key (str): The keyboard key. 544 """ 545 pass 546 def _on_keyup(self,key: str) -> None: 547 """ 548 @public 549 Called every keyboard keyup. Override this func in your subclass to add code. 550 551 Args: 552 key (str): The keyboard key. 553 """ 554 pass 555 def _on_keypressed(self,key: str) -> None: 556 """ 557 @public 558 Called every keypressed. Override this func in your subclass to add code. 559 560 Args: 561 key (str): The keyboard key. 562 """ 563 pass 564 def _on_mousewheel(self,wheel: int) -> None: 565 """ 566 @public 567 Called every mousewheel change. Override this func in your subclass to add code. 568 569 Args: 570 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 571 """ 572 pass 573 574 # Paused Event/Input Methods 575 def _on_paused_event(self, event: pygame.event.Event) -> None: 576 """ 577 @public 578 Called every paused pygame event. Override this func in your subclass to add code. 579 580 Args: 581 event (pygame.Event): The pygame event that occurred. 582 """ 583 pass 584 def _on_paused_keydown(self,key: str) -> None: 585 """ 586 @public 587 Called every paused keyboard keydown. Override this func in your subclass to add code. 588 589 Args: 590 key (str): The keyboard key. 591 """ 592 def _on_paused_keyup(self,key: str) -> None: 593 """ 594 @public 595 Called every paused keyboard keypressed. Override this func in your subclass to add code. 596 597 Args: 598 key (str): The keyboard key. 599 """ 600 pass 601 def _on_paused_keypressed(self,key: str) -> None: 602 """ 603 @public 604 Called every paused keypressed. Override this func in your subclass to add code. 605 606 Args: 607 key (str): The keyboard key. 608 """ 609 pass 610 def _on_paused_mousewheel(self,wheel: int) -> None: 611 """ 612 @public 613 Called every paused mousewheel change. Override this func in your subclass to add code. 614 615 Args: 616 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 617 """ 618 pass 619 620 # Main Loop 621 # Async to support pygbag export 622 async def __run(self) -> None: 623 """ 624 Starts the scene. 625 626 Raises: 627 Exception: If there is any error in the scene. 628 """ 629 try: 630 self.__initialize_runtime() 631 self._start() 632 633 while self.__running: 634 self.__handle_events() 635 self.__update() 636 self.__render() 637 self.__flip() 638 639 await asyncio.sleep(0) 640 641 except Exception as e: 642 self.__handle_error(e) 643 644 # All the methods below are used to handle the scene frames. 645 def __initialize(self,kwargs): 646 try: 647 self._max_fps = 60 648 self._background_color = (0, 0, 0) 649 650 self.__running = True 651 self.__paused = False 652 653 # set it here because the assets are loaded after the scene is initialized 654 Display.set_icon( 655 Assets.get("engine","images","icon") 656 ) 657 658 self.__event_handlers = { 659 True: (self._on_paused_keydown, self._on_paused_keyup, self._on_paused_keypressed, self._on_paused_mousewheel, self._on_paused_event), 660 False: (self._on_keydown, self._on_keyup, self._on_keypressed, self._on_mousewheel, self._on_event) 661 } 662 663 # set manual the scene kwargs to the scene 664 for k, v in kwargs.items(): 665 setattr(self, k, v) 666 self._on_create() 667 except Exception as e: 668 # expect on_create error as is not in the main loop 669 self.__handle_error(e) 670 671 def __initialize_runtime(self): 672 """Sets up initial basic runtime values.""" 673 self._dt = self._fps = self._runtime = self._pausetime = 0 674 self._keys_pressed = set() # we manually keep track with the key_pressed every frame, set so no duplicates 675 self._events = set() # log events every frame 676 self._custom_events = set() # log custom events every frame 677 678 # Create custom Scene events 679 self._event = SceneEvent(self) 680 self._camera = SceneCamera() 681 682 self._start_time = time() 683 self.__running = True 684 685 def __handle_error(self,e): 686 """ Handles every possible error with a nice message.""" 687 self._on_error(e) 688 engine.error(e) 689 engine.quit() 690 691 def __handle_events(self): 692 """Handles events during runtime or when paused.""" 693 self._events.clear() 694 self._custom_events.clear() 695 self._event.update(-1 if self.__paused else 1) 696 697 on_keydown, on_keyup, on_keypressed, on_wheel, on_event = self.__event_handlers[self.__paused] 698 for event in pygame.event.get(): 699 self._events.add(event.type) 700 701 if hasattr(event, "custom"): 702 self._custom_events.add(event.name) 703 continue 704 705 if event.type == pygame.QUIT: 706 self.manager.quit() 707 708 elif event.type == pygame.KEYDOWN: 709 key = pygame.key.name(event.key) 710 self._keys_pressed.add(key) 711 on_keydown(key) 712 713 elif event.type == pygame.KEYUP: 714 key = pygame.key.name(event.key) 715 self._keys_pressed.discard(key) 716 on_keyup(key) 717 718 elif event.type == pygame.MOUSEWHEEL: 719 wheel = "up" if event.y >= 1 else "down" 720 on_wheel(wheel) 721 722 elif event.type == pygame.VIDEORESIZE: 723 Display.set_res((event.w, event.h)) 724 Display._dynamic_zoom and self.camera._dynamic_zoom() 725 on_event(event) 726 727 if self._keys_pressed: 728 on_keypressed(self._keys_pressed) 729 730 731 def __update(self): 732 """Update the scene and timers, depending on whether it's paused or active.""" 733 update = self._paused_update if self.__paused else self._update 734 update_timers = self.__update_paused_timers if self.__paused else self.__update_timers 735 update() 736 update_timers() 737 738 def __update_timers(self): 739 """Updates global and local runtime timers.""" 740 delta = time() - self._start_time 741 global_delta = time() - self.__global_start_time 742 743 self._runtime = delta - self._pausetime 744 self._global_runtime = global_delta - self._global_pausetime 745 746 def __update_paused_timers(self): 747 """Tracks time spent in pause mode.""" 748 749 # That was the easiest way to track the duration during the pause 750 # but not the best :D 751 delta = time() - self.__pause_last_time 752 753 self._pausetime += delta 754 self._global_pausetime += delta 755 756 self.__pause_last_time = time() 757 758 # no point to update the fps every frame as is hard to tell with the eye 759 # maybe i should change it to average instead? 760 def __update_fps(self): 761 """Updates the current scene fps every 250ms.""" 762 if self.is_time(250): 763 self._fps = Display.clock.get_fps() 764 765 def __render(self): 766 """Renders the scene.""" 767 if not self.__paused: # skip background if paused to keep the last frame render 768 self.__draw_background() 769 (self._paused_draw if self.__paused else self._draw)() 770 771 def __draw_background(self): 772 """Clears the screen with the background color.""" 773 Display.surface.fill(self._background_color) 774 775 def __draw_display(self): 776 """Draws the display.""" 777 surf = Display.get_stretch_surf() if Display.is_resized() else Display.surface 778 Display.window.blit(surf,(0,0)) 779 780 def __flip(self): 781 """Updates the display with the latest frame.""" 782 self.__update_fps() # I think updating the fps before the flip is the best place? 783 self._dt = round(Display.clock.tick(self._max_fps) / 1000, 3) # Also take the dt 784 self.__draw_display() 785 pygame.display.flip()
17class SceneManager: 18 """The Main Manager of the Scenes.""" 19 scenes: Dict[str, Tuple[str, "Scene", Any]] = {} 20 """A mapping of scene keys to (name, Scene object, additional data) tuples.""" 21 22 selected: str 23 """The currently selected scene""" 24 25 @classproperty 26 def scene(cls) -> Tuple[str, "Scene", Any]: 27 """Class Property to get the active scene.""" 28 return cls.scenes.get(cls.selected,(None,None,None)) 29 30 # --- Scene Control --- 31 @classmethod 32 async def start(cls) -> None: 33 """ 34 Start the main game loop. 35 """ 36 engine.print_versions() 37 while True: 38 scene = cls.scene 39 if not scene: 40 engine.error(Exception("No scene selected")) 41 engine.quit() 42 scene_object = scene[1] 43 await scene_object._Scene__run() 44 45 @classmethod 46 def create(cls, name: str, **kwargs: Any) -> None: 47 """ 48 Create a new scene instance. 49 50 Args: 51 scene_class (str): The class of the scene. 52 kwargs: Additional arguments passed to the scene's constructor. 53 """ 54 scene = Assets.get("data","scenes",name) 55 if not scene: 56 engine.error(RuntimeError(f"Scene: {name}, not found in data/scenes folder")) 57 engine.quit() 58 cls.scene = (name, scene(**kwargs), kwargs) 59 60 @classmethod 61 def change(cls, name: str, **kwargs) -> None: 62 """ 63 Exit and change to a different scene. 64 65 Args: 66 name (str): The name of the scene to switch to. 67 """ 68 scene_obj = cls.scene[1] 69 scene_obj._on_change() 70 scene_obj._Scene__running = False 71 72 cls.create(name,**kwargs) 73 74 @classmethod 75 def pause(cls) -> None: 76 """ 77 Pause the current scene. 78 """ 79 scene_obj = cls.scene[1] 80 scene_obj._on_pause() 81 scene_obj._Scene__paused = True 82 scene_obj._Scene__pause_last_time = time() 83 84 @classmethod 85 def resume(cls) -> None: 86 """Resumes the current scene.""" 87 scene_obj = cls.scene[1] 88 scene_obj._on_resume() 89 scene_obj._Scene__paused = False 90 scene_obj._Scene__dt = 0 91 92 @classmethod 93 def restart(cls) -> None: 94 """ 95 Restart the current scene. 96 """ 97 scene_obj = cls.scene[1] 98 scene_obj._on_restart() 99 scene_obj._Scene__running = False 100 101 @classmethod 102 def reset(cls) -> None: 103 """ 104 Reset the current scene. 105 """ 106 scene_obj = cls.scene[1] 107 scene_obj._on_reset() 108 scene_obj._Scene__running = False 109 110 # manual create a new scene 111 name, obj, kwargs = cls.scene 112 cls.create(name,**kwargs) 113 114 @classmethod 115 def quit(cls) -> None: 116 """ 117 Quit the application through the current scene. 118 """ 119 scene_obj = cls.scene[1] 120 scene_obj._on_quit() 121 engine.quit()
The Main Manager of the Scenes.
25 @classproperty 26 def scene(cls) -> Tuple[str, "Scene", Any]: 27 """Class Property to get the active scene.""" 28 return cls.scenes.get(cls.selected,(None,None,None))
Class Property to get the active scene.
31 @classmethod 32 async def start(cls) -> None: 33 """ 34 Start the main game loop. 35 """ 36 engine.print_versions() 37 while True: 38 scene = cls.scene 39 if not scene: 40 engine.error(Exception("No scene selected")) 41 engine.quit() 42 scene_object = scene[1] 43 await scene_object._Scene__run()
Start the main game loop.
45 @classmethod 46 def create(cls, name: str, **kwargs: Any) -> None: 47 """ 48 Create a new scene instance. 49 50 Args: 51 scene_class (str): The class of the scene. 52 kwargs: Additional arguments passed to the scene's constructor. 53 """ 54 scene = Assets.get("data","scenes",name) 55 if not scene: 56 engine.error(RuntimeError(f"Scene: {name}, not found in data/scenes folder")) 57 engine.quit() 58 cls.scene = (name, scene(**kwargs), kwargs)
Create a new scene instance.
Arguments:
- scene_class (str): The class of the scene.
- kwargs: Additional arguments passed to the scene's constructor.
60 @classmethod 61 def change(cls, name: str, **kwargs) -> None: 62 """ 63 Exit and change to a different scene. 64 65 Args: 66 name (str): The name of the scene to switch to. 67 """ 68 scene_obj = cls.scene[1] 69 scene_obj._on_change() 70 scene_obj._Scene__running = False 71 72 cls.create(name,**kwargs)
Exit and change to a different scene.
Arguments:
- name (str): The name of the scene to switch to.
74 @classmethod 75 def pause(cls) -> None: 76 """ 77 Pause the current scene. 78 """ 79 scene_obj = cls.scene[1] 80 scene_obj._on_pause() 81 scene_obj._Scene__paused = True 82 scene_obj._Scene__pause_last_time = time()
Pause the current scene.
84 @classmethod 85 def resume(cls) -> None: 86 """Resumes the current scene.""" 87 scene_obj = cls.scene[1] 88 scene_obj._on_resume() 89 scene_obj._Scene__paused = False 90 scene_obj._Scene__dt = 0
Resumes the current scene.
92 @classmethod 93 def restart(cls) -> None: 94 """ 95 Restart the current scene. 96 """ 97 scene_obj = cls.scene[1] 98 scene_obj._on_restart() 99 scene_obj._Scene__running = False
Restart the current scene.
101 @classmethod 102 def reset(cls) -> None: 103 """ 104 Reset the current scene. 105 """ 106 scene_obj = cls.scene[1] 107 scene_obj._on_reset() 108 scene_obj._Scene__running = False 109 110 # manual create a new scene 111 name, obj, kwargs = cls.scene 112 cls.create(name,**kwargs)
Reset the current scene.
123class SceneEvent: 124 """ 125 A helper class to create custom events for the Scenes. 126 """ 127 def __init__(self,scene: "Scene") -> None: 128 """ 129 @private 130 (Not include in the docs as it should be call only inside scene itself) 131 Updates all the custom events and it's properties at the scene state. 132 133 Initializes a Scene Event. 134 135 Args: 136 scene (SceneManager): The reference to the current Scene. 137 """ 138 self.__data = {} 139 self.__scene = scene 140 141 def create(self,name: str,state: int = 0,**kwargs: Any) -> int: 142 """ 143 Create and store a new custom event. 144 145 Args: 146 name (str): The name of the event. 147 state (int): The event state, where: 148 - 1: Runtime 149 - -1: Pause time 150 - 0: Both (default = 0) 151 **kwargs: Additional arguments passed to the event's info. Can be any extra data needed for the event. 152 153 Returns: 154 int: The ID of the new event. 155 """ 156 event_id = pygame.event.custom_type() 157 create_time = self._now(state) 158 last_time = create_time 159 basic_argv = { 160 "name":name,"custom":True, 161 "create_time":create_time,"calls":0, 162 "last_time":last_time,"timer":None, 163 "loops":None,"state":state 164 } 165 166 kwargs.update(basic_argv) 167 event = pygame.event.Event( 168 event_id, 169 kwargs 170 ) 171 172 self.__data[name] = event 173 return event_id 174 175 def get(self,name: str) -> pygame.event.Event: 176 """ 177 Get a custom event by its name. 178 179 Args: 180 name (str): The name of the event. 181 182 Returns: 183 pygame.event.Event or None: The event with the specified name, or None if not found. 184 """ 185 return self.__data.get(name,None) 186 187 def remove(self,name: str) -> "SceneEvent": 188 """ 189 Remove a custom event by its name. 190 191 Args: 192 name (str): The name of the event. 193 194 Returns: 195 SceneEvent: The event that was removed 196 """ 197 return self.__data.pop(event_name) 198 199 def post(self,name: str) -> bool: 200 """ 201 Post a custom event by its name. 202 203 Args: 204 name (str): The name of the event. 205 206 Returns: 207 bool: returns a boolean on whether the event was posted or not 208 """ 209 event = self.get(name) 210 return pygame.event.post(event) 211 212 def match(self,name: str,other_event: "SceneEvent") -> bool: 213 """ 214 Check if a custom event matches by its name. 215 216 Args: 217 name (str): The name of the event. 218 other_event (SceneEvent): The event to compare against. 219 220 Returns: 221 bool: True if the events match, False otherwise. 222 """ 223 event = self.get(name) 224 return event.type == other_event.type 225 226 # Handling events manually seems more flexible and easier for this use case. 227 def schedule(self,name: str,timer: int,loops: int = -1) -> None: 228 """ 229 Shedule a custom event by it's name, for ms timer and loop times. 230 231 Args: 232 name (str): The name of the event. 233 timer (int): The time of the shedule in ms. 234 loops (int): The amount of loop times (default = -1, forever). 235 """ 236 event = self.get(name) 237 event.timer = timer 238 event.loops = loops 239 240 # Update all the scene events and it properties 241 def update(self,state: int): 242 """ 243 @private 244 245 (Not include in the doc as it should be call only inside scene itself) 246 Updates all the custom events and it's properties at the scene state. 247 248 Args: 249 state (int): The scene state. (default = -1, forever). 250 """ 251 for event_name in self.__data: 252 event = self.get(event_name) 253 254 if not(self._is_state(state,event.state)): 255 continue 256 257 if not event.timer: 258 continue 259 260 if event.calls == event.loops: 261 continue 262 263 self._update_time(event) 264 265 def _now(self, state: int) -> float: 266 """Returns the current time based on the state. 267 268 Args: 269 state (int): Determines which time value to return. 270 - If 0: Returns runtime + pausetime. 271 - If >0: Returns runtime. 272 - If <0: Returns pausetime. 273 274 Returns: 275 float: The calculated current time value. 276 """ 277 runtime = self.__scene.runtime 278 pausetime = self.__scene.pausetime 279 if state == 0: 280 return runtime + pausetime 281 282 now = runtime if state > 0 else pausetime 283 return now 284 285 286 def _update_time(self, event:"Event") -> None: 287 """Checks if an event should be triggered based on elapsed time. 288 289 If the time since the last event trigger exceeds the timer threshold, 290 the event is posted and its last_time is updated. 291 292 Args: 293 event (object): An object with the following attributes: 294 - state (int): State used to determine which time value to use. 295 - last_time (float): Timestamp of the last event trigger. 296 - timer (float): Timer threshold in milliseconds. 297 - name (str): Name of the event to post. 298 299 Side Effects: 300 Posts the event via self.post() if the condition is met. 301 Updates event.last_time. 302 """ 303 now = self._now(event.state) 304 diff = (now - event.last_time) * 1000 305 is_time = diff >= event.timer 306 if not is_time: 307 return 308 309 self.post(event.name) 310 event.last_time = now 311 event.calls += 1 312 313 314 @staticmethod 315 def _is_state(state: int, event_state: int)-> bool: 316 """Checks if the event state matches the given state or is neutral (0). 317 318 Args: 319 state (int): Target state to compare against. 320 event_state (int): Current state of the event. 321 322 Returns: 323 bool: True if the event_state equals the state or is 0, False otherwise. 324 """ 325 same = event_state == state 326 is_zero = event_state == 0 327 return (same or is_zero)
A helper class to create custom events for the Scenes.
141 def create(self,name: str,state: int = 0,**kwargs: Any) -> int: 142 """ 143 Create and store a new custom event. 144 145 Args: 146 name (str): The name of the event. 147 state (int): The event state, where: 148 - 1: Runtime 149 - -1: Pause time 150 - 0: Both (default = 0) 151 **kwargs: Additional arguments passed to the event's info. Can be any extra data needed for the event. 152 153 Returns: 154 int: The ID of the new event. 155 """ 156 event_id = pygame.event.custom_type() 157 create_time = self._now(state) 158 last_time = create_time 159 basic_argv = { 160 "name":name,"custom":True, 161 "create_time":create_time,"calls":0, 162 "last_time":last_time,"timer":None, 163 "loops":None,"state":state 164 } 165 166 kwargs.update(basic_argv) 167 event = pygame.event.Event( 168 event_id, 169 kwargs 170 ) 171 172 self.__data[name] = event 173 return event_id
Create and store a new custom event.
Arguments:
- name (str): The name of the event.
- state (int): The event state, where:
- 1: Runtime
- -1: Pause time
- 0: Both (default = 0)
- **kwargs: Additional arguments passed to the event's info. Can be any extra data needed for the event.
Returns:
int: The ID of the new event.
175 def get(self,name: str) -> pygame.event.Event: 176 """ 177 Get a custom event by its name. 178 179 Args: 180 name (str): The name of the event. 181 182 Returns: 183 pygame.event.Event or None: The event with the specified name, or None if not found. 184 """ 185 return self.__data.get(name,None)
Get a custom event by its name.
Arguments:
- name (str): The name of the event.
Returns:
pygame.event.Event or None: The event with the specified name, or None if not found.
187 def remove(self,name: str) -> "SceneEvent": 188 """ 189 Remove a custom event by its name. 190 191 Args: 192 name (str): The name of the event. 193 194 Returns: 195 SceneEvent: The event that was removed 196 """ 197 return self.__data.pop(event_name)
Remove a custom event by its name.
Arguments:
- name (str): The name of the event.
Returns:
SceneEvent: The event that was removed
199 def post(self,name: str) -> bool: 200 """ 201 Post a custom event by its name. 202 203 Args: 204 name (str): The name of the event. 205 206 Returns: 207 bool: returns a boolean on whether the event was posted or not 208 """ 209 event = self.get(name) 210 return pygame.event.post(event)
Post a custom event by its name.
Arguments:
- name (str): The name of the event.
Returns:
bool: returns a boolean on whether the event was posted or not
212 def match(self,name: str,other_event: "SceneEvent") -> bool: 213 """ 214 Check if a custom event matches by its name. 215 216 Args: 217 name (str): The name of the event. 218 other_event (SceneEvent): The event to compare against. 219 220 Returns: 221 bool: True if the events match, False otherwise. 222 """ 223 event = self.get(name) 224 return event.type == other_event.type
Check if a custom event matches by its name.
Arguments:
- name (str): The name of the event.
- other_event (SceneEvent): The event to compare against.
Returns:
bool: True if the events match, False otherwise.
227 def schedule(self,name: str,timer: int,loops: int = -1) -> None: 228 """ 229 Shedule a custom event by it's name, for ms timer and loop times. 230 231 Args: 232 name (str): The name of the event. 233 timer (int): The time of the shedule in ms. 234 loops (int): The amount of loop times (default = -1, forever). 235 """ 236 event = self.get(name) 237 event.timer = timer 238 event.loops = loops
Shedule a custom event by it's name, for ms timer and loop times.
Arguments:
- name (str): The name of the event.
- timer (int): The time of the shedule in ms.
- loops (int): The amount of loop times (default = -1, forever).
329class Scene: 330 """Represents a scene in the game.""" 331 _global_runtime = _global_pausetime = 0 332 __global_start_time = time() 333 def __init__(self,**kwargs: Any) -> None: 334 """ 335 Initializes a Scene object. 336 337 Args: 338 **kwargs: Additional arguments passed to the scene. Can be any extra data needed for the scene. 339 340 Raises: 341 RuntimeError: If the Display has not been initialized. Call Display.init() first. 342 """ 343 if not self.display.window: 344 engine.error(RuntimeError("Display has not been initialized! Call Display.init first.")) 345 self.quit() 346 347 self.__initialize(kwargs) 348 349 @classproperty 350 def manager(cls) -> Type[SceneManager]: 351 """Class Property to get the scene manager class""" 352 return SceneManager 353 354 @classproperty 355 def display(cls) -> Type[Display]: 356 """Class Property to get a direct reference to the engine Display class.""" 357 return Display 358 359 @classproperty 360 def assets(cls) -> Type[Assets]: 361 """Class Property to get a direct reference to the engine Assets class.""" 362 return Assets 363 364 @classproperty 365 def global_runtime(cls) -> float: 366 """Class Property to get the total run time across all scenes.""" 367 return cls._global_runtime 368 369 @classproperty 370 def global_pausetime(cls) -> float: 371 """Class Property to get the total pause time across all scenes.""" 372 return cls._global_pausetime 373 374 @property 375 def camera(self) -> SceneCamera: 376 """Property to get the event camera instance of the current scene.""" 377 return self._camera 378 379 @property 380 def event(self) -> SceneEvent: 381 """Property to get the event handler instance of the current scene.""" 382 return self._event 383 384 @property 385 def events(self) -> set: 386 """Property to get all the events of the current frame.""" 387 return self._events 388 389 @property 390 def custom_events(self) -> set: 391 """Property to get all the custom events of the current frame.""" 392 return self._custom_events 393 394 @property 395 def keys_pressed(self) -> set: 396 """Property to get the keys currently pressed of the current frame.""" 397 return self._keys_pressed 398 399 @property 400 def dt(self) -> float: 401 """Property to get the time elapsed since the last frame.""" 402 return self._dt 403 404 @property 405 def fps(self) -> float: 406 """Property to get the current frames per second.""" 407 return self._fps 408 409 @property 410 def max_fps(self) -> int: 411 """Property to get the maximum frames per second limit.""" 412 return self._max_fps 413 414 @max_fps.setter 415 def max_fps(self, value: int): 416 """Setter to set the maximum frames per second limit.""" 417 self._max_fps = value 418 419 @property 420 def background_color(self) -> str | Tuple[int, int, int]: 421 """Property to get the background color""" 422 return self._background_color 423 424 @background_color.setter 425 def background_color(self, value: str | Tuple[int, int, int]): 426 """Setter to set the background color""" 427 self._background_color = value 428 429 @property 430 def runtime(self) -> float: 431 """Property to get the run time""" 432 return self._runtime 433 434 @property 435 def pausetime(self) -> float: 436 """Property to get the pause time""" 437 return self._pausetime 438 439 # Utils 440 def is_time(self,ms): 441 """Checks if a specified time interval has elapsed since the last frame.""" 442 multiplier = 1/ms*1000 443 return int(self._runtime * multiplier) != int((self._runtime - self._dt) * multiplier) 444 445 def is_event(self,event_id): 446 """Checks if an event is happening during the frame. """ 447 return event_id in self._events 448 449 def is_custom_event(self,event_name): 450 """Checks if a custom event is happening during the frame. """ 451 return event_name in self._custom_events 452 453 def is_paused(self): 454 """Returns if the scene is paused.""" 455 return self.__paused 456 457 # Lifecycle Methods 458 def _start(self) -> None: 459 """ 460 @public 461 Called once at the start of the scene. You must Override this func in your subclass. 462 463 Raises: 464 NotImplementedError: If not overridden. 465 """ 466 raise NotImplementedError("start() must be overridden in subclass.") 467 468 def _update(self) -> None: 469 """ 470 @public 471 Called every frame to update scene logic. You must Override this func in your subclass. 472 473 Raises: 474 NotImplementedError: If not overridden. 475 """ 476 raise NotImplementedError("update() must be overridden in subclass.") 477 478 def _draw(self) -> None: 479 """ 480 @public 481 Called every frame to draw elements to the screen. You must Override this func in your subclass. 482 483 Raises: 484 NotImplementedError: If not overridden. 485 """ 486 raise NotImplementedError("draw() must be overridden in subclass.") 487 488 # Paused Lifecycle Methods 489 def _paused_update(self) -> None: 490 """@public Called every paused frame to update scene logic. Override this func in your subclass.""" 491 pass 492 def _paused_draw(self) -> None: 493 """@public Called every paused frame to draw elements to the screen. Override this func in your subclass.""" 494 pass 495 496 # Scene State Change Methods 497 def _on_create(self) -> None: 498 """@public Called once at the scene creation "manager.create()". Override this func in your subclass to add code.""" 499 pass 500 def _on_quit(self) -> None: 501 """@public Called once at every scene quit "manager.quit()". Override this func in your subclass to add code.""" 502 pass 503 def _on_restart(self) -> None: 504 """@public Called once at every scene restart "manager.restart()". Override this func in your subclass to add code.""" 505 pass 506 def _on_reset(self) -> None: 507 """@public Called once at the scene reset "manager.reset()". Override this func in your subclass to add code.""" 508 pass 509 def _on_change(self) -> None: 510 """@public Called once at the scene change "manager.change()". Override this func in your subclass to add code.""" 511 pass 512 def _on_resume(self) -> None: 513 """@public Called once at the scene resume "manager.resume()". Override this func in your subclass to add code.""" 514 pass 515 def _on_pause(self) -> None: 516 """@public Called once at the scene pause "manager.pause()". Override this func in your subclass to add code.""" 517 pass 518 def _on_error(self,error: BaseException) -> None: 519 """ 520 @public 521 Called once at engine error "Scene.__handle_error()". Override this func in your subclass to add code. 522 523 Args: 524 error (BaseException): The engine error that occurred. 525 """ 526 pass 527 528 # Scene Event/Input Methods 529 def _on_event(self, event: pygame.Event) -> None: 530 """ 531 @public 532 Called every pygame event. Override this func in your subclass to add code. 533 534 Args: 535 event (pygame.Event): The pygame event that occurred. 536 """ 537 pass 538 def _on_keydown(self,key: str) -> None: 539 """ 540 @public 541 Called every keyboard keydown. Override this func in your subclass to add code. 542 543 Args: 544 key (str): The keyboard key. 545 """ 546 pass 547 def _on_keyup(self,key: str) -> None: 548 """ 549 @public 550 Called every keyboard keyup. Override this func in your subclass to add code. 551 552 Args: 553 key (str): The keyboard key. 554 """ 555 pass 556 def _on_keypressed(self,key: str) -> None: 557 """ 558 @public 559 Called every keypressed. Override this func in your subclass to add code. 560 561 Args: 562 key (str): The keyboard key. 563 """ 564 pass 565 def _on_mousewheel(self,wheel: int) -> None: 566 """ 567 @public 568 Called every mousewheel change. Override this func in your subclass to add code. 569 570 Args: 571 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 572 """ 573 pass 574 575 # Paused Event/Input Methods 576 def _on_paused_event(self, event: pygame.event.Event) -> None: 577 """ 578 @public 579 Called every paused pygame event. Override this func in your subclass to add code. 580 581 Args: 582 event (pygame.Event): The pygame event that occurred. 583 """ 584 pass 585 def _on_paused_keydown(self,key: str) -> None: 586 """ 587 @public 588 Called every paused keyboard keydown. Override this func in your subclass to add code. 589 590 Args: 591 key (str): The keyboard key. 592 """ 593 def _on_paused_keyup(self,key: str) -> None: 594 """ 595 @public 596 Called every paused keyboard keypressed. Override this func in your subclass to add code. 597 598 Args: 599 key (str): The keyboard key. 600 """ 601 pass 602 def _on_paused_keypressed(self,key: str) -> None: 603 """ 604 @public 605 Called every paused keypressed. Override this func in your subclass to add code. 606 607 Args: 608 key (str): The keyboard key. 609 """ 610 pass 611 def _on_paused_mousewheel(self,wheel: int) -> None: 612 """ 613 @public 614 Called every paused mousewheel change. Override this func in your subclass to add code. 615 616 Args: 617 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 618 """ 619 pass 620 621 # Main Loop 622 # Async to support pygbag export 623 async def __run(self) -> None: 624 """ 625 Starts the scene. 626 627 Raises: 628 Exception: If there is any error in the scene. 629 """ 630 try: 631 self.__initialize_runtime() 632 self._start() 633 634 while self.__running: 635 self.__handle_events() 636 self.__update() 637 self.__render() 638 self.__flip() 639 640 await asyncio.sleep(0) 641 642 except Exception as e: 643 self.__handle_error(e) 644 645 # All the methods below are used to handle the scene frames. 646 def __initialize(self,kwargs): 647 try: 648 self._max_fps = 60 649 self._background_color = (0, 0, 0) 650 651 self.__running = True 652 self.__paused = False 653 654 # set it here because the assets are loaded after the scene is initialized 655 Display.set_icon( 656 Assets.get("engine","images","icon") 657 ) 658 659 self.__event_handlers = { 660 True: (self._on_paused_keydown, self._on_paused_keyup, self._on_paused_keypressed, self._on_paused_mousewheel, self._on_paused_event), 661 False: (self._on_keydown, self._on_keyup, self._on_keypressed, self._on_mousewheel, self._on_event) 662 } 663 664 # set manual the scene kwargs to the scene 665 for k, v in kwargs.items(): 666 setattr(self, k, v) 667 self._on_create() 668 except Exception as e: 669 # expect on_create error as is not in the main loop 670 self.__handle_error(e) 671 672 def __initialize_runtime(self): 673 """Sets up initial basic runtime values.""" 674 self._dt = self._fps = self._runtime = self._pausetime = 0 675 self._keys_pressed = set() # we manually keep track with the key_pressed every frame, set so no duplicates 676 self._events = set() # log events every frame 677 self._custom_events = set() # log custom events every frame 678 679 # Create custom Scene events 680 self._event = SceneEvent(self) 681 self._camera = SceneCamera() 682 683 self._start_time = time() 684 self.__running = True 685 686 def __handle_error(self,e): 687 """ Handles every possible error with a nice message.""" 688 self._on_error(e) 689 engine.error(e) 690 engine.quit() 691 692 def __handle_events(self): 693 """Handles events during runtime or when paused.""" 694 self._events.clear() 695 self._custom_events.clear() 696 self._event.update(-1 if self.__paused else 1) 697 698 on_keydown, on_keyup, on_keypressed, on_wheel, on_event = self.__event_handlers[self.__paused] 699 for event in pygame.event.get(): 700 self._events.add(event.type) 701 702 if hasattr(event, "custom"): 703 self._custom_events.add(event.name) 704 continue 705 706 if event.type == pygame.QUIT: 707 self.manager.quit() 708 709 elif event.type == pygame.KEYDOWN: 710 key = pygame.key.name(event.key) 711 self._keys_pressed.add(key) 712 on_keydown(key) 713 714 elif event.type == pygame.KEYUP: 715 key = pygame.key.name(event.key) 716 self._keys_pressed.discard(key) 717 on_keyup(key) 718 719 elif event.type == pygame.MOUSEWHEEL: 720 wheel = "up" if event.y >= 1 else "down" 721 on_wheel(wheel) 722 723 elif event.type == pygame.VIDEORESIZE: 724 Display.set_res((event.w, event.h)) 725 Display._dynamic_zoom and self.camera._dynamic_zoom() 726 on_event(event) 727 728 if self._keys_pressed: 729 on_keypressed(self._keys_pressed) 730 731 732 def __update(self): 733 """Update the scene and timers, depending on whether it's paused or active.""" 734 update = self._paused_update if self.__paused else self._update 735 update_timers = self.__update_paused_timers if self.__paused else self.__update_timers 736 update() 737 update_timers() 738 739 def __update_timers(self): 740 """Updates global and local runtime timers.""" 741 delta = time() - self._start_time 742 global_delta = time() - self.__global_start_time 743 744 self._runtime = delta - self._pausetime 745 self._global_runtime = global_delta - self._global_pausetime 746 747 def __update_paused_timers(self): 748 """Tracks time spent in pause mode.""" 749 750 # That was the easiest way to track the duration during the pause 751 # but not the best :D 752 delta = time() - self.__pause_last_time 753 754 self._pausetime += delta 755 self._global_pausetime += delta 756 757 self.__pause_last_time = time() 758 759 # no point to update the fps every frame as is hard to tell with the eye 760 # maybe i should change it to average instead? 761 def __update_fps(self): 762 """Updates the current scene fps every 250ms.""" 763 if self.is_time(250): 764 self._fps = Display.clock.get_fps() 765 766 def __render(self): 767 """Renders the scene.""" 768 if not self.__paused: # skip background if paused to keep the last frame render 769 self.__draw_background() 770 (self._paused_draw if self.__paused else self._draw)() 771 772 def __draw_background(self): 773 """Clears the screen with the background color.""" 774 Display.surface.fill(self._background_color) 775 776 def __draw_display(self): 777 """Draws the display.""" 778 surf = Display.get_stretch_surf() if Display.is_resized() else Display.surface 779 Display.window.blit(surf,(0,0)) 780 781 def __flip(self): 782 """Updates the display with the latest frame.""" 783 self.__update_fps() # I think updating the fps before the flip is the best place? 784 self._dt = round(Display.clock.tick(self._max_fps) / 1000, 3) # Also take the dt 785 self.__draw_display() 786 pygame.display.flip()
Represents a scene in the game.
333 def __init__(self,**kwargs: Any) -> None: 334 """ 335 Initializes a Scene object. 336 337 Args: 338 **kwargs: Additional arguments passed to the scene. Can be any extra data needed for the scene. 339 340 Raises: 341 RuntimeError: If the Display has not been initialized. Call Display.init() first. 342 """ 343 if not self.display.window: 344 engine.error(RuntimeError("Display has not been initialized! Call Display.init first.")) 345 self.quit() 346 347 self.__initialize(kwargs)
Initializes a Scene object.
Arguments:
- **kwargs: Additional arguments passed to the scene. Can be any extra data needed for the scene.
Raises:
- RuntimeError: If the Display has not been initialized. Call Display.init() first.
349 @classproperty 350 def manager(cls) -> Type[SceneManager]: 351 """Class Property to get the scene manager class""" 352 return SceneManager
Class Property to get the scene manager class
354 @classproperty 355 def display(cls) -> Type[Display]: 356 """Class Property to get a direct reference to the engine Display class.""" 357 return Display
Class Property to get a direct reference to the engine Display class.
359 @classproperty 360 def assets(cls) -> Type[Assets]: 361 """Class Property to get a direct reference to the engine Assets class.""" 362 return Assets
Class Property to get a direct reference to the engine Assets class.
364 @classproperty 365 def global_runtime(cls) -> float: 366 """Class Property to get the total run time across all scenes.""" 367 return cls._global_runtime
Class Property to get the total run time across all scenes.
369 @classproperty 370 def global_pausetime(cls) -> float: 371 """Class Property to get the total pause time across all scenes.""" 372 return cls._global_pausetime
Class Property to get the total pause time across all scenes.
374 @property 375 def camera(self) -> SceneCamera: 376 """Property to get the event camera instance of the current scene.""" 377 return self._camera
Property to get the event camera instance of the current scene.
379 @property 380 def event(self) -> SceneEvent: 381 """Property to get the event handler instance of the current scene.""" 382 return self._event
Property to get the event handler instance of the current scene.
384 @property 385 def events(self) -> set: 386 """Property to get all the events of the current frame.""" 387 return self._events
Property to get all the events of the current frame.
389 @property 390 def custom_events(self) -> set: 391 """Property to get all the custom events of the current frame.""" 392 return self._custom_events
Property to get all the custom events of the current frame.
394 @property 395 def keys_pressed(self) -> set: 396 """Property to get the keys currently pressed of the current frame.""" 397 return self._keys_pressed
Property to get the keys currently pressed of the current frame.
399 @property 400 def dt(self) -> float: 401 """Property to get the time elapsed since the last frame.""" 402 return self._dt
Property to get the time elapsed since the last frame.
404 @property 405 def fps(self) -> float: 406 """Property to get the current frames per second.""" 407 return self._fps
Property to get the current frames per second.
409 @property 410 def max_fps(self) -> int: 411 """Property to get the maximum frames per second limit.""" 412 return self._max_fps
Property to get the maximum frames per second limit.
419 @property 420 def background_color(self) -> str | Tuple[int, int, int]: 421 """Property to get the background color""" 422 return self._background_color
Property to get the background color
429 @property 430 def runtime(self) -> float: 431 """Property to get the run time""" 432 return self._runtime
Property to get the run time
434 @property 435 def pausetime(self) -> float: 436 """Property to get the pause time""" 437 return self._pausetime
Property to get the pause time
440 def is_time(self,ms): 441 """Checks if a specified time interval has elapsed since the last frame.""" 442 multiplier = 1/ms*1000 443 return int(self._runtime * multiplier) != int((self._runtime - self._dt) * multiplier)
Checks if a specified time interval has elapsed since the last frame.
445 def is_event(self,event_id): 446 """Checks if an event is happening during the frame. """ 447 return event_id in self._events
Checks if an event is happening during the frame.
449 def is_custom_event(self,event_name): 450 """Checks if a custom event is happening during the frame. """ 451 return event_name in self._custom_events
Checks if a custom event is happening during the frame.
458 def _start(self) -> None: 459 """ 460 @public 461 Called once at the start of the scene. You must Override this func in your subclass. 462 463 Raises: 464 NotImplementedError: If not overridden. 465 """ 466 raise NotImplementedError("start() must be overridden in subclass.")
Called once at the start of the scene. You must Override this func in your subclass.
Raises:
- NotImplementedError: If not overridden.
468 def _update(self) -> None: 469 """ 470 @public 471 Called every frame to update scene logic. You must Override this func in your subclass. 472 473 Raises: 474 NotImplementedError: If not overridden. 475 """ 476 raise NotImplementedError("update() must be overridden in subclass.")
Called every frame to update scene logic. You must Override this func in your subclass.
Raises:
- NotImplementedError: If not overridden.
478 def _draw(self) -> None: 479 """ 480 @public 481 Called every frame to draw elements to the screen. You must Override this func in your subclass. 482 483 Raises: 484 NotImplementedError: If not overridden. 485 """ 486 raise NotImplementedError("draw() must be overridden in subclass.")
Called every frame to draw elements to the screen. You must Override this func in your subclass.
Raises:
- NotImplementedError: If not overridden.
489 def _paused_update(self) -> None: 490 """@public Called every paused frame to update scene logic. Override this func in your subclass.""" 491 pass
Called every paused frame to update scene logic. Override this func in your subclass.
492 def _paused_draw(self) -> None: 493 """@public Called every paused frame to draw elements to the screen. Override this func in your subclass.""" 494 pass
Called every paused frame to draw elements to the screen. Override this func in your subclass.
497 def _on_create(self) -> None: 498 """@public Called once at the scene creation "manager.create()". Override this func in your subclass to add code.""" 499 pass
Called once at the scene creation "manager.create()". Override this func in your subclass to add code.
500 def _on_quit(self) -> None: 501 """@public Called once at every scene quit "manager.quit()". Override this func in your subclass to add code.""" 502 pass
Called once at every scene quit "manager.quit()". Override this func in your subclass to add code.
503 def _on_restart(self) -> None: 504 """@public Called once at every scene restart "manager.restart()". Override this func in your subclass to add code.""" 505 pass
Called once at every scene restart "manager.restart()". Override this func in your subclass to add code.
506 def _on_reset(self) -> None: 507 """@public Called once at the scene reset "manager.reset()". Override this func in your subclass to add code.""" 508 pass
Called once at the scene reset "manager.reset()". Override this func in your subclass to add code.
509 def _on_change(self) -> None: 510 """@public Called once at the scene change "manager.change()". Override this func in your subclass to add code.""" 511 pass
Called once at the scene change "manager.change()". Override this func in your subclass to add code.
512 def _on_resume(self) -> None: 513 """@public Called once at the scene resume "manager.resume()". Override this func in your subclass to add code.""" 514 pass
Called once at the scene resume "manager.resume()". Override this func in your subclass to add code.
515 def _on_pause(self) -> None: 516 """@public Called once at the scene pause "manager.pause()". Override this func in your subclass to add code.""" 517 pass
Called once at the scene pause "manager.pause()". Override this func in your subclass to add code.
518 def _on_error(self,error: BaseException) -> None: 519 """ 520 @public 521 Called once at engine error "Scene.__handle_error()". Override this func in your subclass to add code. 522 523 Args: 524 error (BaseException): The engine error that occurred. 525 """ 526 pass
Called once at engine error "Scene.__handle_error()". Override this func in your subclass to add code.
Arguments:
- error (BaseException): The engine error that occurred.
529 def _on_event(self, event: pygame.Event) -> None: 530 """ 531 @public 532 Called every pygame event. Override this func in your subclass to add code. 533 534 Args: 535 event (pygame.Event): The pygame event that occurred. 536 """ 537 pass
Called every pygame event. Override this func in your subclass to add code.
Arguments:
- event (pygame.Event): The pygame event that occurred.
538 def _on_keydown(self,key: str) -> None: 539 """ 540 @public 541 Called every keyboard keydown. Override this func in your subclass to add code. 542 543 Args: 544 key (str): The keyboard key. 545 """ 546 pass
Called every keyboard keydown. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
547 def _on_keyup(self,key: str) -> None: 548 """ 549 @public 550 Called every keyboard keyup. Override this func in your subclass to add code. 551 552 Args: 553 key (str): The keyboard key. 554 """ 555 pass
Called every keyboard keyup. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
556 def _on_keypressed(self,key: str) -> None: 557 """ 558 @public 559 Called every keypressed. Override this func in your subclass to add code. 560 561 Args: 562 key (str): The keyboard key. 563 """ 564 pass
Called every keypressed. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
565 def _on_mousewheel(self,wheel: int) -> None: 566 """ 567 @public 568 Called every mousewheel change. Override this func in your subclass to add code. 569 570 Args: 571 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 572 """ 573 pass
Called every mousewheel change. Override this func in your subclass to add code.
Arguments:
- wheel (int): The wheel position, wheel>0 = up, wheel<1 = down.
576 def _on_paused_event(self, event: pygame.event.Event) -> None: 577 """ 578 @public 579 Called every paused pygame event. Override this func in your subclass to add code. 580 581 Args: 582 event (pygame.Event): The pygame event that occurred. 583 """ 584 pass
Called every paused pygame event. Override this func in your subclass to add code.
Arguments:
- event (pygame.Event): The pygame event that occurred.
585 def _on_paused_keydown(self,key: str) -> None: 586 """ 587 @public 588 Called every paused keyboard keydown. Override this func in your subclass to add code. 589 590 Args: 591 key (str): The keyboard key. 592 """
Called every paused keyboard keydown. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
593 def _on_paused_keyup(self,key: str) -> None: 594 """ 595 @public 596 Called every paused keyboard keypressed. Override this func in your subclass to add code. 597 598 Args: 599 key (str): The keyboard key. 600 """ 601 pass
Called every paused keyboard keypressed. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
602 def _on_paused_keypressed(self,key: str) -> None: 603 """ 604 @public 605 Called every paused keypressed. Override this func in your subclass to add code. 606 607 Args: 608 key (str): The keyboard key. 609 """ 610 pass
Called every paused keypressed. Override this func in your subclass to add code.
Arguments:
- key (str): The keyboard key.
611 def _on_paused_mousewheel(self,wheel: int) -> None: 612 """ 613 @public 614 Called every paused mousewheel change. Override this func in your subclass to add code. 615 616 Args: 617 wheel (int): The wheel position, wheel>0 = up, wheel<1 = down. 618 """ 619 pass
Called every paused mousewheel change. Override this func in your subclass to add code.
Arguments:
- wheel (int): The wheel position, wheel>0 = up, wheel<1 = down.