"The goal of software architecture is to minimize the human resources required to build and maintain the required system."
Robert C. Martin, Clean Architecture
See also Python PyGame Introduction
Related Stack Overflow questions:
Related Stack Overflow questions:
- Why is my PyGame application not running at all?
- Why is nothing drawn in PyGame at all?
- How to run multiple while loops at a time in Python
- Pygame window freezes when it opens
- Why is my pygame application loop not working properly?
- Is it possible for a sprite to react to a specific color
- Why text display for 2 seconds in pygame
- pygame: low fps causes difficulties to detect events
- Pygame input which isn't keyboard/mouse
If you want something to be drawn permanently, you need to draw it in the application loop
The typical PyGame application loop has to:
- limit the frames per second to limit CPU usage with
pygame.time.Clock.tick
- handle the events by calling either
pygame.event.pump()
orpygame.event.get()
. - update the game states and positions of objects dependent on the input events and time (respectively frames)
- clear the entire display or draw the background
- draw the entire scene (
blit
all the objects) - update the display by calling either
pygame.display.update()
orpygame.display.flip()
import pygame
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
# main application loop
run = True
while run:
# limit frames per second
clock.tick(100)
# event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# clear the display
window.fill('black')
# draw the scene
pygame.draw.circle(window, (255, 0, 0), (250, 250), 100)
# update the display
pygame.display.flip()
pygame.quit()
exit()
You are actually drawing on a Surface
object. If you draw on the Surface associated to the PyGame display, this is not immediately visible in the display. The changes become visible, when the display is updated with either pygame.display.update()
or pygame.display.flip()
.
This will update the contents of the entire display.
repl.it/@Rabbid76/PyGame-MinimalApplicationLoop
📁 Minimal example - Game loop and movement
Related Stack Overflow questions:
- What is the difference between .quit and .QUIT in pygame
- I keep getting this error “pygame.error: display Surface quit” and dont know what to do
- why is the exit window button work but the exit button in the game does not work?
- pygame window closes immediatly after opening up
Related Stack Overflow questions:
-
Why does pygame.display.update() not work if an input is directly followed after it?
You cannot use input
in the application loop. input
waits for an input. While the system is waiting for input, the application loop will halt and the game will not respond. Use the KEYDOWN
event instead of input
:
run = True
while run:
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if pygame.key == pygame.K_1:
# [...]
if pygame.key == pygame.K_2:
# [...]
Another option is to get the input in a separate thread.
import pygame
import threading
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
color = "red"
def get_input():
global color
color = input('enter color (e.g. blue): ')
input_thread = threading.Thread(target=get_input)
input_thread.start()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window_center = window.get_rect().center
window.fill('black')
pygame.draw.circle(window, color, window_center, 100)
pygame.display.flip()
pygame.quit()
exit()
Related Stack Overflow questions:
-
Why doesn't PyGame draw in the window before the delay or sleep?
-
How to wait some time in pygame? 📁 Minimal example - Display a message for a period of time
repl.it/@Rabbid76/PyGame-MessageDelay
Tag: "gamestate", "state of game"
Related Stack Overflow questions:
-
Python : What is the better way to make multiple loops in pygame?
-
Pygame how to enable and disable a function with the same key?
-
In pygame for event.type == ACTIVEEVENT, where is the documentation on what the different event.state and event.gain parameters mean
Related Stack Overflow questions:
- In game loop, how do I restart game properly using nested class or loop?
- Reset and restart pygame program doesn't work
You've overcomplicated the system. What you are actually doing is recursively instantiating a new Gui
object and new application loop into an existing Gui
object and application loop.
If GameState
is implemented correctly, it should be sufficient to create a new GameState
object and continue the existing application loop instead of recursively creating a new Gui
instance:
import board as b
class Gui():
def __init__(self):
pygame.init()
self.gamestate = b.GameState()
def run(self):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.gamestate = b.GameState()
# [...]
if __name__ == '__main__':
Gui().run()
The instruction self.board = s.start_position
doesn't create a new board object. self.board
and s.start_position
refer to the same object. If you change one of the objects, the other object seems to change in the same way since there is only one object.
Either you need to make a deep copy of the board object when you start the game or you need to reset the object when the game restarts.
A solution might be to use the Python copy
module. deepcopy
can create a deep copy of an object:
import copy
self.board = copy.deepcopy(s.start_position)
Note, not all object can be deep copied. For instance a pygame.Surface
cannot be deep copied.
See Time, timer event and clock
- How does the for event in pygame.event.get() in python work?
- PyGame unresponsive display
- PyGame window not responding after a few seconds
- Why pygame.event.get() returns EventList?
- How can I limit events to one event per frame(iteration) in pygame
You have to handle the events in the application loop. See pygame.event.get()
respectively pygame.event.pump()
:
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
pygame.event.get()
get all the messages and remove them from the queue. See the documentation:
This will get all the messages and remove them from the queue. [...]
If pygame.event.get()
is called in multiple event loops, only one loop receives the events, but never all loops receive all events. As a result, some events appear to be missed.
Get the events once per frame and use them in multiple loops or pass the list of events to functions and methods where they are handled:
def handle_events(events):
for event in events:
# [...]
while run:
event_list = pygame.event.get()
# [...]
# 1st event loop
for event in event_list:
# [...]
# [...]
# 2nd event loop
for event in event_list:
# [...]
# [...]
# function which handles events
handle_events(event_list)
Related Stack Overflow questions:
pygame.event.wait()
waits for a single event from the queue. If the queue is empty this function will wait until one is created. That makes your game lag.
Related Stack Overflow questions:
In pygame the event type is an enumerator constant. You have to map this constant to the corresponding class. Use a dictionary
:
def Exit(event):
# [...]
def Keydown():
# [...]
eventhandler = {pygame.QUIT: Exit, pygame.KEYDOWN: Keydown}
For a more general approach, the handlers can be managed in a list. So multiple actions can be associated to an event type
def Exit(event):
# [...]
def Keydown1():
# [...]
def Keydown2():
# [...]
eventhandler = {pygame.QUIT: [Exit], pygame.KEYDOWN: [Keydown1, Keydown2]}
The events must be delegated to the appropriate actions assigned in the event handler:
while True:
for event in pygame.event.get():
if event.type in eventhandler:
for target in eventhandler[event.type]:
target(event)