Amélioration de l'engine et ajout d'éléments de gameplay #20
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 410 B |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 425 B |
BIN
assets/textures/entities/player/shadow.png
Normal file
After Width: | Height: | Size: 476 B |
Before Width: | Height: | Size: 804 B After Width: | Height: | Size: 408 B |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 406 B |
Before Width: | Height: | Size: 771 B After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 405 B |
BIN
assets/textures/entities/player/walking/layers.pdn
Normal file
BIN
assets/textures/entities/wolf/none/0.png
Normal file
After Width: | Height: | Size: 236 B |
82
assets/textures/tileset.pdn
Normal file
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
@ -43,6 +43,12 @@
|
|||
"name":"water",
|
||||
"probability":1,
|
||||
"tile":4
|
||||
},
|
||||
{
|
||||
"color":"#ff00d8",
|
||||
"name":"path",
|
||||
"probability":1,
|
||||
"tile":433
|
||||
}],
|
||||
"name":"forest ground",
|
||||
"tile":1,
|
||||
|
@ -134,7 +140,7 @@
|
|||
},
|
||||
{
|
||||
"tileid":92,
|
||||
"wangid":[0, 0, 0, 5, 0, 0, 0, 5]
|
||||
"wangid":[0, 4, 0, 5, 0, 4, 0, 5]
|
||||
},
|
||||
{
|
||||
"tileid":96,
|
||||
|
@ -415,6 +421,66 @@
|
|||
{
|
||||
"tileid":353,
|
||||
"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]
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
|
2839
maps/map5.tmj
69
src/custom_AI.py
Normal 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)
|
|
@ -7,6 +7,9 @@ class Camera:
|
|||
self.y = 0
|
||||
self.zoom = 1.
|
||||
|
||||
# Décalage lors du mouvement du joueur
|
||||
self.player_moving_offset = 100
|
||||
|
||||
# Variables utilisées pour le scrolling
|
||||
self.target_x = self.x
|
||||
self.target_y = self.y
|
||||
|
@ -21,8 +24,10 @@ class Camera:
|
|||
|
||||
# Si on suit une entité, on met à jour les coordonnées de suivi
|
||||
if self.followed_entity is not None:
|
||||
self.target_x = self.followed_entity.x
|
||||
self.target_y = self.followed_entity.y
|
||||
self.target_x = (self.followed_entity.x + self.followed_entity.mouvements[0] *
|
||||
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.y += (self.target_y - self.y) / self.smoothness
|
||||
|
|
|
@ -29,7 +29,7 @@ class Engine:
|
|||
self.event_handler = EventHandler(self)
|
||||
self.map_manager = MapManager()
|
||||
self.camera = Camera()
|
||||
self.entity_manager = EntityManager()
|
||||
self.entity_manager = EntityManager(self.map_manager)
|
||||
self.boss_fight_manager = BossFightManager(self)
|
||||
|
||||
def loop(self):
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import math
|
||||
|
||||
from src.engine.map_manager import MapManager
|
||||
from src.engine.mobs_AI import MobAI
|
||||
|
||||
|
||||
class Entity:
|
||||
|
@ -8,6 +11,16 @@ class Entity:
|
|||
self.x = 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.max_life_points = -1
|
||||
|
||||
|
@ -19,16 +32,26 @@ class Entity:
|
|||
|
||||
# Time utilisé pour les IA
|
||||
self.time = 0
|
||||
self.brain: MobAI | None = None
|
||||
|
||||
self.name = name
|
||||
|
||||
self.animation_name = None
|
||||
|
||||
self.shadow = None
|
||||
|
||||
def set_default_life(self, life: int):
|
||||
"""Définit le nombre de PV de l'entité. Mettre -1 pour rendre l'entité immortelle."""
|
||||
self.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):
|
||||
"""Met à jour l'entité."""
|
||||
self.time += delta
|
||||
|
@ -38,6 +61,14 @@ class Entity:
|
|||
if 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):
|
||||
"""Inflige {damages} dégâts à l'entité."""
|
||||
|
||||
|
@ -51,15 +82,22 @@ class Entity:
|
|||
if 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):
|
||||
"""Calcule les collisions."""
|
||||
|
||||
# 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),
|
||||
int((y + self.collision_rect[1]) / 16))
|
||||
top_left_corner_tile = (math.floor((x + self.collision_rect[0]) / 16),
|
||||
math.floor((y + self.collision_rect[1]) / 16))
|
||||
|
||||
bottom_right_corner_tile = (int((x + self.collision_rect[2]-1) / 16),
|
||||
int((y + self.collision_rect[3]-1) / 16))
|
||||
bottom_right_corner_tile = (math.floor((x + self.collision_rect[2]-1) / 16),
|
||||
math.floor((y + self.collision_rect[3]-1) / 16))
|
||||
|
||||
collision = False
|
||||
|
||||
|
@ -79,6 +117,19 @@ class Entity:
|
|||
def move(self, x: float, y: float, map_manager: MapManager):
|
||||
"""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
|
||||
if not self.get_collisions(self.x + x, self.y, map_manager):
|
||||
self.x += x
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
from src.engine.entity import Entity
|
||||
from src.engine.map_manager import MapManager
|
||||
|
||||
|
||||
class EntityManager:
|
||||
"""Classe chargée de gérer les entités."""
|
||||
def __init__(self):
|
||||
def __init__(self, map_manager: MapManager):
|
||||
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."""
|
||||
entity = Entity(name)
|
||||
self.entities[name] = 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):
|
||||
"""Met à jour toutes les entités enregistrées."""
|
||||
for entity_name in list(self.entities.keys()):
|
||||
|
@ -20,10 +32,20 @@ class EntityManager:
|
|||
if entity.life_points == 0:
|
||||
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."""
|
||||
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é."""
|
||||
return self.entities[name]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import math
|
||||
|
||||
from pygame import event
|
||||
from pygame.locals import *
|
||||
|
||||
|
@ -22,24 +24,29 @@ class EventHandler:
|
|||
elif e.type == KEYUP:
|
||||
self.key_pressed.remove(e.key)
|
||||
|
||||
player = self.engine.entity_manager.get_by_name("player")
|
||||
player.link_animation("player_none")
|
||||
if self.engine.entity_manager.player_entity_name:
|
||||
if K_RIGHT in self.key_pressed:
|
||||
player.move(2, 0, self.engine.map_manager)
|
||||
player.link_animation("player_walking")
|
||||
self.engine.entity_manager.move_player_controls(1, 0)
|
||||
if K_LEFT in self.key_pressed:
|
||||
player.move(-2, 0, self.engine.map_manager)
|
||||
player.link_animation("player_walking")
|
||||
self.engine.entity_manager.move_player_controls(-1, 0)
|
||||
if K_UP in self.key_pressed:
|
||||
player.move(0, -2, self.engine.map_manager)
|
||||
player.link_animation("player_walking")
|
||||
self.engine.entity_manager.move_player_controls(0, -1)
|
||||
if K_DOWN in self.key_pressed:
|
||||
player.move(0, 2, self.engine.map_manager)
|
||||
player.link_animation("player_walking")
|
||||
self.engine.entity_manager.move_player_controls(0, 1)
|
||||
|
||||
if self.engine.DEBUG_MODE:
|
||||
if K_l in self.key_pressed:
|
||||
self.engine.entity_manager.get_by_name("player").take_damages(1)
|
||||
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:
|
||||
self.engine.camera.target_zoom *= 1.01
|
||||
if K_c in self.key_pressed:
|
||||
self.engine.camera.target_zoom *= 0.99
|
||||
if K_l in self.key_pressed:
|
||||
player.take_damages(1)
|
||||
|
||||
|
|
|
@ -28,18 +28,20 @@ class MapManager:
|
|||
# On calcule les coordonnées du chunk
|
||||
coordinates = (x//self.chunk_width, y//self.chunk_height)
|
||||
|
||||
# On transforme les coordonnées globales en coordonnées dans le chunk
|
||||
x %= 16
|
||||
y %= 16
|
||||
|
||||
if coordinates not in layer:
|
||||
return 0
|
||||
|
||||
# On vérifie que le chunk existe
|
||||
if coordinates in layer:
|
||||
chunk = layer[coordinates]
|
||||
|
||||
# On vérifie que la tile demandée existe sinon on répond "vide"
|
||||
if x >= self.chunk_width or x < 0 or y >= self.chunk_height or y < 0:
|
||||
# On transforme les coordonnées globales en coordonnées dans le chunk,
|
||||
# On calcule l'index et on renvoie la tile
|
||||
return chunk[x % 16 + y % 16 * self.chunk_width]
|
||||
|
||||
# Si on ne trouve pas le chunk, on renvoit "vide"
|
||||
return 0
|
||||
|
||||
# On calcule l'index et on renvoie la tile
|
||||
return chunk[x+y*self.chunk_width]
|
||||
def get_tile_at_quick(self, x: int, y: int, layer_id: int):
|
||||
"""Version optimisée de get_tile_at()."""
|
||||
chunk = self.map_layers[layer_id].get((x//self.chunk_width, y//self.chunk_height))
|
||||
if chunk is not None:
|
||||
return chunk[x % 16 + y % 16 * self.chunk_width]
|
||||
return 0
|
||||
|
|
12
src/engine/mobs_AI.py
Normal 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
|
|
@ -1,6 +1,7 @@
|
|||
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
|
||||
|
||||
import src.engine.engine as engine
|
||||
|
@ -13,7 +14,7 @@ class Renderer:
|
|||
|
||||
def __init__(self, core: 'engine.Engine'):
|
||||
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 = display.set_mode(self.window_size, self.window_type)
|
||||
self.tiles = []
|
||||
|
@ -28,10 +29,44 @@ class Renderer:
|
|||
# Variables utilisées par le menu principal
|
||||
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):
|
||||
"""Charge les assets du menu principal depuis le dossier donné."""
|
||||
|
||||
|
||||
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."""
|
||||
tile_set = image.load(file_path).convert_alpha()
|
||||
|
@ -61,6 +96,7 @@ class Renderer:
|
|||
self.render_layer(0, rendered_surface)
|
||||
self.render_layer(1, rendered_surface)
|
||||
self.render_entities(rendered_surface, gui_surface, delta)
|
||||
self.render_particles(rendered_surface, delta)
|
||||
self.render_layer(2, rendered_surface)
|
||||
|
||||
# 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_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
|
||||
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):
|
||||
"""Enregistre une animation."""
|
||||
self.animations[name] = animation
|
||||
|
@ -91,6 +142,22 @@ class Renderer:
|
|||
"""Ajoute une animation pour le joueur lors d'un combat de boss."""
|
||||
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):
|
||||
"""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):
|
||||
"""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()))
|
||||
|
||||
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):
|
||||
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é
|
||||
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))
|
||||
|
||||
# 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
|
||||
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)
|
||||
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
|
||||
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):
|
||||
|
||||
# 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
|
||||
if tile_id == 0:
|
||||
|
@ -216,5 +300,11 @@ class Renderer:
|
|||
|
||||
# Puis, on cherche à quelle image elle correspond et on la colle sur notre surface
|
||||
rendered_surface.blit(self.tiles[tile_id - 1],
|
||||
(tile_render_x,
|
||||
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)))
|
||||
math.floor(y * self.tile_size - self.engine.camera.y + y_middle_offset),
|
||||
self.tile_size, self.tile_size), width=1)
|
||||
|
|
30
src/main.py
|
@ -1,5 +1,6 @@
|
|||
import pygame.image
|
||||
|
||||
from src.custom_AI import WolfAI
|
||||
from src.engine.animation import Anim
|
||||
from src.engine.engine import Engine
|
||||
from src.engine.enums import GameState
|
||||
|
@ -14,8 +15,9 @@ class Game(Engine):
|
|||
|
||||
self.create_player_entity()
|
||||
self.load_boss_fight_assets()
|
||||
self.spawn_mobs()
|
||||
|
||||
self.DEBUG_MODE = True
|
||||
self.DEBUG_MODE = False
|
||||
|
||||
self.game_state = GameState.NORMAL
|
||||
|
||||
|
@ -33,10 +35,34 @@ class Game(Engine):
|
|||
player.link_animation("player_none")
|
||||
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)
|
||||
|
||||
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):
|
||||
"""Charge les animations de combat des combats de boss."""
|
||||
player_none = Anim(1)
|
||||
|
|