├── .gitignore ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── OFL.txt │ └── PressStart2P-Regular.ttf ├── images │ └── test.jpg └── sounds │ └── intro.wav └── src ├── audio.py ├── components.py ├── game.py ├── input.py ├── main.py ├── settings.py └── states.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ArtBIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyGame Template 2 | ![GitHub issues badge](https://img.shields.io/github/issues/ArtBIT/pygame-template) 3 | ![GitHub forks badge](https://img.shields.io/github/forks/ArtBIT/pygame-template) 4 | ![GitHub stars badge](https://img.shields.io/github/stars/ArtBIT/pygame-template) 5 | ![GitHub license badge](https://img.shields.io/github/license/ArtBIT/pygame-template) 6 | 7 | 8 | ### What is this? 9 | This is a basic project template for [PyGame](https://www.pygame.org/) 10 | It contains the basic pygame setup, the basic game loop, and input and sound helpers. 11 | 12 | --- 13 | 14 | ### Usage 15 | Clone this repo 16 | 17 | git clone https://github.com/ArtBIT/pygame-template 18 | 19 | Install PyGame if you haven't already 20 | 21 | python3 -m pip install -U pygame --user 22 | 23 | And run the main.py 24 | 25 | python3 src/main.py 26 | 27 | ### Example 28 | 29 | ``` 30 | # src/states.py 31 | 32 | class State: 33 | ... 34 | 35 | class Intro(State): 36 | def boot(self): 37 | self.game.audio.load_sound('intro', os.path.join('assets', 'sounds', 'intro.wav')) 38 | 39 | # Show "Press any key" centered on screen 40 | font_path = os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf') 41 | text = "Press any key to switch to Outro" 42 | any_key_text = TextSprite(self.game, font_path, text, 12, color = (255,255,255)) 43 | any_key_text.rect.center = self.game.screen.get_rect().center 44 | self.all_sprites.add(any_key_text) 45 | 46 | def enter(self): 47 | # When this state is entered, play the intro sound 48 | self.game.audio.play('intro') 49 | 50 | def update(self): 51 | # On every frame, update all the sprites and check for keypress 52 | super().update(); 53 | if self.game.input.is_key_pressed('any'): 54 | # you can change to a different state 55 | # self.game.change_state('Outro') 56 | # but for this example we simply quit 57 | self.game.quit() 58 | 59 | ... 60 | 61 | # Define the export order of the states. src/game.py will atomatically register all the states and load the first one 62 | __all__ = ['Intro'] 63 | ``` 64 | 65 | # Credits 66 | 67 | Intro sound is from https://freesound.org/s/438921/ licensed under [CC0 1.0 license](https://creativecommons.org/publicdomain/zero/1.0/). 68 | 69 | # License 70 | MIT 71 | -------------------------------------------------------------------------------- /assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 The Press Start 2P Project Authors (cody@zone38.net), with Reserved Font Name "Press Start 2P". 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/fonts/PressStart2P-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/pygame-template/b4411484208904d292aaec4f595bd3bbd1e4f922/assets/fonts/PressStart2P-Regular.ttf -------------------------------------------------------------------------------- /assets/images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/pygame-template/b4411484208904d292aaec4f595bd3bbd1e4f922/assets/images/test.jpg -------------------------------------------------------------------------------- /assets/sounds/intro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/pygame-template/b4411484208904d292aaec4f595bd3bbd1e4f922/assets/sounds/intro.wav -------------------------------------------------------------------------------- /src/audio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Audio class for loading and playing sounds 3 | """ 4 | import logging 5 | import pygame as pg 6 | 7 | class Audio: 8 | """ 9 | Audio class for loading and playing sounds 10 | """ 11 | def __init__(self): 12 | self.sounds = {} 13 | 14 | def load_sound(self, name, path): 15 | """ 16 | Load a sound file 17 | 18 | name: name of the sound 19 | path: path to the sound file 20 | """ 21 | if name in self.sounds: 22 | logging.warning("Sound %s already loaded", name) 23 | return 24 | self.sounds[name] = pg.mixer.Sound(path) 25 | 26 | def load_sounds(self, sounds): 27 | """ 28 | Load multiple sounds 29 | 30 | sounds: dictionary of sounds, where the key is the name of the 31 | sound and the value is the path to the sound file 32 | """ 33 | for name in sounds: 34 | self.load_sound(name, sounds[name]) 35 | 36 | def play(self, name): 37 | """ 38 | Play a sound 39 | 40 | name: name of the sound 41 | """ 42 | if name in self.sounds: 43 | self.sounds[name].play() 44 | 45 | def stop(self, name=None): 46 | """ 47 | Stop a sound 48 | 49 | name: name of the sound 50 | """ 51 | if name is None: 52 | # stop all 53 | for sound_name in self.sounds: 54 | self.sounds[sound_name].stop() 55 | else: 56 | if name in self.sounds: 57 | self.sounds[name].stop() 58 | -------------------------------------------------------------------------------- /src/components.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains the classes for the components of the game 3 | """ 4 | 5 | import pygame as pg 6 | 7 | class ImageSprite(pg.sprite.Sprite): 8 | """ 9 | Sprite class for loading and displaying images 10 | """ 11 | def __init__(self, game, path): 12 | """ 13 | game: game object 14 | path: path to the image file 15 | """ 16 | 17 | super().__init__() 18 | self.game = game 19 | # load the image 20 | self.image = pg.image.load(path) 21 | # set the colorkey to black 22 | self.image.set_colorkey((0,0,0)) 23 | # get the rect 24 | self.rect = self.image.get_rect() 25 | 26 | class TextSprite(pg.sprite.Sprite): 27 | """ 28 | Sprite class for displaying text 29 | """ 30 | def __init__(self, game, font_path, text="", size = 10, color = (0, 0, 0)): 31 | """ 32 | game: game object 33 | font_path: path to the font file 34 | text: text to display 35 | size: font size 36 | color: font color 37 | """ 38 | super().__init__() 39 | self.game = game 40 | self.font_path = font_path 41 | self.draw_text(text, size, color) 42 | 43 | def draw_text(self, text, size = 10, color = (0,0,0), alias = True): 44 | """ 45 | Draw the text 46 | 47 | text: text to display 48 | size: font size 49 | color: font color 50 | alias: whether to use anti-aliasing 51 | """ 52 | self.text = text 53 | self.image = self.get_font_size(size).render(text, alias, color) 54 | self.rect = self.image.get_rect() 55 | 56 | def get_font_size(self, size): 57 | """ 58 | Get the font object with the given size 59 | """ 60 | return pg.font.Font(self.font_path, size) 61 | -------------------------------------------------------------------------------- /src/game.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the main game file. It is responsible for the game loop and 3 | changing states. 4 | """ 5 | 6 | import os 7 | import sys 8 | import logging 9 | import pygame as pg 10 | from input import Input 11 | from audio import Audio 12 | from settings import * 13 | from components import * 14 | import states 15 | 16 | class Game: 17 | """ 18 | Main game class 19 | """ 20 | def __init__(self): 21 | pg.init() 22 | self.screen = pg.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 23 | pg.display.set_caption(GAME_TITLE) 24 | self.clock = pg.time.Clock() 25 | self.input = Input() 26 | self.audio = Audio() 27 | self.init_states() 28 | 29 | def register_state(self, state): 30 | """ 31 | Register a state 32 | 33 | state: state object 34 | """ 35 | self.states[type(state).__name__] = state 36 | 37 | def init_states(self): 38 | """ 39 | Initialize all the states 40 | """ 41 | self.state = None 42 | self.states = {} 43 | # iterate over __all__ states from states.py instantiate them 44 | # and register to the self.states with state.name as the key and state instance 45 | # as value 46 | for State in map(states.__dict__.get, states.__all__): 47 | self.register_state(State(self)) 48 | 49 | # initialize the first state 50 | first_state = list(self.states.keys())[0] 51 | self.change_state(first_state) 52 | 53 | def change_state(self, state): 54 | """ 55 | Change the current state 56 | 57 | state: name of the state to change to 58 | """ 59 | if state in self.states.keys(): 60 | # exit the current state 61 | if self.state: 62 | self.state.exit() 63 | # enter the new state 64 | self.state = self.states[state] 65 | self.state.enter() 66 | 67 | def loop(self): 68 | """ 69 | Main game loop 70 | """ 71 | self.is_playing = True 72 | while self.is_playing: 73 | # quit the game if escape is pressed 74 | if self.input.is_key_down("escape"): 75 | self.quit() 76 | # elapsed time in ms 77 | self.ms = pg.time.get_ticks() 78 | # elapsed time since last frame 79 | self.dt = self.clock.tick(FPS) / 1000 80 | # handle events 81 | self.handle_events() 82 | # update and draw 83 | self.update() 84 | self.draw() 85 | 86 | def quit(self): 87 | """ 88 | Quit the game 89 | """ 90 | pg.quit() 91 | sys.exit() 92 | 93 | def handle_events(self): 94 | """ 95 | Handle events 96 | """ 97 | self.input.update() 98 | for event in pg.event.get(): 99 | if event.type == pg.QUIT: 100 | self.quit() 101 | self.input.handle_event(event) 102 | 103 | def update(self): 104 | """ 105 | Update the game (update the current state) 106 | """ 107 | if self.state: 108 | self.state.update() 109 | 110 | def draw(self): 111 | """ 112 | Draw the game (draw the current state) 113 | """ 114 | # clear the screen 115 | self.screen.fill(BACKGROUND_COLOR) 116 | # draw the current state 117 | if self.state: 118 | self.state.draw(self.screen) 119 | # flip the display 120 | pg.display.flip() 121 | -------------------------------------------------------------------------------- /src/input.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module handles input from the user. 3 | """ 4 | 5 | import logging 6 | import pygame as pg 7 | import pygame.constants as pgc 8 | from pygame.locals import * 9 | 10 | class Input: 11 | """ 12 | Input class for handling input from the user 13 | """ 14 | def __init__(self): 15 | self.keys_down = set() 16 | self.keys_pressed = set() 17 | # map modifier key values to aliases 18 | pygame_keys = (pgc.KMOD_LSHIFT, pgc.KMOD_RSHIFT, pgc.KMOD_SHIFT, 19 | pgc.KMOD_LCTRL, pgc.KMOD_RCTRL, pgc.KMOD_CTRL, pgc.KMOD_LALT, 20 | pgc.KMOD_RALT, pgc.KMOD_ALT, pgc.KMOD_LMETA, pgc.KMOD_RMETA, 21 | pgc.KMOD_META, pgc.KMOD_CAPS, pgc.KMOD_NUM, pgc.KMOD_MODE) 22 | keys_aliases = ('lshift', 'rshift', 'shift', 'lctrl', 'rctrl', 'ctrl', 'lalt', 'ralt', 'alt', 'lmeta', 'rmeta', 'meta', 'caps', 'num', 'mode') 23 | self.modifier_key_names = dict(zip(pygame_keys, keys_aliases)) 24 | 25 | def is_key_down(self, key): 26 | """ 27 | Check if a key is down 28 | 29 | key: key to check 30 | """ 31 | if key == 'any': 32 | return len(self.keys_down) > 0 33 | return key in self.keys_down 34 | 35 | def is_key_pressed(self, key): 36 | """ 37 | Check if a key is pressed 38 | A key is considered pressed if it was pressed in the current frame 39 | 40 | key: key to check 41 | """ 42 | if key == 'any': 43 | return len(self.keys_pressed) > 0 44 | return key in self.keys_pressed 45 | 46 | def update(self): 47 | self.keys_pressed = set() 48 | 49 | def handle_event(self, event): 50 | """ 51 | Handle an event 52 | Input class cares only about key events (KEYDOWN and KEYUP) 53 | 54 | event: event to handle 55 | """ 56 | if event.type == pgc.KEYDOWN: 57 | if event.mod == pgc.KMOD_NONE: 58 | # If the key is not a modifier key, add the alias to the keys_down set 59 | key_name = pgc.key.name(event.key) 60 | self.keys_down.add(key_name) 61 | self.keys_pressed.add(key_name) 62 | else: 63 | # if the key is a modifier key, add the alias to the keys_down set 64 | for key in self.modifier_key_names.keys(): 65 | if event.mod & key: 66 | key_name = self.modifier_key_names[key] 67 | self.keys_down.add(key_name) 68 | self.keys_pressed.add(key_name) 69 | 70 | if event.type == pgc.KEYUP: 71 | if event.mod == pgc.KMOD_NONE: 72 | # If the key is not a modifier key, remove the alias from the keys_down set 73 | key_name = pgc.key.name(event.key) 74 | self.keys_down.discard(key_name) 75 | else: 76 | # if the key is a modifier key, remove the alias from the keys_down set 77 | for key in self.modifier_key_names.keys(): 78 | if event.mod & key: 79 | key_name = self.modifier_key_names[key] 80 | self.keys_down.discard(key_name) 81 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main file for the game. 3 | """ 4 | from game import Game 5 | 6 | game = Game() 7 | while True: 8 | game.loop() 9 | -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all the settings for the game. 3 | 4 | You can change the settings here instead of changing them in the main file. 5 | 6 | You can also add more settings here and use them in the main file. 7 | 8 | Example: 9 | In this file: 10 | GAME_TITLE = "My Game" 11 | In the main file: 12 | import settings 13 | print(settings.GAME_TITLE) # prints "My Game" 14 | """ 15 | 16 | # Colors (R, G , B) 17 | WHITE = (255, 255, 255) 18 | BLACK = (0, 0, 0) 19 | RED = (255, 0, 0) 20 | GREEN = (0, 255, 0) 21 | BLUE = (0, 0, 255) 22 | YELLOW = (255, 255, 0) 23 | DARKGRAY = (40, 40, 40) 24 | LIGHTGRAY = (100, 100, 100) 25 | 26 | # Game settings 27 | GAME_TITLE = "PyGame Template" 28 | SCREEN_WIDTH = 800 29 | SCREEN_HEIGHT = 600 30 | FPS = 60 31 | BACKGROUND_COLOR = DARKGRAY 32 | -------------------------------------------------------------------------------- /src/states.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the various states that the game can be in. Each state is a class that inherits from the State class. 3 | 4 | The State class is a base class that contains the basic functionality that every state should have. It is not meant to be used directly. 5 | 6 | The State class has the following methods: 7 | boot: called when the state is first initialized 8 | update: called on every frame 9 | enter: called when the state becomes the current state 10 | exit: called when the state is no longer the current state 11 | draw: called on every frame 12 | 13 | The State class has the following attributes: 14 | game: the game object 15 | all_sprites: a sprite group that contains all the sprites in the state 16 | """ 17 | 18 | import os 19 | import pygame as pg 20 | from components import * 21 | 22 | class State: 23 | """ 24 | Base class for all states 25 | """ 26 | def __init__(self, game): 27 | """ 28 | game: game object 29 | """ 30 | self.game = game 31 | # all sprites in the state 32 | self.all_sprites = pg.sprite.Group() 33 | self.boot() 34 | 35 | def boot(self): 36 | """ 37 | Called when the state is first initialized 38 | """ 39 | # override this method to add logic that happens when the state is first initialized 40 | pass 41 | 42 | def update(self): 43 | """ 44 | Called on every frame 45 | """ 46 | # override this method to add logic that happens on every frame 47 | self.all_sprites.update() 48 | 49 | def enter(self): 50 | """ 51 | Called when the state becomes the current state 52 | """ 53 | # override this method to add logic that happens when this state becomes the current state 54 | pass 55 | 56 | def exit(self): 57 | """ 58 | Called when the state is no longer the current state 59 | """ 60 | # override this method to add logic that happens when changing 61 | # from this state to some other state 62 | pass 63 | 64 | def draw(self, screen): 65 | """ 66 | Called on every frame 67 | """ 68 | self.all_sprites.draw(screen) 69 | 70 | class Intro(State): 71 | """ 72 | Intro state 73 | """ 74 | def boot(self): 75 | # load the intro sound 76 | self.game.audio.load_sound('intro', os.path.join('assets', 'sounds', 'intro.wav')) 77 | # add a background image 78 | self.all_sprites.add(ImageSprite(self.game, os.path.join('assets', 'images', 'test.jpg'))) 79 | # add some text 80 | text = TextSprite(self.game, os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf'), 81 | "Intro", 24, color = (255,255,255)) 82 | # center text on screen 83 | text.rect.center = self.game.screen.get_rect().center 84 | # add the text to the all_sprites group 85 | self.all_sprites.add(text) 86 | # add some more text 87 | any_key_text = TextSprite(self.game, 88 | os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf'), 89 | "Press any key to switch to Outro", 12, color = (255,255,255)) 90 | any_key_text.rect.center = self.game.screen.get_rect().center 91 | any_key_text.rect.top = text.rect.bottom + 10 92 | # add the text to the all_sprites group 93 | self.all_sprites.add(any_key_text) 94 | 95 | def enter(self): 96 | # when the state becomes the current state, play the intro sound 97 | self.game.audio.play('intro') 98 | 99 | def update(self): 100 | # on every frame, call the update method of the base class 101 | super().update() 102 | # check if any key is pressed 103 | if self.game.input.is_key_pressed('any'): 104 | # if any key is pressed, change the state to Outro 105 | self.game.change_state('Outro') 106 | 107 | class Outro(State): 108 | """ 109 | Outro state 110 | """ 111 | def boot(self): 112 | # add some text 113 | text = TextSprite(self.game, 114 | os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf'), 115 | "Outro", 24, color = (255,255,255)) 116 | # center text on screen 117 | text.rect.center = self.game.screen.get_rect().center 118 | # add the text to the all_sprites group 119 | self.all_sprites.add(text) 120 | 121 | # add the "any key" text 122 | any_key_text = TextSprite(self.game, 123 | os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf'), 124 | "Press any key to quit", 12, color = (255,255,255)) 125 | any_key_text.rect.center = self.game.screen.get_rect().center 126 | any_key_text.rect.top = text.rect.bottom + 10 127 | # add the text to the all_sprites group 128 | self.all_sprites.add(any_key_text) 129 | 130 | def update(self): 131 | super().update() 132 | # check if any key is pressed 133 | if self.game.input.is_key_pressed('any'): 134 | self.game.quit() 135 | 136 | # add the states to the __all__ list 137 | # this is needed so that the states can be imported using the * syntax 138 | # the first item in the list is the default state 139 | __all__ = ['Intro', 'Outro'] 140 | --------------------------------------------------------------------------------