{"id":20299846,"url":"https://github.com/python-ninja-hebi/wonder","last_synced_at":"2025-03-04T06:21:41.235Z","repository":{"id":178032487,"uuid":"423519307","full_name":"Python-Ninja-Hebi/wonder","owner":"Python-Ninja-Hebi","description":"Python Game Engine based on pygame and physics engine box2d","archived":false,"fork":false,"pushed_at":"2022-01-02T15:57:20.000Z","size":9099,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-14T10:33:39.004Z","etag":null,"topics":["box2d","game-engine","pygame","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Python-Ninja-Hebi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-01T15:35:10.000Z","updated_at":"2024-01-23T20:02:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"7d3dad09-b6f9-471f-af2d-2ae57ab1a01d","html_url":"https://github.com/Python-Ninja-Hebi/wonder","commit_stats":null,"previous_names":["python-ninja-hebi/wonder"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Python-Ninja-Hebi%2Fwonder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Python-Ninja-Hebi%2Fwonder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Python-Ninja-Hebi%2Fwonder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Python-Ninja-Hebi%2Fwonder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Python-Ninja-Hebi","download_url":"https://codeload.github.com/Python-Ninja-Hebi/wonder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241795065,"owners_count":20021386,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["box2d","game-engine","pygame","python"],"created_at":"2024-11-14T16:16:31.824Z","updated_at":"2025-03-04T06:21:41.221Z","avatar_url":"https://github.com/Python-Ninja-Hebi.png","language":"Python","readme":"# wonder - Python Game Engine\n\n## Whenever I have played a great computer game for the first time, \u003cbr\u003eI was **wonder**ing how they do it. Then I tried to recreate it.\n\n\u003cimg src=\"img/wonder_logo.png\" width=\"128\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n**Pygame** (https://www.pygame.org) is a great library for making your own game with python. \n\nWhen starting a new game from scratch you always need to build the basic structure. The game engine `wonder` gives you a collection of components you can use with **Pygame**.\n\nThe game engine `wonder` is only a frame for your programming, so you have to know Pygame for creating new games.\n\nMany ideas in `wonder` are inspired by *Unity 3D* (https://unity.com)\n\nIf you are looking for more simple to use Pygame frameworks:\n\n* **pygame zero** https://github.com/lordmauve/pgzero\n* **python arcade library** https://arcade.academy\n\nThe game engine `wonder` includes Box2D as physics engine.\n\nGoals:\n   \n* explicit is better then implicit - wonder is only the frame for your game\n* Component based - more components, less classes\n* Inspired by Unity 3D - Similar names for object types and methods\n* Physics engine included\n\n\n\u003cimg src=\"img/ground_parts.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\nIf you like it, use it. If you have some suggestions, tell me (hebi@ninja-python.com).\n\nAll game assets that I use in examples are free and from https://www.kenney.nl. Thank you.\n\n## acclaimer\n\nYou can use this alpha version 0.1.0 of the `wonder` game engine but there will be some changes in the future.\n\n## installing wonder game engine\n\nInstall with pip\n\n\n```python\npip install wonder\n```\n\nIf that does not work on your platform you can install the different components separately\n\nInstall **pygame**\n\n\n```python\npip install pygame\n```\n\nInstall physics engine **Box2D**\n\n\n```python\npip install box2d\n```\n\nFor installing **wonder** simply copy file **wonder.py** to your directory.\n\n\n```python\ncp wonder.py\n```\n\n## wonder game engine - making a new game \n\nThe first game with `wonder`game engine is a classical 'Ball and Paddle game' like Arkanoid, Breakout or Alleyway.  \n\nArkanoid https://en.wikipedia.org/wiki/Arkanoid  \nBreakeout https://en.wikipedia.org/wiki/Breakout_(video_game)  \nAlleyway https://en.wikipedia.org/wiki/Alleyway_(video_game)\n\nYou can find the complete game in the file *game_blocks.py*\n\n\u003cimg src=\"img/game_blocks.png\" width=\"512\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n### main game\n\nThe easiest way to get access to all classes of the **wonder** game engine is to include them completely.\n\n\n```python\nfrom wonder import *\n```\n\nFirst you need an object of the class **Game**. It represents the game itself.\n\n\n```python\nif __name__ == \"__main__\":\n    game = Game(width=860,height=600,name='game_blocks.py',scenes=[Level()])\n    game.quit()\n```\n\nClass **Game**:\n* *width* .. screen width in pixel\n* *height* .. screen height in pixel\n* *name* .. name of the game, shown as window title\n* *scenes* .. list of sences, levels of the game\n\nMethod **game.quit()** stops the game.\n\n### scene\n\nAn object of the class **Scene** is a container that contains all things (gameobjects) that are currently required by the game. Often a scene corresponds to a level.\n\n\n```python\nclass Level(Scene):\n    def create(self):\n        self.background_color = WHITE\n        #create gameobjects\n```\n\nTo create a new level you have to derive your own class from the **Scene** class.\nThe **create** method is called by the game engine to create all game objects of the scene.\n\n\u003cimg src=\"img/game_blocks_1.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n### gameobject\n\nThe first part of the game is the **paddle**, on which the player has to bounce the ball with it in order to hit colored blocks.\n\nThe paddle has its own class **Paddle** that is derived from the **GameObject** class.\n\n\n```python\nclass Paddle(GameObject):\n    def __init__(self):\n        super().__init__()\n\n    def update(self, delta_time):\n        # all action\n        pass\n```\n\nClass **GameObject**:\n\n* *\\_\\_init\\_\\_* .. creates all components and property, allways have to call super().\\_\\_init\\_\\_()\n* *update* .. is called as often as possible by the game engine.\n* *delta_time* .. describes the time since the last call \n\nA **GameObject** can have **Components** that do some jobs for them.  \n\nThe component **SpriteRender** draws an image (sprite) on the screen that represents the  **GameObject**.\nposition\nadd Racket to Scene\n\n\n```python\nclass Paddle(GameObject):\n    DISTANCE = 20\n    def __init__(self):\n        super().__init__()\n        self.sprite_renderer = self.add(SpriteRenderer(self, \n                                        load_from_file='res_blocks/paddleBlu.png'))\n        self.transform.position = Vector2(Game.instance.width//4*3//2, \n               Game.instance.height - self.sprite_renderer.rect.height - self.DISTANCE)\n\n```\n\n* *self.add* .. method self.add adds the component *SpriteRenderer* to the gameobject and returns the added component\n* *self.transform.position* .. a gameobject has *transform* property. With *transform.position* you can change the position.  \n\n**wonder** game engine uses pygame **Vector2** for positions.\n\nWith **Game.instance** you get the current game object.\n\nTo see anything you have to add the gameobject to the scene.\n\n\n```python\nclass Level(Scene):\n    def create(self):\n        ..\n\n        self.add(Paddle())\n```\n\n\u003cimg src=\"img/game_blocks_2.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nIn order for the physics engine to realistically calculate for example the movements of the ball, it needs information about the physical properties of the paddle.  \n\n\n```python\nclass Paddle(GameObject):\n    DISTANCE = 20\n    def __init__(self):\n        super().__init__()\n        ..\n        self.rigidbody = self.add(Rigidbody(self,DYNAMIC_BODY))\n        self.rigidbody.fixed_rotation = True\n        self.add(BoxCollider(self,self.rigidbody,\n                             box=(self.sprite_renderer.rect.width,\n                                  self.sprite_renderer.rect.height)))\n\n```\n\n* *Rigidbody(self,DYNAMIC_BODY)* .. the component **Rigidbody** defines the gameobject as a rigid object. It is not soft.\n* *DYNAMIC_BODY* .. means that the gameobject can be moved by the physics engien\n* *self.rigidbody.fixed_rotation = True* .. The paddle is always level. It shouldn't be rotated.\n* *BoxCollider(self,self.rigidbody,box=(width,height))* .. the component **BoxCollider** defines the extension of the gameobject. The paddle is like a box. You can get width and height from the **SpriteRenderer**. It is the width and height of the image. \n\n\n```python\nclass Level(Scene):\n    def create(self):\n        ..\n        Game.instance.physic_system.gravity = (0.0,0.0)\n```\n\nIn this game should not be used any gravity.\n\n### create border\n\nThe game has a border on the left, one on the right, and one on top. The ball can bounce off these. There is no limit below. There it goes out.\n\n\u003cimg src=\"img/game_blocks_3.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nA border object is from the **Border** class that is derived from the **GameObject** class.\n\n\n```python\nclass Border(GameObject):\n    HEIGHT = 20\n    def __init__(self, width, height, position):\n        super().__init__()\n        image = pygame.Surface((width, height))\n        image.fill(GRAY)\n        self.add(SpriteRenderer(self, image=image))\n        self.transform.position = position\n```\n\nClass **Border**  \n\n* *\\_\\_init\\_\\_(self, width, height, position)* .. with, height and position of the border that should be created\n* *image = pygame.Surface((width, height))* .. the **Surface** class of pygame can create a local image\n* *image.fill(GRAY)* .. the image is a grey rectangle\n* *self.add(SpriteRenderer(self, image=image))* .. add **SpriteRenderer** component\n* *self.transform.position = position* .. set border position\n\n\n```python\nclass Border(GameObject):\n    HEIGHT = 20\n    def __init__(self, width, height, position):\n        ..\n        rigidbody = self.add(Rigidbody(self,STATIC_BODY))\n        self.add(BoxCollider(self, rigidbody, box=(width,height)))\n```\n\n* *Rigidbody(self,STATIC_BODY)* .. the border is also a rigid body.\n* *STATIC_BODY* .. means that the gameobject can not be moved by the physics engine\n* *BoxCollider(self,self.rigidbody,box=(width,height))* .. the border is like a box.\n\nThe **Scene** class creates the borders.\n\n\n```python\nclass Level(Scene):\n    def create(self):\n        ..\n        three_quarter = Game.instance.width//4*3\n\n        self.add(Border(three_quarter, Border.HEIGHT, \n                        Vector2(three_quarter//2,Border.HEIGHT//2)))\n        self.add(Border(Border.HEIGHT, Game.instance.height-Border.HEIGHT,\n                        Vector2(Border.HEIGHT//2,\n                                (Game.instance.height+Border.HEIGHT)//2) ))\n        self.add(Border(Border.HEIGHT, \n                        Game.instance.height-Border.HEIGHT,\n                        Vector2(three_quarter-Border.HEIGHT//2, \n                                (Game.instance.height+Border.HEIGHT)//2) ))\n```\n\n### move paddle\n\nThe user can move the paddle with the left and write arrow keys.\n\n\n```python\nclass Paddle(GameObject):\n    ..\n    SPEED = 120\n    \n    ..\n    def update(self, delta_time):\n        direction = 0.0\n\n        keys=pygame.key.get_pressed()\n    \n        if keys[pygame.K_RIGHT]:\n            direction = 1\n        elif keys[pygame.K_LEFT]:\n            direction = -1\n\n        self.rigidbody.velocity = Vector2(1,0) * direction * self.SPEED\n```\n\n* *SPEED = 120* .. constant speed when paddle is moved. It is 120 pixle per second.\n* *direction* .. 0 not moved, -1 moving left, 1 moving right\n* *keys=pygame.key.get_pressed()* .. pygame list with pressed or not pressed keys\n* *keys[pygame.K_RIGHT]* .. is True when right arrow key is pressed\n* *keys[pygame.K_LEFT]* .. is True when left arrow key is pressed\n* *self.rigidbody.velocity = Vector2(1,0) * direction * self.SPEED* .. sets the velocity of the paddle for the game engine \n\n### debug physics\n\nYou can switch to a special display for troubleshooting in connection with the physics engine.\n\n\u003cimg src=\"img/game_blocks_4.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n```python\nclass Paddle(GameObject):\n    ..\n    def update(self, delta_time):\n        ..\n        if keys[pygame.K_ESCAPE]:\n            Game.instance.debug_physic_system_tag = not Game.instance.debug_physic_system_tag\n```\n\n* *Game.instance.debug_physic_system_tag* .. when this property is True the game engine debug display is shown\n\n### create ball\n\nA ball has a **SpriteRenderer**, a **Rigidbody**, and a **CircleBollider** component.\n\n\n```python\nclass Ball(GameObject):\n    SPEED = 240\n    def __init__(self):\n        super().__init__()\n        sprite_renderer = self.add(SpriteRenderer(self, \n                                   load_from_file='res_blocks/ballGrey.png'))\n        self.transform.position = Vector2(Game.instance.width//4*3//2, \n                                          Game.instance.height//2)\n        \n        self.rigidbody = self.add(Rigidbody(self,DYNAMIC_BODY))\n        self.add(CircleCollider(self,self.rigidbody,\n                                radius=sprite_renderer.rect.width//2,\n                                restitution=1.0,friction=0))\n        self.rigidbody.velocity = Vector2(0,0) * self.SPEED\n        self.rigidbody.mass = 0.2\n```\n\n* *self.rigidbody = self.add(Rigidbody(self,DYNAMIC_BODY))* .. add Rigidbody component \n* *self.add(CircleCollider(self,self.rigidbody,radius=sprite_renderer.rect.width//2,restitution=1.0,friction=0))* .. CircleCollider component\n* *self.rigidbody.velocity = Vector2(0,0)* .. sets start velocity to zero\n* *self.rigidbody.mass = 0.2* .. sets mass\n\nAdd ball to scene.\n\n\n```python\nclass Level(Scene):\n    def create(self):\n        ..\n        self.add(Ball())\n```\n\n\u003cimg src=\"img/game_blocks_5.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n### create block\n\nA single block has a **SpriteRenderer**, a **Rigidbody**, and a **BoxCollider** component. \n\n\n```python\nclass Block(GameObject):\n    def __init__(self, file_name):\n        super().__init__()\n        self.sprite_renderer = self.add(SpriteRenderer(self,load_from_file=file_name))\n        self.rigidbody = self.add(Rigidbody(self,DYNAMIC_BODY))\n        self.rigidbody.fixed_rotation = True\n        self.add(BoxCollider(self,self.rigidbody,\n                             box=(self.sprite_renderer.rect.width,\n                                  self.sprite_renderer.rect.height)))\n```\n\nEvery level has a different pattern of blocks.  An object of the class **BlockManager** creates the blocks according to the pattern of the level.\n\n\n```python\nclass BlockManager(GameObject):\n    FILES = ['res_blocks/element_blue_rectangle.png',\n             'res_blocks/element_green_rectangle.png',\n             'res_blocks/element_red_rectangle.png',\n             'res_blocks/element_yellow_rectangle.png']\n    SPACE = 10\n\n    def __init__(self, scene):\n        super().__init__()\n        self.scene = scene\n        self.count = 0\n```\n\nThe pattern of the first level is\n\n[[0,1,2,3,0,1,2,3],  \n [0,1,2,3,0,1,2,3],  \n [0,1,2,3,0,1,2,3],  \n [0,1,2,3,0,1,2,3]]  \n                            \nEvery number represents a different color. The number 0 means an empty space.\n\nThe **BlockManager.make** method creates the blocks.\n\n\n```python\nclass BlockManager(GameObject):\n    ..\n\n    def make(self, block_pattern) -\u003e None:\n        for i, value in enumerate(block_pattern):\n            for j, file_nr in enumerate(value):\n                self.count += 1\n                block = self.scene.add(Block(self.FILES[file_nr]))\n                block.transform.position = Vector2(Border.HEIGHT+self.SPACE+block.sprite_renderer.rect.width*(j+0.5)+self.SPACE*j,\n                                                   Border.HEIGHT+self.SPACE+block.sprite_renderer.rect.height*(i+0.5)+self.SPACE*i)\n        \n```\n\n* *for i, value in enumerate(block_pattern)* .. for every line in block_pattern\n* *for j, file_nr in enumerate(value)* .. for every value in line, value represents different png-file\n* *block = self.scene.add(Block(self.FILES[file_nr]))* .. add Block GameObject to scene\n* *block.transform.position = Vector2(..)* .. set position\n\nAdd **BlockManager** to **Level**. So that the **create** method of the **Level** class does not come across to the standard **create** method, this is renamed to **create_level**.\n\n\n```python\nclass Level(Scene):\n    def create_level(self,pattern):\n        ..\n        block_manager = self.add(BlockManager(self))\n        block_manager.make(pattern)\n```\n\nCreate two levels with different block pattern.\n\n\n```python\nclass Level1(Level):\n    def create(self) -\u003e None:\n        self.create_level([[0,1,2,3,0,1,2,3],\n                            [0,1,2,3,0,1,2,3],\n                            [0,1,2,3,0,1,2,3],\n                            [0,1,2,3,0,1,2,3]])        \n\nclass Level2(Level):\n    def create(self):\n        self.create_level([[0,1,2,3,2,1,2,0],\n                            [0,1,2,0,0,1,2,0],\n                            [0,1,0,3,1,0,2,0],\n                            [0,0,2,3,1,1,0,0]])\n```\n\nAdd levels to *Game* object.\n\n\n```python\nif __name__ == \"__main__\":\n    game = Game(width=860,height=600,name='game_blocks.py', \n                scenes=[Level1(), Level2()])\n    game.quit()\n```\n\n\u003cimg src=\"img/game_blocks_6.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n### create scoremanager\n\nTasks of the **ScoreManager** are\n\n* managing the game\n* restarting the game\n* do the scoring\n\n\n```python\nclass ScoreManager(GameObject,MixinDraw):\n    def __init__(self):\n        super().__init__()\n        \n        self.init()\n\n        self.text_in_play_field = Vector2(Game.instance.width//4*3//2,\n                                          Game.instance.height//4*3)\n        self.text_right = Vector2(Game.instance.width//4*3+Game.instance.width//4//2,\n                                  Game.instance.height//8)\n        self.text_space = 40\n\n    def init(self):\n        self.score = 0\n        self.level = 1\n        self.ball = 48\n\n        self.block_manager = GetObject(BlockManager)\n        self.start_tag = True\n```\n\n* *self.text_in_play_field = Vector2(..)* .. position of the central text, like 'press key to start game'\n* *self.text_right = Vector2(..)* .. position of text right, like score\n* *self.text_space = 40* .. space between texts\n\n* *def init(self)* .. when game restarts, some properties of the ScoreManageer has to be initialized\n* *self.block_manager = GetObject(BlockManager)* .. get the BlockManager\n* *self.start_tag = True* .. ScoreManager is in starting mode\n\nThe ScoreManager draws the numbers of current score itself. There is no special object like a SpriteRenderer. The ScoreManager is also inhereted by MixinDraw so it gets the draw method which is called every frame by the game engine.\n\n\n```python\nclass ScoreManager(GameObject,MixinDraw):\n    ..\n    def draw(self, screen: pygame.Surface):\n        if self.start_tag:\n            draw_text(screen, 'press space to start game',48, ORANGE,\n                      self.text_in_play_field,alignment=TEXT_ALIGNMENT_MID)\n\n        draw_text(screen, f'Score {self.score}',48, ORANGE,\n                  self.text_right,alignment=TEXT_ALIGNMENT_MID)\n        draw_text(screen, f'Level {self.level}',48, ORANGE,\n                  Vector2(self.text_right.x, \n                          self.text_right.y+self.text_space),\n                  alignment=TEXT_ALIGNMENT_MID)\n        draw_text(screen, f'Ball {self.ball}',48, ORANGE,\n                  Vector2(self.text_right.x, \n                          self.text_right.y+2*self.text_space),\n                  alignment=TEXT_ALIGNMENT_MID)\n```\n\n* *if self.start_tag* .. when in starting mode show text 'press space to start game'\n* *draw_text(screen, f'Score {self.score}',48, ORANGE,self.text_right,alignment=TEXT_ALIGNMENT_MID)* .. text to be drawn in pygame, the convinient draw_text methods helps\n\nParameter of draw_text\n\n* *screen* .. on which Surface should be drawn\n* *text* .. the text itself\n* *number of pixels*\n* *color*\n* *alignment* .. left or mid\n\n\n```python\nclass ScoreManager(GameObject,MixinDraw):\n    ..\n    def update(self, delta_time: float):\n        if self.start_tag:\n            keys=pygame.key.get_pressed()\n        \n            if keys[pygame.K_SPACE]:\n                self.start_tag = False\n                Game.instance.get_object(Ball).start()\n```\n\n* *if self.start_tag:* .. when **ScoreManager** is in starting mode it waits until a key is pressed\n        \n* *if keys[pygame.K_SPACE]:* .. is it the space key?\n* *self.start_tag = False* .. than starting mode is over\n* *Game.instance.get_object(Ball).start()* .. get ball object and start it\n\n\n```python\nclass Ball(GameObject):\n..     \n    def start(self):\n        self.rigidbody.velocity = Vector2(0,-1) * self.SPEED\n```\n\nAdd **ScoreManager** to **Level**\n\n\n```python\nclass Level(Scene):\n    def create_level(self,pattern):\n        self.background_color = WHITE\n        Game.instance.physic_system.gravity = (0.0,0.0)\n\n        score_manager = Game.instance.get_object(ScoreManager)\n\n        if not score_manager:\n            score_manager = self.add(ScoreManager())\n            self.dont_destroy_on_load(score_manager)\n```\n\n* *score_manager = Game.instance.get_object(ScoreManager)* .. search for **ScoreManager**\n* *if not score_manager* .. if not available, create one\n* *score_manager = self.add(ScoreManager())* .. create **ScoreManager** and add to scene\n* *self.dont_destroy_on_load(score_manager)* .. tell game engine never destroy **ScoreManager**\n\nWhen changing to a new scene (level), the game engine removes all old GameObjects before generating the new ones. However, the ScoreManager should always remain so that information such as highscores or the like do not disappear.\n\n### blocks and ball\n\nWhen the ball hits against the paddle it bounces.\n\n\n```python\nclass Ball(GameObject):\n    SPEED = 240\n    ..\n        \n    def on_collision_enter(self, collider, impulse):\n        if isinstance(collider,Paddle):\n            factor = self.hit_factor(self.transform.position, \n                                     collider.transform.position,collider.width)\n            direction = Vector2(factor,1).normalize()\n            self.rigidbody.velocity = direction * self.SPEED\n```\n\n* *def on_collision_enter(self, collider, impulse)* .. this methode is called if something collides with the ball\n* *if isinstance(collider,Paddle):* .. is the collider the paddle?\n* *factor = self.hit_factor(..)* .. the further the ball is from the center of the paddle, the more obliquely it will bounce off \n\n\n```python\nclass Ball(GameObject):\n    ..\n    def hit_factor(self, ball_position, paddle_position, paddle_width):\n        return (ball_position.x - paddle_position.x) / float(paddle_width)\n\n```\n\nThe width of the paddle depends on the with of the picture that the **SpriteRenderer** is using.\n\n\n```python\nclass Paddle(GameObject):\n    ..\n    @property\n    def width(self)-\u003eint:\n        return self.sprite_renderer.rect.width\n```\n\nIf an block object collides with something, what only can be the ball, it will be removed.\n\n\n```python\nclass Block(GameObject):\n    ..\n    def on_collision_enter(self, collider, impulse):\n        get_object(ScoreManager).add(80)\n        destroy(self)\n```\n\n* *get_object(ScoreManager).add(80)* .. get the ScoreManger and add 80 points to the score\n* *destroy(self)* .. the game engine will remove this block\n\n\n```python\nclass ScoreManager(GameObject,MixinDraw):\n     ..\n    def add(self, value):\n        self.score += value\n\n        self.block_manager.count -=1\n\n        if self.block_manager.count == 0:\n            Game.instance.load_scene(self.level)\n            self.level += 1\n            self.ball += 2 \n```\n\nScoreManager.add\n\n* *self.score += value* .. add points to the score\n* *self.block_manager.count -=1* .. tell **BlockManager** that one block is removed\n* *if self.block_manager.count == 0* .. are blocks available?\n* *Game.instance.load_scene(self.level)* .. if not, tell game engine to load next scene\n\n### restart\n\nIf the ball flies out below, restart the game.\n\n\n```python\nclass Ball(GameObject):\n    SPEED = 240\n    ..\n    def __init__(self):\n        ..\n        self.limit = Game.instance.height //4 * 5\n\n    def update(self, delta_time: float):\n        if self.transform.position.y \u003e self.limit:\n            get_object(ScoreManager).restart()\n```\n\nThe **ScoreManager** restarts the game.\n\n\n```python\nclass ScoreManager(GameObject,MixinDraw):\n    ..\n    def restart(self):\n        self.ball -=1\n        if self.ball \u003e= 0:\n            self.start_tag = True\n            get_object(Ball).restart()\n        else:\n            Game.instance.load_scene(0)\n            self.init()\n```\n\n* *self.ball -=1* .. one ball less\n* *if self.ball \u003e= 0* .. is a ball left?\n* *self.start_tag = True* .. set starting mode\n* *get_object(Ball).restart()* .. restart ball\n* *Game.instance.load_scene(0)* .. if no ball left, start from level 0\n\nFirst game is completed.\n\n## wonder game engine -  behind the curtain\n\n### central engine and the systems\n\n\n```python\n\n```\n\n#### pattern singleton\n\n\n```python\n\n```\n\n#### game loop update draw\n\nevent  \nupdate  \nlate_update  \ndraw  \n\n#### timing\n\n\n```python\n\n```\n\n#### event system\n\non_load_scene\n\nobserver pattern\n\n#### get_object\n\n#### GetObject\n\n### gameobject\n\n#### mixin\n\n\n```python\n\n```\n\n#### transform\n\n\n```python\n\n```\n\n#### components\n\nSpriteRenderer\n\n## scene\n\n### layered container for gameobject\n\n\n```python\n\n```\n\n### render system\n\nlayered observer\n\n#### Component SpriteRenderer\n\nSurface  \nload_from_file\n\nconsists of surface and rect\n\n### change current scene\n\n#### add or remove gameobject\n\n#### add or remove component\n\n\n```python\n\n```\n\n## physic and collision system\n\nusing Box2D https://box2d.org/documentation/md__d_1__git_hub_box2d_docs_dynamics.html  \npython https://github.com/pybox2d/pybox2d\n\n### bodies\n\nComponent Rigidbody is b2Body\n\n#### synchornize transform\n\n\n```python\n\n```\n\n#### body types\n\nSTATIC_BODY  \n    physic system does not simulate this body  \n    body has zero velocity  \n    body does not collide with other static or kinematic bodies  \n\nKINEMATIC_BODY  \n    physic system simulates this body  \n    body does not respond to forces  \n    program can move body normally by setting velocity  \n    body does not collide with other static or kinematic bodies  \n\nDYNAMIC_BODY  \n    physic system simulates this body  \n    body collides with other bodies  \n\n#### fixtures\n\ncomponent collider is b2Fixture\n\nboxcollider\n\n#### debug\n\n### joints\n\n#### distance joints\n\nget_gameobject\n\n## animator component\n\nanimator has states  \nstate has clips\n\n\n```python\n\n```\n\n## particle system\n\n\n```python\n\n```\n\n## tile system\n\nA **TileMap** is an GameObject and consists of *width* x *height* tiles.  \nEvery tile has a width of *tile_width* pixels and a height of *tile_height*. \n\n\n```python\nGRID_WIDTH = 5\nGRID_HEIGHT = 7\n\nCELL_WIDTH = 64\nCELL_HEIGHT = 64\n\ntilemap = TileMap(GRID_WIDTH,GRID_HEIGHT,CELL_WIDTH,CELL_HEIGHT)\n```\n\nThe tilemap.transform.position is always the top left position of the map. With changing position you can move the complete map.\n\nA TileMap has a **palette** with different **TilePaletteItem** you can use in a tilemap.  \nA **TilePaletteItem** has an unique **id**, an unique **tile_type** and an **image**.\n\n\n```python\ntilemap.palette.add(TilePaletteItem(0, tile_type='ground', \n                                    image=pygame.image.load('res_tile/ground.png')))\ntilemap.palette.add(TilePaletteItem(1, tile_type='wall', \n                                    image=pygame.image.load('res_tile/wall.png')))\n..\n```\n\nTo create a tile from the palette at a specific position in the tile map use the function **create_tile_from_palette**(*position_x*,*position_y*,*tile_type* or *id*)\n\n\n```python\ntilemap.create_tile_from_palette(0,0,'ground')\n```\n\n\u003cimg src=\"img/grid_system_example_1.png\" width=\"320\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nYou can create a complete tile map with **set_all_tiles** \n\n\n```python\ntilemap.set_all_tiles([[1,1,1,1,1],\n                       [1,0,0,0,1],\n                       [1,0,0,0,1],\n                       [1,0,0,0,1],\n                       [1,0,0,0,1],\n                       [1,0,0,0,1],\n                       [1,1,1,1,1]])\n```\n\n\u003cimg src=\"img/grid_system_example_2.png\" width=\"320\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nA class **TileMap** can have more than one layer of tiles. Negative values are None.\n\n\n```python\nnew_layer = tilemap.add_layer()\n\ntilemap.set_all_tiles([[-1,-1,-1,-1,-1],\n                       [-1, 4,-1,-1,-1],\n                      ..\n                       [-1,-1,-1, 2,-1],\n                       [-1,-1,-1,-1,-1]],tile_layer=new_layer)\n```\n\n\u003cimg src=\"img/grid_system_example_3.png\" width=\"320\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nTo see something tilemap as gameobject needs rendering component\n\n\n```python\ntilemap.add(TileMapRenderer(tilemap))\n```\n\nWith class **TileController** a tile can react\n\n\n```python\ntilemap.palette.add(TilePaletteItem(4, tile_type='player', \n                                    image=pygame.image.load('res_tile/player_01.png'),\n                                    tile_controller_class=Player))\n```\n\nClass **Player** is in gameloop update cycle\n\n\n```python\nclass Player(TileController):\n    def __init__(self,tile:Tile):\n        super().__init__(tile)\n        ..\n        \n    def update(self, delta_time: float):\n        ..\n```\n\nClass **TileController** has some convinient methods.\n\n`get_position()` .. current tile position  \n`tile = self.get_tile(pos)` .. get tile at postion  \n`tile.has_type('ground')` .. has tile the that type  \n`set_position(new_pos)`.. change position of tile  \n\n### using editor tiled\n\nYou can also use the free editor **Tiled** for creating **TileMap**.\nhttps://www.mapeditor.org\n\n\u003cimg src=\"img/tiled.png\" width=\"512\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\nAll things are saved in a JSON file (res_tile/tile.json). You can work with layers.\n\n\n```python\ntilemap = TileMap.createFromTiledJSON('res_tile/tile.json')\n```\n\nIn Tiled you can give every tile a specific tile type.\n\n\u003cimg src=\"img/tiled_1.png\" width=\"256\" align=\"left\"\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n\n```python\ntilemap = TileMap.createFromTiledJSON('res_tile/tile.json',\n                                      {'box':Box, 'player':Player})\n```\n\nSo every tile type can have its own controller.\n\nComplete example *game_tile_tiled.py*\n\n## Changelog\n\n|Version       |                                                              |\n|--------------|--------------------------------------------------------------|\n|  0.1.0       | first version - August 2021                                  |\n|  0.1.1       | one tutorial and some documentation - Dezember 2021          |\n\n\n```python\n\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-ninja-hebi%2Fwonder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpython-ninja-hebi%2Fwonder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-ninja-hebi%2Fwonder/lists"}