-
Notifications
You must be signed in to change notification settings - Fork 1
Minigame with a render loop
IMPORTANT: Currently the only way to take advantage of pygame.draw functions without having major flashing is: pygame.RenpyGameByTimerForDraw
INFO: it is recommended to use webp images (more info)
To create a minigame with a rendering loop, you need to create a class that inherits from pygame.RenpyGameByTimer
or pygame.RenpyGameByLoop
and implement the methods (lambda) first_step
, update_process
and etc.
pygame.RenpyGameByTimer
and pygame.RenpyGameByLoop
are similar, but pygame.RenpyGameByTimer
use a timer for update a game, and pygame.RenpyGameByLoop
use a renpy.redraw
(renpy.redraw is affected, in part, by the time the functions take (so it can be considered as if it were a loop)) for update a game.
pygame.RenpyGameByTimer
update the render based on a event timer. And has an extra parameter called is_full_redraw_lamda
minigame = pygame.RenpyGameByTimer(
first_step=my_game_first_step,
update_process=my_game_logic,
event_lambda=game_event,
delay=0.04,
# is_full_redraw_lamda=is_full_redraw_lamda
)
Also if is_full_redraw_lamda
return False performs a update of render, and if it returns True performs a full redraw. (the update of render is more performant)
by default is_full_redraw_lamda
is:
def _is_full_redraw(self, current_frame_number: int) -> bool:
if self.is_full_redraw_lamda:
return self.is_full_redraw_lamda(current_frame_number)
return current_frame_number % 2 == 0
if you want to change it, you can pass a function lambda to is_full_redraw_lamda
parameter.
pygame.RenpyGameByLoop
update the render based on a renpy.redraw
(renpy.redraw is affected, in part, by the time the functions take (so it can be considered as if it were a loop)).
(if you pass is_full_redraw_lamda
to pygame.RenpyGameByLoop
, it will be ignored)
minigame = pygame.RenpyGameByLoop(
first_step=my_game_first_step,
update_process=my_game_logic,
event_lambda=game_event,
delay=0.04,
)
pygame.RenpyGameByTimer
Pro:
- more performant
- respect the delay
- you can use the draw function of pygame
pygame.RenpyGameByTimer
Cons:
- could lose a frame
- if use a small delay -> in the firsts renders, the game 'flickers'
pygame.RenpyGameByLoop
Pro:
- don't lose a frame
pygame.RenpyGameByLoop
Cons:
- less performant
- could don't respect the delay
- you can't use the draw function of pygame
first_step()
and update_process()
is a function lambda that return a child_render
, it is a current frame of the game.
first_step()
is a function lambda that return a child_render
, it is a first frame of the game.
If you show a game and not start a render loop, you can use a first_step for show a menu. (more info in start a game between a sterted menu)
example:
def my_game_first_step(width: int, height: int, st: float, at: float) -> pygame.Surface:
bestdepth = pygame.display.mode_ok((0, 0), winstyle, 32)
screen = pygame.display.set_mode((0, 0), winstyle, bestdepth)
# set up the vales
# ...
# decorate the game window
pygame.display.set_icon(pygame.image.load("icon.webp").pygame_image)
pygame.display.set_caption("My Game")
# create the background
bgdtile = pygame.image.load("background.webp")
bgdtile = bgdtile.convert(st, at)
sh.background = pygame.Surface(SCREENRECT.size)
for x in range(0, SCREENRECT.width, bgdtile.get_width()):
sh.background.blit(bgdtile, (x, 0))
screen.blit(sh.background, (0, 0))
pygame.display.flip()
# play music
# ...
return screen
update_process()
is a function that edit a child_render
.
Return a delay or None, if is None, the game will end.
Not return a child_render
for set a child_render
, because it is a Object, so it is not a copy, but a reference.
For pass a game end, return None.
example:
def my_game_logic(
cur_screen: pygame.Surface,
st: float,
at: float,
next_frame_time: Optional[float],
current_frame_number: int,
) -> Optional[float]:
if sh.player.alive():
# clear/erase the last drawn sprites
sh.all.clear(cur_screen, sh.background)
# update all the sprites
sh.all.update()
# ...
return next_frame_time
else:
# game end
return None
For handle a event, you need to create a function lambda pass it to event_lambda
.
example:
def my_game_logic(
cur_screen: pygame.Surface,
st: float,
at: float,
next_frame_time: float,
current_frame_number: int,
) -> Optional[float]:
# ...
# handle player input
direction = sh.player_move
sh.player.move(direction)
# ...
def game_event(ev: EventType, x: int, y: int, st: float):
if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
sh.player_move = -1
elif ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
if sh.player_move == -1:
sh.player_move = 0
elif ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
sh.player_move = 1
elif ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
if sh.player_move == 1:
sh.player_move = 0
return
Delay is a time between frames, if is None, the game will end.
To start a game, you need to call show(show_and_start = True)
method of pygame.RenpyGameByTimer
object.
Or you can use a show(show_and_start = False)
and call start()
method of pygame.RenpyGameByTimer
object, but you must call start()
in the event()
.
def show(self, show_and_start: bool = True)
: Show a game, if show_and_start is True, start a render loop.
If show_and_start is False, you must call start() method.
Show a game and not start a render loop, is useful for show a game in a menu, and start a render loop into a event().
def start(self)
: Start a render loop. Attention, this method work only if a game is not started. If you want to show a game, use show() method.
You can use a global variable, but it is not a good idea, because it is a global variable, so you can use a class for shared data.
example:
class SharedData:
def __init__(self):
self.all = None
self.player = None
self.score = 0
sh = SharedData()
def main():
# # Initialize a shared data
global sh
if not sh:
sh = SharedData()
# Initialize a game ...
# Start a game ...
# clean up the shared data
score = sh.score
sh = None
return score
If you want to start a game between a sterted menu, you need to use a first_step()
for show a menu and show(show_and_start = False)
.
And in the event()
you need to call start()
method of pygame.RenpyGameByTimer
object.
example:
def main():
minigame = pygame.RenpyGameByTimer(
first_step=my_game_first_step,
update_process=my_game_logic,
event_lambda=game_event,
delay=0.04,
)
minigame.show(show_and_start = False)
def my_game_first_step(width: int, height: int, st: float, at: float) -> pygame.Surface:
# ...
# show a menu
# ...
return screen
def game_event(ev: EventType, x: int, y: int, st: float):
# ...
if click_on_start_game_button:
minigame.start()
return
In renpy CDD, for end a game, you need to return a value from event()
.
In renpygame, for end a game, you need set a delay to None, or for force a game end, you can use a quit()
. Also return a value from event()
, but is not recommended.
For a game end menu you can use a end_game_frame()
and use a quit()
for force a game end. Attention, if you use a quit()
, the game not call a end_game_frame()
.