Implementation de la base de l'engine #3
BIN
assets/entities/test/none/0.png
Normal file
BIN
assets/entities/test/none/0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 B |
BIN
assets/entities/test/none/1.png
Normal file
BIN
assets/entities/test/none/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 158 B |
BIN
assets/entities/test/none/2.png
Normal file
BIN
assets/entities/test/none/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 159 B |
BIN
assets/tiles.png
BIN
assets/tiles.png
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
66
src/animation.py
Normal file
66
src/animation.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Classe animation adaptée depuis ESG Engine :
|
||||
# https://github.com/yannis300307/ESG_Engine/blob/main/ESG_Engine/client/animation.py
|
||||
import os
|
||||
|
||||
import pygame
|
||||
|
||||
|
||||
class Anim:
|
||||
"""Une animation contenant les images pygame et le temps entre chaque frames."""
|
||||
def __init__(self, change_frame_time: float):
|
||||
self.frames = []
|
||||
self.change_frame_time = change_frame_time # Delay entre chaque frames
|
||||
self.current_frame = 0
|
||||
self.time = 0
|
||||
|
||||
def add_frame(self, frame: pygame.Surface):
|
||||
"""Enregistre une nouvelle image dans la liste des frames."""
|
||||
self.frames.append(frame.convert_alpha())
|
||||
|
||||
def get_frame_nbr(self):
|
||||
"""Renvoie le nombre d'images enregistrées."""
|
||||
return len(self.frames)
|
||||
|
||||
def get_frame(self, delta: float):
|
||||
"""Donne l'image courante de l'animation."""
|
||||
# Avant de retourner l'image, on met à jour le delay
|
||||
self.update_current_frame(delta)
|
||||
|
||||
# Si le delay entre deux images est écoulé, on incrémente le numéro de l'image et on remet le temps à 0
|
||||
if self.time >= self.change_frame_time:
|
||||
self.current_frame += 1
|
||||
self.time = 0
|
||||
|
||||
# Si on sort de la liste d'images, on revient au début
|
||||
if self.current_frame >= len(self.frames):
|
||||
self.current_frame = 0
|
||||
|
||||
return self.frames[self.current_frame]
|
||||
|
||||
def update_current_frame(self, delta: float):
|
||||
"""Met à jour le delay de l'image courante avec le delta time."""
|
||||
self.time += delta
|
||||
|
||||
def get_specific_frame(self, base: int):
|
||||
"""Donne la {base} ème image apres l'image courante."""
|
||||
# Si le delay entre deux images est écoulé, on incrémente le numéro de l'image et on remet le temps à 0
|
||||
if self.time >= self.change_frame_time:
|
||||
self.current_frame += 1
|
||||
self.time = 0
|
||||
|
||||
# Si on sort de la liste d'images, on revient au début
|
||||
if self.current_frame >= len(self.frames):
|
||||
self.current_frame = 0
|
||||
|
||||
return self.frames[(self.current_frame + base) % len(self.frames)]
|
||||
|
||||
def load_animation_from_directory(self, path: str):
|
||||
"""Récupère toutes les images au format png dans un dossier et les enregistre comme frames de l'animation."""
|
||||
|
||||
# On récupère tous les fichiers dans le dossier donné
|
||||
files = os.listdir(path)
|
||||
|
||||
# Si ils sont bien en PNG, on les enregistre
|
||||
for file in files:
|
||||
if file.endswith(".png"):
|
||||
self.add_frame(pygame.image.load(path + os.sep + file))
|
|
@ -1,3 +1,6 @@
|
|||
from src.entity import Entity
|
||||
|
||||
|
||||
class Camera:
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
|
@ -11,8 +14,19 @@ class Camera:
|
|||
|
||||
self.smoothness = 20.
|
||||
|
||||
self.followed_entity: Entity | None = None
|
||||
|
||||
def update(self):
|
||||
"""Met à jour la caméra. Permet, par exemple, de faire le scrolling."""
|
||||
|
||||
# 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.x += (self.target_x - self.x) / self.smoothness
|
||||
self.y += (self.target_y - self.y) / self.smoothness
|
||||
self.zoom += (self.target_zoom - self.zoom) / self.smoothness
|
||||
|
||||
def follow_entity(self, entity: Entity):
|
||||
self.followed_entity = entity
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from src.animation import Anim
|
||||
from src.camera import Camera
|
||||
from src.entity_manager import EntityManager
|
||||
from src.event_handler import EventHandler
|
||||
from src.map_manager import MapManager
|
||||
from src.renderer import Renderer
|
||||
|
@ -18,11 +20,19 @@ class Engine:
|
|||
self.event_handler = EventHandler(self)
|
||||
self.map_manager = MapManager()
|
||||
self.camera = Camera()
|
||||
self.entity_manager = EntityManager()
|
||||
|
||||
self.map_manager.load_new("maps/map1.tmj")
|
||||
self.map_manager.load_new("maps/map2.tmj")
|
||||
|
||||
self.renderer.load_tile_set("assets/tiles.png", 16)
|
||||
|
||||
anim = Anim(0.5) # TODO : REMOVE (ONLY USED FOR TESTING)
|
||||
anim.load_animation_from_directory("assets/entities/test/none")
|
||||
self.renderer.register_animation(anim, "test_none")
|
||||
|
||||
test_entity = self.entity_manager.register_entity("test")
|
||||
test_entity.link_animation("test_none")
|
||||
|
||||
def loop(self):
|
||||
"""Fonction à lancer au début du programme et qui va lancer les updates dans une boucle.
|
||||
Attend jusqu'à la fin du jeu."""
|
||||
|
@ -34,9 +44,10 @@ class Engine:
|
|||
def update(self):
|
||||
"""Fonction qui regroupe toutes les updates des composants. Elle permet de mettre à jour le jeu quand on
|
||||
l'appelle."""
|
||||
self.camera.update()
|
||||
self.entity_manager.update(0.016666666)
|
||||
self.renderer.update()
|
||||
self.event_handler.update()
|
||||
self.camera.update()
|
||||
|
||||
def stop(self):
|
||||
"""Arrête le programme."""
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
class Entity:
|
||||
def __init__(self):
|
||||
"""Classe permettant de gérer les entités. Créée automatiquement par `EntityManager.register_entity()`"""
|
||||
def __init__(self, name: str):
|
||||
self.x = 2
|
||||
self.y = 2
|
||||
|
||||
# Time utilisé pour les IA
|
||||
self.time = 0
|
||||
|
||||
self.name = name
|
||||
|
||||
self.animation_name = None
|
||||
|
||||
def update(self, delta: float):
|
||||
"""Met à jour l'entité."""
|
||||
self.x += 1
|
||||
|
||||
self.time += delta
|
||||
|
||||
def link_animation(self, name: str):
|
||||
self.animation_name = name
|
||||
|
|
22
src/entity_manager.py
Normal file
22
src/entity_manager.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from src.entity import Entity
|
||||
|
||||
|
||||
class EntityManager:
|
||||
"""Classe chargée de gérer les entités."""
|
||||
def __init__(self):
|
||||
self.entities: dict[str:Entity] = {}
|
||||
|
||||
def register_entity(self, name: str):
|
||||
"""Crée une entité et l'enregistre dans un dictionnaire."""
|
||||
entity = Entity(name)
|
||||
self.entities[name] = entity
|
||||
return entity
|
||||
|
||||
def update(self, delta: float):
|
||||
"""Met à jour toutes les entités enregistrées."""
|
||||
for entity in self.entities.values():
|
||||
entity.update(delta)
|
||||
|
||||
def get_all_entities(self):
|
||||
"""Donne la liste de toutes les entités enregistrées."""
|
||||
return list(self.entities.values())
|
|
@ -1,7 +1,10 @@
|
|||
import math
|
||||
|
||||
from pygame import display, image, surface, transform
|
||||
from pygame.locals import RESIZABLE
|
||||
|
||||
import src.engine as engine
|
||||
from src.animation import Anim
|
||||
|
||||
|
||||
class Renderer:
|
||||
|
@ -11,6 +14,7 @@ class Renderer:
|
|||
self.window = display.set_mode((600, 600), RESIZABLE)
|
||||
self.tiles = []
|
||||
self.tile_size = 0
|
||||
self.animations: dict[str: Anim] = {}
|
||||
|
||||
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."""
|
||||
|
@ -28,12 +32,40 @@ class Renderer:
|
|||
"""Fait le rendu du jeu."""
|
||||
self.window.fill((255, 255, 255))
|
||||
|
||||
self.render_map()
|
||||
# On crée une surface temporaire qui nous permettra de faire le rendu à l'échelle 1:1
|
||||
rendered_surface_size = (display.get_window_size()[0] / self.engine.camera.zoom, display.get_window_size()[1] / self.engine.camera.zoom)
|
||||
rendered_surface = surface.Surface(rendered_surface_size)
|
||||
|
||||
self.renderer_layer(0, rendered_surface)
|
||||
self.render_entities(rendered_surface)
|
||||
self.renderer_layer(1, rendered_surface)
|
||||
self.renderer_layer(2, rendered_surface)
|
||||
|
||||
# Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale
|
||||
self.window.blit(transform.scale(rendered_surface, (math.ceil(rendered_surface_size[0] * self.engine.camera.zoom),
|
||||
math.ceil(rendered_surface_size[1] * self.engine.camera.zoom))),
|
||||
(0, 0))
|
||||
|
||||
# Apres avoir tout rendu, on met à jour l'écran
|
||||
display.update()
|
||||
|
||||
def render_map(self):
|
||||
def register_animation(self, animation: Anim, name: str):
|
||||
"""Enregistre une animation."""
|
||||
self.animations[name] = animation
|
||||
|
||||
def render_entities(self, rendered_surface: surface.Surface):
|
||||
"""Rend toutes les entités."""
|
||||
# On calcule le décalage pour centrer la caméra
|
||||
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 entity in self.engine.entity_manager.get_all_entities():
|
||||
anim: Anim = self.animations[entity.animation_name]
|
||||
frame = anim.get_frame(0.01666667)
|
||||
rendered_surface.blit(frame, (entity.x-self.engine.camera.x+x_middle_offset, entity.y-self.engine.camera.y+y_middle_offset))
|
||||
|
||||
def renderer_layer(self, layer_id: int, rendered_surface: surface.Surface):
|
||||
"""Rend la map."""
|
||||
# On calcule le nombre de tiles à mettre sur notre écran en prenant en compte le zoom
|
||||
x_map_range = int(display.get_window_size()[0] / 16 / self.engine.camera.zoom) + 2
|
||||
y_map_range = int(display.get_window_size()[1] / 16 / self.engine.camera.zoom) + 2
|
||||
|
@ -46,27 +78,19 @@ class Renderer:
|
|||
x_map_offset = int((self.engine.camera.x - x_middle_offset) / self.tile_size)
|
||||
y_map_offset = int((self.engine.camera.y - y_middle_offset) / self.tile_size)
|
||||
|
||||
# On crée une surface temporaire qui nous permettra de la redimensionner
|
||||
rendered_surface_size = (x_map_range * self.tile_size, y_map_range * self.tile_size)
|
||||
rendered_surface = surface.Surface(rendered_surface_size)
|
||||
|
||||
# On itère pour chaque couche, toutes les tiles visibles par la caméra
|
||||
for i in range(len(self.engine.map_manager.map_layers)):
|
||||
for x in range(x_map_offset, x_map_offset + x_map_range):
|
||||
for y in range(y_map_offset, y_map_offset + y_map_range):
|
||||
for x in range(x_map_offset, x_map_offset + x_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
|
||||
tile_id = self.engine.map_manager.get_tile_at(x, y, i)
|
||||
# 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)
|
||||
|
||||
# Si l'id est 0, il s'agit de vide donc on saute le rendu
|
||||
if tile_id == 0:
|
||||
continue
|
||||
# Si l'id est 0, il s'agit de vide donc on saute le rendu
|
||||
if tile_id == 0:
|
||||
continue
|
||||
|
||||
# Puis, on cherche à quelle image elle correspond et on la colle sur notre surface
|
||||
rendered_surface.blit(self.tiles[tile_id-1],
|
||||
((x*self.tile_size-self.engine.camera.x+x_middle_offset),
|
||||
(y*self.tile_size-self.engine.camera.y+y_middle_offset)))
|
||||
# Puis, on cherche à quelle image elle correspond et on la colle sur notre surface
|
||||
rendered_surface.blit(self.tiles[tile_id-1],
|
||||
((x*self.tile_size-self.engine.camera.x+x_middle_offset),
|
||||
(y*self.tile_size-self.engine.camera.y+y_middle_offset)))
|
||||
|
||||
# Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale
|
||||
self.window.blit(transform.scale(rendered_surface, (rendered_surface_size[0]*self.engine.camera.zoom,
|
||||
rendered_surface_size[1]*self.engine.camera.zoom)), (0, 0))
|
||||
|
|
Loading…
Reference in a new issue