pyscratch.sprite

Everything in this module is directly under the pyscratch namespace. For example, instead of doing pysc.sprite.create_animated_sprite, you can also directly do pysc.create_animated_sprite.

   1"""
   2Everything in this module is directly under the pyscratch namespace. 
   3For example, instead of doing `pysc.sprite.create_animated_sprite`,
   4you can also directly do `pysc.create_animated_sprite`.
   5"""
   6
   7
   8
   9from __future__ import annotations
  10from enum import Enum
  11from typing import Any, Dict, Hashable, Iterable, List, Optional, ParamSpec, Tuple, Union, cast, override
  12from typing_extensions import deprecated
  13
  14
  15import numpy as np
  16import pygame 
  17import pymunk
  18
  19import pyscratch.game_module
  20from .game_module import Game, game
  21from .helper import adjust_brightness, set_transparency, create_rect, create_circle, load_frames_from_folder
  22from pathlib import Path
  23
  24
  25def create_animated_sprite(folder_path,  *args, **kwargs):
  26    """
  27    Create a sprite using the images inside a folder. 
  28
  29    The folder should be organised in one of these two ways: 
  30
  31    **Option 1**: Only one frame mode. *Note that the images must be numbered.*
  32    ```
  33    ├─ player/
  34        ├─ 0.png
  35        ├─ 1.png
  36        ├─ 2.png
  37        ...
  38    ```
  39
  40    **Option 2**: Multiple frame modes. *Note that the images must be numbered.*
  41    ```
  42    ├─ player/  
  43        ├─ walking/  
  44            ├─ 0.png  
  45            ├─ 1.png  
  46            ...
  47        ├─ idling/
  48            ├─ 0.png
  49            ├─ 1.png
  50            ... 
  51        ...
  52    ``` 
  53
  54
  55    Example
  56    ```python
  57    # takes the path of the folder 
  58    my_sprite1 = create_animated_sprite("assets/player") 
  59    
  60    # optionally take in whatever parameters that the `Sprite` constructor take. 
  61    my_sprite2 = create_animated_sprite("assets/player", position=(200, 200)) 
  62    
  63    # For option 2 only: optionally set the `starting_animation` parameter in the `Sprite` constructor, but it'd still be fine without it.
  64    my_sprite3 = create_animated_sprite("assets/player", "idling")
  65    
  66    ```
  67    Parameters
  68    ---
  69    folder_path: str
  70        The path of the folder that contains the images
  71
  72    \*args & \*\*kwargs: Optional
  73        Whatever the `Sprite` constructor takes, except the `frame_dict` parameter.
  74    """
  75    frame_dict = load_frames_from_folder(folder_path)
  76    return Sprite(frame_dict, *args, **kwargs)
  77
  78
  79def create_single_costume_sprite(image_path, *args, **kwargs):
  80    """
  81    Create a sprite with only one costume, given the path to the image
  82    
  83    Example
  84    ```python
  85    my_sprite1 = create_single_costume_sprite("assets/player.png")
  86
  87    # Optionally pass some parameters to the `Sprite` constructor
  88    my_sprite2 = create_single_costume_sprite("assets/player.png", position=(200, 200)) 
  89    
  90    ```
  91    Parameters
  92    ---
  93    image_path: str
  94        The path of the images
  95
  96    \*args & \*\*kwargs: Optional
  97        Whatever the `Sprite` constructor takes, except `frame_dict` & `starting_animation`.
  98    """
  99    img = pygame.image.load(image_path).convert_alpha()
 100    frame_dict = {"always": [img]}
 101    return Sprite(frame_dict, "always", *args, **kwargs)
 102
 103
 104def create_shared_data_display_sprite(key, font, size = (150, 50), bg_colour=(127, 127, 127), text_colour=(255,255,255), position: Optional[Tuple[float, float]]=None, update_period=0.1, **kwargs):
 105    """
 106    Create a display for a variable inside shared_data given the dictionary key (i.e. the name of the variable). 
 107    The variable display will update every `update_period` seconds.
 108    The variable display is created as a sprite. 
 109
 110    This function is created using only basic pyscratch functions and events. 
 111    If you want a more customised display, you may want to have a look at the source code as an example how it's done.
 112
 113    Example
 114    ```python
 115    
 116    # if the font is shared by multiple sprites, consider putting it in `settings.py`
 117    font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size
 118
 119    # the shared data
 120    game.shared_data['hp'] = 10
 121    
 122    # the variable display is created as a sprite 
 123    health_display = create_shared_data_display_sprite('hp', font, position=(100, 100), update_period=0.5)
 124    
 125    ```
 126    Parameters
 127    ---
 128    key: str
 129        The dictionary key of the variable in `game.shared_data` that you want to display.
 130    font: pygame.font.Font
 131        The pygame font object. Refer to the website of pygame for more details.
 132    size: Tuple[float, float]
 133        The size of the display panel
 134    bg_colour: Tuple[int, int, int] or Tuple[int, int, int, int] 
 135        The colour of the display panel in RGB or RGBA. Value range: [0-255]
 136    text_colour: Tuple[int, int, int] or Tuple[int, int, int, int] 
 137        The colour of the text in RGB or RGBA. Value range: [0-255]
 138    position: Tuple[int, int] 
 139        The position of the display
 140    update_period: float
 141        The variable display will update every `update_period` seconds.
 142    \*\*kwargs: Optional
 143        Whatever the `Sprite` constructor takes, except `frame_dict`,`starting_animation` & `position`
 144    """
 145
 146    w, h = size
 147    if position is None:
 148        position = w/2+25, h/2+25
 149    sprite = create_rect_sprite(bg_colour, w, h, position=position, **kwargs)
 150
 151    def update_value():
 152        while True: 
 153            sprite.write_text(f"{key}: {game.shared_data[key]}", font=font, colour=text_colour, offset=(w/2, h/2))
 154            yield update_period
 155
 156    sprite.when_game_start().add_handler(update_value)
 157    return sprite
 158
 159def create_circle_sprite(colour, radius:float, *args, **kwargs):
 160    """
 161    Create a circle sprite given the colour and radius
 162    Also optionally takes in any parameters that the `Sprite` constructor takes, except `frame_dict` and `starting_animation`
 163
 164    Example
 165    ```python
 166    green_transparent = (0, 255, 0, 127)
 167    my_rect_sprite = create_rect_sprite(green_transparent, radius=10)
 168    ```
 169
 170    Parameters
 171    ---
 172    colour: Tuple[int, int, int] or Tuple[int, int, int, int]
 173        The colour of the rectangle in RGB or RGBA. Value range: [0-255].
 174    radius: float
 175        the radius of the cirlce.
 176    \*args & \*\*kwargs: Optional
 177        Whatever the `Sprite` constructor takes, except `frame_dict` & `starting_animation`.
 178    """
 179
 180    circle = create_circle(colour, radius)
 181    return Sprite({"always":[circle]}, "always", *args, **kwargs)
 182
 183
 184def create_rect_sprite(colour, width, height, *args, **kwargs):
 185    """
 186    Create a rectanglar sprite given the colour, width and height
 187    Also optionally takes in any parameters that the `Sprite` constructor takes, except `frame_dict` and `starting_animation`
 188
 189    Example
 190    ```python
 191    green_transparent = (0, 255, 0, 127)
 192    my_rect_sprite = create_rect_sprite(green_transparent, width=10, height=20)
 193    ```
 194
 195    Parameters
 196    ---
 197    colour: Tuple[int, int, int] or Tuple[int, int, int, int]
 198        The colour of the rectangle in RGB or RGBA. Value range: [0-255].
 199    width: float
 200        the width (x length) of the rectangle.
 201    height: float
 202        the height (y length) of the rectangle.
 203    \*args & \*\*kwargs: Optional
 204        Whatever the `Sprite` constructor take, except `frame_dict` & `starting_animation`.
 205    """
 206    rect = create_rect(colour, width, height)
 207    return Sprite({"always":[rect]}, "always", *args, **kwargs)
 208
 209
 210def create_edge_sprites(edge_colour = (255, 0, 0), thickness=4, collision_type=1):
 211    """
 212    A convenience function to create the top, left, bottom and right edges as sprites
 213
 214    Usage
 215    ```python
 216    # consider putting the edges in settings.py
 217    top_edge, left_edge, bottom_edge, right_edge = create_edge_sprites()
 218    ```
 219    """
 220    
 221    # TODO: make the edge way thicker to avoid escape due to physics inaccuracy 
 222    # edges
 223    screen_w, screen_h = game._screen.get_width(), game._screen.get_height()
 224
 225    top_edge = create_rect_sprite(edge_colour, screen_w, thickness, (screen_w//2, 0),body_type= pymunk.Body.STATIC)
 226    bottom_edge = create_rect_sprite(edge_colour, screen_w, thickness, (screen_w//2, screen_h),body_type= pymunk.Body.STATIC)
 227    left_edge = create_rect_sprite(edge_colour, thickness, screen_h, (0, screen_h//2),body_type= pymunk.Body.STATIC)
 228    right_edge = create_rect_sprite(edge_colour, thickness, screen_h, (screen_w,  screen_h//2),body_type= pymunk.Body.STATIC)
 229
 230    top_edge.set_collision_type(collision_type)
 231    bottom_edge.set_collision_type(collision_type)
 232    left_edge.set_collision_type(collision_type)
 233    right_edge.set_collision_type(collision_type)
 234
 235    return top_edge, left_edge, bottom_edge, right_edge
 236
 237class _RotationStyle(Enum):
 238    ALL_AROUND = 0
 239    LEFTRIGHT = 1
 240    FIXED = 2
 241
 242
 243_FrameDictType = Dict[str, List[pygame.Surface]]
 244class _DrawingManager:
 245    def __init__(self, frame_dict, starting_animation):
 246
 247        
 248        self.frame_dict_original: _FrameDictType = {k: [i.copy() for i in v] for k, v in frame_dict.items()} # never changed
 249        self.frame_dict: _FrameDictType = {k: [i.copy() for i in v] for k, v in frame_dict.items()} # transformed on the spot
 250        
 251
 252        self.animation_name = starting_animation
 253        self.frames = self.frame_dict[self.animation_name]
 254        self.frame_idx: int = 0
 255
 256        # transforming parameters -> performed during update, but only when the transform is requested
 257        self.request_transform = False
 258        self.transparency_factor = 1.0
 259        self.brightness_factor = 1.0
 260        self.scale_factor: float = 1.0
 261        self.rotation_offset: float # TODO: to be implemented
 262
 263        def create_blit_surfaces():
 264            blit_surfaces = {}
 265            for k in self.frame_dict_original:
 266                for i in range(len(self.frame_dict_original[k])):
 267                    blit_surfaces[(k, i)] = []
 268            return blit_surfaces
 269        self.blit_surfaces: Dict[Tuple[str, int], List[Tuple[pygame.Surface, Tuple[float, float]]]] = create_blit_surfaces()
 270
 271
 272        # rotation and flips -> performed every update on the current frame
 273        self.rotation_style: _RotationStyle = _RotationStyle.ALL_AROUND
 274        self.flip_x: bool = False
 275        self.flip_y: bool = False
 276
 277    def set_rotation_style(self, flag: _RotationStyle):
 278        self.rotation_style = flag
 279
 280    def flip_horizontal(self):
 281        self.flip_x = not self.flip_x
 282
 283    def flip_vertical(self):
 284        self.flip_y = not self.flip_y
 285
 286    def set_animation(self, new_mode):
 287        if new_mode == self.animation_name:
 288            return 
 289        self.animation_name = new_mode
 290        self.frames = self.frame_dict[new_mode]
 291        self.frame_idx = 0
 292
 293    def set_frame(self, idx):
 294        # also allow direct setting of frame_idx
 295        self.frame_idx = idx
 296
 297    def next_frame(self):
 298        self.frame_idx = (self.frame_idx+1) % len(self.frames)
 299
 300    # core transform requests 
 301    def set_scale(self, factor):
 302        self.scale_factor = factor
 303        self.request_transform = True
 304
 305    def set_brightness(self, factor):
 306        self.brightness_factor = factor
 307        self.request_transform = True
 308
 309    def set_transparency(self, factor):
 310        self.transparency_factor = factor
 311        self.request_transform = True
 312
 313    def blit_persist(self, surface: pygame.Surface, offset=(0,0), centre=True, reset=True):
 314        w, h = surface.get_width(), surface.get_height()
 315        if centre:
 316            offset = (offset[0]-w/2, offset[1]-h/2)
 317        if reset: 
 318            self.blit_surfaces[(self.animation_name, self.frame_idx)] = [(surface, offset)]
 319        else: 
 320            self.blit_surfaces[(self.animation_name, self.frame_idx)].append((surface, offset))
 321        self.request_transform = True
 322        
 323    # transform related helper
 324    def scale_by(self, factor):
 325        self.set_scale(self.scale_factor*factor)
 326
 327    def write_text(self, text: str, font: pygame.font.Font, colour=(255,255,255), offset=(0,0), centre=True, reset=True):
 328        text_surface = font.render(text, True, colour) 
 329        self.blit_persist(text_surface, offset, centre, reset)
 330
 331    # transform
 332    def transform_frames(self):
 333        self.request_transform = False
 334        for k, frames in self.frame_dict_original.items():
 335            new_frames = []
 336            for idx, f in enumerate(frames):
 337                f_new = f.copy()
 338                for s, o in self.blit_surfaces[(k, idx)]:
 339                    f_new.blit(s, o)
 340                f_new = set_transparency(f_new, self.transparency_factor)
 341                f_new = adjust_brightness(f_new, self.brightness_factor)
 342                f_new = pygame.transform.scale_by(f_new, self.scale_factor)
 343                new_frames.append(f_new)
 344
 345            self.frame_dict[k] = new_frames
 346            
 347        self.frames = self.frame_dict[self.animation_name]
 348
 349    def on_update(self, x, y, angle) -> Tuple[pygame.Surface, pygame.Rect, pygame.Mask]:
 350        if self.request_transform:
 351            self.transform_frames()
 352
 353        img = self.frames[self.frame_idx]
 354
 355        if self.rotation_style == _RotationStyle.ALL_AROUND: 
 356            img = pygame.transform.rotate(img, -angle)
 357    
 358        elif self.rotation_style == _RotationStyle.LEFTRIGHT:
 359            if angle > -90 and angle < 90:
 360                pass
 361            else:
 362                img = pygame.transform.flip(img, True, False)
 363             
 364        elif self.rotation_style == _RotationStyle.FIXED:
 365            pass
 366
 367        img = pygame.transform.flip(img, self.flip_x, self.flip_y)
 368
 369        self.image = img
 370
 371        img_w, img_h = img.get_width(), img.get_height()
 372        rect = img.get_rect(
 373            center=(x, y),
 374              width=img_w,
 375              height=img_h,
 376              )
 377        mask = pygame.mask.from_surface(self.image, 127)
 378        return img, rect, mask 
 379
 380class ShapeType(Enum):
 381    BOX = 'box'
 382    CIRCLE = 'circle'
 383    CIRCLE_WIDTH = 'circle_width'
 384    CIRCLE_HEIGHT = 'circle_height'
 385    
 386
 387class _PhysicsManager:
 388    def __init__(self, game, body_type, shape_type, shape_size_factor, position, initial_image):
 389
 390        # shape properties that requires shape changes
 391        self.shape_type: ShapeType = shape_type
 392        self.collision_type: int = 1
 393        self.shape_size_factor: float = shape_size_factor
 394
 395        # shape properties that does not require shape changes
 396        self.elasticity: float = 1.0
 397        self.friction: float = 0
 398
 399        # update
 400        self.__request_shape_update = False
 401
 402        # core variables
 403        self.game = game
 404        self.space = game._space
 405
 406        self.body =  pymunk.Body(1, 100, body_type=body_type)
 407        self.body.position = position
 408        self.shape = self.create_new_shape(initial_image)
 409
 410        self.space.add(self.body, self.shape)   
 411
 412
 413
 414    def request_shape_update(self):
 415        self.__request_shape_update = True
 416
 417    def set_shape_type(self, shape_type: ShapeType):
 418        if shape_type == self.shape_type:
 419            return 
 420        self.shape_type = shape_type 
 421        self.__request_shape_update = True     
 422
 423    def set_shape_size_factor(self, shape_size_factor: float):
 424        if shape_size_factor == self.shape_size_factor:
 425            return 
 426        self.shape_size_factor = shape_size_factor 
 427        self.__request_shape_update = True     
 428
 429
 430    def set_collision_type(self, collision_type):
 431        if collision_type == self.collision_type:
 432            return 
 433        self.collision_type = collision_type 
 434        self.__request_shape_update = True
 435    
 436    def create_new_shape(self, image: pygame.Surface):
 437        rect = image.get_rect()
 438        width = rect.width*self.shape_size_factor
 439        height = rect.height*self.shape_size_factor
 440        
 441        if self.shape_type == ShapeType.BOX: 
 442            new_shape = pymunk.Poly.create_box(self.body, (width, height))
 443
 444        elif self.shape_type == ShapeType.CIRCLE:
 445            radius = (width+height)//4
 446            new_shape = pymunk.Circle(self.body,radius)
 447
 448        elif self.shape_type == ShapeType.CIRCLE_WIDTH:
 449            new_shape = pymunk.Circle(self.body, rect.width//2)
 450
 451        elif self.shape_type == ShapeType.CIRCLE_HEIGHT:
 452            new_shape = pymunk.Circle(self.body, height//2)
 453        else:
 454            raise ValueError('invalid shape_type')
 455        
 456        new_shape.collision_type = self.collision_type
 457        new_shape.elasticity = self.elasticity 
 458        new_shape.friction = self.friction 
 459
 460
 461        return new_shape
 462
 463
 464    def on_update(self, image: pygame.Surface):
 465        
 466        if self.__request_shape_update: 
 467            self.__request_shape_update = False
 468
 469            new_shape = self.create_new_shape(image)
 470
 471            game._cleanup_old_shape(self.shape)
 472            self.space.remove(self.shape)
 473
 474            self.shape = new_shape
 475            self.space.add(self.shape)         
 476
 477
 478
 479class Sprite(pygame.sprite.Sprite):
 480    """
 481    Objects of the Sprite class represents a sprite.
 482    """
 483    def __init__(
 484            self, 
 485            frame_dict: Dict[str, List[pygame.Surface]], 
 486            starting_mode:Optional[str]=None, 
 487            position= (100, 100), 
 488            shape_type = ShapeType.BOX, 
 489            shape_size_factor=1.0, 
 490            body_type=pymunk.Body.KINEMATIC
 491        ):
 492        """
 493        You might not need to create the sprite from this constructor function. 
 494        Consider functions like `create_single_costume_sprite` or `create_animated_sprite`
 495        as they would be easier to work with. 
 496
 497        Example:
 498        ```python
 499        image1 = helper.load_image("assets/image1.png")
 500        image2 = helper.load_image("assets/image2.png")
 501        image3 = helper.load_image("assets/image3.png")
 502        image4 = helper.load_image("assets/image4.png")
 503
 504        frame_dict = {"walking": [image1, image2], "idling": [image3, image4]}
 505        my_sprite = Sprite(frame_dict, "walking", shape_type="circle", body_type=pymunk.Body.DYNAMIC)
 506        
 507        # alternative (exactly the same)
 508        my_sprite = Sprite(frame_dict, "walking", shape_type=ShapeType.CIRCLE, body_type=pymunk.Body.DYNAMIC)
 509        ```
 510
 511        Parameters
 512        ---
 513        frame_dict: Dict[str, List[pygame.Surface]]
 514            A dictionary with different frame modes (str) as the keys 
 515            and lists of images as the values 
 516
 517        starting_mode:Optional[str]
 518            The starting frame mode. If not provided, 
 519            any one of the frame mode might be picked 
 520            as the starting frame mode.
 521
 522        position: Tuple[float, float]
 523
 524        shape_type: ShapeType 
 525            The collision shape. See `set_shape` for more details.
 526        shape_size_factor: float 
 527
 528        body_type: int 
 529            The pymunk body type. Leave out the parameter if unsure. 
 530            Can be `pymunk.Body.KINEMATIC`, `pymunk.Body.DYNAMIC` or `pymunk.Body.STATIC` 
 531            - Use kinematic if you want the sprite to move when when you tell it to. 
 532            - Use dynamic if you want the sprite to be freely moving by physics. Also refer to `set_collision_type` to enable collision.  
 533            - Use static if you do not want it to move at all. 
 534        """
 535        super().__init__()
 536
 537        self.image: pygame.Surface # rotated and flipped every update during self.update
 538        "@private"
 539        self.rect: pygame.Rect # depends on the rotated image and thus should be redefined during self.update
 540        "@private"
 541
 542        if starting_mode is None:
 543            starting_mode = next(iter(frame_dict))
 544
 545        self._drawing_manager = _DrawingManager(frame_dict, starting_mode)
 546        _initial_frame = frame_dict[starting_mode][0]
 547        self._physics_manager = _PhysicsManager(game, body_type, shape_type, shape_size_factor, position,_initial_frame)
 548        
 549        self.private_data = {}
 550        """
 551        A dictionary similar to `game.shared_data`. 
 552        You can put any data or variable that should belong to the individuals sprite. 
 553        A good example would be the health point of a charactor. 
 554
 555        Let say if you have a uncertain number of enemy in game created by cloning or otherwise, 
 556        it would be messy to put the health point of each enemy to `game.shared_data`. In this 
 557        case, putting the health point in the private data is a better choice.
 558
 559        Example:
 560        ```python
 561        my_sprite.private_data['hp'] = 10
 562
 563        def on_hit(damage):
 564            my_sprite.private_data['hp'] -= damage
 565            print("how much hp I have left: ", my_sprite.private_data['hp'])
 566
 567        my_sprite.when_received_message('hit').add_handler(on_hit)
 568        
 569        game.broadcast_message('hit', 2)
 570        ```
 571        """
 572
 573        self._mouse_selected = False
 574        self.__is_dragging = False
 575        self.draggable = False
 576        """Whether or not this sprite is draggable."""
 577
 578
 579        self._lock_to_sprite = None
 580        self._lock_offset = 0, 0
 581        self.__x, self.__y = self._physics_manager.body.position[0],  self._physics_manager.body.position[1]
 582
 583
 584        self.__direction: pymunk.Vec2d = self._body.rotation_vector     
 585        self.__rotation_style = _RotationStyle.ALL_AROUND
 586        
 587
 588        game._add_sprite(self)
 589
 590    def __getitem__(self, key):
 591        return self.private_data[key]
 592    
 593    def __setitem__(self, k, v):
 594        self.private_data[k] = v
 595
 596    @property
 597    def _body(self):
 598        return self._physics_manager.body    
 599    
 600    @property
 601    def _shape(self):
 602        return self._physics_manager.shape    
 603
 604    @override
 605    def update(self, space):
 606        "@private"
 607
 608        if self._lock_to_sprite:
 609            self._body.position = self._lock_to_sprite._body.position + (self.__x, self.__y) + self._lock_offset 
 610            self._body.velocity = 0, 0 
 611
 612        x, y = self._body.position
 613        self.image, self.rect, self.mask = self._drawing_manager.on_update(x, y, self.__direction.angle_degrees)
 614        
 615        #self.image = self.mask.to_surface()
 616        self._physics_manager.on_update(self.image)
 617        
 618        if self.__is_dragging:
 619            self._body.velocity=0,0 
 620            # TODO: should be done every physics loop or reset location every frame
 621            # or can i change it to kinamatic temporarily
 622
 623    def _is_mouse_selected(self):
 624        # TODO: wtf 
 625        return self._mouse_selected
 626    
 627    def set_draggable(self, draggable):
 628        """
 629        Set whether or not this sprite is draggable.
 630
 631        Example: 
 632        ```python
 633        # Make the sprite draggable
 634        my_sprite.set_draggable(True)
 635        ```
 636        """
 637        self.draggable = draggable
 638
 639    def _set_is_dragging(self, is_dragging):
 640        self.__is_dragging = is_dragging
 641
 642
 643    # START: motions   
 644    @property
 645    def x(self):
 646        """
 647        The x position of the sprite.
 648        You can change this property to change the x position of the sprite. 
 649
 650        Remember that the top-left corner is (x=0, y=0), 
 651        and x increases as the sprite goes right. 
 652
 653        so setting x to 0 sends the sprite to the left edge. 
 654
 655        Example: 
 656        ```python
 657        # moves the sprite 10 pixels to the right
 658        my_sprite.x += 10 
 659        ```
 660        """
 661        if self._lock_to_sprite: 
 662            return self.__x        
 663        return self._body.position[0]
 664    
 665    @property
 666    def y(self):
 667        """
 668        The y position of the sprite.
 669        You can change this property to change the y position of the sprite. 
 670        
 671        Remember that the top-left corner is (x=0, y=0), 
 672        and y increases as the sprite goes ***down***. 
 673
 674        so setting y to 0 sends the sprite to the top edge.         
 675
 676        Example: 
 677        ```python
 678        # moves the sprite 10 pixels down
 679        my_sprite.y += 10 
 680        ```
 681        """
 682        if self._lock_to_sprite: 
 683            return self.__y
 684        return self._body.position[1]
 685    
 686    @property
 687    def direction(self):
 688        """
 689        The direction of movement of the sprite. 
 690        Also rotates the sprite image depending on the rotation style.
 691        You can change this property to change the direction of movement of the sprite. 
 692
 693        - 0 degree is pointing to the left 
 694        - 90 degree is pointing ***down***
 695        - 180 degree is pointing to the right
 696        - -90 degree or 270 degree is pointing up 
 697
 698        Therefore, increasing this value turns the sprite clockwise
 699    
 700        (If you find it strange that 90 degree is pointing down, 
 701        it is because y is positive when going down)
 702
 703        Example: 
 704        ```python
 705        # moves the sprite 10 degrees clockwise
 706        my_sprite.direction += 10 
 707        ```        
 708        """
 709
 710        #self.__direction
 711        if self.__rotation_style == _RotationStyle.ALL_AROUND:
 712            return self._body.rotation_vector.angle_degrees
 713        else: 
 714            return self.__direction.angle_degrees
 715   
 716    @x.setter
 717    def x(self, v):
 718        if self._lock_to_sprite: 
 719            self.__x = v
 720        else: 
 721            self._body.position =  v, self._body.position[1]
 722        
 723    
 724    @y.setter
 725    def y(self, v):
 726        if self._lock_to_sprite: 
 727            self.__y = v
 728        else: 
 729            self._body.position = self._body.position[0], v
 730
 731    @direction.setter
 732    def direction(self, degree):
 733        if self.__rotation_style == _RotationStyle.ALL_AROUND:
 734            self._body.angle = degree/180*np.pi
 735
 736        self.__direction = pymunk.Vec2d.from_polar(1, degree/180*np.pi) 
 737        #print(self.__direction)
 738
 739
 740    def move_indir(self, steps: float):
 741        """
 742        Moves the sprite forward along `direction`.   
 743        """
 744        #self._body.position += 
 745        
 746        xs, ys = self.__direction*steps
 747        self.x += xs
 748        self.y += ys
 749        
 750    def move_across_dir(self, steps: float):
 751        """
 752        Moves the sprite forward along `direction` + 90 degrees  
 753        """
 754        xs, ys = self.__direction.perpendicular()*steps
 755        self.x += xs
 756        self.y += ys        
 757        
 758
 759    def move_xy(self, xy: Tuple[float, float]):
 760        """
 761        Increments both x and y. 
 762
 763        Example: 
 764        ```python
 765        # increase x by 10 and decrease y by 5
 766        my_sprite.move_xy((10, -5))
 767        ```
 768        """
 769        self.x += xy[0]
 770        self.y += xy[1]
 771
 772    def set_xy(self, xy: Tuple[float, float]):
 773        """
 774        Sets the x and y coordinate. 
 775
 776        Example: 
 777        ```python
 778        # put the sprite to the top-left corner
 779        my_sprite.set_xy((0, 0))
 780        ```
 781        """        
 782        self.x, self.y = xy
 783
 784
 785    def distance_to(self, position: Tuple[float, float]) -> float:
 786        """
 787        Gets the distance from the centre of this sprite to a location. 
 788
 789        Example: 
 790        ```python   
 791        # returns the distance to the centre of the screen
 792        distance_to_centre = my_sprite.distance_to((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
 793        ```
 794        """   
 795        return (position - self._body.position).length
 796
 797    def distance_to_sprite(self, sprite: Sprite)-> float:
 798        """
 799        Gets the distance between the centres of two sprites. 
 800        Returns one float or a tuple of two floats. 
 801
 802        Example: 
 803        ```python   
 804        # returns the distance to another sprite
 805        distance_to_centre = my_sprite.distance_to_sprite(my_sprite2)
 806        ```
 807        """  
 808
 809        return self.distance_to(sprite._body.position)
 810    
 811
 812    def point_towards(self, position: Tuple[float, float], offset_degree=0):
 813        """
 814        Changes the direction to point to a location. 
 815
 816        Example: 
 817        ```python   
 818        # point to the centre of the screen
 819        my_sprite.point_towards((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
 820        ```
 821        """          
 822
 823        
 824        rot_vec = (position - self._body.position).normalized()
 825        self.direction = (rot_vec.angle_degrees + offset_degree) 
 826
 827
 828    def point_towards_sprite(self, sprite: Sprite, offset_degree=0):
 829        """
 830        Changes the direction to point to a sprite. 
 831
 832        Example: 
 833        ```python   
 834        # point to another sprite
 835        my_sprite.point_towards_sprite(another_sprite2)
 836        ```
 837        """  
 838        self.point_towards(sprite._body.position, offset_degree)
 839
 840    def point_towards_mouse(self, offset_degree=0):
 841        """
 842        Changes the direction to point to the mouse. 
 843
 844        Example: 
 845        ```python   
 846        # point to the mouse
 847        my_sprite.point_towards_mouse()
 848        ```
 849        """  
 850        self.point_towards(pygame.mouse.get_pos(), offset_degree)
 851
 852
 853    
 854    def lock_to(self, sprite: Sprite, offset: Tuple[float, float], reset_xy = False):
 855        """
 856        *EXTENDED FEATURE*
 857
 858        Locks in the position of this sprite relative to the position of another sprite, 
 859        so the sprite will always be in the same location relative to the other sprite.  
 860        This method only need to run once (instead of continuously in a loop)
 861
 862        Example: 
 863        ```python
 864        # a very rudimentary text bubble
 865        text_bubble_sprite = create_rect_sprite(...)
 866
 867        # lock the position of the text_bubble_sprite relative to the player_sprite. 
 868        text_bubble_sprite.lock_to(player_sprite, offset=(-100, -100))
 869
 870        # a very rudimentary implementation that assumes 
 871        # that you won't have more than one text message within 3 seconds
 872        def on_message(data):
 873        
 874            text_bubble_sprite.write_text(data)
 875            text_bubble_sprite.show()
 876
 877            yield 3 # wait for three seconds
 878            text_bubble_sprite.hide()
 879
 880        text_bubble_sprite.when_received_message('dialogue').add_handler(on_message)
 881
 882        ```
 883        """
 884        assert self._body.body_type == pymunk.Body.KINEMATIC, "only KINEMATIC object can be locked to another sprite"
 885        
 886        self._lock_to_sprite = sprite
 887        self._lock_offset = offset
 888        if reset_xy: 
 889            self.x = 0
 890            self.y = 0
 891
 892    def release_position_lock(self):
 893        """
 894        *EXTENDED FEATURE*
 895
 896        Release the position lock set by `lock_to`
 897        """        
 898        self._lock_to_sprite = None
 899        self._lock_offset = None
 900        pass
 901
 902    # END: motions  
 903
 904
 905    # START: drawing related
 906    def set_rotation_style_all_around(self):
 907        """
 908        Same as the block "set rotation style [all around]" in Scratch. 
 909        Allow the image to rotate all around with `direction`
 910        """
 911        self._drawing_manager.set_rotation_style(_RotationStyle.ALL_AROUND)
 912        self.__rotation_style = _RotationStyle.ALL_AROUND
 913
 914    def set_rotation_style_left_right(self):
 915        """
 916        Same as the block "set rotation style [left-right]" in Scratch. 
 917        Only allows the image to flip left or right depending on the `direction`. 
 918
 919        Does not constrain the direction of movement to only left and right. 
 920        """        
 921        self._drawing_manager.set_rotation_style(_RotationStyle.LEFTRIGHT)
 922        self.__rotation_style = _RotationStyle.LEFTRIGHT
 923
 924    def set_rotation_style_no_rotation(self):
 925        """
 926        Same as the block "set rotation style [don't rotate]" in Scratch. 
 927        Does not allow the image flip or rotate with `direction`. 
 928
 929        Does not constrain the direction of movement.
 930
 931        """
 932        self._drawing_manager.set_rotation_style(_RotationStyle.FIXED)
 933        self.__rotation_style = _RotationStyle.FIXED
 934
 935
 936    def set_frame(self, idx:int):
 937        """
 938        Same as the block "switch costume to [costume]" in Scratch, 
 939        except that you are specifying the frame (i.e. the costume) by the index. 
 940        
 941        TODO: link to sprite creation 
 942        """
 943        self._drawing_manager.set_frame(idx)
 944    
 945    def next_frame(self):
 946        """
 947        Same as the block "next costume" in Scratch, 
 948        """
 949        self._drawing_manager.next_frame()
 950
 951    def set_animation(self, name:str):
 952        """
 953        *EXTENDED FEATURE*
 954
 955        Changes the set of frames that is used by `set_frame` and `next_frame`.
 956        This is mainly for sprites that have different animations for different actions. 
 957
 958        See the [guide](https://kwdchan.github.io/pyscratch/guides/2-adding-animated-sprites.html) for more details.
 959        """   
 960        self._drawing_manager.set_animation(name)
 961        
 962    @property
 963    def frame_idx(self):
 964        """
 965        In Scratch, this is the costume number. 
 966        
 967        To change costume, you will need to call `set_frame`. 
 968        """
 969        return self._drawing_manager.frame_idx
 970    
 971    @property
 972    def animation_name(self):
 973        """
 974        *EXTENDED FEATURE*
 975
 976        The name of the set of frames that is currently used. 
 977
 978        Set by `set_animation`
 979        """        
 980        return self._drawing_manager.animation_name
 981
 982    def set_scale(self, factor: float):
 983        """
 984        Sets the size factor of the sprite.
 985
 986        For example:
 987        - A factor of 1.0 means 100% of the *original* image size
 988        - A factor of 1.2 means 120%
 989        - A factor of 0.8 means 80%
 990        """
 991        self._drawing_manager.set_scale(factor)
 992        self._physics_manager.request_shape_update()
 993
 994    def scale_by(self, factor: float):
 995        """
 996        Changes the size of the sprite by a factor
 997
 998        For example:
 999        - A factor of 1.2 is a 20% increase of the *current* size (not original size)
1000        - A factor of 0.8 makes the sprite 80% of the *current* size
1001        """
1002        self._drawing_manager.scale_by(factor)
1003        self._physics_manager.request_shape_update()
1004
1005    @property
1006    def scale_factor(self):
1007        """
1008        The scale factor of the sprite size
1009        """
1010        return self._drawing_manager.scale_factor
1011
1012    def flip_horizontal(self):
1013        """
1014        Flips the image horizontally. 
1015        Does not affect the direction of movement.
1016        """        
1017        self._drawing_manager.flip_horizontal()
1018
1019    def flip_vertical(self):
1020        """
1021        Flips the image vertically. 
1022        Does not affect the direction of movement.
1023        """ 
1024        self._drawing_manager.flip_vertical()
1025
1026    def set_brightness(self, factor):
1027        """
1028        Changes the brightness of the sprite. 
1029        """ 
1030        self._drawing_manager.set_brightness(factor)
1031
1032    def set_transparency(self, factor):
1033        """
1034        Changes the transparency of the sprite. 
1035
1036        ***IMCOMPLETE IMPLEMENTATION***: 
1037        The transparency of the transparent background of the image is also changed
1038        """ 
1039        self._drawing_manager.set_transparency(factor)
1040
1041    def write_text(self, text: str, font: pygame.font.Font, colour=(255,255,255), offset=(0,0), centre=True, reset=True):
1042        """
1043        *EXTENDED FEATURE*
1044
1045        Writes text on the sprite given a font. 
1046        ```python
1047        # if the font is shared by multiple sprites, consider putting it in `settings.py`
1048        font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size
1049
1050        my_sprite.write_text("hello_world", font)
1051
1052        ```
1053        Parameters
1054        ---
1055        text: str
1056            The text to display.
1057
1058        font: pygame.font.Font
1059            The pygame font object. Refer to the website of pygame for more details. 
1060        
1061        colour: Tuple[int, int, int] or Tuple[int, int, int, int]
1062            The colour the of text. Takes RGB or RGBA, where A is the transparency. Value range: [0-255]
1063
1064        offset: Tuple[float, float]
1065            The location of the text image relative to the sprite
1066
1067        centre: bool
1068            If False, the top-left corner of the text, instead of the center, would be considered as its location.
1069        
1070        reset: bool
1071            Whether or not to clear all the existing drawing (including previous text)  
1072
1073        """ 
1074        text_surface = font.render(text, True, colour) 
1075        self._drawing_manager.blit_persist(text_surface, offset, centre=centre, reset=reset)
1076    
1077    def draw(self, image: pygame.Surface,  offset=(0,0), centre=True, reset=True):
1078        """
1079        *EXTENDED FEATURE*
1080
1081        Draws an image on the sprite.
1082        ```python
1083        an_image = pysc.helper.load_image("assets/an_image.png")
1084        my_sprite.draw(an_image)
1085        ```
1086        Parameters
1087        ---
1088        image: pygame.Surface
1089            An image (pygame surface). You can use `helper.load_image` to load the image for you.
1090
1091        offset: Tuple[float, float]
1092            The location of the image relative to the sprite
1093
1094        centre: bool
1095            If False, the top-left corner of the image, instead of the center, would be considered as its location.
1096        
1097        reset: bool
1098            Whether or not to clear all the existing drawing (including the text)  
1099
1100        """ 
1101
1102        self._drawing_manager.blit_persist(image, offset, centre=centre, reset=reset)
1103
1104    # END: drawing related    
1105    
1106
1107    ## other blocks
1108    def is_touching(self, other_sprite) -> bool:
1109        
1110        if not self in game._all_sprites_to_show: 
1111            return False
1112        
1113        if not other_sprite in game._all_sprites_to_show:
1114            return False
1115            
1116        
1117        if not pygame.sprite.collide_rect(self, other_sprite): 
1118            return False
1119        
1120        return not (pygame.sprite.collide_mask(self, other_sprite) is None)
1121    
1122    def is_touching_mouse(self):
1123        """
1124        Returns whether or not this sprite is touching the mouse
1125        """
1126        mos_x, mos_y = pygame.mouse.get_pos()
1127
1128        if not self.rect.collidepoint((mos_x, mos_y)): 
1129            return False
1130
1131        x = mos_x-self.rect.left
1132        y = mos_y-self.rect.top
1133        
1134        return self.mask.get_at((x, y))
1135    
1136        
1137
1138    # def is_touching(self, other_sprite) -> bool:
1139    #     """
1140    #     Returns whether or not this sprite is touching another sprite.
1141    #     """
1142    #     return pyscratch.game_module._is_touching(self, other_sprite)
1143    
1144    
1145    # def is_touching_mouse(self):
1146    #     """
1147    #     Returns whether or not this sprite is touching the mouse
1148    #     """
1149    #     return pyscratch.game_module._is_touching_mouse(self)
1150    
1151    def hide(self):
1152        """
1153        Hides the sprite. 
1154        The hidden sprite is still in the space and can still interact with other sprites.
1155        
1156        Just hidden. 
1157        """
1158        game.hide_sprite(self)
1159
1160    def show(self):
1161        """
1162        Shows the sprite.
1163        """        
1164        game.show_sprite(self)
1165
1166    @override
1167    def remove(self, *_):
1168        """
1169        Removes the sprite and all the events and conditions associated to it. 
1170        Takes no parameter.
1171
1172        Usage:
1173        ```python
1174        # remove the sprite.
1175        my_sprite.remove()
1176        ```
1177        """
1178        game.remove_sprite(self)
1179
1180
1181    def create_clone(self):
1182        """
1183        Create a clone of this sprite. 
1184        Even though is method is provided to align with Scratch, 
1185        The prefered way to create identitical or similar sprites 
1186        is to create the sprite within a function or an event. 
1187
1188        ***INCOMPLETE IMPLEMENTATION***: 
1189        - Transparency and brightness aren't transferred to the clone
1190        """
1191
1192        sprite = type(self)(
1193            frame_dict = self._drawing_manager.frame_dict_original, 
1194            starting_mode = self._drawing_manager.animation_name, 
1195            position = (self.x, self.y),
1196            shape_type = self._physics_manager.shape_type, 
1197            shape_size_factor = self._physics_manager.shape_size_factor, 
1198            body_type = self._body.body_type, 
1199        )
1200        if not self in game._all_sprites_to_show:
1201            game.hide_sprite(sprite)
1202
1203        if self.__rotation_style == _RotationStyle.LEFTRIGHT:
1204            sprite.set_rotation_style_left_right()
1205        elif self.__rotation_style == _RotationStyle.FIXED:
1206            sprite.set_rotation_style_no_rotation()
1207            
1208        sprite.direction = self.direction
1209        sprite.scale_by(self._drawing_manager.scale_factor)
1210        sprite.set_frame(self._drawing_manager.frame_idx)
1211        sprite.set_draggable(self.draggable)
1212        sprite.elasticity = self.elasticity
1213        sprite.friction = self.friction
1214
1215
1216
1217        if self._body.body_type == pymunk.Body.DYNAMIC: 
1218            sprite.mass = self.mass
1219            sprite.moment = self.moment
1220
1221        sprite._drawing_manager.set_rotation_style(self._drawing_manager.rotation_style)
1222
1223
1224        game._clone_event_manager.on_clone(self, sprite)
1225        return sprite
1226
1227
1228    # alias of pygame method
1229
1230    def when_game_start(self, other_associated_sprites: Iterable[Sprite]=[]):
1231        """
1232        Returns an `Event` that is triggered when you call `game.start`. 
1233        The event handler does not take in any parameter.
1234
1235        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1236
1237        Parameters
1238        ---
1239        other_associated_sprites: List[Sprite]
1240            A list of sprites that this event depends on. 
1241            Removal of any of these sprites leads to the removal of the event. 
1242        """
1243        
1244
1245        associated_sprites = list(other_associated_sprites) + [self]
1246        return game.when_game_start(associated_sprites)
1247
1248    def when_any_key_pressed(self, other_associated_sprites: Iterable[Sprite]=[]):
1249        """
1250        Returns an `Event` that is triggered when a key is pressed or released. 
1251        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1252        
1253        The event handler have to take two parameters:
1254        - **key** (str): The key that is pressed. For example, 'w', 'd', 'left', 'right', 'space'. 
1255            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1256        
1257        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1258
1259        Parameters
1260        ---
1261        other_associated_sprites: List[Sprite]
1262            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1263    
1264        """    
1265        associated_sprites = list(other_associated_sprites) + [self]
1266        return game.when_any_key_pressed(associated_sprites)
1267
1268    def when_key_pressed(self, key, other_associated_sprites: Iterable[Sprite]=[]):
1269        """   
1270        Returns an `Event` that is triggered when a specific key is pressed or released. 
1271        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1272
1273        The event handler have to take one parameter:
1274        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1275        
1276        Parameters
1277        ---
1278        key: str
1279            The key that triggers the event. For example, 'w', 'd', 'left', 'right', 'space'. 
1280            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1281
1282        other_associated_sprites: List[Sprite]
1283            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1284        """
1285             
1286        associated_sprites = list(other_associated_sprites) + [self]
1287        return game.when_key_pressed(key, associated_sprites)
1288    
1289
1290    
1291    def when_this_sprite_clicked(self, other_associated_sprites: Iterable[Sprite]=[]):
1292        """
1293        Returns an `Event` that is triggered when the given sprite is clicked by mouse. 
1294        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1295
1296        The event handler does not take in any parameter.
1297                
1298        Parameters
1299        ---
1300        sprite: Sprite
1301            The sprite on which you want the click to be detected. The removal of this sprite will lead to the removal of this event so
1302            it does not need to be included in `other_assoicated_sprite`
1303        
1304        other_associated_sprites: List[Sprite]
1305            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1306        """        
1307        return game.when_this_sprite_clicked(self, other_associated_sprites)
1308       
1309    def when_backdrop_switched(self, idx, other_associated_sprites : Iterable[Sprite]=[]):
1310        """
1311        Returns an `Event` that is triggered when the game is switched to a backdrop at `backdrop_index`.
1312        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1313        
1314        The event handler does not take in any parameter.
1315
1316        Parameters
1317        ---
1318        backdrop_index: int
1319            The index of the backdrop  
1320
1321        other_associated_sprites: List[Sprite]
1322            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1323        """
1324        
1325        associated_sprites = list(other_associated_sprites) + [self]
1326        return game.when_backdrop_switched(idx, associated_sprites)
1327    
1328    def when_any_backdrop_switched(self, other_associated_sprites : Iterable[Sprite]=[]):
1329        """
1330        Returns an `Event` that is triggered when the backdrop is switched. 
1331        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1332        
1333        The event handler have to take one parameter:
1334        - **idx** (int): The index of the new backdrop  
1335        
1336        Parameters
1337        ---
1338        other_associated_sprites: List[Sprite]
1339            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1340        """        
1341        associated_sprites = list(other_associated_sprites) + [self]
1342        return game.when_any_backdrop_switched(associated_sprites)
1343
1344    def when_timer_above(self, t, other_associated_sprites : Iterable[Sprite]=[]):
1345        """      
1346        Returns a `Condition` that is triggered after the game have started for `t` seconds.
1347        A `Condition` works the same way an `Event` does. 
1348
1349        Also associates the condition to the sprite so the condition is removed when the sprite is removed. 
1350
1351
1352        The event handler have to take one parameter:
1353        - **n** (int): This value will always be zero
1354
1355        Parameters
1356        ---
1357        other_associated_sprites: List[Sprite]
1358            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1359        """        
1360        associated_sprites = list(other_associated_sprites) + [self]
1361        return game.when_timer_above(t, associated_sprites)
1362    
1363    def when_started_as_clone(self, associated_sprites: Iterable[Sprite]=[]):
1364        """
1365        Returns an `Event` that is triggered after the given sprite is cloned by `Sprite.create_clone`.
1366        Cloning of the clone will also trigger the event. Thus the removal of original sprite does not remove the event. 
1367
1368        The event handler have to take one parameter:
1369        - **clone_sprite** (Sprite): The newly created clone.
1370                
1371        Parameters
1372        ---
1373        associated_sprites: List[Sprite]
1374            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1375        """
1376                
1377        return game.when_started_as_clone(self, associated_sprites)    
1378
1379    def when_receive_message(self, topic: str, other_associated_sprites : Iterable[Sprite]=[]):
1380        """
1381        Returns an `Event` that is triggered after a message of the given `topic` is broadcasted.
1382        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1383        
1384        The event handler have to take one parameter:
1385        - **data** (Any): This parameter can be anything passed on by the message.
1386
1387        Parameters
1388        ---
1389        topic: str
1390            Can be any string. If the topic equals the topic of a broadcast, the event will be triggered. 
1391        other_associated_sprites: List[Sprite]
1392            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1393        """
1394        
1395        
1396        associated_sprites = list(other_associated_sprites) + [self]
1397        return game.when_receive_message(topic, associated_sprites)
1398    
1399    def broadcast_message(self, topic: str, data: Any=None):
1400        """
1401        Completely the same as `game.broadcast_message`. 
1402        Just an alias. 
1403
1404        Sends a message of a given `topic` and `data`.
1405        Triggers any event that subscribes to the topic. 
1406        The handlers of the events will receive `data` as the parameter.
1407
1408        Example:
1409        ```python
1410        def event_handler(data):
1411            print(data) # data will be "hello world!"
1412
1413        my_sprite.when_receive_message('print_message').add_handler(event_handler)
1414        my_sprite2.broadcast_message('print_message', data='hello world!')
1415
1416        # "hello world!" will be printed out
1417        ```
1418        Parameters
1419        ---
1420        topic: str
1421            Can be any string. If the topic of an message event equals the topic of the broadcast, the event will be triggered. 
1422
1423        data: Any
1424            Any arbitory data that will be passed to the event handler
1425        
1426        """
1427        return game.broadcast_message(topic, data)
1428
1429
1430    ## additional events
1431    def when_condition_met(self, checker=lambda: False, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1432        """
1433        *EXTENDED FEATURE*
1434
1435        DOCUMENTATION NOT COMPLETED
1436
1437        Parameters
1438        ---
1439        associated_sprites: List[Sprite]
1440            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1441        """
1442        associated_sprites = list(other_associated_sprites) + [self]
1443
1444        return game.when_condition_met(checker, repeats, associated_sprites)
1445    
1446    
1447    def when_timer_reset(self, reset_period=np.inf, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1448        """
1449        *EXTENDED FEATURE*
1450
1451        DOCUMENTATION NOT COMPLETED
1452
1453        Parameters
1454        ---
1455        associated_sprites: List[Sprite]
1456            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1457        """
1458        associated_sprites = list(other_associated_sprites) + [self]
1459
1460        return game.when_timer_reset(reset_period, repeats, associated_sprites)
1461    
1462    
1463    def create_specific_collision_trigger(self, other_sprite: Sprite, other_associated_sprites: Iterable[Sprite]=[]):
1464        """
1465        *EXTENDED FEATURE*
1466
1467        DOCUMENTATION NOT COMPLETED
1468
1469        Parameters
1470        ---
1471        associated_sprites: List[Sprite]
1472            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1473        """
1474        return game.create_specific_collision_trigger(self, other_sprite, other_associated_sprites)
1475    
1476
1477    # START: TODO: physics property getters and setters
1478
1479    def set_shape(self, shape_type: ShapeType=ShapeType.BOX):
1480        """
1481        Sets the collision shape of the sprite. The shape type can be one of the followings
1482        - box
1483        - circle
1484        - circle_height
1485        - circle_width
1486
1487        You can think of the collision shape as the actual shape of the sprite, 
1488        while the sprite image (the costume) is just like a phantom projection 
1489        that cannot be touched.
1490
1491        To see what it means, set `debug_draw` to True when you start the game. 
1492        ```python
1493        game.start(60, debug_draw=True)
1494        ```
1495        """
1496        self._physics_manager.set_shape_type(shape_type)
1497
1498    def set_shape_size_factor(self, factor=0.8):
1499        """
1500        Changes the size of the collision shape relative to the size of the image of the sprite. 
1501        For example: 
1502        - factor = 1.0 -> same size
1503        - factor = 0.8 -> the collision shape is 80% of the sprite image 
1504        - factor = 1.2 -> the collision shape is 120% of the sprite image
1505        
1506        """
1507        self._physics_manager.set_shape_size_factor(factor)
1508
1509    def set_collision_type(self, value: int=0):
1510        """
1511        *EXTENDED FEATURE*
1512
1513        Set the collision type of the sprite for detection purposes.
1514        The collision type can be any integer except that 
1515        **a sprite with a collision type of 0 (which is the default) will not collide with anything.**
1516
1517        Note that touching can still be detected.
1518        """
1519        self._physics_manager.set_collision_type(value)
1520
1521    @property
1522    def mass(self):
1523        """
1524        *EXTENDED FEATURE*
1525
1526        The mass of the collision shape. 
1527        Only work for dynamic objects.
1528
1529        You can make changes to this property. 
1530        """
1531        return self._body.mass
1532    
1533    @property
1534    def moment(self):
1535        """
1536        *EXTENDED FEATURE*
1537
1538        The moment of the collision shape. 
1539        The lower it is, the more easy it spins. 
1540        Only work for dynamic objects.
1541
1542        You can make changes to this property. 
1543        """
1544        return self._body.moment
1545    
1546    @property
1547    def elasticity(self):
1548        """
1549        *EXTENDED FEATURE*
1550
1551        The elasticity of the collision shape. 
1552        Elasticity of 1 means no energy loss after each collision. 
1553
1554        You can make changes to this property. 
1555        """
1556        return self._shape.elasticity
1557    
1558    @property
1559    def friction(self):
1560        """
1561        *EXTENDED FEATURE*
1562
1563        The friction of the collision shape. 
1564
1565        You can make changes to this property. 
1566        """
1567        return self._shape.friction
1568    
1569    @mass.setter
1570    def mass(self, value):
1571        self._body.mass = value
1572
1573    @moment.setter
1574    def moment(self, value):
1575        self._body.moment = value
1576    
1577    @elasticity.setter
1578    def elasticity(self, value):
1579        self._physics_manager.elasticity = value
1580    
1581    @friction.setter
1582    def friction(self, value):
1583        self._physics_manager.friction = value
1584    
1585    # END: physics property
1586
1587    
def create_animated_sprite(folder_path, *args, **kwargs):
26def create_animated_sprite(folder_path,  *args, **kwargs):
27    """
28    Create a sprite using the images inside a folder. 
29
30    The folder should be organised in one of these two ways: 
31
32    **Option 1**: Only one frame mode. *Note that the images must be numbered.*
33    ```
34    ├─ player/
35        ├─ 0.png
36        ├─ 1.png
37        ├─ 2.png
38        ...
39    ```
40
41    **Option 2**: Multiple frame modes. *Note that the images must be numbered.*
42    ```
43    ├─ player/  
44        ├─ walking/  
45            ├─ 0.png  
46            ├─ 1.png  
47            ...
48        ├─ idling/
49            ├─ 0.png
50            ├─ 1.png
51            ... 
52        ...
53    ``` 
54
55
56    Example
57    ```python
58    # takes the path of the folder 
59    my_sprite1 = create_animated_sprite("assets/player") 
60    
61    # optionally take in whatever parameters that the `Sprite` constructor take. 
62    my_sprite2 = create_animated_sprite("assets/player", position=(200, 200)) 
63    
64    # For option 2 only: optionally set the `starting_animation` parameter in the `Sprite` constructor, but it'd still be fine without it.
65    my_sprite3 = create_animated_sprite("assets/player", "idling")
66    
67    ```
68    Parameters
69    ---
70    folder_path: str
71        The path of the folder that contains the images
72
73    \*args & \*\*kwargs: Optional
74        Whatever the `Sprite` constructor takes, except the `frame_dict` parameter.
75    """
76    frame_dict = load_frames_from_folder(folder_path)
77    return Sprite(frame_dict, *args, **kwargs)

Create a sprite using the images inside a folder.

The folder should be organised in one of these two ways:

Option 1: Only one frame mode. Note that the images must be numbered.

├─ player/
    ├─ 0.png
    ├─ 1.png
    ├─ 2.png
    ...

Option 2: Multiple frame modes. Note that the images must be numbered.

├─ player/  
    ├─ walking/  
        ├─ 0.png  
        ├─ 1.png  
        ...
    ├─ idling/
        ├─ 0.png
        ├─ 1.png
        ... 
    ...

Example

# takes the path of the folder 
my_sprite1 = create_animated_sprite("assets/player") 

# optionally take in whatever parameters that the `Sprite` constructor take. 
my_sprite2 = create_animated_sprite("assets/player", position=(200, 200)) 

# For option 2 only: optionally set the `starting_animation` parameter in the `Sprite` constructor, but it'd still be fine without it.
my_sprite3 = create_animated_sprite("assets/player", "idling")
Parameters
  • folder_path (str): The path of the folder that contains the images
  • *args & **kwargs (Optional): Whatever the Sprite constructor takes, except the frame_dict parameter.
def create_single_costume_sprite(image_path, *args, **kwargs):
 80def create_single_costume_sprite(image_path, *args, **kwargs):
 81    """
 82    Create a sprite with only one costume, given the path to the image
 83    
 84    Example
 85    ```python
 86    my_sprite1 = create_single_costume_sprite("assets/player.png")
 87
 88    # Optionally pass some parameters to the `Sprite` constructor
 89    my_sprite2 = create_single_costume_sprite("assets/player.png", position=(200, 200)) 
 90    
 91    ```
 92    Parameters
 93    ---
 94    image_path: str
 95        The path of the images
 96
 97    \*args & \*\*kwargs: Optional
 98        Whatever the `Sprite` constructor takes, except `frame_dict` & `starting_animation`.
 99    """
100    img = pygame.image.load(image_path).convert_alpha()
101    frame_dict = {"always": [img]}
102    return Sprite(frame_dict, "always", *args, **kwargs)

Create a sprite with only one costume, given the path to the image

Example

my_sprite1 = create_single_costume_sprite("assets/player.png")

# Optionally pass some parameters to the `Sprite` constructor
my_sprite2 = create_single_costume_sprite("assets/player.png", position=(200, 200)) 
Parameters
  • image_path (str): The path of the images
  • *args & **kwargs (Optional): Whatever the Sprite constructor takes, except frame_dict & starting_animation.
def create_shared_data_display_sprite( key, font, size=(150, 50), bg_colour=(127, 127, 127), text_colour=(255, 255, 255), position: Optional[Tuple[float, float]] = None, update_period=0.1, **kwargs):
105def create_shared_data_display_sprite(key, font, size = (150, 50), bg_colour=(127, 127, 127), text_colour=(255,255,255), position: Optional[Tuple[float, float]]=None, update_period=0.1, **kwargs):
106    """
107    Create a display for a variable inside shared_data given the dictionary key (i.e. the name of the variable). 
108    The variable display will update every `update_period` seconds.
109    The variable display is created as a sprite. 
110
111    This function is created using only basic pyscratch functions and events. 
112    If you want a more customised display, you may want to have a look at the source code as an example how it's done.
113
114    Example
115    ```python
116    
117    # if the font is shared by multiple sprites, consider putting it in `settings.py`
118    font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size
119
120    # the shared data
121    game.shared_data['hp'] = 10
122    
123    # the variable display is created as a sprite 
124    health_display = create_shared_data_display_sprite('hp', font, position=(100, 100), update_period=0.5)
125    
126    ```
127    Parameters
128    ---
129    key: str
130        The dictionary key of the variable in `game.shared_data` that you want to display.
131    font: pygame.font.Font
132        The pygame font object. Refer to the website of pygame for more details.
133    size: Tuple[float, float]
134        The size of the display panel
135    bg_colour: Tuple[int, int, int] or Tuple[int, int, int, int] 
136        The colour of the display panel in RGB or RGBA. Value range: [0-255]
137    text_colour: Tuple[int, int, int] or Tuple[int, int, int, int] 
138        The colour of the text in RGB or RGBA. Value range: [0-255]
139    position: Tuple[int, int] 
140        The position of the display
141    update_period: float
142        The variable display will update every `update_period` seconds.
143    \*\*kwargs: Optional
144        Whatever the `Sprite` constructor takes, except `frame_dict`,`starting_animation` & `position`
145    """
146
147    w, h = size
148    if position is None:
149        position = w/2+25, h/2+25
150    sprite = create_rect_sprite(bg_colour, w, h, position=position, **kwargs)
151
152    def update_value():
153        while True: 
154            sprite.write_text(f"{key}: {game.shared_data[key]}", font=font, colour=text_colour, offset=(w/2, h/2))
155            yield update_period
156
157    sprite.when_game_start().add_handler(update_value)
158    return sprite

Create a display for a variable inside shared_data given the dictionary key (i.e. the name of the variable). The variable display will update every update_period seconds. The variable display is created as a sprite.

This function is created using only basic pyscratch functions and events. If you want a more customised display, you may want to have a look at the source code as an example how it's done.

Example

# if the font is shared by multiple sprites, consider putting it in `settings.py`
font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size

# the shared data
game.shared_data['hp'] = 10

# the variable display is created as a sprite 
health_display = create_shared_data_display_sprite('hp', font, position=(100, 100), update_period=0.5)
Parameters
  • key (str): The dictionary key of the variable in game.shared_data that you want to display.
  • font (pygame.font.Font): The pygame font object. Refer to the website of pygame for more details.
  • size (Tuple[float, float]): The size of the display panel
  • bg_colour (Tuple[int, int, int] or Tuple[int, int, int, int]): The colour of the display panel in RGB or RGBA. Value range: [0-255]
  • text_colour (Tuple[int, int, int] or Tuple[int, int, int, int]): The colour of the text in RGB or RGBA. Value range: [0-255]
  • position (Tuple[int, int]): The position of the display
  • update_period (float): The variable display will update every update_period seconds.
  • **kwargs (Optional): Whatever the Sprite constructor takes, except frame_dict,starting_animation & position
def create_circle_sprite(colour, radius: float, *args, **kwargs):
160def create_circle_sprite(colour, radius:float, *args, **kwargs):
161    """
162    Create a circle sprite given the colour and radius
163    Also optionally takes in any parameters that the `Sprite` constructor takes, except `frame_dict` and `starting_animation`
164
165    Example
166    ```python
167    green_transparent = (0, 255, 0, 127)
168    my_rect_sprite = create_rect_sprite(green_transparent, radius=10)
169    ```
170
171    Parameters
172    ---
173    colour: Tuple[int, int, int] or Tuple[int, int, int, int]
174        The colour of the rectangle in RGB or RGBA. Value range: [0-255].
175    radius: float
176        the radius of the cirlce.
177    \*args & \*\*kwargs: Optional
178        Whatever the `Sprite` constructor takes, except `frame_dict` & `starting_animation`.
179    """
180
181    circle = create_circle(colour, radius)
182    return Sprite({"always":[circle]}, "always", *args, **kwargs)

Create a circle sprite given the colour and radius Also optionally takes in any parameters that the Sprite constructor takes, except frame_dict and starting_animation

Example

green_transparent = (0, 255, 0, 127)
my_rect_sprite = create_rect_sprite(green_transparent, radius=10)
Parameters
  • colour (Tuple[int, int, int] or Tuple[int, int, int, int]): The colour of the rectangle in RGB or RGBA. Value range: [0-255].
  • radius (float): the radius of the cirlce.
  • *args & **kwargs (Optional): Whatever the Sprite constructor takes, except frame_dict & starting_animation.
def create_rect_sprite(colour, width, height, *args, **kwargs):
185def create_rect_sprite(colour, width, height, *args, **kwargs):
186    """
187    Create a rectanglar sprite given the colour, width and height
188    Also optionally takes in any parameters that the `Sprite` constructor takes, except `frame_dict` and `starting_animation`
189
190    Example
191    ```python
192    green_transparent = (0, 255, 0, 127)
193    my_rect_sprite = create_rect_sprite(green_transparent, width=10, height=20)
194    ```
195
196    Parameters
197    ---
198    colour: Tuple[int, int, int] or Tuple[int, int, int, int]
199        The colour of the rectangle in RGB or RGBA. Value range: [0-255].
200    width: float
201        the width (x length) of the rectangle.
202    height: float
203        the height (y length) of the rectangle.
204    \*args & \*\*kwargs: Optional
205        Whatever the `Sprite` constructor take, except `frame_dict` & `starting_animation`.
206    """
207    rect = create_rect(colour, width, height)
208    return Sprite({"always":[rect]}, "always", *args, **kwargs)

Create a rectanglar sprite given the colour, width and height Also optionally takes in any parameters that the Sprite constructor takes, except frame_dict and starting_animation

Example

green_transparent = (0, 255, 0, 127)
my_rect_sprite = create_rect_sprite(green_transparent, width=10, height=20)
Parameters
  • colour (Tuple[int, int, int] or Tuple[int, int, int, int]): The colour of the rectangle in RGB or RGBA. Value range: [0-255].
  • width (float): the width (x length) of the rectangle.
  • height (float): the height (y length) of the rectangle.
  • *args & **kwargs (Optional): Whatever the Sprite constructor take, except frame_dict & starting_animation.
def create_edge_sprites(edge_colour=(255, 0, 0), thickness=4, collision_type=1):
211def create_edge_sprites(edge_colour = (255, 0, 0), thickness=4, collision_type=1):
212    """
213    A convenience function to create the top, left, bottom and right edges as sprites
214
215    Usage
216    ```python
217    # consider putting the edges in settings.py
218    top_edge, left_edge, bottom_edge, right_edge = create_edge_sprites()
219    ```
220    """
221    
222    # TODO: make the edge way thicker to avoid escape due to physics inaccuracy 
223    # edges
224    screen_w, screen_h = game._screen.get_width(), game._screen.get_height()
225
226    top_edge = create_rect_sprite(edge_colour, screen_w, thickness, (screen_w//2, 0),body_type= pymunk.Body.STATIC)
227    bottom_edge = create_rect_sprite(edge_colour, screen_w, thickness, (screen_w//2, screen_h),body_type= pymunk.Body.STATIC)
228    left_edge = create_rect_sprite(edge_colour, thickness, screen_h, (0, screen_h//2),body_type= pymunk.Body.STATIC)
229    right_edge = create_rect_sprite(edge_colour, thickness, screen_h, (screen_w,  screen_h//2),body_type= pymunk.Body.STATIC)
230
231    top_edge.set_collision_type(collision_type)
232    bottom_edge.set_collision_type(collision_type)
233    left_edge.set_collision_type(collision_type)
234    right_edge.set_collision_type(collision_type)
235
236    return top_edge, left_edge, bottom_edge, right_edge

A convenience function to create the top, left, bottom and right edges as sprites

Usage

# consider putting the edges in settings.py
top_edge, left_edge, bottom_edge, right_edge = create_edge_sprites()
class ShapeType(enum.Enum):
381class ShapeType(Enum):
382    BOX = 'box'
383    CIRCLE = 'circle'
384    CIRCLE_WIDTH = 'circle_width'
385    CIRCLE_HEIGHT = 'circle_height'
BOX = <ShapeType.BOX: 'box'>
CIRCLE = <ShapeType.CIRCLE: 'circle'>
CIRCLE_WIDTH = <ShapeType.CIRCLE_WIDTH: 'circle_width'>
CIRCLE_HEIGHT = <ShapeType.CIRCLE_HEIGHT: 'circle_height'>
class Sprite(pygame.sprite.Sprite):
 480class Sprite(pygame.sprite.Sprite):
 481    """
 482    Objects of the Sprite class represents a sprite.
 483    """
 484    def __init__(
 485            self, 
 486            frame_dict: Dict[str, List[pygame.Surface]], 
 487            starting_mode:Optional[str]=None, 
 488            position= (100, 100), 
 489            shape_type = ShapeType.BOX, 
 490            shape_size_factor=1.0, 
 491            body_type=pymunk.Body.KINEMATIC
 492        ):
 493        """
 494        You might not need to create the sprite from this constructor function. 
 495        Consider functions like `create_single_costume_sprite` or `create_animated_sprite`
 496        as they would be easier to work with. 
 497
 498        Example:
 499        ```python
 500        image1 = helper.load_image("assets/image1.png")
 501        image2 = helper.load_image("assets/image2.png")
 502        image3 = helper.load_image("assets/image3.png")
 503        image4 = helper.load_image("assets/image4.png")
 504
 505        frame_dict = {"walking": [image1, image2], "idling": [image3, image4]}
 506        my_sprite = Sprite(frame_dict, "walking", shape_type="circle", body_type=pymunk.Body.DYNAMIC)
 507        
 508        # alternative (exactly the same)
 509        my_sprite = Sprite(frame_dict, "walking", shape_type=ShapeType.CIRCLE, body_type=pymunk.Body.DYNAMIC)
 510        ```
 511
 512        Parameters
 513        ---
 514        frame_dict: Dict[str, List[pygame.Surface]]
 515            A dictionary with different frame modes (str) as the keys 
 516            and lists of images as the values 
 517
 518        starting_mode:Optional[str]
 519            The starting frame mode. If not provided, 
 520            any one of the frame mode might be picked 
 521            as the starting frame mode.
 522
 523        position: Tuple[float, float]
 524
 525        shape_type: ShapeType 
 526            The collision shape. See `set_shape` for more details.
 527        shape_size_factor: float 
 528
 529        body_type: int 
 530            The pymunk body type. Leave out the parameter if unsure. 
 531            Can be `pymunk.Body.KINEMATIC`, `pymunk.Body.DYNAMIC` or `pymunk.Body.STATIC` 
 532            - Use kinematic if you want the sprite to move when when you tell it to. 
 533            - Use dynamic if you want the sprite to be freely moving by physics. Also refer to `set_collision_type` to enable collision.  
 534            - Use static if you do not want it to move at all. 
 535        """
 536        super().__init__()
 537
 538        self.image: pygame.Surface # rotated and flipped every update during self.update
 539        "@private"
 540        self.rect: pygame.Rect # depends on the rotated image and thus should be redefined during self.update
 541        "@private"
 542
 543        if starting_mode is None:
 544            starting_mode = next(iter(frame_dict))
 545
 546        self._drawing_manager = _DrawingManager(frame_dict, starting_mode)
 547        _initial_frame = frame_dict[starting_mode][0]
 548        self._physics_manager = _PhysicsManager(game, body_type, shape_type, shape_size_factor, position,_initial_frame)
 549        
 550        self.private_data = {}
 551        """
 552        A dictionary similar to `game.shared_data`. 
 553        You can put any data or variable that should belong to the individuals sprite. 
 554        A good example would be the health point of a charactor. 
 555
 556        Let say if you have a uncertain number of enemy in game created by cloning or otherwise, 
 557        it would be messy to put the health point of each enemy to `game.shared_data`. In this 
 558        case, putting the health point in the private data is a better choice.
 559
 560        Example:
 561        ```python
 562        my_sprite.private_data['hp'] = 10
 563
 564        def on_hit(damage):
 565            my_sprite.private_data['hp'] -= damage
 566            print("how much hp I have left: ", my_sprite.private_data['hp'])
 567
 568        my_sprite.when_received_message('hit').add_handler(on_hit)
 569        
 570        game.broadcast_message('hit', 2)
 571        ```
 572        """
 573
 574        self._mouse_selected = False
 575        self.__is_dragging = False
 576        self.draggable = False
 577        """Whether or not this sprite is draggable."""
 578
 579
 580        self._lock_to_sprite = None
 581        self._lock_offset = 0, 0
 582        self.__x, self.__y = self._physics_manager.body.position[0],  self._physics_manager.body.position[1]
 583
 584
 585        self.__direction: pymunk.Vec2d = self._body.rotation_vector     
 586        self.__rotation_style = _RotationStyle.ALL_AROUND
 587        
 588
 589        game._add_sprite(self)
 590
 591    def __getitem__(self, key):
 592        return self.private_data[key]
 593    
 594    def __setitem__(self, k, v):
 595        self.private_data[k] = v
 596
 597    @property
 598    def _body(self):
 599        return self._physics_manager.body    
 600    
 601    @property
 602    def _shape(self):
 603        return self._physics_manager.shape    
 604
 605    @override
 606    def update(self, space):
 607        "@private"
 608
 609        if self._lock_to_sprite:
 610            self._body.position = self._lock_to_sprite._body.position + (self.__x, self.__y) + self._lock_offset 
 611            self._body.velocity = 0, 0 
 612
 613        x, y = self._body.position
 614        self.image, self.rect, self.mask = self._drawing_manager.on_update(x, y, self.__direction.angle_degrees)
 615        
 616        #self.image = self.mask.to_surface()
 617        self._physics_manager.on_update(self.image)
 618        
 619        if self.__is_dragging:
 620            self._body.velocity=0,0 
 621            # TODO: should be done every physics loop or reset location every frame
 622            # or can i change it to kinamatic temporarily
 623
 624    def _is_mouse_selected(self):
 625        # TODO: wtf 
 626        return self._mouse_selected
 627    
 628    def set_draggable(self, draggable):
 629        """
 630        Set whether or not this sprite is draggable.
 631
 632        Example: 
 633        ```python
 634        # Make the sprite draggable
 635        my_sprite.set_draggable(True)
 636        ```
 637        """
 638        self.draggable = draggable
 639
 640    def _set_is_dragging(self, is_dragging):
 641        self.__is_dragging = is_dragging
 642
 643
 644    # START: motions   
 645    @property
 646    def x(self):
 647        """
 648        The x position of the sprite.
 649        You can change this property to change the x position of the sprite. 
 650
 651        Remember that the top-left corner is (x=0, y=0), 
 652        and x increases as the sprite goes right. 
 653
 654        so setting x to 0 sends the sprite to the left edge. 
 655
 656        Example: 
 657        ```python
 658        # moves the sprite 10 pixels to the right
 659        my_sprite.x += 10 
 660        ```
 661        """
 662        if self._lock_to_sprite: 
 663            return self.__x        
 664        return self._body.position[0]
 665    
 666    @property
 667    def y(self):
 668        """
 669        The y position of the sprite.
 670        You can change this property to change the y position of the sprite. 
 671        
 672        Remember that the top-left corner is (x=0, y=0), 
 673        and y increases as the sprite goes ***down***. 
 674
 675        so setting y to 0 sends the sprite to the top edge.         
 676
 677        Example: 
 678        ```python
 679        # moves the sprite 10 pixels down
 680        my_sprite.y += 10 
 681        ```
 682        """
 683        if self._lock_to_sprite: 
 684            return self.__y
 685        return self._body.position[1]
 686    
 687    @property
 688    def direction(self):
 689        """
 690        The direction of movement of the sprite. 
 691        Also rotates the sprite image depending on the rotation style.
 692        You can change this property to change the direction of movement of the sprite. 
 693
 694        - 0 degree is pointing to the left 
 695        - 90 degree is pointing ***down***
 696        - 180 degree is pointing to the right
 697        - -90 degree or 270 degree is pointing up 
 698
 699        Therefore, increasing this value turns the sprite clockwise
 700    
 701        (If you find it strange that 90 degree is pointing down, 
 702        it is because y is positive when going down)
 703
 704        Example: 
 705        ```python
 706        # moves the sprite 10 degrees clockwise
 707        my_sprite.direction += 10 
 708        ```        
 709        """
 710
 711        #self.__direction
 712        if self.__rotation_style == _RotationStyle.ALL_AROUND:
 713            return self._body.rotation_vector.angle_degrees
 714        else: 
 715            return self.__direction.angle_degrees
 716   
 717    @x.setter
 718    def x(self, v):
 719        if self._lock_to_sprite: 
 720            self.__x = v
 721        else: 
 722            self._body.position =  v, self._body.position[1]
 723        
 724    
 725    @y.setter
 726    def y(self, v):
 727        if self._lock_to_sprite: 
 728            self.__y = v
 729        else: 
 730            self._body.position = self._body.position[0], v
 731
 732    @direction.setter
 733    def direction(self, degree):
 734        if self.__rotation_style == _RotationStyle.ALL_AROUND:
 735            self._body.angle = degree/180*np.pi
 736
 737        self.__direction = pymunk.Vec2d.from_polar(1, degree/180*np.pi) 
 738        #print(self.__direction)
 739
 740
 741    def move_indir(self, steps: float):
 742        """
 743        Moves the sprite forward along `direction`.   
 744        """
 745        #self._body.position += 
 746        
 747        xs, ys = self.__direction*steps
 748        self.x += xs
 749        self.y += ys
 750        
 751    def move_across_dir(self, steps: float):
 752        """
 753        Moves the sprite forward along `direction` + 90 degrees  
 754        """
 755        xs, ys = self.__direction.perpendicular()*steps
 756        self.x += xs
 757        self.y += ys        
 758        
 759
 760    def move_xy(self, xy: Tuple[float, float]):
 761        """
 762        Increments both x and y. 
 763
 764        Example: 
 765        ```python
 766        # increase x by 10 and decrease y by 5
 767        my_sprite.move_xy((10, -5))
 768        ```
 769        """
 770        self.x += xy[0]
 771        self.y += xy[1]
 772
 773    def set_xy(self, xy: Tuple[float, float]):
 774        """
 775        Sets the x and y coordinate. 
 776
 777        Example: 
 778        ```python
 779        # put the sprite to the top-left corner
 780        my_sprite.set_xy((0, 0))
 781        ```
 782        """        
 783        self.x, self.y = xy
 784
 785
 786    def distance_to(self, position: Tuple[float, float]) -> float:
 787        """
 788        Gets the distance from the centre of this sprite to a location. 
 789
 790        Example: 
 791        ```python   
 792        # returns the distance to the centre of the screen
 793        distance_to_centre = my_sprite.distance_to((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
 794        ```
 795        """   
 796        return (position - self._body.position).length
 797
 798    def distance_to_sprite(self, sprite: Sprite)-> float:
 799        """
 800        Gets the distance between the centres of two sprites. 
 801        Returns one float or a tuple of two floats. 
 802
 803        Example: 
 804        ```python   
 805        # returns the distance to another sprite
 806        distance_to_centre = my_sprite.distance_to_sprite(my_sprite2)
 807        ```
 808        """  
 809
 810        return self.distance_to(sprite._body.position)
 811    
 812
 813    def point_towards(self, position: Tuple[float, float], offset_degree=0):
 814        """
 815        Changes the direction to point to a location. 
 816
 817        Example: 
 818        ```python   
 819        # point to the centre of the screen
 820        my_sprite.point_towards((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
 821        ```
 822        """          
 823
 824        
 825        rot_vec = (position - self._body.position).normalized()
 826        self.direction = (rot_vec.angle_degrees + offset_degree) 
 827
 828
 829    def point_towards_sprite(self, sprite: Sprite, offset_degree=0):
 830        """
 831        Changes the direction to point to a sprite. 
 832
 833        Example: 
 834        ```python   
 835        # point to another sprite
 836        my_sprite.point_towards_sprite(another_sprite2)
 837        ```
 838        """  
 839        self.point_towards(sprite._body.position, offset_degree)
 840
 841    def point_towards_mouse(self, offset_degree=0):
 842        """
 843        Changes the direction to point to the mouse. 
 844
 845        Example: 
 846        ```python   
 847        # point to the mouse
 848        my_sprite.point_towards_mouse()
 849        ```
 850        """  
 851        self.point_towards(pygame.mouse.get_pos(), offset_degree)
 852
 853
 854    
 855    def lock_to(self, sprite: Sprite, offset: Tuple[float, float], reset_xy = False):
 856        """
 857        *EXTENDED FEATURE*
 858
 859        Locks in the position of this sprite relative to the position of another sprite, 
 860        so the sprite will always be in the same location relative to the other sprite.  
 861        This method only need to run once (instead of continuously in a loop)
 862
 863        Example: 
 864        ```python
 865        # a very rudimentary text bubble
 866        text_bubble_sprite = create_rect_sprite(...)
 867
 868        # lock the position of the text_bubble_sprite relative to the player_sprite. 
 869        text_bubble_sprite.lock_to(player_sprite, offset=(-100, -100))
 870
 871        # a very rudimentary implementation that assumes 
 872        # that you won't have more than one text message within 3 seconds
 873        def on_message(data):
 874        
 875            text_bubble_sprite.write_text(data)
 876            text_bubble_sprite.show()
 877
 878            yield 3 # wait for three seconds
 879            text_bubble_sprite.hide()
 880
 881        text_bubble_sprite.when_received_message('dialogue').add_handler(on_message)
 882
 883        ```
 884        """
 885        assert self._body.body_type == pymunk.Body.KINEMATIC, "only KINEMATIC object can be locked to another sprite"
 886        
 887        self._lock_to_sprite = sprite
 888        self._lock_offset = offset
 889        if reset_xy: 
 890            self.x = 0
 891            self.y = 0
 892
 893    def release_position_lock(self):
 894        """
 895        *EXTENDED FEATURE*
 896
 897        Release the position lock set by `lock_to`
 898        """        
 899        self._lock_to_sprite = None
 900        self._lock_offset = None
 901        pass
 902
 903    # END: motions  
 904
 905
 906    # START: drawing related
 907    def set_rotation_style_all_around(self):
 908        """
 909        Same as the block "set rotation style [all around]" in Scratch. 
 910        Allow the image to rotate all around with `direction`
 911        """
 912        self._drawing_manager.set_rotation_style(_RotationStyle.ALL_AROUND)
 913        self.__rotation_style = _RotationStyle.ALL_AROUND
 914
 915    def set_rotation_style_left_right(self):
 916        """
 917        Same as the block "set rotation style [left-right]" in Scratch. 
 918        Only allows the image to flip left or right depending on the `direction`. 
 919
 920        Does not constrain the direction of movement to only left and right. 
 921        """        
 922        self._drawing_manager.set_rotation_style(_RotationStyle.LEFTRIGHT)
 923        self.__rotation_style = _RotationStyle.LEFTRIGHT
 924
 925    def set_rotation_style_no_rotation(self):
 926        """
 927        Same as the block "set rotation style [don't rotate]" in Scratch. 
 928        Does not allow the image flip or rotate with `direction`. 
 929
 930        Does not constrain the direction of movement.
 931
 932        """
 933        self._drawing_manager.set_rotation_style(_RotationStyle.FIXED)
 934        self.__rotation_style = _RotationStyle.FIXED
 935
 936
 937    def set_frame(self, idx:int):
 938        """
 939        Same as the block "switch costume to [costume]" in Scratch, 
 940        except that you are specifying the frame (i.e. the costume) by the index. 
 941        
 942        TODO: link to sprite creation 
 943        """
 944        self._drawing_manager.set_frame(idx)
 945    
 946    def next_frame(self):
 947        """
 948        Same as the block "next costume" in Scratch, 
 949        """
 950        self._drawing_manager.next_frame()
 951
 952    def set_animation(self, name:str):
 953        """
 954        *EXTENDED FEATURE*
 955
 956        Changes the set of frames that is used by `set_frame` and `next_frame`.
 957        This is mainly for sprites that have different animations for different actions. 
 958
 959        See the [guide](https://kwdchan.github.io/pyscratch/guides/2-adding-animated-sprites.html) for more details.
 960        """   
 961        self._drawing_manager.set_animation(name)
 962        
 963    @property
 964    def frame_idx(self):
 965        """
 966        In Scratch, this is the costume number. 
 967        
 968        To change costume, you will need to call `set_frame`. 
 969        """
 970        return self._drawing_manager.frame_idx
 971    
 972    @property
 973    def animation_name(self):
 974        """
 975        *EXTENDED FEATURE*
 976
 977        The name of the set of frames that is currently used. 
 978
 979        Set by `set_animation`
 980        """        
 981        return self._drawing_manager.animation_name
 982
 983    def set_scale(self, factor: float):
 984        """
 985        Sets the size factor of the sprite.
 986
 987        For example:
 988        - A factor of 1.0 means 100% of the *original* image size
 989        - A factor of 1.2 means 120%
 990        - A factor of 0.8 means 80%
 991        """
 992        self._drawing_manager.set_scale(factor)
 993        self._physics_manager.request_shape_update()
 994
 995    def scale_by(self, factor: float):
 996        """
 997        Changes the size of the sprite by a factor
 998
 999        For example:
1000        - A factor of 1.2 is a 20% increase of the *current* size (not original size)
1001        - A factor of 0.8 makes the sprite 80% of the *current* size
1002        """
1003        self._drawing_manager.scale_by(factor)
1004        self._physics_manager.request_shape_update()
1005
1006    @property
1007    def scale_factor(self):
1008        """
1009        The scale factor of the sprite size
1010        """
1011        return self._drawing_manager.scale_factor
1012
1013    def flip_horizontal(self):
1014        """
1015        Flips the image horizontally. 
1016        Does not affect the direction of movement.
1017        """        
1018        self._drawing_manager.flip_horizontal()
1019
1020    def flip_vertical(self):
1021        """
1022        Flips the image vertically. 
1023        Does not affect the direction of movement.
1024        """ 
1025        self._drawing_manager.flip_vertical()
1026
1027    def set_brightness(self, factor):
1028        """
1029        Changes the brightness of the sprite. 
1030        """ 
1031        self._drawing_manager.set_brightness(factor)
1032
1033    def set_transparency(self, factor):
1034        """
1035        Changes the transparency of the sprite. 
1036
1037        ***IMCOMPLETE IMPLEMENTATION***: 
1038        The transparency of the transparent background of the image is also changed
1039        """ 
1040        self._drawing_manager.set_transparency(factor)
1041
1042    def write_text(self, text: str, font: pygame.font.Font, colour=(255,255,255), offset=(0,0), centre=True, reset=True):
1043        """
1044        *EXTENDED FEATURE*
1045
1046        Writes text on the sprite given a font. 
1047        ```python
1048        # if the font is shared by multiple sprites, consider putting it in `settings.py`
1049        font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size
1050
1051        my_sprite.write_text("hello_world", font)
1052
1053        ```
1054        Parameters
1055        ---
1056        text: str
1057            The text to display.
1058
1059        font: pygame.font.Font
1060            The pygame font object. Refer to the website of pygame for more details. 
1061        
1062        colour: Tuple[int, int, int] or Tuple[int, int, int, int]
1063            The colour the of text. Takes RGB or RGBA, where A is the transparency. Value range: [0-255]
1064
1065        offset: Tuple[float, float]
1066            The location of the text image relative to the sprite
1067
1068        centre: bool
1069            If False, the top-left corner of the text, instead of the center, would be considered as its location.
1070        
1071        reset: bool
1072            Whether or not to clear all the existing drawing (including previous text)  
1073
1074        """ 
1075        text_surface = font.render(text, True, colour) 
1076        self._drawing_manager.blit_persist(text_surface, offset, centre=centre, reset=reset)
1077    
1078    def draw(self, image: pygame.Surface,  offset=(0,0), centre=True, reset=True):
1079        """
1080        *EXTENDED FEATURE*
1081
1082        Draws an image on the sprite.
1083        ```python
1084        an_image = pysc.helper.load_image("assets/an_image.png")
1085        my_sprite.draw(an_image)
1086        ```
1087        Parameters
1088        ---
1089        image: pygame.Surface
1090            An image (pygame surface). You can use `helper.load_image` to load the image for you.
1091
1092        offset: Tuple[float, float]
1093            The location of the image relative to the sprite
1094
1095        centre: bool
1096            If False, the top-left corner of the image, instead of the center, would be considered as its location.
1097        
1098        reset: bool
1099            Whether or not to clear all the existing drawing (including the text)  
1100
1101        """ 
1102
1103        self._drawing_manager.blit_persist(image, offset, centre=centre, reset=reset)
1104
1105    # END: drawing related    
1106    
1107
1108    ## other blocks
1109    def is_touching(self, other_sprite) -> bool:
1110        
1111        if not self in game._all_sprites_to_show: 
1112            return False
1113        
1114        if not other_sprite in game._all_sprites_to_show:
1115            return False
1116            
1117        
1118        if not pygame.sprite.collide_rect(self, other_sprite): 
1119            return False
1120        
1121        return not (pygame.sprite.collide_mask(self, other_sprite) is None)
1122    
1123    def is_touching_mouse(self):
1124        """
1125        Returns whether or not this sprite is touching the mouse
1126        """
1127        mos_x, mos_y = pygame.mouse.get_pos()
1128
1129        if not self.rect.collidepoint((mos_x, mos_y)): 
1130            return False
1131
1132        x = mos_x-self.rect.left
1133        y = mos_y-self.rect.top
1134        
1135        return self.mask.get_at((x, y))
1136    
1137        
1138
1139    # def is_touching(self, other_sprite) -> bool:
1140    #     """
1141    #     Returns whether or not this sprite is touching another sprite.
1142    #     """
1143    #     return pyscratch.game_module._is_touching(self, other_sprite)
1144    
1145    
1146    # def is_touching_mouse(self):
1147    #     """
1148    #     Returns whether or not this sprite is touching the mouse
1149    #     """
1150    #     return pyscratch.game_module._is_touching_mouse(self)
1151    
1152    def hide(self):
1153        """
1154        Hides the sprite. 
1155        The hidden sprite is still in the space and can still interact with other sprites.
1156        
1157        Just hidden. 
1158        """
1159        game.hide_sprite(self)
1160
1161    def show(self):
1162        """
1163        Shows the sprite.
1164        """        
1165        game.show_sprite(self)
1166
1167    @override
1168    def remove(self, *_):
1169        """
1170        Removes the sprite and all the events and conditions associated to it. 
1171        Takes no parameter.
1172
1173        Usage:
1174        ```python
1175        # remove the sprite.
1176        my_sprite.remove()
1177        ```
1178        """
1179        game.remove_sprite(self)
1180
1181
1182    def create_clone(self):
1183        """
1184        Create a clone of this sprite. 
1185        Even though is method is provided to align with Scratch, 
1186        The prefered way to create identitical or similar sprites 
1187        is to create the sprite within a function or an event. 
1188
1189        ***INCOMPLETE IMPLEMENTATION***: 
1190        - Transparency and brightness aren't transferred to the clone
1191        """
1192
1193        sprite = type(self)(
1194            frame_dict = self._drawing_manager.frame_dict_original, 
1195            starting_mode = self._drawing_manager.animation_name, 
1196            position = (self.x, self.y),
1197            shape_type = self._physics_manager.shape_type, 
1198            shape_size_factor = self._physics_manager.shape_size_factor, 
1199            body_type = self._body.body_type, 
1200        )
1201        if not self in game._all_sprites_to_show:
1202            game.hide_sprite(sprite)
1203
1204        if self.__rotation_style == _RotationStyle.LEFTRIGHT:
1205            sprite.set_rotation_style_left_right()
1206        elif self.__rotation_style == _RotationStyle.FIXED:
1207            sprite.set_rotation_style_no_rotation()
1208            
1209        sprite.direction = self.direction
1210        sprite.scale_by(self._drawing_manager.scale_factor)
1211        sprite.set_frame(self._drawing_manager.frame_idx)
1212        sprite.set_draggable(self.draggable)
1213        sprite.elasticity = self.elasticity
1214        sprite.friction = self.friction
1215
1216
1217
1218        if self._body.body_type == pymunk.Body.DYNAMIC: 
1219            sprite.mass = self.mass
1220            sprite.moment = self.moment
1221
1222        sprite._drawing_manager.set_rotation_style(self._drawing_manager.rotation_style)
1223
1224
1225        game._clone_event_manager.on_clone(self, sprite)
1226        return sprite
1227
1228
1229    # alias of pygame method
1230
1231    def when_game_start(self, other_associated_sprites: Iterable[Sprite]=[]):
1232        """
1233        Returns an `Event` that is triggered when you call `game.start`. 
1234        The event handler does not take in any parameter.
1235
1236        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1237
1238        Parameters
1239        ---
1240        other_associated_sprites: List[Sprite]
1241            A list of sprites that this event depends on. 
1242            Removal of any of these sprites leads to the removal of the event. 
1243        """
1244        
1245
1246        associated_sprites = list(other_associated_sprites) + [self]
1247        return game.when_game_start(associated_sprites)
1248
1249    def when_any_key_pressed(self, other_associated_sprites: Iterable[Sprite]=[]):
1250        """
1251        Returns an `Event` that is triggered when a key is pressed or released. 
1252        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1253        
1254        The event handler have to take two parameters:
1255        - **key** (str): The key that is pressed. For example, 'w', 'd', 'left', 'right', 'space'. 
1256            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1257        
1258        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1259
1260        Parameters
1261        ---
1262        other_associated_sprites: List[Sprite]
1263            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1264    
1265        """    
1266        associated_sprites = list(other_associated_sprites) + [self]
1267        return game.when_any_key_pressed(associated_sprites)
1268
1269    def when_key_pressed(self, key, other_associated_sprites: Iterable[Sprite]=[]):
1270        """   
1271        Returns an `Event` that is triggered when a specific key is pressed or released. 
1272        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1273
1274        The event handler have to take one parameter:
1275        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1276        
1277        Parameters
1278        ---
1279        key: str
1280            The key that triggers the event. For example, 'w', 'd', 'left', 'right', 'space'. 
1281            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1282
1283        other_associated_sprites: List[Sprite]
1284            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1285        """
1286             
1287        associated_sprites = list(other_associated_sprites) + [self]
1288        return game.when_key_pressed(key, associated_sprites)
1289    
1290
1291    
1292    def when_this_sprite_clicked(self, other_associated_sprites: Iterable[Sprite]=[]):
1293        """
1294        Returns an `Event` that is triggered when the given sprite is clicked by mouse. 
1295        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1296
1297        The event handler does not take in any parameter.
1298                
1299        Parameters
1300        ---
1301        sprite: Sprite
1302            The sprite on which you want the click to be detected. The removal of this sprite will lead to the removal of this event so
1303            it does not need to be included in `other_assoicated_sprite`
1304        
1305        other_associated_sprites: List[Sprite]
1306            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1307        """        
1308        return game.when_this_sprite_clicked(self, other_associated_sprites)
1309       
1310    def when_backdrop_switched(self, idx, other_associated_sprites : Iterable[Sprite]=[]):
1311        """
1312        Returns an `Event` that is triggered when the game is switched to a backdrop at `backdrop_index`.
1313        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1314        
1315        The event handler does not take in any parameter.
1316
1317        Parameters
1318        ---
1319        backdrop_index: int
1320            The index of the backdrop  
1321
1322        other_associated_sprites: List[Sprite]
1323            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1324        """
1325        
1326        associated_sprites = list(other_associated_sprites) + [self]
1327        return game.when_backdrop_switched(idx, associated_sprites)
1328    
1329    def when_any_backdrop_switched(self, other_associated_sprites : Iterable[Sprite]=[]):
1330        """
1331        Returns an `Event` that is triggered when the backdrop is switched. 
1332        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1333        
1334        The event handler have to take one parameter:
1335        - **idx** (int): The index of the new backdrop  
1336        
1337        Parameters
1338        ---
1339        other_associated_sprites: List[Sprite]
1340            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1341        """        
1342        associated_sprites = list(other_associated_sprites) + [self]
1343        return game.when_any_backdrop_switched(associated_sprites)
1344
1345    def when_timer_above(self, t, other_associated_sprites : Iterable[Sprite]=[]):
1346        """      
1347        Returns a `Condition` that is triggered after the game have started for `t` seconds.
1348        A `Condition` works the same way an `Event` does. 
1349
1350        Also associates the condition to the sprite so the condition is removed when the sprite is removed. 
1351
1352
1353        The event handler have to take one parameter:
1354        - **n** (int): This value will always be zero
1355
1356        Parameters
1357        ---
1358        other_associated_sprites: List[Sprite]
1359            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1360        """        
1361        associated_sprites = list(other_associated_sprites) + [self]
1362        return game.when_timer_above(t, associated_sprites)
1363    
1364    def when_started_as_clone(self, associated_sprites: Iterable[Sprite]=[]):
1365        """
1366        Returns an `Event` that is triggered after the given sprite is cloned by `Sprite.create_clone`.
1367        Cloning of the clone will also trigger the event. Thus the removal of original sprite does not remove the event. 
1368
1369        The event handler have to take one parameter:
1370        - **clone_sprite** (Sprite): The newly created clone.
1371                
1372        Parameters
1373        ---
1374        associated_sprites: List[Sprite]
1375            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1376        """
1377                
1378        return game.when_started_as_clone(self, associated_sprites)    
1379
1380    def when_receive_message(self, topic: str, other_associated_sprites : Iterable[Sprite]=[]):
1381        """
1382        Returns an `Event` that is triggered after a message of the given `topic` is broadcasted.
1383        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1384        
1385        The event handler have to take one parameter:
1386        - **data** (Any): This parameter can be anything passed on by the message.
1387
1388        Parameters
1389        ---
1390        topic: str
1391            Can be any string. If the topic equals the topic of a broadcast, the event will be triggered. 
1392        other_associated_sprites: List[Sprite]
1393            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1394        """
1395        
1396        
1397        associated_sprites = list(other_associated_sprites) + [self]
1398        return game.when_receive_message(topic, associated_sprites)
1399    
1400    def broadcast_message(self, topic: str, data: Any=None):
1401        """
1402        Completely the same as `game.broadcast_message`. 
1403        Just an alias. 
1404
1405        Sends a message of a given `topic` and `data`.
1406        Triggers any event that subscribes to the topic. 
1407        The handlers of the events will receive `data` as the parameter.
1408
1409        Example:
1410        ```python
1411        def event_handler(data):
1412            print(data) # data will be "hello world!"
1413
1414        my_sprite.when_receive_message('print_message').add_handler(event_handler)
1415        my_sprite2.broadcast_message('print_message', data='hello world!')
1416
1417        # "hello world!" will be printed out
1418        ```
1419        Parameters
1420        ---
1421        topic: str
1422            Can be any string. If the topic of an message event equals the topic of the broadcast, the event will be triggered. 
1423
1424        data: Any
1425            Any arbitory data that will be passed to the event handler
1426        
1427        """
1428        return game.broadcast_message(topic, data)
1429
1430
1431    ## additional events
1432    def when_condition_met(self, checker=lambda: False, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1433        """
1434        *EXTENDED FEATURE*
1435
1436        DOCUMENTATION NOT COMPLETED
1437
1438        Parameters
1439        ---
1440        associated_sprites: List[Sprite]
1441            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1442        """
1443        associated_sprites = list(other_associated_sprites) + [self]
1444
1445        return game.when_condition_met(checker, repeats, associated_sprites)
1446    
1447    
1448    def when_timer_reset(self, reset_period=np.inf, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1449        """
1450        *EXTENDED FEATURE*
1451
1452        DOCUMENTATION NOT COMPLETED
1453
1454        Parameters
1455        ---
1456        associated_sprites: List[Sprite]
1457            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1458        """
1459        associated_sprites = list(other_associated_sprites) + [self]
1460
1461        return game.when_timer_reset(reset_period, repeats, associated_sprites)
1462    
1463    
1464    def create_specific_collision_trigger(self, other_sprite: Sprite, other_associated_sprites: Iterable[Sprite]=[]):
1465        """
1466        *EXTENDED FEATURE*
1467
1468        DOCUMENTATION NOT COMPLETED
1469
1470        Parameters
1471        ---
1472        associated_sprites: List[Sprite]
1473            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1474        """
1475        return game.create_specific_collision_trigger(self, other_sprite, other_associated_sprites)
1476    
1477
1478    # START: TODO: physics property getters and setters
1479
1480    def set_shape(self, shape_type: ShapeType=ShapeType.BOX):
1481        """
1482        Sets the collision shape of the sprite. The shape type can be one of the followings
1483        - box
1484        - circle
1485        - circle_height
1486        - circle_width
1487
1488        You can think of the collision shape as the actual shape of the sprite, 
1489        while the sprite image (the costume) is just like a phantom projection 
1490        that cannot be touched.
1491
1492        To see what it means, set `debug_draw` to True when you start the game. 
1493        ```python
1494        game.start(60, debug_draw=True)
1495        ```
1496        """
1497        self._physics_manager.set_shape_type(shape_type)
1498
1499    def set_shape_size_factor(self, factor=0.8):
1500        """
1501        Changes the size of the collision shape relative to the size of the image of the sprite. 
1502        For example: 
1503        - factor = 1.0 -> same size
1504        - factor = 0.8 -> the collision shape is 80% of the sprite image 
1505        - factor = 1.2 -> the collision shape is 120% of the sprite image
1506        
1507        """
1508        self._physics_manager.set_shape_size_factor(factor)
1509
1510    def set_collision_type(self, value: int=0):
1511        """
1512        *EXTENDED FEATURE*
1513
1514        Set the collision type of the sprite for detection purposes.
1515        The collision type can be any integer except that 
1516        **a sprite with a collision type of 0 (which is the default) will not collide with anything.**
1517
1518        Note that touching can still be detected.
1519        """
1520        self._physics_manager.set_collision_type(value)
1521
1522    @property
1523    def mass(self):
1524        """
1525        *EXTENDED FEATURE*
1526
1527        The mass of the collision shape. 
1528        Only work for dynamic objects.
1529
1530        You can make changes to this property. 
1531        """
1532        return self._body.mass
1533    
1534    @property
1535    def moment(self):
1536        """
1537        *EXTENDED FEATURE*
1538
1539        The moment of the collision shape. 
1540        The lower it is, the more easy it spins. 
1541        Only work for dynamic objects.
1542
1543        You can make changes to this property. 
1544        """
1545        return self._body.moment
1546    
1547    @property
1548    def elasticity(self):
1549        """
1550        *EXTENDED FEATURE*
1551
1552        The elasticity of the collision shape. 
1553        Elasticity of 1 means no energy loss after each collision. 
1554
1555        You can make changes to this property. 
1556        """
1557        return self._shape.elasticity
1558    
1559    @property
1560    def friction(self):
1561        """
1562        *EXTENDED FEATURE*
1563
1564        The friction of the collision shape. 
1565
1566        You can make changes to this property. 
1567        """
1568        return self._shape.friction
1569    
1570    @mass.setter
1571    def mass(self, value):
1572        self._body.mass = value
1573
1574    @moment.setter
1575    def moment(self, value):
1576        self._body.moment = value
1577    
1578    @elasticity.setter
1579    def elasticity(self, value):
1580        self._physics_manager.elasticity = value
1581    
1582    @friction.setter
1583    def friction(self, value):
1584        self._physics_manager.friction = value
1585    
1586    # END: physics property

Objects of the Sprite class represents a sprite.

Sprite( frame_dict: Dict[str, List[pygame.surface.Surface]], starting_mode: Optional[str] = None, position=(100, 100), shape_type=<ShapeType.BOX: 'box'>, shape_size_factor=1.0, body_type=1)
484    def __init__(
485            self, 
486            frame_dict: Dict[str, List[pygame.Surface]], 
487            starting_mode:Optional[str]=None, 
488            position= (100, 100), 
489            shape_type = ShapeType.BOX, 
490            shape_size_factor=1.0, 
491            body_type=pymunk.Body.KINEMATIC
492        ):
493        """
494        You might not need to create the sprite from this constructor function. 
495        Consider functions like `create_single_costume_sprite` or `create_animated_sprite`
496        as they would be easier to work with. 
497
498        Example:
499        ```python
500        image1 = helper.load_image("assets/image1.png")
501        image2 = helper.load_image("assets/image2.png")
502        image3 = helper.load_image("assets/image3.png")
503        image4 = helper.load_image("assets/image4.png")
504
505        frame_dict = {"walking": [image1, image2], "idling": [image3, image4]}
506        my_sprite = Sprite(frame_dict, "walking", shape_type="circle", body_type=pymunk.Body.DYNAMIC)
507        
508        # alternative (exactly the same)
509        my_sprite = Sprite(frame_dict, "walking", shape_type=ShapeType.CIRCLE, body_type=pymunk.Body.DYNAMIC)
510        ```
511
512        Parameters
513        ---
514        frame_dict: Dict[str, List[pygame.Surface]]
515            A dictionary with different frame modes (str) as the keys 
516            and lists of images as the values 
517
518        starting_mode:Optional[str]
519            The starting frame mode. If not provided, 
520            any one of the frame mode might be picked 
521            as the starting frame mode.
522
523        position: Tuple[float, float]
524
525        shape_type: ShapeType 
526            The collision shape. See `set_shape` for more details.
527        shape_size_factor: float 
528
529        body_type: int 
530            The pymunk body type. Leave out the parameter if unsure. 
531            Can be `pymunk.Body.KINEMATIC`, `pymunk.Body.DYNAMIC` or `pymunk.Body.STATIC` 
532            - Use kinematic if you want the sprite to move when when you tell it to. 
533            - Use dynamic if you want the sprite to be freely moving by physics. Also refer to `set_collision_type` to enable collision.  
534            - Use static if you do not want it to move at all. 
535        """
536        super().__init__()
537
538        self.image: pygame.Surface # rotated and flipped every update during self.update
539        "@private"
540        self.rect: pygame.Rect # depends on the rotated image and thus should be redefined during self.update
541        "@private"
542
543        if starting_mode is None:
544            starting_mode = next(iter(frame_dict))
545
546        self._drawing_manager = _DrawingManager(frame_dict, starting_mode)
547        _initial_frame = frame_dict[starting_mode][0]
548        self._physics_manager = _PhysicsManager(game, body_type, shape_type, shape_size_factor, position,_initial_frame)
549        
550        self.private_data = {}
551        """
552        A dictionary similar to `game.shared_data`. 
553        You can put any data or variable that should belong to the individuals sprite. 
554        A good example would be the health point of a charactor. 
555
556        Let say if you have a uncertain number of enemy in game created by cloning or otherwise, 
557        it would be messy to put the health point of each enemy to `game.shared_data`. In this 
558        case, putting the health point in the private data is a better choice.
559
560        Example:
561        ```python
562        my_sprite.private_data['hp'] = 10
563
564        def on_hit(damage):
565            my_sprite.private_data['hp'] -= damage
566            print("how much hp I have left: ", my_sprite.private_data['hp'])
567
568        my_sprite.when_received_message('hit').add_handler(on_hit)
569        
570        game.broadcast_message('hit', 2)
571        ```
572        """
573
574        self._mouse_selected = False
575        self.__is_dragging = False
576        self.draggable = False
577        """Whether or not this sprite is draggable."""
578
579
580        self._lock_to_sprite = None
581        self._lock_offset = 0, 0
582        self.__x, self.__y = self._physics_manager.body.position[0],  self._physics_manager.body.position[1]
583
584
585        self.__direction: pymunk.Vec2d = self._body.rotation_vector     
586        self.__rotation_style = _RotationStyle.ALL_AROUND
587        
588
589        game._add_sprite(self)

You might not need to create the sprite from this constructor function. Consider functions like create_single_costume_sprite or create_animated_sprite as they would be easier to work with.

Example:

image1 = helper.load_image("assets/image1.png")
image2 = helper.load_image("assets/image2.png")
image3 = helper.load_image("assets/image3.png")
image4 = helper.load_image("assets/image4.png")

frame_dict = {"walking": [image1, image2], "idling": [image3, image4]}
my_sprite = Sprite(frame_dict, "walking", shape_type="circle", body_type=pymunk.Body.DYNAMIC)

# alternative (exactly the same)
my_sprite = Sprite(frame_dict, "walking", shape_type=ShapeType.CIRCLE, body_type=pymunk.Body.DYNAMIC)
Parameters
  • frame_dict (Dict[str, List[pygame.Surface]]): A dictionary with different frame modes (str) as the keys and lists of images as the values
  • starting_mode (Optional[str]): The starting frame mode. If not provided, any one of the frame mode might be picked as the starting frame mode.
  • position (Tuple[float, float]):

  • shape_type (ShapeType): The collision shape. See set_shape for more details.

  • shape_size_factor (float):

  • body_type (int): The pymunk body type. Leave out the parameter if unsure. Can be pymunk.Body.KINEMATIC, pymunk.Body.DYNAMIC or pymunk.Body.STATIC

    • Use kinematic if you want the sprite to move when when you tell it to.
    • Use dynamic if you want the sprite to be freely moving by physics. Also refer to set_collision_type to enable collision.
    • Use static if you do not want it to move at all.
private_data

A dictionary similar to game.shared_data. You can put any data or variable that should belong to the individuals sprite. A good example would be the health point of a charactor.

Let say if you have a uncertain number of enemy in game created by cloning or otherwise, it would be messy to put the health point of each enemy to game.shared_data. In this case, putting the health point in the private data is a better choice.

Example:

my_sprite.private_data['hp'] = 10

def on_hit(damage):
    my_sprite.private_data['hp'] -= damage
    print("how much hp I have left: ", my_sprite.private_data['hp'])

my_sprite.when_received_message('hit').add_handler(on_hit)

game.broadcast_message('hit', 2)
draggable

Whether or not this sprite is draggable.

def set_draggable(self, draggable):
628    def set_draggable(self, draggable):
629        """
630        Set whether or not this sprite is draggable.
631
632        Example: 
633        ```python
634        # Make the sprite draggable
635        my_sprite.set_draggable(True)
636        ```
637        """
638        self.draggable = draggable

Set whether or not this sprite is draggable.

Example:

# Make the sprite draggable
my_sprite.set_draggable(True)
x
645    @property
646    def x(self):
647        """
648        The x position of the sprite.
649        You can change this property to change the x position of the sprite. 
650
651        Remember that the top-left corner is (x=0, y=0), 
652        and x increases as the sprite goes right. 
653
654        so setting x to 0 sends the sprite to the left edge. 
655
656        Example: 
657        ```python
658        # moves the sprite 10 pixels to the right
659        my_sprite.x += 10 
660        ```
661        """
662        if self._lock_to_sprite: 
663            return self.__x        
664        return self._body.position[0]

The x position of the sprite. You can change this property to change the x position of the sprite.

Remember that the top-left corner is (x=0, y=0), and x increases as the sprite goes right.

so setting x to 0 sends the sprite to the left edge.

Example:

# moves the sprite 10 pixels to the right
my_sprite.x += 10 
y
666    @property
667    def y(self):
668        """
669        The y position of the sprite.
670        You can change this property to change the y position of the sprite. 
671        
672        Remember that the top-left corner is (x=0, y=0), 
673        and y increases as the sprite goes ***down***. 
674
675        so setting y to 0 sends the sprite to the top edge.         
676
677        Example: 
678        ```python
679        # moves the sprite 10 pixels down
680        my_sprite.y += 10 
681        ```
682        """
683        if self._lock_to_sprite: 
684            return self.__y
685        return self._body.position[1]

The y position of the sprite. You can change this property to change the y position of the sprite.

Remember that the top-left corner is (x=0, y=0), and y increases as the sprite goes down.

so setting y to 0 sends the sprite to the top edge.

Example:

# moves the sprite 10 pixels down
my_sprite.y += 10 
direction
687    @property
688    def direction(self):
689        """
690        The direction of movement of the sprite. 
691        Also rotates the sprite image depending on the rotation style.
692        You can change this property to change the direction of movement of the sprite. 
693
694        - 0 degree is pointing to the left 
695        - 90 degree is pointing ***down***
696        - 180 degree is pointing to the right
697        - -90 degree or 270 degree is pointing up 
698
699        Therefore, increasing this value turns the sprite clockwise
700    
701        (If you find it strange that 90 degree is pointing down, 
702        it is because y is positive when going down)
703
704        Example: 
705        ```python
706        # moves the sprite 10 degrees clockwise
707        my_sprite.direction += 10 
708        ```        
709        """
710
711        #self.__direction
712        if self.__rotation_style == _RotationStyle.ALL_AROUND:
713            return self._body.rotation_vector.angle_degrees
714        else: 
715            return self.__direction.angle_degrees

The direction of movement of the sprite. Also rotates the sprite image depending on the rotation style. You can change this property to change the direction of movement of the sprite.

  • 0 degree is pointing to the left
  • 90 degree is pointing down
  • 180 degree is pointing to the right
  • -90 degree or 270 degree is pointing up

Therefore, increasing this value turns the sprite clockwise

(If you find it strange that 90 degree is pointing down, it is because y is positive when going down)

Example:

# moves the sprite 10 degrees clockwise
my_sprite.direction += 10 
def move_indir(self, steps: float):
741    def move_indir(self, steps: float):
742        """
743        Moves the sprite forward along `direction`.   
744        """
745        #self._body.position += 
746        
747        xs, ys = self.__direction*steps
748        self.x += xs
749        self.y += ys

Moves the sprite forward along direction.

def move_across_dir(self, steps: float):
751    def move_across_dir(self, steps: float):
752        """
753        Moves the sprite forward along `direction` + 90 degrees  
754        """
755        xs, ys = self.__direction.perpendicular()*steps
756        self.x += xs
757        self.y += ys        

Moves the sprite forward along direction + 90 degrees

def move_xy(self, xy: Tuple[float, float]):
760    def move_xy(self, xy: Tuple[float, float]):
761        """
762        Increments both x and y. 
763
764        Example: 
765        ```python
766        # increase x by 10 and decrease y by 5
767        my_sprite.move_xy((10, -5))
768        ```
769        """
770        self.x += xy[0]
771        self.y += xy[1]

Increments both x and y.

Example:

# increase x by 10 and decrease y by 5
my_sprite.move_xy((10, -5))
def set_xy(self, xy: Tuple[float, float]):
773    def set_xy(self, xy: Tuple[float, float]):
774        """
775        Sets the x and y coordinate. 
776
777        Example: 
778        ```python
779        # put the sprite to the top-left corner
780        my_sprite.set_xy((0, 0))
781        ```
782        """        
783        self.x, self.y = xy

Sets the x and y coordinate.

Example:

# put the sprite to the top-left corner
my_sprite.set_xy((0, 0))
def distance_to(self, position: Tuple[float, float]) -> float:
786    def distance_to(self, position: Tuple[float, float]) -> float:
787        """
788        Gets the distance from the centre of this sprite to a location. 
789
790        Example: 
791        ```python   
792        # returns the distance to the centre of the screen
793        distance_to_centre = my_sprite.distance_to((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
794        ```
795        """   
796        return (position - self._body.position).length

Gets the distance from the centre of this sprite to a location.

Example:

# returns the distance to the centre of the screen
distance_to_centre = my_sprite.distance_to((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
def distance_to_sprite(self, sprite: Sprite) -> float:
798    def distance_to_sprite(self, sprite: Sprite)-> float:
799        """
800        Gets the distance between the centres of two sprites. 
801        Returns one float or a tuple of two floats. 
802
803        Example: 
804        ```python   
805        # returns the distance to another sprite
806        distance_to_centre = my_sprite.distance_to_sprite(my_sprite2)
807        ```
808        """  
809
810        return self.distance_to(sprite._body.position)

Gets the distance between the centres of two sprites. Returns one float or a tuple of two floats.

Example:

# returns the distance to another sprite
distance_to_centre = my_sprite.distance_to_sprite(my_sprite2)
def point_towards(self, position: Tuple[float, float], offset_degree=0):
813    def point_towards(self, position: Tuple[float, float], offset_degree=0):
814        """
815        Changes the direction to point to a location. 
816
817        Example: 
818        ```python   
819        # point to the centre of the screen
820        my_sprite.point_towards((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
821        ```
822        """          
823
824        
825        rot_vec = (position - self._body.position).normalized()
826        self.direction = (rot_vec.angle_degrees + offset_degree) 

Changes the direction to point to a location.

Example:

# point to the centre of the screen
my_sprite.point_towards((SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
def point_towards_sprite(self, sprite: Sprite, offset_degree=0):
829    def point_towards_sprite(self, sprite: Sprite, offset_degree=0):
830        """
831        Changes the direction to point to a sprite. 
832
833        Example: 
834        ```python   
835        # point to another sprite
836        my_sprite.point_towards_sprite(another_sprite2)
837        ```
838        """  
839        self.point_towards(sprite._body.position, offset_degree)

Changes the direction to point to a sprite.

Example:

# point to another sprite
my_sprite.point_towards_sprite(another_sprite2)
def point_towards_mouse(self, offset_degree=0):
841    def point_towards_mouse(self, offset_degree=0):
842        """
843        Changes the direction to point to the mouse. 
844
845        Example: 
846        ```python   
847        # point to the mouse
848        my_sprite.point_towards_mouse()
849        ```
850        """  
851        self.point_towards(pygame.mouse.get_pos(), offset_degree)

Changes the direction to point to the mouse.

Example:

# point to the mouse
my_sprite.point_towards_mouse()
def lock_to( self, sprite: Sprite, offset: Tuple[float, float], reset_xy=False):
855    def lock_to(self, sprite: Sprite, offset: Tuple[float, float], reset_xy = False):
856        """
857        *EXTENDED FEATURE*
858
859        Locks in the position of this sprite relative to the position of another sprite, 
860        so the sprite will always be in the same location relative to the other sprite.  
861        This method only need to run once (instead of continuously in a loop)
862
863        Example: 
864        ```python
865        # a very rudimentary text bubble
866        text_bubble_sprite = create_rect_sprite(...)
867
868        # lock the position of the text_bubble_sprite relative to the player_sprite. 
869        text_bubble_sprite.lock_to(player_sprite, offset=(-100, -100))
870
871        # a very rudimentary implementation that assumes 
872        # that you won't have more than one text message within 3 seconds
873        def on_message(data):
874        
875            text_bubble_sprite.write_text(data)
876            text_bubble_sprite.show()
877
878            yield 3 # wait for three seconds
879            text_bubble_sprite.hide()
880
881        text_bubble_sprite.when_received_message('dialogue').add_handler(on_message)
882
883        ```
884        """
885        assert self._body.body_type == pymunk.Body.KINEMATIC, "only KINEMATIC object can be locked to another sprite"
886        
887        self._lock_to_sprite = sprite
888        self._lock_offset = offset
889        if reset_xy: 
890            self.x = 0
891            self.y = 0

EXTENDED FEATURE

Locks in the position of this sprite relative to the position of another sprite, so the sprite will always be in the same location relative to the other sprite.
This method only need to run once (instead of continuously in a loop)

Example:

# a very rudimentary text bubble
text_bubble_sprite = create_rect_sprite(...)

# lock the position of the text_bubble_sprite relative to the player_sprite. 
text_bubble_sprite.lock_to(player_sprite, offset=(-100, -100))

# a very rudimentary implementation that assumes 
# that you won't have more than one text message within 3 seconds
def on_message(data):

    text_bubble_sprite.write_text(data)
    text_bubble_sprite.show()

    yield 3 # wait for three seconds
    text_bubble_sprite.hide()

text_bubble_sprite.when_received_message('dialogue').add_handler(on_message)
def release_position_lock(self):
893    def release_position_lock(self):
894        """
895        *EXTENDED FEATURE*
896
897        Release the position lock set by `lock_to`
898        """        
899        self._lock_to_sprite = None
900        self._lock_offset = None
901        pass

EXTENDED FEATURE

Release the position lock set by lock_to

def set_rotation_style_all_around(self):
907    def set_rotation_style_all_around(self):
908        """
909        Same as the block "set rotation style [all around]" in Scratch. 
910        Allow the image to rotate all around with `direction`
911        """
912        self._drawing_manager.set_rotation_style(_RotationStyle.ALL_AROUND)
913        self.__rotation_style = _RotationStyle.ALL_AROUND

Same as the block "set rotation style [all around]" in Scratch. Allow the image to rotate all around with direction

def set_rotation_style_left_right(self):
915    def set_rotation_style_left_right(self):
916        """
917        Same as the block "set rotation style [left-right]" in Scratch. 
918        Only allows the image to flip left or right depending on the `direction`. 
919
920        Does not constrain the direction of movement to only left and right. 
921        """        
922        self._drawing_manager.set_rotation_style(_RotationStyle.LEFTRIGHT)
923        self.__rotation_style = _RotationStyle.LEFTRIGHT

Same as the block "set rotation style [left-right]" in Scratch. Only allows the image to flip left or right depending on the direction.

Does not constrain the direction of movement to only left and right.

def set_rotation_style_no_rotation(self):
925    def set_rotation_style_no_rotation(self):
926        """
927        Same as the block "set rotation style [don't rotate]" in Scratch. 
928        Does not allow the image flip or rotate with `direction`. 
929
930        Does not constrain the direction of movement.
931
932        """
933        self._drawing_manager.set_rotation_style(_RotationStyle.FIXED)
934        self.__rotation_style = _RotationStyle.FIXED

Same as the block "set rotation style [don't rotate]" in Scratch. Does not allow the image flip or rotate with direction.

Does not constrain the direction of movement.

def set_frame(self, idx: int):
937    def set_frame(self, idx:int):
938        """
939        Same as the block "switch costume to [costume]" in Scratch, 
940        except that you are specifying the frame (i.e. the costume) by the index. 
941        
942        TODO: link to sprite creation 
943        """
944        self._drawing_manager.set_frame(idx)

Same as the block "switch costume to [costume]" in Scratch, except that you are specifying the frame (i.e. the costume) by the index.

TODO: link to sprite creation

def next_frame(self):
946    def next_frame(self):
947        """
948        Same as the block "next costume" in Scratch, 
949        """
950        self._drawing_manager.next_frame()

Same as the block "next costume" in Scratch,

def set_animation(self, name: str):
952    def set_animation(self, name:str):
953        """
954        *EXTENDED FEATURE*
955
956        Changes the set of frames that is used by `set_frame` and `next_frame`.
957        This is mainly for sprites that have different animations for different actions. 
958
959        See the [guide](https://kwdchan.github.io/pyscratch/guides/2-adding-animated-sprites.html) for more details.
960        """   
961        self._drawing_manager.set_animation(name)

EXTENDED FEATURE

Changes the set of frames that is used by set_frame and next_frame. This is mainly for sprites that have different animations for different actions.

See the guide for more details.

frame_idx
963    @property
964    def frame_idx(self):
965        """
966        In Scratch, this is the costume number. 
967        
968        To change costume, you will need to call `set_frame`. 
969        """
970        return self._drawing_manager.frame_idx

In Scratch, this is the costume number.

To change costume, you will need to call set_frame.

animation_name
972    @property
973    def animation_name(self):
974        """
975        *EXTENDED FEATURE*
976
977        The name of the set of frames that is currently used. 
978
979        Set by `set_animation`
980        """        
981        return self._drawing_manager.animation_name

EXTENDED FEATURE

The name of the set of frames that is currently used.

Set by set_animation

def set_scale(self, factor: float):
983    def set_scale(self, factor: float):
984        """
985        Sets the size factor of the sprite.
986
987        For example:
988        - A factor of 1.0 means 100% of the *original* image size
989        - A factor of 1.2 means 120%
990        - A factor of 0.8 means 80%
991        """
992        self._drawing_manager.set_scale(factor)
993        self._physics_manager.request_shape_update()

Sets the size factor of the sprite.

For example:

  • A factor of 1.0 means 100% of the original image size
  • A factor of 1.2 means 120%
  • A factor of 0.8 means 80%
def scale_by(self, factor: float):
 995    def scale_by(self, factor: float):
 996        """
 997        Changes the size of the sprite by a factor
 998
 999        For example:
1000        - A factor of 1.2 is a 20% increase of the *current* size (not original size)
1001        - A factor of 0.8 makes the sprite 80% of the *current* size
1002        """
1003        self._drawing_manager.scale_by(factor)
1004        self._physics_manager.request_shape_update()

Changes the size of the sprite by a factor

For example:

  • A factor of 1.2 is a 20% increase of the current size (not original size)
  • A factor of 0.8 makes the sprite 80% of the current size
scale_factor
1006    @property
1007    def scale_factor(self):
1008        """
1009        The scale factor of the sprite size
1010        """
1011        return self._drawing_manager.scale_factor

The scale factor of the sprite size

def flip_horizontal(self):
1013    def flip_horizontal(self):
1014        """
1015        Flips the image horizontally. 
1016        Does not affect the direction of movement.
1017        """        
1018        self._drawing_manager.flip_horizontal()

Flips the image horizontally. Does not affect the direction of movement.

def flip_vertical(self):
1020    def flip_vertical(self):
1021        """
1022        Flips the image vertically. 
1023        Does not affect the direction of movement.
1024        """ 
1025        self._drawing_manager.flip_vertical()

Flips the image vertically. Does not affect the direction of movement.

def set_brightness(self, factor):
1027    def set_brightness(self, factor):
1028        """
1029        Changes the brightness of the sprite. 
1030        """ 
1031        self._drawing_manager.set_brightness(factor)

Changes the brightness of the sprite.

def set_transparency(self, factor):
1033    def set_transparency(self, factor):
1034        """
1035        Changes the transparency of the sprite. 
1036
1037        ***IMCOMPLETE IMPLEMENTATION***: 
1038        The transparency of the transparent background of the image is also changed
1039        """ 
1040        self._drawing_manager.set_transparency(factor)

Changes the transparency of the sprite.

IMCOMPLETE IMPLEMENTATION: The transparency of the transparent background of the image is also changed

def write_text( self, text: str, font: pygame.font.Font, colour=(255, 255, 255), offset=(0, 0), centre=True, reset=True):
1042    def write_text(self, text: str, font: pygame.font.Font, colour=(255,255,255), offset=(0,0), centre=True, reset=True):
1043        """
1044        *EXTENDED FEATURE*
1045
1046        Writes text on the sprite given a font. 
1047        ```python
1048        # if the font is shared by multiple sprites, consider putting it in `settings.py`
1049        font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size
1050
1051        my_sprite.write_text("hello_world", font)
1052
1053        ```
1054        Parameters
1055        ---
1056        text: str
1057            The text to display.
1058
1059        font: pygame.font.Font
1060            The pygame font object. Refer to the website of pygame for more details. 
1061        
1062        colour: Tuple[int, int, int] or Tuple[int, int, int, int]
1063            The colour the of text. Takes RGB or RGBA, where A is the transparency. Value range: [0-255]
1064
1065        offset: Tuple[float, float]
1066            The location of the text image relative to the sprite
1067
1068        centre: bool
1069            If False, the top-left corner of the text, instead of the center, would be considered as its location.
1070        
1071        reset: bool
1072            Whether or not to clear all the existing drawing (including previous text)  
1073
1074        """ 
1075        text_surface = font.render(text, True, colour) 
1076        self._drawing_manager.blit_persist(text_surface, offset, centre=centre, reset=reset)

EXTENDED FEATURE

Writes text on the sprite given a font.

# if the font is shared by multiple sprites, consider putting it in `settings.py`
font = pygame.font.SysFont(None, 48)  # None = default font, 48 = font size

my_sprite.write_text("hello_world", font)
Parameters
  • text (str): The text to display.
  • font (pygame.font.Font): The pygame font object. Refer to the website of pygame for more details.
  • colour (Tuple[int, int, int] or Tuple[int, int, int, int]): The colour the of text. Takes RGB or RGBA, where A is the transparency. Value range: [0-255]
  • offset (Tuple[float, float]): The location of the text image relative to the sprite
  • centre (bool): If False, the top-left corner of the text, instead of the center, would be considered as its location.
  • reset (bool): Whether or not to clear all the existing drawing (including previous text)
def draw( self, image: pygame.surface.Surface, offset=(0, 0), centre=True, reset=True):
1078    def draw(self, image: pygame.Surface,  offset=(0,0), centre=True, reset=True):
1079        """
1080        *EXTENDED FEATURE*
1081
1082        Draws an image on the sprite.
1083        ```python
1084        an_image = pysc.helper.load_image("assets/an_image.png")
1085        my_sprite.draw(an_image)
1086        ```
1087        Parameters
1088        ---
1089        image: pygame.Surface
1090            An image (pygame surface). You can use `helper.load_image` to load the image for you.
1091
1092        offset: Tuple[float, float]
1093            The location of the image relative to the sprite
1094
1095        centre: bool
1096            If False, the top-left corner of the image, instead of the center, would be considered as its location.
1097        
1098        reset: bool
1099            Whether or not to clear all the existing drawing (including the text)  
1100
1101        """ 
1102
1103        self._drawing_manager.blit_persist(image, offset, centre=centre, reset=reset)

EXTENDED FEATURE

Draws an image on the sprite.

an_image = pysc.helper.load_image("assets/an_image.png")
my_sprite.draw(an_image)
Parameters
  • image (pygame.Surface): An image (pygame surface). You can use helper.load_image to load the image for you.
  • offset (Tuple[float, float]): The location of the image relative to the sprite
  • centre (bool): If False, the top-left corner of the image, instead of the center, would be considered as its location.
  • reset (bool): Whether or not to clear all the existing drawing (including the text)
def is_touching(self, other_sprite) -> bool:
1109    def is_touching(self, other_sprite) -> bool:
1110        
1111        if not self in game._all_sprites_to_show: 
1112            return False
1113        
1114        if not other_sprite in game._all_sprites_to_show:
1115            return False
1116            
1117        
1118        if not pygame.sprite.collide_rect(self, other_sprite): 
1119            return False
1120        
1121        return not (pygame.sprite.collide_mask(self, other_sprite) is None)
def is_touching_mouse(self):
1123    def is_touching_mouse(self):
1124        """
1125        Returns whether or not this sprite is touching the mouse
1126        """
1127        mos_x, mos_y = pygame.mouse.get_pos()
1128
1129        if not self.rect.collidepoint((mos_x, mos_y)): 
1130            return False
1131
1132        x = mos_x-self.rect.left
1133        y = mos_y-self.rect.top
1134        
1135        return self.mask.get_at((x, y))

Returns whether or not this sprite is touching the mouse

def hide(self):
1152    def hide(self):
1153        """
1154        Hides the sprite. 
1155        The hidden sprite is still in the space and can still interact with other sprites.
1156        
1157        Just hidden. 
1158        """
1159        game.hide_sprite(self)

Hides the sprite. The hidden sprite is still in the space and can still interact with other sprites.

Just hidden.

def show(self):
1161    def show(self):
1162        """
1163        Shows the sprite.
1164        """        
1165        game.show_sprite(self)

Shows the sprite.

@override
def remove(self, *_):
1167    @override
1168    def remove(self, *_):
1169        """
1170        Removes the sprite and all the events and conditions associated to it. 
1171        Takes no parameter.
1172
1173        Usage:
1174        ```python
1175        # remove the sprite.
1176        my_sprite.remove()
1177        ```
1178        """
1179        game.remove_sprite(self)

Removes the sprite and all the events and conditions associated to it. Takes no parameter.

Usage:

# remove the sprite.
my_sprite.remove()
def create_clone(self):
1182    def create_clone(self):
1183        """
1184        Create a clone of this sprite. 
1185        Even though is method is provided to align with Scratch, 
1186        The prefered way to create identitical or similar sprites 
1187        is to create the sprite within a function or an event. 
1188
1189        ***INCOMPLETE IMPLEMENTATION***: 
1190        - Transparency and brightness aren't transferred to the clone
1191        """
1192
1193        sprite = type(self)(
1194            frame_dict = self._drawing_manager.frame_dict_original, 
1195            starting_mode = self._drawing_manager.animation_name, 
1196            position = (self.x, self.y),
1197            shape_type = self._physics_manager.shape_type, 
1198            shape_size_factor = self._physics_manager.shape_size_factor, 
1199            body_type = self._body.body_type, 
1200        )
1201        if not self in game._all_sprites_to_show:
1202            game.hide_sprite(sprite)
1203
1204        if self.__rotation_style == _RotationStyle.LEFTRIGHT:
1205            sprite.set_rotation_style_left_right()
1206        elif self.__rotation_style == _RotationStyle.FIXED:
1207            sprite.set_rotation_style_no_rotation()
1208            
1209        sprite.direction = self.direction
1210        sprite.scale_by(self._drawing_manager.scale_factor)
1211        sprite.set_frame(self._drawing_manager.frame_idx)
1212        sprite.set_draggable(self.draggable)
1213        sprite.elasticity = self.elasticity
1214        sprite.friction = self.friction
1215
1216
1217
1218        if self._body.body_type == pymunk.Body.DYNAMIC: 
1219            sprite.mass = self.mass
1220            sprite.moment = self.moment
1221
1222        sprite._drawing_manager.set_rotation_style(self._drawing_manager.rotation_style)
1223
1224
1225        game._clone_event_manager.on_clone(self, sprite)
1226        return sprite

Create a clone of this sprite. Even though is method is provided to align with Scratch, The prefered way to create identitical or similar sprites is to create the sprite within a function or an event.

INCOMPLETE IMPLEMENTATION:

  • Transparency and brightness aren't transferred to the clone
def when_game_start( self, other_associated_sprites: Iterable[Sprite] = []):
1231    def when_game_start(self, other_associated_sprites: Iterable[Sprite]=[]):
1232        """
1233        Returns an `Event` that is triggered when you call `game.start`. 
1234        The event handler does not take in any parameter.
1235
1236        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1237
1238        Parameters
1239        ---
1240        other_associated_sprites: List[Sprite]
1241            A list of sprites that this event depends on. 
1242            Removal of any of these sprites leads to the removal of the event. 
1243        """
1244        
1245
1246        associated_sprites = list(other_associated_sprites) + [self]
1247        return game.when_game_start(associated_sprites)

Returns an Event that is triggered when you call game.start. The event handler does not take in any parameter.

Also associates the event to the sprite so the event is removed when the sprite is removed.

Parameters
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_any_key_pressed( self, other_associated_sprites: Iterable[Sprite] = []):
1249    def when_any_key_pressed(self, other_associated_sprites: Iterable[Sprite]=[]):
1250        """
1251        Returns an `Event` that is triggered when a key is pressed or released. 
1252        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1253        
1254        The event handler have to take two parameters:
1255        - **key** (str): The key that is pressed. For example, 'w', 'd', 'left', 'right', 'space'. 
1256            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1257        
1258        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1259
1260        Parameters
1261        ---
1262        other_associated_sprites: List[Sprite]
1263            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1264    
1265        """    
1266        associated_sprites = list(other_associated_sprites) + [self]
1267        return game.when_any_key_pressed(associated_sprites)

Returns an Event that is triggered when a key is pressed or released. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler have to take two parameters:

  • key (str): The key that is pressed. For example, 'w', 'd', 'left', 'right', 'space'. Uses pygame.key.key_code under the hood.
  • updown (str): Either 'up' or 'down' that indicates whether it is a press or a release
Parameters
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_key_pressed( self, key, other_associated_sprites: Iterable[Sprite] = []):
1269    def when_key_pressed(self, key, other_associated_sprites: Iterable[Sprite]=[]):
1270        """   
1271        Returns an `Event` that is triggered when a specific key is pressed or released. 
1272        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1273
1274        The event handler have to take one parameter:
1275        - **updown** (str): Either 'up' or 'down' that indicates whether it is a press or a release
1276        
1277        Parameters
1278        ---
1279        key: str
1280            The key that triggers the event. For example, 'w', 'd', 'left', 'right', 'space'. 
1281            Uses [pygame.key.key_code](https://www.pygame.org/docs/ref/key.html#pygame.key.key_code) under the hood. 
1282
1283        other_associated_sprites: List[Sprite]
1284            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1285        """
1286             
1287        associated_sprites = list(other_associated_sprites) + [self]
1288        return game.when_key_pressed(key, associated_sprites)

Returns an Event that is triggered when a specific key is pressed or released. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler have to take one parameter:

  • updown (str): Either 'up' or 'down' that indicates whether it is a press or a release
Parameters
  • key (str): The key that triggers the event. For example, 'w', 'd', 'left', 'right', 'space'. Uses pygame.key.key_code under the hood.
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_this_sprite_clicked( self, other_associated_sprites: Iterable[Sprite] = []):
1292    def when_this_sprite_clicked(self, other_associated_sprites: Iterable[Sprite]=[]):
1293        """
1294        Returns an `Event` that is triggered when the given sprite is clicked by mouse. 
1295        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1296
1297        The event handler does not take in any parameter.
1298                
1299        Parameters
1300        ---
1301        sprite: Sprite
1302            The sprite on which you want the click to be detected. The removal of this sprite will lead to the removal of this event so
1303            it does not need to be included in `other_assoicated_sprite`
1304        
1305        other_associated_sprites: List[Sprite]
1306            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1307        """        
1308        return game.when_this_sprite_clicked(self, other_associated_sprites)

Returns an Event that is triggered when the given sprite is clicked by mouse. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler does not take in any parameter.

Parameters
  • sprite (Sprite): The sprite on which you want the click to be detected. The removal of this sprite will lead to the removal of this event so it does not need to be included in other_assoicated_sprite
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_backdrop_switched( self, idx, other_associated_sprites: Iterable[Sprite] = []):
1310    def when_backdrop_switched(self, idx, other_associated_sprites : Iterable[Sprite]=[]):
1311        """
1312        Returns an `Event` that is triggered when the game is switched to a backdrop at `backdrop_index`.
1313        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1314        
1315        The event handler does not take in any parameter.
1316
1317        Parameters
1318        ---
1319        backdrop_index: int
1320            The index of the backdrop  
1321
1322        other_associated_sprites: List[Sprite]
1323            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1324        """
1325        
1326        associated_sprites = list(other_associated_sprites) + [self]
1327        return game.when_backdrop_switched(idx, associated_sprites)

Returns an Event that is triggered when the game is switched to a backdrop at backdrop_index. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler does not take in any parameter.

Parameters
  • backdrop_index (int): The index of the backdrop
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_any_backdrop_switched( self, other_associated_sprites: Iterable[Sprite] = []):
1329    def when_any_backdrop_switched(self, other_associated_sprites : Iterable[Sprite]=[]):
1330        """
1331        Returns an `Event` that is triggered when the backdrop is switched. 
1332        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1333        
1334        The event handler have to take one parameter:
1335        - **idx** (int): The index of the new backdrop  
1336        
1337        Parameters
1338        ---
1339        other_associated_sprites: List[Sprite]
1340            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1341        """        
1342        associated_sprites = list(other_associated_sprites) + [self]
1343        return game.when_any_backdrop_switched(associated_sprites)

Returns an Event that is triggered when the backdrop is switched. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler have to take one parameter:

  • idx (int): The index of the new backdrop
Parameters
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_timer_above( self, t, other_associated_sprites: Iterable[Sprite] = []):
1345    def when_timer_above(self, t, other_associated_sprites : Iterable[Sprite]=[]):
1346        """      
1347        Returns a `Condition` that is triggered after the game have started for `t` seconds.
1348        A `Condition` works the same way an `Event` does. 
1349
1350        Also associates the condition to the sprite so the condition is removed when the sprite is removed. 
1351
1352
1353        The event handler have to take one parameter:
1354        - **n** (int): This value will always be zero
1355
1356        Parameters
1357        ---
1358        other_associated_sprites: List[Sprite]
1359            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1360        """        
1361        associated_sprites = list(other_associated_sprites) + [self]
1362        return game.when_timer_above(t, associated_sprites)

Returns a Condition that is triggered after the game have started for t seconds. A Condition works the same way an Event does.

Also associates the condition to the sprite so the condition is removed when the sprite is removed.

The event handler have to take one parameter:

  • n (int): This value will always be zero
Parameters
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_started_as_clone(self, associated_sprites: Iterable[Sprite] = []):
1364    def when_started_as_clone(self, associated_sprites: Iterable[Sprite]=[]):
1365        """
1366        Returns an `Event` that is triggered after the given sprite is cloned by `Sprite.create_clone`.
1367        Cloning of the clone will also trigger the event. Thus the removal of original sprite does not remove the event. 
1368
1369        The event handler have to take one parameter:
1370        - **clone_sprite** (Sprite): The newly created clone.
1371                
1372        Parameters
1373        ---
1374        associated_sprites: List[Sprite]
1375            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1376        """
1377                
1378        return game.when_started_as_clone(self, associated_sprites)    

Returns an Event that is triggered after the given sprite is cloned by Sprite.create_clone. Cloning of the clone will also trigger the event. Thus the removal of original sprite does not remove the event.

The event handler have to take one parameter:

  • clone_sprite (Sprite): The newly created clone.
Parameters
  • associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_receive_message( self, topic: str, other_associated_sprites: Iterable[Sprite] = []):
1380    def when_receive_message(self, topic: str, other_associated_sprites : Iterable[Sprite]=[]):
1381        """
1382        Returns an `Event` that is triggered after a message of the given `topic` is broadcasted.
1383        Also associates the event to the sprite so the event is removed when the sprite is removed. 
1384        
1385        The event handler have to take one parameter:
1386        - **data** (Any): This parameter can be anything passed on by the message.
1387
1388        Parameters
1389        ---
1390        topic: str
1391            Can be any string. If the topic equals the topic of a broadcast, the event will be triggered. 
1392        other_associated_sprites: List[Sprite]
1393            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1394        """
1395        
1396        
1397        associated_sprites = list(other_associated_sprites) + [self]
1398        return game.when_receive_message(topic, associated_sprites)

Returns an Event that is triggered after a message of the given topic is broadcasted. Also associates the event to the sprite so the event is removed when the sprite is removed.

The event handler have to take one parameter:

  • data (Any): This parameter can be anything passed on by the message.
Parameters
  • topic (str): Can be any string. If the topic equals the topic of a broadcast, the event will be triggered.
  • other_associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def broadcast_message(self, topic: str, data: Any = None):
1400    def broadcast_message(self, topic: str, data: Any=None):
1401        """
1402        Completely the same as `game.broadcast_message`. 
1403        Just an alias. 
1404
1405        Sends a message of a given `topic` and `data`.
1406        Triggers any event that subscribes to the topic. 
1407        The handlers of the events will receive `data` as the parameter.
1408
1409        Example:
1410        ```python
1411        def event_handler(data):
1412            print(data) # data will be "hello world!"
1413
1414        my_sprite.when_receive_message('print_message').add_handler(event_handler)
1415        my_sprite2.broadcast_message('print_message', data='hello world!')
1416
1417        # "hello world!" will be printed out
1418        ```
1419        Parameters
1420        ---
1421        topic: str
1422            Can be any string. If the topic of an message event equals the topic of the broadcast, the event will be triggered. 
1423
1424        data: Any
1425            Any arbitory data that will be passed to the event handler
1426        
1427        """
1428        return game.broadcast_message(topic, data)

Completely the same as game.broadcast_message. Just an alias.

Sends a message of a given topic and data. Triggers any event that subscribes to the topic. The handlers of the events will receive data as the parameter.

Example:

def event_handler(data):
    print(data) # data will be "hello world!"

my_sprite.when_receive_message('print_message').add_handler(event_handler)
my_sprite2.broadcast_message('print_message', data='hello world!')

# "hello world!" will be printed out
Parameters
  • topic (str): Can be any string. If the topic of an message event equals the topic of the broadcast, the event will be triggered.
  • data (Any): Any arbitory data that will be passed to the event handler
def when_condition_met( self, checker=<function Sprite.<lambda>>, repeats=inf, other_associated_sprites: Iterable[Sprite] = []):
1432    def when_condition_met(self, checker=lambda: False, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1433        """
1434        *EXTENDED FEATURE*
1435
1436        DOCUMENTATION NOT COMPLETED
1437
1438        Parameters
1439        ---
1440        associated_sprites: List[Sprite]
1441            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1442        """
1443        associated_sprites = list(other_associated_sprites) + [self]
1444
1445        return game.when_condition_met(checker, repeats, associated_sprites)

EXTENDED FEATURE

DOCUMENTATION NOT COMPLETED

Parameters
  • associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def when_timer_reset( self, reset_period=inf, repeats=inf, other_associated_sprites: Iterable[Sprite] = []):
1448    def when_timer_reset(self, reset_period=np.inf, repeats=np.inf, other_associated_sprites: Iterable[Sprite]=[]):
1449        """
1450        *EXTENDED FEATURE*
1451
1452        DOCUMENTATION NOT COMPLETED
1453
1454        Parameters
1455        ---
1456        associated_sprites: List[Sprite]
1457            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1458        """
1459        associated_sprites = list(other_associated_sprites) + [self]
1460
1461        return game.when_timer_reset(reset_period, repeats, associated_sprites)

EXTENDED FEATURE

DOCUMENTATION NOT COMPLETED

Parameters
  • associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def create_specific_collision_trigger( self, other_sprite: Sprite, other_associated_sprites: Iterable[Sprite] = []):
1464    def create_specific_collision_trigger(self, other_sprite: Sprite, other_associated_sprites: Iterable[Sprite]=[]):
1465        """
1466        *EXTENDED FEATURE*
1467
1468        DOCUMENTATION NOT COMPLETED
1469
1470        Parameters
1471        ---
1472        associated_sprites: List[Sprite]
1473            A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event. 
1474        """
1475        return game.create_specific_collision_trigger(self, other_sprite, other_associated_sprites)

EXTENDED FEATURE

DOCUMENTATION NOT COMPLETED

Parameters
  • associated_sprites (List[Sprite]): A list of sprites that this event depends on. Removal of any of these sprites leads to the removal of the event.
def set_shape( self, shape_type: ShapeType = <ShapeType.BOX: 'box'>):
1480    def set_shape(self, shape_type: ShapeType=ShapeType.BOX):
1481        """
1482        Sets the collision shape of the sprite. The shape type can be one of the followings
1483        - box
1484        - circle
1485        - circle_height
1486        - circle_width
1487
1488        You can think of the collision shape as the actual shape of the sprite, 
1489        while the sprite image (the costume) is just like a phantom projection 
1490        that cannot be touched.
1491
1492        To see what it means, set `debug_draw` to True when you start the game. 
1493        ```python
1494        game.start(60, debug_draw=True)
1495        ```
1496        """
1497        self._physics_manager.set_shape_type(shape_type)

Sets the collision shape of the sprite. The shape type can be one of the followings

  • box
  • circle
  • circle_height
  • circle_width

You can think of the collision shape as the actual shape of the sprite, while the sprite image (the costume) is just like a phantom projection that cannot be touched.

To see what it means, set debug_draw to True when you start the game.

game.start(60, debug_draw=True)
def set_shape_size_factor(self, factor=0.8):
1499    def set_shape_size_factor(self, factor=0.8):
1500        """
1501        Changes the size of the collision shape relative to the size of the image of the sprite. 
1502        For example: 
1503        - factor = 1.0 -> same size
1504        - factor = 0.8 -> the collision shape is 80% of the sprite image 
1505        - factor = 1.2 -> the collision shape is 120% of the sprite image
1506        
1507        """
1508        self._physics_manager.set_shape_size_factor(factor)

Changes the size of the collision shape relative to the size of the image of the sprite. For example:

  • factor = 1.0 -> same size
  • factor = 0.8 -> the collision shape is 80% of the sprite image
  • factor = 1.2 -> the collision shape is 120% of the sprite image
def set_collision_type(self, value: int = 0):
1510    def set_collision_type(self, value: int=0):
1511        """
1512        *EXTENDED FEATURE*
1513
1514        Set the collision type of the sprite for detection purposes.
1515        The collision type can be any integer except that 
1516        **a sprite with a collision type of 0 (which is the default) will not collide with anything.**
1517
1518        Note that touching can still be detected.
1519        """
1520        self._physics_manager.set_collision_type(value)

EXTENDED FEATURE

Set the collision type of the sprite for detection purposes. The collision type can be any integer except that a sprite with a collision type of 0 (which is the default) will not collide with anything.

Note that touching can still be detected.

mass
1522    @property
1523    def mass(self):
1524        """
1525        *EXTENDED FEATURE*
1526
1527        The mass of the collision shape. 
1528        Only work for dynamic objects.
1529
1530        You can make changes to this property. 
1531        """
1532        return self._body.mass

EXTENDED FEATURE

The mass of the collision shape. Only work for dynamic objects.

You can make changes to this property.

moment
1534    @property
1535    def moment(self):
1536        """
1537        *EXTENDED FEATURE*
1538
1539        The moment of the collision shape. 
1540        The lower it is, the more easy it spins. 
1541        Only work for dynamic objects.
1542
1543        You can make changes to this property. 
1544        """
1545        return self._body.moment

EXTENDED FEATURE

The moment of the collision shape. The lower it is, the more easy it spins. Only work for dynamic objects.

You can make changes to this property.

elasticity
1547    @property
1548    def elasticity(self):
1549        """
1550        *EXTENDED FEATURE*
1551
1552        The elasticity of the collision shape. 
1553        Elasticity of 1 means no energy loss after each collision. 
1554
1555        You can make changes to this property. 
1556        """
1557        return self._shape.elasticity

EXTENDED FEATURE

The elasticity of the collision shape. Elasticity of 1 means no energy loss after each collision.

You can make changes to this property.

friction
1559    @property
1560    def friction(self):
1561        """
1562        *EXTENDED FEATURE*
1563
1564        The friction of the collision shape. 
1565
1566        You can make changes to this property. 
1567        """
1568        return self._shape.friction

EXTENDED FEATURE

The friction of the collision shape.

You can make changes to this property.