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",
|
"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]
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
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.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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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 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,10 +29,44 @@ 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."""
|
||||||
tile_set = image.load(file_path).convert_alpha()
|
tile_set = image.load(file_path).convert_alpha()
|
||||||
|
@ -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)
|
||||||
|
|
30
src/main.py
|
@ -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)
|
||||||
|
|