Compare commits
29 Commits
e97d55fbc3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ef4fc8dfc7 | |||
| 60cabb227c | |||
| 40b00a71dc | |||
| db943fef5a | |||
| c7156112dc | |||
| 8893fd90c6 | |||
| 9d6f775ab9 | |||
| a9d95c65cb | |||
| 28f9c01f14 | |||
| 24d6addc24 | |||
| c0ebfa42b7 | |||
| 44696a0ac3 | |||
| 1686e53b28 | |||
| 132cfab990 | |||
| c2db8b386e | |||
| 265322f58b | |||
| 5dbd9211de | |||
| 2a0def7fa6 | |||
| 08d6261d3f | |||
| 95afab5431 | |||
| d897a3744d | |||
| 4161ec9c5b | |||
| a421069a63 | |||
| 44f75b52d0 | |||
| 8f2dcbfd21 | |||
| 2c79adaf36 | |||
| f4e0e24774 | |||
| f1c6c7c23d | |||
| 8ce99ca561 |
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "gameUtils"
|
name = "gameUtils"
|
||||||
version = "0.0.1.dev0"
|
version = "1.0.0"
|
||||||
description = "A set of utilities to make the game development process less painful (despite the organisation name) and more unified."
|
description = "A set of utilities to make the game development process less painful (despite the organisation name) and more unified."
|
||||||
authors = [{ name = "Brosef" }]
|
authors = [{ name = "Brosef" }]
|
||||||
dependencies = ["pygame-ce"]
|
dependencies = ["pygame-ce", "pygame_gui"]
|
||||||
@ -1,4 +0,0 @@
|
|||||||
from .base import Game
|
|
||||||
from .utils import centre
|
|
||||||
from .anim import AnimatedHandler
|
|
||||||
from .anim import AnimatedObject
|
|
||||||
67
src/anim.py
67
src/anim.py
@ -1,67 +0,0 @@
|
|||||||
import pygame
|
|
||||||
|
|
||||||
class AnimationHandler(pygame.Surface):
|
|
||||||
"""
|
|
||||||
TODO: Write documentation on this, it takes in a
|
|
||||||
webp file, and handles drawing it to the screen in
|
|
||||||
a way that doesn't rely on frame counters.
|
|
||||||
"""
|
|
||||||
def __init__(self, animationPath):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class AnimatedObject:
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, baseFrame):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
baseFrame: A still image that gets displayed when no other animation is playing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._currentAnim = None
|
|
||||||
self._animStart = None
|
|
||||||
self._animations = {}
|
|
||||||
#self.baseFrame = pygame.image.load(baseFrame)
|
|
||||||
|
|
||||||
def getFrame(self) -> pygame.Surface:
|
|
||||||
"""
|
|
||||||
Gets the current frame based on which animation is playing, and the current time.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
pygame.Surface: The current frame
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def addAnimation(self, animation: AnimationHandler, animationID: str):
|
|
||||||
"""
|
|
||||||
Adds an animation to the object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
animation (AnimationHandler): The actual animation.
|
|
||||||
animationID (str): The ID that's later used to play the animation.
|
|
||||||
"""
|
|
||||||
self.animations.update({animationID: animation})
|
|
||||||
|
|
||||||
def playAnim(self, animationID: str):
|
|
||||||
"""
|
|
||||||
Plays an animation.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
animationID (str): The animation ID to play.
|
|
||||||
"""
|
|
||||||
self._animStart = time.perf_counter()
|
|
||||||
self._currentAnim = self._animations[animationID]
|
|
||||||
|
|
||||||
def get_view(self):
|
|
||||||
print(f'get_view called')
|
|
||||||
return super().get_view()
|
|
||||||
|
|
||||||
def get_buffer(self):
|
|
||||||
print(f'get_buffer called')
|
|
||||||
return super().get_buffer()
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
print(f'copy called')
|
|
||||||
return super().copy()
|
|
||||||
12
src/base.py
12
src/base.py
@ -1,12 +0,0 @@
|
|||||||
class Game:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onEvent(self, event):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
9
src/gameUtils/__init__.py
Normal file
9
src/gameUtils/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from .base import Game
|
||||||
|
from .utils import centre
|
||||||
|
from .anim import AnimationHandler
|
||||||
|
from .anim import AnimatedObject
|
||||||
|
from .events import AnimStart
|
||||||
|
from .events import AnimFinish
|
||||||
|
from .events import Timeout
|
||||||
|
from .turns import BaseTurnHandler
|
||||||
|
from .turns import RoundTable
|
||||||
85
src/gameUtils/anim.py
Normal file
85
src/gameUtils/anim.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
from .events import AnimStart
|
||||||
|
from .events import AnimFinish
|
||||||
|
import pygame
|
||||||
|
import time
|
||||||
|
|
||||||
|
class AnimationHandler(pygame.Surface):
|
||||||
|
"""
|
||||||
|
TODO: Write documentation on this, it takes in a
|
||||||
|
webp file, and handles drawing it to the screen in
|
||||||
|
a way that doesn't rely on frame counters.
|
||||||
|
"""
|
||||||
|
def __init__(self, animationID: str, animationPath: str, fps: int):
|
||||||
|
self.animationID = animationID
|
||||||
|
self.frames = pygame.image.load_animation(animationPath)
|
||||||
|
self.fps = fps
|
||||||
|
self.frameCount = len(self.frames)
|
||||||
|
|
||||||
|
# TODO: Add optional transparancy?
|
||||||
|
class AnimatedObject:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, _game, objectID: str, baseFrame: str):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
_game: Used internally.
|
||||||
|
objectID: Used to identify this animated object.
|
||||||
|
baseFrame: A still image that gets displayed when no other animation is playing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._game = _game
|
||||||
|
self.objectID = objectID
|
||||||
|
self._currentAnim = None
|
||||||
|
self._animStart = None
|
||||||
|
self._animations = {}
|
||||||
|
self.baseFrame = pygame.image.load(baseFrame)
|
||||||
|
self.size = self.baseFrame.size
|
||||||
|
self._surf = pygame.Surface(self.baseFrame.size)
|
||||||
|
|
||||||
|
def getFrame(self) -> pygame.Surface:
|
||||||
|
"""
|
||||||
|
Gets the current frame based on which animation is playing, and the current time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pygame.Surface: The current frame
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._surf.fill((0, 0, 0))
|
||||||
|
|
||||||
|
if self._currentAnim == None:
|
||||||
|
self._surf.blit(self.baseFrame, (0, 0))
|
||||||
|
else:
|
||||||
|
frame = min(round((time.perf_counter() - self._animStart) * self._currentAnim.fps), self._currentAnim.frameCount - 1)
|
||||||
|
self._surf.blit(self._currentAnim.frames[frame][0], (0, 0))
|
||||||
|
if frame == self._currentAnim.frameCount - 1:
|
||||||
|
self._game.onEvent(AnimFinish(self.objectID, self._currentAnim.animationID))
|
||||||
|
self._currentAnim = None
|
||||||
|
self._animStart = None
|
||||||
|
|
||||||
|
return self._surf
|
||||||
|
|
||||||
|
|
||||||
|
def addAnimation(self, animation: AnimationHandler):
|
||||||
|
"""
|
||||||
|
Adds an animation to the object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
animation (AnimationHandler): The actual animation.
|
||||||
|
animationID (str): The ID that's later used to play the animation.
|
||||||
|
"""
|
||||||
|
self._animations.update({animation.animationID: animation})
|
||||||
|
|
||||||
|
def playAnim(self, animationID: str, overrideCurrentAnim=False):
|
||||||
|
"""
|
||||||
|
Plays an animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
animationID (str): The animation ID to play.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._currentAnim != None and not overrideCurrentAnim: return
|
||||||
|
|
||||||
|
self._animStart = time.perf_counter()
|
||||||
|
self._currentAnim = self._animations[animationID]
|
||||||
|
self._game.onEvent(AnimStart(self.objectID, animationID))
|
||||||
185
src/gameUtils/base.py
Normal file
185
src/gameUtils/base.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import tomllib
|
||||||
|
import time
|
||||||
|
|
||||||
|
import NoPELib
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from .events import Timeout
|
||||||
|
from .anim import AnimatedObject
|
||||||
|
from .utils import centre
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, surface):
|
||||||
|
"""
|
||||||
|
Initialises some things for the game developers.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
surface (pygame.Surface): The surface the game devs draw on.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open('./game.toml', 'r') as f:
|
||||||
|
self.cfg = tomllib.loads(f.read())
|
||||||
|
|
||||||
|
# The surface that gets drawn by the launcher.
|
||||||
|
# Contains the game surface, and overlays.
|
||||||
|
self._surf = surface
|
||||||
|
# The games serface
|
||||||
|
self.surf = pygame.Surface(self._surf.size)
|
||||||
|
self.size = self.surf.size
|
||||||
|
# Used for time delta, stores when the last frame was drawn
|
||||||
|
self._lastFrame = time.perf_counter()
|
||||||
|
# How many seconds it's been between frames
|
||||||
|
self.timeDelta = 0
|
||||||
|
# Pre-render semi-transparent black surface
|
||||||
|
self._dimSurf = pygame.Surface(self._surf.size)
|
||||||
|
self._dimSurf.set_alpha(128)
|
||||||
|
pygame.draw.rect(self._dimSurf, (50, 50, 50), (0, 0, self.size[0], self.size[1]))
|
||||||
|
|
||||||
|
self.pm = NoPELib.PlayersManager(self.cfg['name'], playersPath='../players.json')
|
||||||
|
# Holds all the timeouts that haven't been fired yet
|
||||||
|
self._timeouts = []
|
||||||
|
# The following are used by self.popup() to halt some events
|
||||||
|
self._haltUpdate = False
|
||||||
|
self._haltEvents = False
|
||||||
|
# Will become a pygame.Surface with the popup contents
|
||||||
|
self._popupMenu = None
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Intended to be overridden by the game developer.
|
||||||
|
See BaseGame for documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
"""
|
||||||
|
Updates some core things in the background, and calls update()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Handle timeouts
|
||||||
|
for timeout in self._timeouts.copy():
|
||||||
|
if timeout.fireOn <= time.perf_counter():
|
||||||
|
self.onEvent(timeout)
|
||||||
|
self._timeouts.remove(timeout)
|
||||||
|
|
||||||
|
# Game update
|
||||||
|
if not self._haltUpdate:
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
# Draw the game surface onto the main surface
|
||||||
|
self._surf.blit(self.surf, (0, 0))
|
||||||
|
|
||||||
|
# If there's a popup
|
||||||
|
if self._popupMenu is not None:
|
||||||
|
# Dim the screen
|
||||||
|
self._surf.blit(self._dimSurf, (0, 0))
|
||||||
|
self._surf.blit(centre(self._popupMenu, self.size), (0, 0))
|
||||||
|
|
||||||
|
self.timeDelta = time.perf_counter() - self._lastFrame
|
||||||
|
self._lastFrame = time.perf_counter()
|
||||||
|
|
||||||
|
def onEvent(self, event):
|
||||||
|
"""
|
||||||
|
Intended to be overridden by the game developer.
|
||||||
|
See BaseGame for documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _onEvent(self, event):
|
||||||
|
"""
|
||||||
|
Updates some core things in the background, and calls onEvent()
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._haltEvents:
|
||||||
|
self.onEvent(event)
|
||||||
|
|
||||||
|
if event.type == pygame.KEYDOWN and self._popupMenu is not None:
|
||||||
|
if event.key == 13 and event.mod & 0b10000000: # R-CTRL + ENTER
|
||||||
|
self._popupMenu = None
|
||||||
|
self._haltUpdate = False
|
||||||
|
self._haltEvents = False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Intended to be overridden by the game developer.
|
||||||
|
See BaseGame for documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def createAnimObj(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Creates an animated object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objectID (str): The ID of the object.
|
||||||
|
baseFrame (str): The path to the base frame.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return AnimatedObject(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def timeout(self, id: str, delay: float):
|
||||||
|
"""
|
||||||
|
Fires a Timeout event with the specified ID
|
||||||
|
after the specified delay.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (str): The timeout ID.
|
||||||
|
delay (float): How long (in seconds)
|
||||||
|
to wait before firing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._timeouts.append(Timeout(id, time.perf_counter()+delay))
|
||||||
|
|
||||||
|
def awaitingTimeout(self, timeoutID: str):
|
||||||
|
"""
|
||||||
|
Tells you if a timeout ID is on the timeout stack.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeoutID (str): The timeout ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if there is a timeout with that ID
|
||||||
|
ID on the stack, False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return any(timeout.timeoutID == timeoutID for timeout in self._timeouts)
|
||||||
|
|
||||||
|
def popup(self, title: str, message: str, haltUpdate: bool=False, haltEvents: bool=True):
|
||||||
|
"""
|
||||||
|
Pops up a dialogue box.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): The big "title" text to be displayed.
|
||||||
|
message (str): The main body text to be displayed.
|
||||||
|
haltUpdate (bool): If True, the games update() method will not be called until
|
||||||
|
the popup is dismissed. Default: False.
|
||||||
|
haltEvents (bool): If True, the games onEvent() method will not be called until
|
||||||
|
the popup is dismissed. Default: True.
|
||||||
|
"""
|
||||||
|
|
||||||
|
POPUP_MARGINS = 10
|
||||||
|
FONT_BIG = pygame.font.SysFont('', 50)
|
||||||
|
FONT_NORM = pygame.font.SysFont('', 32)
|
||||||
|
|
||||||
|
titleSurf = FONT_BIG.render(title, True, (128, 128, 128))
|
||||||
|
messageSurf = FONT_NORM.render(message, True, (128, 128, 128))
|
||||||
|
|
||||||
|
w = max(titleSurf.get_width(), messageSurf.get_width())
|
||||||
|
w += POPUP_MARGINS*2
|
||||||
|
h = titleSurf.get_height() + messageSurf.get_height()
|
||||||
|
h += POPUP_MARGINS*3
|
||||||
|
|
||||||
|
self._haltUpdate = haltUpdate
|
||||||
|
self._haltEvents = haltEvents
|
||||||
|
self._popupMenu = pygame.Surface((w, h))
|
||||||
|
self._popupMenu.fill((0, 0, 0))
|
||||||
|
self._popupMenu.blit(centre(titleSurf, (w, titleSurf.get_height())), (0, POPUP_MARGINS))
|
||||||
|
self._popupMenu.blit(centre(messageSurf, (w, messageSurf.get_height())), (0, titleSurf.get_height()+POPUP_MARGINS*2))
|
||||||
|
|
||||||
|
def punish(self, player, intensity: float):
|
||||||
|
"""
|
||||||
|
Handles punishing players for most games.
|
||||||
|
Handles games preferences on PDO order?
|
||||||
|
Handles auto popups for messaged events.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player (NoPELib.Player): The player to punish.
|
||||||
|
intensity (float): The intensity to punish the player.
|
||||||
|
"""
|
||||||
36
src/gameUtils/events.py
Normal file
36
src/gameUtils/events.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
class _event:
|
||||||
|
def __init__(self):
|
||||||
|
self.type = self.__class__
|
||||||
|
|
||||||
|
class AnimStart(_event):
|
||||||
|
def __init__(self, objectID, animationID):
|
||||||
|
super().__init__()
|
||||||
|
self.objectID = objectID
|
||||||
|
self.animationID = animationID
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<AnimFinish | {self.objectID=} | {self.animationID=}>'
|
||||||
|
|
||||||
|
class AnimFinish(_event):
|
||||||
|
def __init__(self, objectID, animationID):
|
||||||
|
super().__init__()
|
||||||
|
self.objectID = objectID
|
||||||
|
self.animationID = animationID
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<AnimFinish | {self.objectID=} | {self.animationID=}>'
|
||||||
|
|
||||||
|
class Timeout(_event):
|
||||||
|
def __init__(self, timeoutID, fireOn):
|
||||||
|
super().__init__()
|
||||||
|
# The timeout ID specified by the user
|
||||||
|
self.timeoutID = timeoutID
|
||||||
|
# When the event should be fired
|
||||||
|
self.fireOn = fireOn
|
||||||
|
# When it was created
|
||||||
|
self.created = time.perf_counter()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Timeout | {self.timeoutID=} | {self.fireOn=} | {self.created=}>'
|
||||||
69
src/gameUtils/turns.py
Normal file
69
src/gameUtils/turns.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# TODO: Add a variable for the game developers to see if pain is "avaialbe",
|
||||||
|
# I.E. has the shocker been passed to the next player.
|
||||||
|
# Also add a pop-up, probably using pygame-gui, to tell the player
|
||||||
|
# to pass the shocker to the next person.
|
||||||
|
# NOTE: Each expansion is assigned multiple players, if an expansion has a tag
|
||||||
|
# such as "ONE_PERSON_ONLY", it means that when the game developer wants
|
||||||
|
# to go to the next player, it must self.notify() the players to pass the
|
||||||
|
# physical object around.
|
||||||
|
# NOTE: That tag IS NOT a part of NoPE-Lib or PDO-Lib. It simply acts as an
|
||||||
|
# identifier for games to use.
|
||||||
|
# TODO: Add player config editor.
|
||||||
|
|
||||||
|
class BaseTurnHandler:
|
||||||
|
def __init__(self, playersManager):
|
||||||
|
"""
|
||||||
|
The base turn handler metaclass, used to create round
|
||||||
|
handlers such as round table.
|
||||||
|
"""
|
||||||
|
self._pm = playersManager
|
||||||
|
# The current turn number (0 based)
|
||||||
|
self.turn = 0
|
||||||
|
# The maximum number of turns
|
||||||
|
self.turns = len(self._pm)
|
||||||
|
# The current round number (0 based)
|
||||||
|
self.round = 0
|
||||||
|
# Which players are playing in this turn
|
||||||
|
self.playing = []
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class RoundTable(BaseTurnHandler):
|
||||||
|
"""
|
||||||
|
A simple "round table" round system.
|
||||||
|
"""
|
||||||
|
# TODO: Add some kind of implementation with NoPE
|
||||||
|
# to determain if we actually need to re-assign PDOs.
|
||||||
|
def __init__(self, teamSize: int, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if teamSize > len(self._pm):
|
||||||
|
raise Exception('Too little players for specified team size.') # TODO: Custom exception
|
||||||
|
|
||||||
|
self.teamSize = teamSize
|
||||||
|
self.turns //= self.teamSize
|
||||||
|
|
||||||
|
self.turn -= 1
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
"""
|
||||||
|
Oh god I hate it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.turn += 1
|
||||||
|
if self.turn >= self.turns:
|
||||||
|
self.turn -= self.turns
|
||||||
|
self.round += 1
|
||||||
|
|
||||||
|
self.playing = []
|
||||||
|
playerNames = list(self._pm.keys())
|
||||||
|
|
||||||
|
for i in range(self.teamSize):
|
||||||
|
self.playing.append(self._pm[playerNames[(i+self.turn)%len(playerNames)]])
|
||||||
|
|
||||||
|
return self.playing
|
||||||
21
src/gameUtils/utils.py
Normal file
21
src/gameUtils/utils.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
def centre(surface: pygame.Surface, size: tuple[int, int]) -> pygame.Surface:
|
||||||
|
"""
|
||||||
|
Centres a surface within a given rectangle.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
surface: The surface to be centred.
|
||||||
|
size: The size of the rectangle the surface will be centred in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pygame.Surface
|
||||||
|
"""
|
||||||
|
|
||||||
|
surf = pygame.Surface(size, pygame.SRCALPHA, 32)
|
||||||
|
offX = surf.size[0]//2 - surface.size[0]//2
|
||||||
|
offY = surf.size[1]//2 - surface.size[1]//2
|
||||||
|
|
||||||
|
surf.blit(surface, (offX, offY))
|
||||||
|
|
||||||
|
return surf
|
||||||
14
src/utils.py
14
src/utils.py
@ -1,14 +0,0 @@
|
|||||||
import pygame
|
|
||||||
|
|
||||||
def centre(surface: pygame.Surface, rect: tuple[int, int, int, int]) -> pygame.Surface:
|
|
||||||
"""
|
|
||||||
Centres a surface within a given rectangle.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
surface: The surface to be centred.
|
|
||||||
rect: The rectangle the surface will be centred in.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
pygame.Surface
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
Reference in New Issue
Block a user