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
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 theframe_dict
parameter.
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, exceptframe_dict
&starting_animation
.
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, exceptframe_dict
&starting_animation
.
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, exceptframe_dict
&starting_animation
.
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()
381class ShapeType(Enum): 382 BOX = 'box' 383 CIRCLE = 'circle' 384 CIRCLE_WIDTH = 'circle_width' 385 CIRCLE_HEIGHT = 'circle_height'
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.
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
orpymunk.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.
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)
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)
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
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
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
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
.
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
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))
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))
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))
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)
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))
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)
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()
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)
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
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
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.
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.
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
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,
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.
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
.
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%
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
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
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.
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.
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.
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
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)
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)
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)
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
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.
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()
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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)
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
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.
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.
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.
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.
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.