| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958 |
- import os
- from time import sleep
- from typing import Type
- import pygame
- import jogai
- import jogai._globals as _globals
- from jogai import logger, settings, utils, widgets
- from jogai.translations import gettext as _
- from jogai.utils import _get_keys
- class Velocity:
- x = 0
- y = 0
- def __repr__(self):
- return f'{self.x}, {self.y}'
- class Character(pygame.sprite.Sprite):
- """
- A videogame character with a bunch of actions.
- """
- TOP_COLLISION = _('top')
- BOTTOM_COLLISION = _('bottom')
- LEFT_COLLISION = _('left')
- RIGHT_COLLISION = _('right')
- STATIC_COLLISION = _('static')
- _predefined_animations_allowed = [
- _('backward'),
- _('down'),
- _('forward'),
- _('stop'),
- _('jump'),
- _('up'),
- ]
- _predefined_attributes_allowed = [
- _('collidable'),
- _('chronometer'),
- _('gravitative'),
- _('height'),
- _('keys'),
- _('lives'),
- _('position'),
- _('score'),
- _('speed'),
- _('width'),
- ]
- _predefined_key_actions_allowed = [
- _('backward'),
- _('decrease'),
- _('down'),
- _('forward'),
- _('increase'),
- _('show_inventory'),
- _('jump'),
- _('up'),
- ]
- def __init__(
- self,
- image_filename: str,
- name: str = '',
- animation_filenames: dict = {},
- ):
- if not isinstance(name, str):
- raise TypeError(_('The character name must be a string.'))
- self.scale_factor = 1.0
- self.speed = 0
- self.velocity = Velocity()
- self.predefined_keys = {}
- self.image_filename = image_filename
- self.image = None
- self.thumbnail = None
- self.thumbnail_rect = None
- self.animation_filenames = {}
- self._original_image = None
- self._original_rect = None
- self.rect = None
- self.name = name
- self.x = 0
- self.y = 0
- self.gravitative = False
- self.is_jumping = False
- self.jump_force = settings.CHARACTER_DEFAULT_JUMP_FORCE
- self._jump_counter = self.jump_force
- self.angle = 0
- self.collidable = True
- self.flipped = {'horizontal': False, 'vertical': False}
- self.score = None
- self.lives = None
- self.chronometer = None
- self.inventory = widgets.Inventory(name)
- super().__init__()
- self.load(image_filename)
- if not animation_filenames and name:
- animation_filenames = {
- action: f'{name}_{action}.png'
- for action in self._predefined_animations_allowed
- }
- animation_filenames.update({_('stop'): f'{name}.png'})
- self.set_animations(animation_filenames)
- def __repr__(self):
- return f'{_("Character")}: {self.name}'
- def _load_data(self, data: dict):
- """
- Load character attributes from data schema.
- Only declarated attributes in predefined_attributes_allowed are allowed.
- Parameters:
- data: input schema
- """
- if data is None:
- data = {}
- if not isinstance(data, dict):
- raise TypeError(_('The data input must be a dict.'))
- for attribute in data.keys():
- # utils._convert_function_arguments_to_string()
- if attribute in self._predefined_attributes_allowed:
- eval(f'self.set_{attribute}(data["{attribute}"])')
- @property
- def height(self) -> int:
- return self.rect.height
- @property
- def width(self) -> int:
- return self.rect.width
- def _eval_predefined_function(
- self, function: str, arguments: str | list | dict
- ):
- if not isinstance(function, str):
- raise TypeError(_('The function name must be a string.'))
- processed_arguments = utils._convert_function_arguments_to_string(
- arguments
- )
- eval(f'self.{function}({processed_arguments})')
- def _get_all_collidables(self):
- other_characters = _globals.vg_characters.copy()
- other_groups = _globals.vg_groups.copy()
- for other_group in other_groups:
- other_characters.extend(other_group.sprites())
- if self in other_characters:
- other_characters.remove(self)
- return [other for other in other_characters if other.collidable]
- def calculate_collisions(self, position: tuple | list) -> list:
- """
- Calculate all collisions if the character is moved to a position.
- Parameters:
- position: A tuple with the x and y coordinates.
- Returns:
- A list of all other collidable characters that collide with the character.
- """
- if not isinstance(position, tuple) and not isinstance(position, list):
- raise TypeError(
- _(
- 'The position must be a tuple or a list with the coordinates (x, y).'
- )
- )
- if len(position) != 2 or not all(
- map(lambda x: isinstance(x, int), position)
- ):
- raise ValueError(
- _(
- 'The position must be a tuple or a list of integers with the coordinates (x, y).'
- )
- )
- tracking = (
- True
- if self == _globals.character_focus and not position
- else False
- )
- offset_rect = self._update_rect_position(
- simulate=position, tracking=tracking
- )
- collisions = []
- for collidable in self._get_all_collidables():
- if offset_rect.colliderect(collidable):
- collisions.append(collidable)
- return collisions
- def move(
- self,
- x: int | None = None,
- y: int | None = None,
- key: str = '',
- relative: bool = True,
- ):
- """
- Move the character the number of pixels indicated in the x and y parameters
- Parameters:
- x: Number of pixels to move on the abscissa axis
- y: Number of pixels to move on the ordinate axis
- key: Optional key to perform the action
- relative: True for a incremental movement, False for an absolute movement
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.move(5, 3)
- >>> protagonist.move()
- """
- if x is None:
- x = self.speed
- if y is None:
- y = self.speed
- if not isinstance(x, int) or not isinstance(y, int):
- raise TypeError(_('Amount to move must be an integer.'))
- if not isinstance(key, str):
- raise TypeError(_('Key pressed must be a string.'))
- if not key or _get_keys()[utils.eval_key(key)]:
- if relative:
- updated_x, updated_y = self.x + x, self.y + y
- self.velocity.x, self.velocity.y = x, y
- else:
- updated_x, updated_y = x, y
- collisions = self.calculate_collisions((updated_x, updated_y))
- if collisions:
- collisions.sort(key=lambda c: c.rect.y)
- if self.velocity.y > 0:
- self.y = collisions[0].y - self.height
- self.velocity.y = 0
- elif self.velocity.y < 0:
- self.y = collisions[0].y + collisions[0].height
- self.velocity.y = 0
- collisions.sort(key=lambda c: c.rect.x)
- if self.velocity.x > 0:
- self.x = collisions[0].x - self.width
- self.velocity.x = 0
- elif self.velocity.x < 0:
- self.x = collisions[0].x + collisions[0].width
- self.velocity.x = 0
- else:
- self.x, self.y = updated_x, updated_y
- def move_to(self, x: int, y: int, key: str = ''):
- """
- Move the character to (x, y) position.
- Parameters:
- x: coordinate in horizontal axis
- y: coordinate in vertical axis
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.move_to(0, 0)
- """
- self.move(x, y, key, relative=False)
- def backward(self, amount: int | None = None, key: str = ''):
- """
- Move the character in the negative direction of the abscissa.
- Parameters:
- amount: Number of pixels to move
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.backward(3)
- >>> protagonist.backward()
- """
- if amount is None:
- amount = self.speed
- self.move(-amount, 0, key)
- def down(self, amount: int | None = None, key: str = ''):
- """
- Move the character in the positive direction of the ordinate.
- Parameters:
- amount: Number of pixels to move
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.down(3)
- >>> protagonist.down()
- """
- if amount is None:
- amount = self.speed
- if not self.is_jumping:
- self.move(0, amount, key)
- def forward(self, amount: int | None = None, key: str = ''):
- """
- Move the character in the positive direction of the abscissa.
- Parameters:
- amount: Number of pixels to move
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.forward(3)
- >>> protagonist.forward()
- """
- if amount is None:
- amount = self.speed
- self.move(amount, 0, key)
- def up(self, amount: int | None = None, key: str = ''):
- """
- Move the character in the negative direction of the ordinate.
- Parameters:
- amount: Number of pixels to move
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.up(3)
- >>> protagonist.up()
- """
- if amount is None:
- amount = self.speed
- if not self.is_jumping:
- self.move(0, -amount, key)
- def jump(self, force: float | None = None, key: str = ''):
- """
- Jump the character.
- Parameters:
- force: Jump force
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.jump()
- """
- if force is None:
- force = settings.CHARACTER_DEFAULT_JUMP_FORCE
- if isinstance(force, int):
- force = float(force)
- if not isinstance(force, float):
- raise TypeError(_('The jump force must be a float number.'))
- if force < 0:
- raise ValueError(_('The jump force must be a non-negative float.'))
- if not isinstance(key, str):
- raise TypeError(_('Key pressed must be a string.'))
- self.jump_force = force
- self.jump_counter = force
- if (
- not self.is_jumping
- and not key
- or _get_keys()[utils.eval_key(key)]
- and bool(self.calculate_collisions((self.x, self.y + 1)))
- ):
- self.is_jumping = True
- if _('jump') in self.animation_filenames.keys():
- self.load(
- self.animation_filenames[_('jump')], preserve_size=True
- )
- def rotate(self, increment: int = 1):
- """
- Rotate the character clockwise.
- Parameters:
- increment: Amount to be rotated
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.rotate(90)
- """
- # TODO: rotate from the center
- if not isinstance(increment, int):
- raise TypeError(_('The rotation increment must be an integer.'))
- self.angle -= increment
- self.set_angle()
- def decrease(self, decrement: float | int = 0.001, key: str = ''):
- """
- Decreases the size of the character.
- Parameters:
- decrement: Amount to increase to the scale factor
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.decrease(0.1)
- """
- if isinstance(decrement, int):
- decrement = float(decrement)
- if not isinstance(decrement, float):
- raise TypeError(_('The decrement must be a float number.'))
- self.increase(-decrement, key)
- def increase(self, increment: float | int = 0.001, key: str = ''):
- """
- Increases the size of the character.
- Parameters:
- increment: Amount to increase to the scale factor
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.increase(0.1)
- """
- if isinstance(increment, int):
- increment = float(increment)
- if not isinstance(increment, float):
- raise TypeError(_('The increment must be a float number.'))
- if not key or _get_keys()[utils.eval_key(key)]:
- if self.scale_factor + increment < 0:
- self.scale_to(0)
- else:
- self.scale_to(self.scale_factor + increment)
- def scale_to(self, scale_factor: float = 1.0):
- """
- Scale the character according to the scale factor.
- Parameters:
- scale_factor: Factor to scale
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.scale_to(2)
- """
- if not isinstance(scale_factor, float) and not isinstance(
- scale_factor, int
- ):
- raise TypeError(
- _('The scale factor must be a positive number (float or int).')
- )
- if scale_factor < 0:
- raise ValueError(
- _(
- 'The scale factor must be a non-negative number (float or int).'
- )
- )
- self.image = pygame.transform.scale(
- self._original_image,
- (
- int(self._original_rect.width * scale_factor),
- int(self._original_rect.height * scale_factor),
- ),
- )
- self.scale_factor = scale_factor
- self.rect = self.image.get_rect()
- def flip(self, horizontal: bool = True, vertical: bool = True):
- """
- Flip the character on horizontal and/or vertical axis.
- Parameters:
- horizontal: Flipped on horizontal axis
- vertical: Flipped on vertical axis
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.flip(True, False)
- """
- if not isinstance(horizontal, bool) or not isinstance(vertical, bool):
- raise TypeError(
- _('Both the horizontal and vertical axis must be booleans.')
- )
- self.image = pygame.transform.flip(
- self.image,
- self.flipped['horizontal'] ^ horizontal,
- self.flipped['vertical'] ^ vertical,
- )
- self.flipped['horizontal'] = horizontal
- self.flipped['vertical'] = vertical
- def set_angle(self, angle: int | None = None):
- """
- Set the character rotation.
- Parameters:
- angle: Angle to be rotated.
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_angle(90)
- """
- # TODO: rotate from the center
- if not isinstance(angle, int) and angle is not None:
- raise TypeError(_('The rotation angle must be an integer.'))
- if angle is not None:
- self.angle = 360 - angle
- self.image = pygame.transform.rotozoom(
- self._original_image, self.angle, self.scale_factor
- )
- def set_animations(self, animations: dict):
- """
- Set the image animation filenames for this character.
- Parameters:
- animations: A dict with pairs {action: filename}
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_animations({'jump': 'protagonist_jump.png'})
- """
- if not isinstance(animations, dict):
- raise TypeError(_('The animations must be a dict.'))
- filtered_allowed_animations = dict(
- filter(
- lambda pair: pair[0] in self._predefined_animations_allowed,
- animations.items(),
- )
- )
- self.animation_filenames = dict(
- filter(
- lambda pair: os.path.isfile(
- os.path.join(settings.CHARACTERS_DIR, pair[1])
- ),
- filtered_allowed_animations.items(),
- )
- )
- def set_chronometer(self, time: int):
- """
- Set a chronometer for this character.
- Parameters:
- time: Initial time to countdown. If zero, progressive count.
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_chronometer(30)
- """
- if not isinstance(time, int):
- raise TypeError(_('Time must be a integer.'))
- chronometer_pos = len(_globals.vg_chronometers)
- self.chronometer = widgets.Chronometer(
- time,
- position=(
- settings.CHRONOMETERS_DEFAULT_POSITION[0],
- settings.CHRONOMETERS_DEFAULT_POSITION[1]
- - chronometer_pos * settings.CHRONOMETERS_DEFAULT_HEIGHT,
- ),
- )
- self.chronometer.start()
- _globals.vg_chronometers.append(self.chronometer)
- def set_collidable(self, collidable: bool = True):
- """
- Set the character collidable.
- Parameters:
- collidable: True if it is collidable, False otherwise.
- """
- if not isinstance(collidable, bool):
- raise TypeError(_('The collidable argument must be a boolean.'))
- self.collidable = collidable
- def set_gravitative(self, gravitative: bool = True):
- """
- Set the character gravitative.
- Parameters:
- gravitative: True if it is collidable, False otherwise.
- """
- if not isinstance(gravitative, bool):
- raise TypeError(_('The gravitative argument must be a boolean.'))
- self.gravitative = gravitative
- def set_height(self, height: int):
- """
- Set the character height.
- Parameters:
- height: The character height in pixels
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_height(100)
- """
- if not isinstance(height, int):
- raise TypeError(_('Height must be a not negative integer.'))
- if height < 1 or self._original_rect.height < 1:
- raise ValueError(_('Height must be a not negative integer.'))
- self.scale_factor = height / self._original_rect.height
- self.image = pygame.transform.scale(
- self._original_image,
- (int(self._original_rect.width * self.scale_factor), height),
- )
- self.rect = self.image.get_rect()
- def set_inventory(self, key: str):
- """
- Set the character lives.
- Parameters:
- lives: The character lives
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_lives(5)
- """
- self.inventory = widgets.Inventory(key)
- def set_lives(self, lives: int):
- """
- Set the character lives.
- Parameters:
- lives: The character lives
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_lives(5)
- """
- if self.lives is None:
- self.lives = widgets.LevelBar(lives)
- _globals.vg_levelbars.append(self.lives)
- self.lives.set(lives)
- def set_keys(self, predefined_keys: dict):
- """
- Set the character predefined keys to perform actions.
- Only declarated actions in _predefined_key_actions_allowed are allowed.
- Parameters:
- predefined_keys: The predefined keys with the associated action.
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_keys({'forward': 'd'})
- """
- if not isinstance(predefined_keys, dict):
- raise TypeError(
- _('The predefined keys must be a dict. No keys are loaded.')
- )
- filtered_predefined_keys = {}
- for action, key in predefined_keys.items():
- if action in self._predefined_key_actions_allowed:
- filtered_predefined_keys.update({action: key})
- self.predefined_keys.update(filtered_predefined_keys)
- def set_position(self, position: tuple | list, key: str = ''):
- """
- Set the character position as tuple. To use as separate coordinates call move_to().
- Parameters:
- position: A tuple with abscissa and ordinate coordinates
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_position((0, 0))
- """
- if not isinstance(position, tuple) and not isinstance(position, list):
- raise TypeError(_('The position must be a tuple or list (x, y).'))
- if len(position) != 2:
- raise ValueError(_('The position has two coordinates (x, y).'))
- self.move(position[0], position[1], key, relative=False)
- def set_score(self, score: int):
- """
- Set the character score and the scoreboard.
- Parameters:
- score: the new character score
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_score(5)
- """
- if not isinstance(score, int):
- raise TypeError(_('The score must be an integer.'))
- scoreboard_pos = len(_globals.vg_scoreboards)
- if self.score is None:
- self.score = widgets.Scoreboard(
- f'{self.name} ',
- score=score,
- position=(
- settings.SCOREBOARDS_DEFAULT_POSITION[0],
- settings.SCOREBOARDS_DEFAULT_POSITION[1]
- + scoreboard_pos * 50,
- ),
- )
- _globals.vg_scoreboards.append(self.score)
- else:
- self.score.set(score)
- def set_speed(self, speed: int):
- """
- Set the character speed.
- Parameters:
- speed: The character speed
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_speed(10)
- """
- if not isinstance(speed, int):
- raise TypeError(_('The speed must be an integer.'))
- self.speed = speed
- self.speed = speed
- def set_width(self, width: int):
- """
- Set the character width.
- Parameters:
- width: the character width in pixels
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.set_width(100)
- """
- if not isinstance(width, int):
- raise TypeError(_('Width must be a not negative integer.'))
- if width < 1 or self._original_rect.width < 1:
- raise ValueError(_('Width must be a not negative integer.'))
- self.scale_factor = width / self._original_rect.width
- self.image = pygame.transform.scale(
- self._original_image,
- (width, int(self._original_rect.height * self.scale_factor)),
- )
- self.rect = self.image.get_rect()
- def show_inventory(self, key: str = '', delay: float = 0.5):
- """
- Show and hide the character inventory.
- Parameters:
- key: Optional key to perform the action
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.show_inventory()
- """
- if _get_keys()[utils.eval_key(key)]:
- if self.inventory is None:
- self.set_inventory()
- if _globals.visible_inventory:
- _globals.visible_inventory = None
- else:
- _globals.visible_inventory = self.inventory
- sleep(delay)
- def touch(
- self,
- other_characters: Type['Character'] | Type['jogai.group.Group'] | list,
- key: str = '',
- ) -> str:
- """
- Detect if one character is touching another(s).
- Parameters:
- other_characters: character or list of characters to check
- Returns:
- True, if it is touching; False, otherwise.
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> secondary = Character('secondary.png')
- >>> protagonist.touch(secondary)
- 'static'
- """
- if isinstance(other_characters, Character):
- other_characters = [other_characters]
- if not isinstance(other_characters, list) and not isinstance(
- other_characters, jogai.group.Group
- ):
- raise TypeError(
- _(
- 'The instances to be touched must be a character or a list of characters.'
- )
- )
- if isinstance(other_characters, list) and not all(
- map(lambda x: isinstance(x, Character), other_characters)
- ):
- raise ValueError(
- _('All instances to be touched must be characters.')
- )
- if not isinstance(key, str):
- raise TypeError(_('Key pressed must be a string.'))
- for other_character in other_characters:
- if not key or _get_keys()[utils.eval_key(key)]:
- if self.rect.colliderect(other_character.rect):
- if self.velocity.y > 0:
- return self.TOP_COLLISION
- elif self.velocity.y < 0:
- return self.BOTTOM_COLLISION
- if self.velocity.x > 0:
- return self.LEFT_COLLISION
- elif self.velocity.x < 0:
- return self.RIGHT_COLLISION
- return self.STATIC_COLLISION
- return ''
- def generate_thumbnail(
- self, height: int = settings.CHARACTER_DEFAULT_THUMBNAIL_SIZE
- ):
- self.scale_factor = height / self._original_rect.height
- self.thumbnail = pygame.transform.scale(
- self._original_image,
- (int(self._original_rect.width * self.scale_factor), height),
- )
- self.thumbnail_rect = self.thumbnail.get_rect()
- def load(self, filename: str, preserve_size: bool = False):
- """
- >>> protagonist = Character('protagonist.png')
- >>> protagonist.load('secondary.png')
- """
- self._original_image, self._original_rect = utils._load_image(
- settings.CHARACTERS_DIR, filename
- )
- if preserve_size:
- height = self.height
- self.image = self._original_image
- self.generate_thumbnail()
- self._original_rect = self._original_image.get_rect()
- self.rect = self.image.get_rect()
- self.scale_factor = 1
- self.image_filename = filename
- if preserve_size:
- self.set_height(height)
- def copy(self):
- """
- Create a copy of this character keeping size and position.
- Examples:
- >>> protagonist = Character('protagonist.png')
- >>> other = protagonist.copy()
- """
- new_character = Character(self.image_filename)
- new_character.set_collidable(self.collidable)
- new_character.set_gravitative(self.gravitative)
- new_character.set_height(self.height)
- new_character.set_width(self.width)
- new_character.set_position((self.x, self.y))
- return new_character
- def _update_jump(self):
- """
- Calculate and update the character position during the jump.
- """
- if self.is_jumping:
- if self._jump_counter >= -self.jump_force:
- self.move(
- 0,
- -int((self._jump_counter * abs(self._jump_counter)) * 0.5),
- )
- self._jump_counter -= 1
- else:
- self._jump_counter = settings.CHARACTER_DEFAULT_JUMP_FORCE
- self.is_jumping = False
- def _update_rect_position(
- self,
- tracking: bool = False,
- simulate: tuple = (),
- ) -> Type['pygame.Rect']:
- """
- Calculate and update the position where the character must be painted.
- Only one character can be tracked.
- Parameters:
- tracking: True if the character must be followed, False otherwise.
- Returns:
- A rect placed in the videogame window.
- """
- if not isinstance(tracking, bool):
- raise TypeError(_('The tracking parameter must be a boolean.'))
- if not _globals.vg_scene:
- return None
- if simulate:
- offset_rect = self.rect.copy()
- x, y = simulate
- else:
- offset_rect = self.rect
- x, y = self.x, self.y
- if tracking:
- if x <= settings.WINDOW_WIDTH // 2 or x > _globals.vg_scene.width:
- offset_rect.x = x
- elif (
- settings.WINDOW_WIDTH // 2
- < x
- < _globals.vg_scene.width - settings.WINDOW_WIDTH
- ):
- offset_rect.x = settings.WINDOW_WIDTH // 2
- elif x >= _globals.vg_scene.width - settings.WINDOW_WIDTH:
- offset_rect.x = settings.WINDOW_WIDTH * 3 // 2 - (
- -x % _globals.vg_scene.width
- )
- else:
- offset_rect.x = x + _globals.vg_scene.x
- offset_rect.y = y
- return offset_rect
- def update(self):
- for action, key in self.predefined_keys.items():
- eval(f'self.{action}(key="{key}")')
- if self.gravitative:
- self.down(_globals._gravity)
- self._update_jump()
|