Merge pull request 'Amélioration de l'engine et ajout d'éléments de gameplay' (#20) from debut_jeu into main

Reviewed-on: Really-Fun-Games/NSI-RPG#20
This commit is contained in:
Yannis 2024-01-03 19:20:44 +00:00 committed by Gitea
commit e082164896
No known key found for this signature in database
24 changed files with 3206 additions and 169 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 B

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -43,6 +43,12 @@
"name":"water", "name":"water",
"probability":1, "probability":1,
"tile":4 "tile":4
},
{
"color":"#ff00d8",
"name":"path",
"probability":1,
"tile":433
}], }],
"name":"forest ground", "name":"forest ground",
"tile":1, "tile":1,
@ -134,7 +140,7 @@
}, },
{ {
"tileid":92, "tileid":92,
"wangid":[0, 0, 0, 5, 0, 0, 0, 5] "wangid":[0, 4, 0, 5, 0, 4, 0, 5]
}, },
{ {
"tileid":96, "tileid":96,
@ -415,6 +421,66 @@
{ {
"tileid":353, "tileid":353,
"wangid":[0, 3, 0, 3, 0, 3, 0, 2] "wangid":[0, 3, 0, 3, 0, 3, 0, 2]
},
{
"tileid":408,
"wangid":[0, 1, 0, 6, 0, 1, 0, 1]
},
{
"tileid":409,
"wangid":[0, 1, 0, 6, 0, 6, 0, 1]
},
{
"tileid":410,
"wangid":[0, 1, 0, 1, 0, 6, 0, 1]
},
{
"tileid":417,
"wangid":[0, 1, 0, 6, 0, 1, 0, 6]
},
{
"tileid":432,
"wangid":[0, 6, 0, 6, 0, 1, 0, 1]
},
{
"tileid":433,
"wangid":[0, 6, 0, 6, 0, 6, 0, 6]
},
{
"tileid":434,
"wangid":[0, 1, 0, 1, 0, 6, 0, 6]
},
{
"tileid":437,
"wangid":[0, 6, 0, 1, 0, 6, 0, 6]
},
{
"tileid":438,
"wangid":[0, 6, 0, 6, 0, 1, 0, 6]
},
{
"tileid":441,
"wangid":[0, 6, 0, 1, 0, 6, 0, 1]
},
{
"tileid":456,
"wangid":[0, 6, 0, 1, 0, 1, 0, 1]
},
{
"tileid":457,
"wangid":[0, 6, 0, 1, 0, 1, 0, 6]
},
{
"tileid":458,
"wangid":[0, 1, 0, 1, 0, 1, 0, 6]
},
{
"tileid":461,
"wangid":[0, 1, 0, 6, 0, 6, 0, 6]
},
{
"tileid":462,
"wangid":[0, 6, 0, 6, 0, 6, 0, 1]
}] }]
}, },
{ {

File diff suppressed because it is too large Load diff

69
src/custom_AI.py Normal file
View file

@ -0,0 +1,69 @@
import math
import random
from src.engine.entity import Entity
from src.engine.entity_manager import EntityManager
from src.engine.map_manager import MapManager
from src.engine.mobs_AI import MobAI
class WolfAI(MobAI):
def __init__(self, entity: 'Entity', entity_manager: 'EntityManager', map_manager: 'MapManager'):
super().__init__(entity, entity_manager, map_manager)
self.ATTACK_DISTANCE = 160
self.timer = 0
self.walk_x = 0
self.walk_y = 0
self.comportment = 0 # 0 : waiting, 1: walking
def update(self, delta: float):
"""Fonction executée à chaque tick pour chaque loup et qui gère l'IA."""
# On récupère l'entité joueur
player: Entity = self.entity_manager.get_by_name(self.entity_manager.player_entity_name)
# On calcule la distance en x et en y entre le loup et le joueur
x_distance = (player.x - self.entity.x)
y_distance = (player.y - self.entity.y)
# On calcule la distance
player_distance = math.sqrt(x_distance ** 2 + y_distance ** 2)
# On vérifie que le loup peut voir le joueur
if player_distance <= self.ATTACK_DISTANCE:
# On rétablit la vitesse du loup à 1
self.entity.max_speed = 1.
# Si le loup touche le joueur, il lui inflige des dégats
if player.get_collisions_with_entity(self.entity):
player.take_damages(1)
# Si le loup n'est pas déja sur le joueur, on le fait s'en raprocher
if player_distance > self.entity.max_speed:
self.entity.move(x_distance / player_distance*self.entity.max_speed,
y_distance / player_distance*self.entity.max_speed, self.map_manager)
else:
# Comportement d'attente
# On diminue la vitesse
self.entity.max_speed = 0.5
self.timer -= delta
# Si le timer est fini et que le loup était en train d'attendre, il commence à marcher
if self.timer <= 0 and self.comportment == 0:
self.comportment = 1
self.timer = random.random() * 5.
# On choisit la direction
self.walk_x = (random.random()-0.5)*2
self.walk_y = (random.random()-0.5)*2
# Si le timer est fini et que le loup était de marcher, il commence à attendre
elif self.timer <= 0 and self.comportment == 1:
self.comportment = 0
self.timer = random.random() * 3
# On fait avancer le loup quand il le doit
if self.comportment == 1:
self.entity.move(self.walk_x, self.walk_y, self.map_manager)

View file

@ -7,6 +7,9 @@ class Camera:
self.y = 0 self.y = 0
self.zoom = 1. self.zoom = 1.
# Décalage lors du mouvement du joueur
self.player_moving_offset = 100
# Variables utilisées pour le scrolling # Variables utilisées pour le scrolling
self.target_x = self.x self.target_x = self.x
self.target_y = self.y self.target_y = self.y
@ -21,8 +24,10 @@ class Camera:
# Si on suit une entité, on met à jour les coordonnées de suivi # Si on suit une entité, on met à jour les coordonnées de suivi
if self.followed_entity is not None: if self.followed_entity is not None:
self.target_x = self.followed_entity.x self.target_x = (self.followed_entity.x + self.followed_entity.mouvements[0] *
self.target_y = self.followed_entity.y self.player_moving_offset / self.zoom)
self.target_y = (self.followed_entity.y + self.followed_entity.mouvements[1] *
self.player_moving_offset / self.zoom)
self.x += (self.target_x - self.x) / self.smoothness self.x += (self.target_x - self.x) / self.smoothness
self.y += (self.target_y - self.y) / self.smoothness self.y += (self.target_y - self.y) / self.smoothness

View file

@ -29,7 +29,7 @@ class Engine:
self.event_handler = EventHandler(self) self.event_handler = EventHandler(self)
self.map_manager = MapManager() self.map_manager = MapManager()
self.camera = Camera() self.camera = Camera()
self.entity_manager = EntityManager() self.entity_manager = EntityManager(self.map_manager)
self.boss_fight_manager = BossFightManager(self) self.boss_fight_manager = BossFightManager(self)
def loop(self): def loop(self):

View file

@ -1,4 +1,7 @@
import math
from src.engine.map_manager import MapManager from src.engine.map_manager import MapManager
from src.engine.mobs_AI import MobAI
class Entity: class Entity:
@ -8,6 +11,16 @@ class Entity:
self.x = 8 self.x = 8
self.y = 8 self.y = 8
self.direction = 0 # 0 : tourné vers la droite (ou sens par défaut), 1 : tourné vers la gauche (ou retourné)
# Variables utilisées pour détecter les mouvements
self.last_x = 0
self.last_y = 0
self.mouvements = [0., 0.]
self.max_speed = 1.
self.life_points = -1 self.life_points = -1
self.max_life_points = -1 self.max_life_points = -1
@ -19,16 +32,26 @@ class Entity:
# Time utilisé pour les IA # Time utilisé pour les IA
self.time = 0 self.time = 0
self.brain: MobAI | None = None
self.name = name self.name = name
self.animation_name = None self.animation_name = None
self.shadow = None
def set_default_life(self, life: int): def set_default_life(self, life: int):
"""Définit le nombre de PV de l'entité. Mettre -1 pour rendre l'entité immortelle.""" """Définit le nombre de PV de l'entité. Mettre -1 pour rendre l'entité immortelle."""
self.life_points = life self.life_points = life
self.max_life_points = life self.max_life_points = life
def set_ai(self, ai: MobAI, engine: 'Engine'):
"""Enregistre une classe permettant de gérer l'IA du mob."""
# La ligne suivante crée une instance de la classe d'IA. Cette ligne peut causer des warnings sur certains IDE
# mais elle est bien valide
self.brain = ai(self, engine.entity_manager, engine.map_manager)
def update(self, delta: float): def update(self, delta: float):
"""Met à jour l'entité.""" """Met à jour l'entité."""
self.time += delta self.time += delta
@ -38,6 +61,14 @@ class Entity:
if self.damage_cooldown < 0: if self.damage_cooldown < 0:
self.damage_cooldown = 0 self.damage_cooldown = 0
# Si les coordonnées ont changé, l'entité a bougé
self.mouvements[0] = (self.x - self.last_x) / self.max_speed
self.mouvements[1] = (self.y - self.last_y) / self.max_speed
self.last_x = self.x
self.last_y = self.y
def take_damages(self, damages: int): def take_damages(self, damages: int):
"""Inflige {damages} dégâts à l'entité.""" """Inflige {damages} dégâts à l'entité."""
@ -51,15 +82,22 @@ class Entity:
if self.life_points < 0: if self.life_points < 0:
self.life_points = 0 self.life_points = 0
def get_collisions_with_entity(self, other: 'Entity'):
"""Retourne True si l'entité courante est en collision avec l'entité donnée."""
return (self.x+self.collision_rect[0] <= other.x+other.collision_rect[2] and
self.x+self.collision_rect[2] >= other.x+other.collision_rect[0] and
self.y+self.collision_rect[3] >= other.y+other.collision_rect[1] and
self.y+self.collision_rect[1] <= other.y+other.collision_rect[3])
def get_collisions(self, x: float, y: float, map_manager: MapManager): def get_collisions(self, x: float, y: float, map_manager: MapManager):
"""Calcule les collisions.""" """Calcule les collisions."""
# On calcule les coordonnées des points en haut à gauche et en bas à droite # On calcule les coordonnées des points en haut à gauche et en bas à droite
top_left_corner_tile = (int((x + self.collision_rect[0]) / 16), top_left_corner_tile = (math.floor((x + self.collision_rect[0]) / 16),
int((y + self.collision_rect[1]) / 16)) math.floor((y + self.collision_rect[1]) / 16))
bottom_right_corner_tile = (int((x + self.collision_rect[2]-1) / 16), bottom_right_corner_tile = (math.floor((x + self.collision_rect[2]-1) / 16),
int((y + self.collision_rect[3]-1) / 16)) math.floor((y + self.collision_rect[3]-1) / 16))
collision = False collision = False
@ -79,6 +117,19 @@ class Entity:
def move(self, x: float, y: float, map_manager: MapManager): def move(self, x: float, y: float, map_manager: MapManager):
"""Fait bouger l'entité en tenant compte des collisions.""" """Fait bouger l'entité en tenant compte des collisions."""
# On vérifie le sens du mouvement pour changer self.direction
if x > 0:
self.direction = 0
elif x < 0:
self.direction = 1
# On ne met pas de else car si x = 0, on ne change pas de direction
# On normalise la vitesse
initial_speed = math.sqrt(x**2+y**2)
x = x/initial_speed*self.max_speed
y = y/initial_speed*self.max_speed
# On simule le mouvement. Si on ne rencontre pas de collision, on applique le mouvement # On simule le mouvement. Si on ne rencontre pas de collision, on applique le mouvement
if not self.get_collisions(self.x + x, self.y, map_manager): if not self.get_collisions(self.x + x, self.y, map_manager):
self.x += x self.x += x

View file

@ -1,17 +1,29 @@
from src.engine.entity import Entity from src.engine.entity import Entity
from src.engine.map_manager import MapManager
class EntityManager: class EntityManager:
"""Classe chargée de gérer les entités.""" """Classe chargée de gérer les entités."""
def __init__(self): def __init__(self, map_manager: MapManager):
self.entities: dict[str:Entity] = {} self.entities: dict[str:Entity] = {}
self.player_entity_name = ""
self.map_manager = map_manager
def register_entity(self, name: str): def register_entity(self, name: str) -> Entity:
"""Crée une entité et l'enregistre dans un dictionnaire.""" """Crée une entité et l'enregistre dans un dictionnaire."""
entity = Entity(name) entity = Entity(name)
self.entities[name] = entity self.entities[name] = entity
return entity return entity
def set_player_entity(self, name: str):
"""Définit l'entité donnée comme le joueur. Elle peut donc être controlée."""
self.player_entity_name = name
def move_player_controls(self, x: float, y: float):
"""Bouge le joueur. X et y doivent être compris entre 0 et 1"""
player: Entity = self.get_by_name(self.player_entity_name)
player.move(x, y, self.map_manager)
def update(self, delta: float): def update(self, delta: float):
"""Met à jour toutes les entités enregistrées.""" """Met à jour toutes les entités enregistrées."""
for entity_name in list(self.entities.keys()): for entity_name in list(self.entities.keys()):
@ -20,10 +32,20 @@ class EntityManager:
if entity.life_points == 0: if entity.life_points == 0:
self.entities.pop(entity_name) self.entities.pop(entity_name)
def get_all_entities(self): if entity.brain is not None:
entity.brain.update(delta)
if self.player_entity_name:
player: Entity = self.get_by_name(self.player_entity_name)
if player.mouvements[0] != 0. or player.mouvements[1] != 0.:
player.link_animation("player_walking")
else:
player.link_animation("player_none")
def get_all_entities(self) -> list[Entity]:
"""Donne la liste de toutes les entités enregistrées.""" """Donne la liste de toutes les entités enregistrées."""
return list(self.entities.values()) return list(self.entities.values())
def get_by_name(self, name: str): def get_by_name(self, name: str) -> Entity:
"""Donne l'entité avec le nom donné.""" """Donne l'entité avec le nom donné."""
return self.entities[name] return self.entities[name]

View file

@ -1,3 +1,5 @@
import math
from pygame import event from pygame import event
from pygame.locals import * from pygame.locals import *
@ -22,24 +24,29 @@ class EventHandler:
elif e.type == KEYUP: elif e.type == KEYUP:
self.key_pressed.remove(e.key) self.key_pressed.remove(e.key)
player = self.engine.entity_manager.get_by_name("player") if self.engine.entity_manager.player_entity_name:
player.link_animation("player_none") if K_RIGHT in self.key_pressed:
if K_RIGHT in self.key_pressed: self.engine.entity_manager.move_player_controls(1, 0)
player.move(2, 0, self.engine.map_manager) if K_LEFT in self.key_pressed:
player.link_animation("player_walking") self.engine.entity_manager.move_player_controls(-1, 0)
if K_LEFT in self.key_pressed: if K_UP in self.key_pressed:
player.move(-2, 0, self.engine.map_manager) self.engine.entity_manager.move_player_controls(0, -1)
player.link_animation("player_walking") if K_DOWN in self.key_pressed:
if K_UP in self.key_pressed: self.engine.entity_manager.move_player_controls(0, 1)
player.move(0, -2, self.engine.map_manager)
player.link_animation("player_walking") if self.engine.DEBUG_MODE:
if K_DOWN in self.key_pressed: if K_l in self.key_pressed:
player.move(0, 2, self.engine.map_manager) self.engine.entity_manager.get_by_name("player").take_damages(1)
player.link_animation("player_walking") if K_p in self.key_pressed:
self.engine.renderer.emit_particles(math.floor(self.engine.entity_manager.get_by_name("player").x),
math.floor(self.engine.entity_manager.get_by_name("player").y),
16, 16, 16, 1, 8, 0, 1, 0.2, 1., (0, 200, 200))
if K_o in self.key_pressed:
print(f"Player pos: X = {self.engine.entity_manager.get_by_name('player').x} "
f"Y = {self.engine.entity_manager.get_by_name('player').y}")
if K_x in self.key_pressed: if K_x in self.key_pressed:
self.engine.camera.target_zoom *= 1.01 self.engine.camera.target_zoom *= 1.01
if K_c in self.key_pressed: if K_c in self.key_pressed:
self.engine.camera.target_zoom *= 0.99 self.engine.camera.target_zoom *= 0.99
if K_l in self.key_pressed:
player.take_damages(1)

View file

@ -28,18 +28,20 @@ class MapManager:
# On calcule les coordonnées du chunk # On calcule les coordonnées du chunk
coordinates = (x//self.chunk_width, y//self.chunk_height) coordinates = (x//self.chunk_width, y//self.chunk_height)
# On transforme les coordonnées globales en coordonnées dans le chunk # On vérifie que le chunk existe
x %= 16 if coordinates in layer:
y %= 16 chunk = layer[coordinates]
if coordinates not in layer: # On transforme les coordonnées globales en coordonnées dans le chunk,
return 0 # On calcule l'index et on renvoie la tile
return chunk[x % 16 + y % 16 * self.chunk_width]
chunk = layer[coordinates] # Si on ne trouve pas le chunk, on renvoit "vide"
return 0
# On vérifie que la tile demandée existe sinon on répond "vide" def get_tile_at_quick(self, x: int, y: int, layer_id: int):
if x >= self.chunk_width or x < 0 or y >= self.chunk_height or y < 0: """Version optimisée de get_tile_at()."""
return 0 chunk = self.map_layers[layer_id].get((x//self.chunk_width, y//self.chunk_height))
if chunk is not None:
# On calcule l'index et on renvoie la tile return chunk[x % 16 + y % 16 * self.chunk_width]
return chunk[x+y*self.chunk_width] return 0

12
src/engine/mobs_AI.py Normal file
View file

@ -0,0 +1,12 @@
import math
class MobAI:
def __init__(self, entity_: 'entity.Entity', entity_manager: 'EntityManager', map_manager: 'MapManager'):
self.entity = entity_
self.entity_manager = entity_manager
self.map_manager = map_manager
def update(self):
pass

View file

@ -1,6 +1,7 @@
import math import math
import random
from pygame import display, image, surface, transform, draw from pygame import display, image, surface, transform, draw, font
from pygame.locals import RESIZABLE, SRCALPHA, FULLSCREEN from pygame.locals import RESIZABLE, SRCALPHA, FULLSCREEN
import src.engine.engine as engine import src.engine.engine as engine
@ -13,7 +14,7 @@ class Renderer:
def __init__(self, core: 'engine.Engine'): def __init__(self, core: 'engine.Engine'):
self.engine = core self.engine = core
self.window_type = FULLSCREEN self.window_type = RESIZABLE
self.window_size = (display.Info().current_w, display.Info().current_h) if self.window_type == FULLSCREEN else (600, 600) self.window_size = (display.Info().current_w, display.Info().current_h) if self.window_type == FULLSCREEN else (600, 600)
self.window = display.set_mode(self.window_size, self.window_type) self.window = display.set_mode(self.window_size, self.window_type)
self.tiles = [] self.tiles = []
@ -28,9 +29,43 @@ class Renderer:
# Variables utilisées par le menu principal # Variables utilisées par le menu principal
self.main_menu_assets: dict[str: Anim] = {} self.main_menu_assets: dict[str: Anim] = {}
# Ombres d'entités
self.shadows = {}
# Particules affichées
self.particles = []
def emit_particles(self, x: int, y: int, w: int, h: int, count: int, min_size: int, max_size: int,
min_speed: float, max_speed: float, min_life_time: float, max_life_time: float,
color: tuple[int, int, int]):
"""Emmet des particules aux coordonnées données dans un rectangle de demi-largeur {w} et de demi-hauteur {h}."""
for _ in range(count):
# On choisit la taille de la particule
part_size = random.randint(min_size, max_size)
# On choisit sa vitesse en x et en y
part_speed_x = random.uniform(min_speed, max_speed)
# On inverse la vitesse de manière aléatoire
if random.randint(0, 1) == 1:
part_speed_x = - part_speed_x
part_speed_y = random.uniform(min_speed, max_speed)
if random.randint(0, 1) == 1:
part_speed_y = - part_speed_y
# On choisit sa position dans le rectangle
part_x = random.randint(x-w, x+w-part_size)
part_y = random.randint(y-h, y+h-part_size)
# On choisit la durée de vie
part_life_time = random.uniform(min_life_time, max_life_time)
# On ajoute la particule dans la liste des particules
# Le 0 correspond au temps de vie depuis la création de la particule
self.particles.append([part_x, part_y, part_size, part_speed_x, part_speed_y, 0., part_life_time, color])
def load_main_menu_assets(self, path: str): def load_main_menu_assets(self, path: str):
"""Charge les assets du menu principal depuis le dossier donné.""" """Charge les assets du menu principal depuis le dossier donné."""
def load_tile_set(self, file_path: str, tile_size: int): def load_tile_set(self, file_path: str, tile_size: int):
"""Charge le jeu de tuiles en utilisant le fichier donné et la taille donnée.""" """Charge le jeu de tuiles en utilisant le fichier donné et la taille donnée."""
@ -61,6 +96,7 @@ class Renderer:
self.render_layer(0, rendered_surface) self.render_layer(0, rendered_surface)
self.render_layer(1, rendered_surface) self.render_layer(1, rendered_surface)
self.render_entities(rendered_surface, gui_surface, delta) self.render_entities(rendered_surface, gui_surface, delta)
self.render_particles(rendered_surface, delta)
self.render_layer(2, rendered_surface) self.render_layer(2, rendered_surface)
# Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale # Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale
@ -76,9 +112,24 @@ class Renderer:
self.render_boss_fight_scene(delta) self.render_boss_fight_scene(delta)
self.render_boss_fight_gui() self.render_boss_fight_gui()
# Conteur de FPS en mode DEBUG
if self.engine.DEBUG_MODE:
self.window.blit(font.SysFont("Arial", 20).render(f"FPS: {self.engine.clock.get_fps()}", True, (255, 0, 0)),
(0, 0))
player = self.engine.entity_manager.get_by_name('player')
self.window.blit(font.SysFont("Arial", 20).render(f"X: {player.x} Y:{player.y}",
True, (255, 0, 0)), (0, 30))
self.window.blit(font.SysFont("Arial", 20).render(f"Zoom: {self.engine.camera.zoom}",
True, (255, 0, 0)), (0, 60))
# Apres avoir tout rendu, on met à jour l'écran # Apres avoir tout rendu, on met à jour l'écran
display.update() display.update()
def register_shadow(self, file_path: str, name: str):
"""Enregistre une image d'ombre utilisée pour le rendu des entités."""
shadow = image.load(file_path).convert_alpha()
self.shadows[name] = shadow
def register_animation(self, animation: Anim, name: str): def register_animation(self, animation: Anim, name: str):
"""Enregistre une animation.""" """Enregistre une animation."""
self.animations[name] = animation self.animations[name] = animation
@ -91,6 +142,22 @@ class Renderer:
"""Ajoute une animation pour le joueur lors d'un combat de boss.""" """Ajoute une animation pour le joueur lors d'un combat de boss."""
self.boss_fight_player_animations[name] = animation self.boss_fight_player_animations[name] = animation
def render_particles(self, rendered_surface: surface.Surface, delta: float):
"""Update et rend les particules."""
x_middle_offset = display.get_window_size()[0] / 2 / self.engine.camera.zoom
y_middle_offset = display.get_window_size()[1] / 2 / self.engine.camera.zoom
for part in self.particles.copy():
part_dest = (math.floor(part[0] - self.engine.camera.x + x_middle_offset),
math.floor(part[1] - self.engine.camera.y + y_middle_offset))
draw.rect(rendered_surface, part[7], part_dest + (part[2], part[2]))
part[5] += delta
part[0] += part[3]
part[1] += part[4]
if part[5] > part[6]:
self.particles.remove(part)
def render_boss_fight_scene(self, delta: float): def render_boss_fight_scene(self, delta: float):
"""Rend les sprites du joueur et du boss lors d'un combat de boss.""" """Rend les sprites du joueur et du boss lors d'un combat de boss."""
@ -118,7 +185,8 @@ class Renderer:
def render_boss_fight_gui(self): def render_boss_fight_gui(self):
"""Rend la barre d'action en bas de l'écran pendant le combat de boss.""" """Rend la barre d'action en bas de l'écran pendant le combat de boss."""
resized_container = transform.scale(self.boss_fight_GUI_container, (display.get_window_size()[0], self.boss_fight_GUI_container.get_height()/self.boss_fight_GUI_container.get_width()*display.get_window_size()[0])) resized_container = transform.scale(self.boss_fight_GUI_container,
(display.get_window_size()[0], self.boss_fight_GUI_container.get_height()/self.boss_fight_GUI_container.get_width()*display.get_window_size()[0]))
self.window.blit(resized_container, (0, display.get_window_size()[1]-resized_container.get_height())) self.window.blit(resized_container, (0, display.get_window_size()[1]-resized_container.get_height()))
def render_entities(self, rendered_surface: surface.Surface, gui_surface: surface.Surface, delta: float): def render_entities(self, rendered_surface: surface.Surface, gui_surface: surface.Surface, delta: float):
@ -139,10 +207,20 @@ class Renderer:
entity.y - self.engine.camera.y - y_middle_offset - frame.get_height() > 0): entity.y - self.engine.camera.y - y_middle_offset - frame.get_height() > 0):
continue continue
# On flip l'image horizontalement si l'entité est retournée
if entity.direction == 1:
frame = transform.flip(frame, True, False)
# On calcule les coordonnées de rendu de l'entité # On calcule les coordonnées de rendu de l'entité
entity_dest = (math.floor(entity.x - self.engine.camera.x + x_middle_offset - frame.get_width() / 2), entity_dest = (math.floor(entity.x - self.engine.camera.x + x_middle_offset - frame.get_width() / 2),
math.floor(entity.y - self.engine.camera.y + y_middle_offset - frame.get_height() / 2)) math.floor(entity.y - self.engine.camera.y + y_middle_offset - frame.get_height() / 2))
# On récupert l'ombre de l'entité
if entity.shadow is not None:
shadow_image = self.shadows[entity.shadow]
# On rend l'ombre
rendered_surface.blit(shadow_image, entity_dest)
# On affiche l'image # On affiche l'image
rendered_surface.blit(frame, entity_dest) rendered_surface.blit(frame, entity_dest)
@ -203,12 +281,18 @@ class Renderer:
x_map_offset = math.floor((self.engine.camera.x - x_middle_offset) / self.tile_size) x_map_offset = math.floor((self.engine.camera.x - x_middle_offset) / self.tile_size)
y_map_offset = math.floor((self.engine.camera.y - y_middle_offset) / self.tile_size) y_map_offset = math.floor((self.engine.camera.y - y_middle_offset) / self.tile_size)
# On précalcule le décallage des tiles sur l'écran
tile_x_offset = - self.engine.camera.x + x_middle_offset
tile_y_offset = - self.engine.camera.y + y_middle_offset
# On itère pour chaque couche, toutes les tiles visibles par la caméra # On itère pour chaque couche, toutes les tiles visibles par la caméra
for x in range(x_map_offset, x_map_offset + x_map_range): for x in range(x_map_offset, x_map_offset + x_map_range):
# On précalcule les coordonnées en x
tile_render_x = math.floor(x * self.tile_size + tile_x_offset)
for y in range(y_map_offset, y_map_offset + y_map_range): for y in range(y_map_offset, y_map_offset + y_map_range):
# On récupère l'id de la tile à la position donnée # On récupère l'id de la tile à la position donnée
tile_id = self.engine.map_manager.get_tile_at(x, y, layer_id) tile_id = self.engine.map_manager.get_tile_at_quick(x, y, layer_id)
# Si l'id est 0, il s'agit de vide donc on saute le rendu # Si l'id est 0, il s'agit de vide donc on saute le rendu
if tile_id == 0: if tile_id == 0:
@ -216,5 +300,11 @@ class Renderer:
# Puis, on cherche à quelle image elle correspond et on la colle sur notre surface # Puis, on cherche à quelle image elle correspond et on la colle sur notre surface
rendered_surface.blit(self.tiles[tile_id - 1], rendered_surface.blit(self.tiles[tile_id - 1],
(math.floor(x * self.tile_size - self.engine.camera.x + x_middle_offset), (tile_render_x,
math.floor(y * self.tile_size - self.engine.camera.y + y_middle_offset))) math.floor(y * self.tile_size + tile_y_offset)))
if self.engine.DEBUG_MODE and layer_id == 1:
draw.rect(rendered_surface, (100, 100, 255),
(math.floor(x * self.tile_size - self.engine.camera.x + x_middle_offset),
math.floor(y * self.tile_size - self.engine.camera.y + y_middle_offset),
self.tile_size, self.tile_size), width=1)

View file

@ -1,5 +1,6 @@
import pygame.image import pygame.image
from src.custom_AI import WolfAI
from src.engine.animation import Anim from src.engine.animation import Anim
from src.engine.engine import Engine from src.engine.engine import Engine
from src.engine.enums import GameState from src.engine.enums import GameState
@ -14,8 +15,9 @@ class Game(Engine):
self.create_player_entity() self.create_player_entity()
self.load_boss_fight_assets() self.load_boss_fight_assets()
self.spawn_mobs()
self.DEBUG_MODE = True self.DEBUG_MODE = False
self.game_state = GameState.NORMAL self.game_state = GameState.NORMAL
@ -33,10 +35,34 @@ class Game(Engine):
player.link_animation("player_none") player.link_animation("player_none")
player.collision_rect = [-6, -7, 6, 16] player.collision_rect = [-6, -7, 6, 16]
player.set_default_life(10) player.set_default_life(15)
player.max_speed = 1.1
self.entity_manager.set_player_entity("player")
player.shadow = "player_shadow"
self.renderer.register_shadow("assets/textures/entities/player/shadow.png", "player_shadow")
self.camera.follow_entity(player) self.camera.follow_entity(player)
def spawn_mobs(self):
"""Fait apparaitre les mobs de la map."""
anim = Anim(0.5)
anim.load_animation_from_directory("assets/textures/entities/wolf/none")
self.renderer.register_animation(anim, "wolf_none")
mob = self.entity_manager.register_entity("wolf1")
mob.set_ai(WolfAI, self)
mob.link_animation("wolf_none")
mob.collision_rect = [-15, -7, 12, 7]
mob.set_default_life(5)
mob.max_speed = 1.
mob.x, mob.y = 160, 16
def load_boss_fight_assets(self): def load_boss_fight_assets(self):
"""Charge les animations de combat des combats de boss.""" """Charge les animations de combat des combats de boss."""
player_none = Anim(1) player_none = Anim(1)