325 lines
14 KiB
Python
325 lines
14 KiB
Python
import pygame, os, math, time
|
|
import gamedata.definitions as lib
|
|
import gamedata.scenes as scenes
|
|
import gamedata.objects.gameloop as gameloop
|
|
from gamedata.objects.particles import Particle
|
|
|
|
class Game():
|
|
def __init__(self):
|
|
|
|
self.datadir = lib.get_save_dir("slime")
|
|
self.DISPLAY_WIDTH, self.DISPLAY_HEIGHT = 1280, 720
|
|
self.window = pygame.Surface((self.DISPLAY_WIDTH,self.DISPLAY_HEIGHT))
|
|
self.realwindow = pygame.display.set_mode((self.DISPLAY_WIDTH,self.DISPLAY_HEIGHT))
|
|
pygame.display.set_caption("Overflown")
|
|
pygame.init()
|
|
pygame.mixer.init()
|
|
|
|
self.fps = 60
|
|
|
|
self.logs = []
|
|
|
|
self.running = True
|
|
self.gameloop = gameloop.GameLoop() # Je crée une boucle de jeu
|
|
def load_image(filename):
|
|
return pygame.image.load(filename).convert_alpha()
|
|
self.sprite_lib = self.init_assets("gamedata/assets/",load_image) # Dico qui contient chaques images du dossier assets
|
|
pygame.display.set_icon(self.sprite_lib["icon.png"])
|
|
self.sound_lib = self.init_assets("gamedata/sounds/",pygame.mixer.Sound) # Pareil, mais pour les musiques / sons
|
|
self.fontmap = self.sprite_lib["fontmap.png"]
|
|
|
|
# Chargement des niveaux
|
|
def loadlvldata(mapfolder):
|
|
mapdico = {}
|
|
mapdico["name"] = mapfolder.split(os.sep)[-1]
|
|
mapdico["cover"] = None
|
|
mapdico["backgrounds"] = []
|
|
mapdico["data"] = None
|
|
mapdico["filler"] = None
|
|
mapdico["tilesets"] = {}
|
|
scanner = os.scandir(path=mapfolder)
|
|
for i in scanner: # Je check tout les fichiers du dossier
|
|
name = i.name
|
|
try:
|
|
if name.endswith(".png"):
|
|
if name=="cover.png":
|
|
mapdico["cover"] = pygame.image.load(i.path)
|
|
elif name.startswith("background"):
|
|
mapdico["backgrounds"].append({"sprite":pygame.image.load(i.path).convert_alpha(),"name":i.path})
|
|
elif name=="filler.png":
|
|
mapdico["filler"] = pygame.image.load(i.path).convert_alpha()
|
|
else:
|
|
mapdico["tilesets"][i.name] = pygame.image.load(i.path).convert_alpha()
|
|
except:
|
|
self.log("Erreur",mapfolder,name,"Fichier invalide")
|
|
if name=="map.json":
|
|
try:
|
|
with open(i.path,"r") as jsonfile:
|
|
mapdico["data"] = lib.json.loads(jsonfile.read())
|
|
except:
|
|
self.log("Erreur",mapfolder,name)
|
|
if mapdico["data"]:
|
|
mapdico["backgrounds"].sort(key=lambda x: x["name"])
|
|
return mapdico
|
|
return None
|
|
|
|
self.levels_lib = self.init_assets("gamedata/maps/",loadlvldata,recursive=False) # Je charge le dossier de maps
|
|
|
|
|
|
self.sound_volumes = {}
|
|
for i in self.sound_lib.keys():
|
|
self.sound_volumes[i] = 1
|
|
self.spriteLists = {} # Tiendra des listes préarrangées
|
|
|
|
self.scenes = scenes # Je stoque mes modules à l'intérieur de ma classe pour y avoir accès partout
|
|
self.scene = None
|
|
self.lib = lib
|
|
self.pygame = pygame
|
|
self.math = math
|
|
self.clock = pygame.time.Clock()
|
|
|
|
self.elapsedtime = 0
|
|
|
|
self.globals = {} # Un dico pour ranger toute les valeurs globales, pour communiquer entre objets par exemples
|
|
self.globals["camerax"] = 0
|
|
self.globals["cameray"] = 0
|
|
self.globals["scamerax"] = 0
|
|
self.globals["scameray"] = 0
|
|
self.globals["players"] = []
|
|
self.globals["AZERTY"] = True
|
|
self.globals["pause"] = False
|
|
self.globals["finishedlevels"] = [] # Levels where the player went to the end
|
|
self.globals["completedlevels"] = [] # Levels where the player kicked all non-respawnable ennemies
|
|
self.globals["speedrunlevels"] = [] # Levels where the player finished fast enough
|
|
self.globals["allunlocked"] = True
|
|
self.globals["bgms"] = ["city.ogg","docks.ogg","isle.ogg","isle.ogg","docks.ogg","city.ogg"]
|
|
self.init_inputs()
|
|
self.scaleCamera()
|
|
|
|
settings = {"sfx":1,"bgm":0.2}
|
|
self.globals["bgmvolume"] = settings["bgm"]
|
|
self.globals["sfxvolume"] = settings["sfx"]
|
|
|
|
self.reinit_volumes()
|
|
|
|
self.pasttime = time.time()
|
|
|
|
# Je charge la scene de base
|
|
scenes.overworld(self)
|
|
|
|
def set_camera(self,posx,posy):
|
|
self.globals["camerax"], self.globals["cameray"] = posx,posy
|
|
|
|
def log(*args):
|
|
args[0].logs.append(" ".join(args[1:]))
|
|
print(" ".join(args[1:]))
|
|
|
|
def reinit_volumes(self):
|
|
|
|
for i in self.sound_lib.keys(): # J'applique de base les volumes
|
|
if i.startswith("sfx/"):
|
|
self.sound_lib[i].set_volume(self.globals["sfxvolume"]*self.sound_volumes[i])
|
|
if i.startswith("bgm/"):
|
|
self.sound_lib[i].set_volume(self.globals["bgmvolume"]*self.sound_volumes[i])
|
|
|
|
def set_volume(self,sound,newvolume):
|
|
self.sound_volumes[sound] = newvolume
|
|
self.reinit_volumes()
|
|
|
|
def game_loop(self):
|
|
|
|
self.dt = 1/self.fps
|
|
self.elapsedtime += self.dt
|
|
self.pasttime = time.time()
|
|
|
|
self.check_events() # Détecte les entrées clavier
|
|
self.window.fill((0)*3) # Remplis l'écran de noir
|
|
if self.gameloop: # Si j'ai une boucle de jeu, la lancer
|
|
self.gameloop.step(self) # La logique de la boucle
|
|
self.gameloop.draw(self) # L'affichage de la boucle
|
|
if self.scene :
|
|
self.scene(self)
|
|
self.scene = False
|
|
pygame.display.update() # Mettre à jour l'affichage
|
|
self.clock.tick(self.fps)
|
|
|
|
def init_inputs(self):
|
|
|
|
self.inputs = {}
|
|
self.inputs["keys"] = {
|
|
"escape":{
|
|
"keycode" : [pygame.K_ESCAPE,pygame.K_RETURN] # Code pygame de la touche en question
|
|
},
|
|
"left":{
|
|
"keycode" : [pygame.K_LEFT] # Code pygame de la touche en question
|
|
},
|
|
"right":{
|
|
"keycode" : [pygame.K_RIGHT] # Code pygame de la touche en question
|
|
},
|
|
"up":{
|
|
"keycode" : [pygame.K_SPACE,pygame.K_UP] # Code pygame de la touche en question
|
|
},
|
|
"down":{
|
|
"keycode" : [pygame.K_DOWN] # Code pygame de la touche en question
|
|
}
|
|
}
|
|
if self.globals["AZERTY"]: # Azerty controls
|
|
self.inputs["keys"]["left"]["keycode"].append(pygame.K_q)
|
|
self.inputs["keys"]["right"]["keycode"].append(pygame.K_d)
|
|
self.inputs["keys"]["up"]["keycode"].append(pygame.K_z)
|
|
self.inputs["keys"]["down"]["keycode"].append(pygame.K_s)
|
|
else: # Qwerty controls
|
|
self.inputs["keys"]["left"]["keycode"].append(pygame.K_a)
|
|
self.inputs["keys"]["right"]["keycode"].append(pygame.K_d)
|
|
self.inputs["keys"]["up"]["keycode"].append(pygame.K_w)
|
|
self.inputs["keys"]["down"]["keycode"].append(pygame.K_s)
|
|
|
|
|
|
for key in self.inputs["keys"].keys():
|
|
self.inputs["keys"][key]["timer"] = 0
|
|
self.inputs["keys"][key]["realtime"] = 0
|
|
self.inputs["keys"][key]["pressed"] = False
|
|
self.inputs["keys"][key]["counted"] = False
|
|
|
|
self.inputs["joysticks"] = {}
|
|
|
|
def check_events(self):
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.QUIT:
|
|
self.running = False
|
|
if event.type == pygame.KEYDOWN:
|
|
for i in self.inputs["keys"].keys():
|
|
if event.key in self.inputs["keys"][i]["keycode"]: # Vérifie si une des touches du dico est préssée
|
|
self.inputs["keys"][i]["pressed"] = True
|
|
if event.type == pygame.KEYUP:
|
|
for i in self.inputs["keys"].keys():
|
|
if event.key in self.inputs["keys"][i]["keycode"]: # Vérifie si une des touches du dico est préssée
|
|
self.inputs["keys"][i]["pressed"] = False
|
|
if event.type == pygame.JOYDEVICEADDED:
|
|
index = event.device_index
|
|
self.inputs["joysticks"][index] = pygame.joystick.Joystick(index)
|
|
if event.type == pygame.JOYDEVICEREMOVED:
|
|
index = event.instance_id
|
|
del(self.inputs["joysticks"][index])
|
|
|
|
# Check for controller input
|
|
for i in self.inputs["keys"].keys():
|
|
self.inputs["keys"][i]["counted"] = False
|
|
for joy in self.inputs["joysticks"].values():
|
|
nbhats = joy.get_numhats()
|
|
for i in range(nbhats):
|
|
x,y = joy.get_hat(i)
|
|
if x==-1:
|
|
self.inputs["keys"]["left"]["counted"] = True
|
|
if y==1:
|
|
self.inputs["keys"]["up"]["counted"] = True
|
|
if x==1:
|
|
self.inputs["keys"]["right"]["counted"] = True
|
|
if y==-1:
|
|
self.inputs["keys"]["down"]["counted"] = True
|
|
nbaxis = joy.get_numaxes()
|
|
if nbaxis>=2:
|
|
x = joy.get_axis(0)
|
|
y = joy.get_axis(1)
|
|
deadzone = 0.2
|
|
if x<-deadzone:
|
|
self.inputs["keys"]["left"]["counted"] = True
|
|
if x>deadzone:
|
|
self.inputs["keys"]["right"]["counted"] = True
|
|
if y>deadzone*3:
|
|
self.inputs["keys"]["down"]["counted"] = True
|
|
nbbuttons = joy.get_numbuttons()
|
|
for i in range(8):
|
|
if nbbuttons>=i and joy.get_button(i)==1:
|
|
if i==0: # A button
|
|
self.inputs["keys"]["up"]["counted"] = True
|
|
if i>0 and i<=3: # B,X, and Y buttons
|
|
self.inputs["keys"]["down"]["counted"] = True
|
|
if i>5: # Start and Select
|
|
self.inputs["keys"]["escape"]["counted"] = True
|
|
|
|
for i in self.inputs["keys"].keys():
|
|
self.inputs["keys"][i]["counted"] = self.inputs["keys"][i]["counted"] or self.inputs["keys"][i]["pressed"]
|
|
|
|
# Augmente le timer si la touche est préssée, le reset sinon
|
|
for i in self.inputs["keys"].keys():
|
|
if self.inputs["keys"][i]["counted"]:
|
|
self.inputs["keys"][i]["timer"]+=1
|
|
self.inputs["keys"][i]["realtime"]+=self.dt
|
|
else:
|
|
self.inputs["keys"][i]["timer"]=0
|
|
self.inputs["keys"][i]["realtime"]=0
|
|
|
|
def init_assets(self,path,function,recursive=True):
|
|
dico = {}
|
|
self.scan_dir(path,path,dico,function,recursive)
|
|
return dico
|
|
|
|
def scan_dir(self,dirpath,origin,dico,function,recursive=True):
|
|
scanner = os.scandir(path=dirpath)
|
|
for i in scanner: # Je passe à travers toutes les données d'un dossier, fichiers et sous dossiers compris
|
|
# i.path est le chemin du fichier, par exemple
|
|
# Si c'est une image, je l'importe et l'ajoute à la librairie
|
|
finalpath = i.path.replace("\\","/")
|
|
if i.is_file() or not recursive:
|
|
finalpath = finalpath.replace(origin,'') # J'enleve l'origine (dans ce cas 'assets/' car c'est redontant, tout les sprites sont dedans
|
|
dico[finalpath] = function(i.path)
|
|
# Si c'est un dossier, je répete l'opération mais à l'intérieur de celui ci
|
|
if i.is_dir() and recursive:
|
|
self.scan_dir(i.path,origin,dico,function,recursive)
|
|
scanner.close()
|
|
|
|
def getSpriteDir(self,directory,ext=".png",assetdir="sprite_lib"):
|
|
keys = (directory,ext,assetdir)
|
|
assetdir = getattr(self,assetdir) # Si je ne précise pas quel type de ressource, je cherche dans mes sprites
|
|
if keys in self.spriteLists.keys():
|
|
return self.spriteLists[keys]
|
|
else:
|
|
# Organise les sprites
|
|
sprite_list = []
|
|
index = 0
|
|
while directory+str(index)+ext in assetdir.keys():
|
|
sprite_list.append(assetdir[directory+str(index)+ext])
|
|
index+=1
|
|
# Le stoque pour éviter de les réorganiser à chaque fois
|
|
self.spriteLists[keys] = sprite_list
|
|
|
|
return sprite_list
|
|
|
|
def addParticle(self,sprites,posx,posy,velx=0,vely=0,modvelx=0,modvely=0,flipx=False,flipy=False,fps=15,depth=0):
|
|
p = Particle(self,sprites,posx,posy,velx,vely,modvelx,modvely,flipx,flipy,fps)
|
|
p.depth = depth
|
|
self.gameloop.summon(p)
|
|
|
|
def scaleCamera(self,neww=None,newh=None):
|
|
if not neww:
|
|
neww=self.DISPLAY_WIDTH
|
|
if not newh:
|
|
newh=self.DISPLAY_HEIGHT
|
|
self.globals["cameraw"] = neww
|
|
self.globals["camerah"] = newh
|
|
|
|
self.globals["tempsubsurface"] = pygame.Surface((neww,newh))
|
|
|
|
def getchar(self,char,width=9,height=9):
|
|
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@\"#&()*:;?!abcdefghijklmnopqrstuvwxyz/"
|
|
charsperline = self.fontmap.get_width()//width
|
|
result = None
|
|
if char in chars:
|
|
index = chars.index(char)
|
|
line = index//charsperline
|
|
column = index%charsperline
|
|
result = self.pygame.Surface((width,height),flags=self.pygame.SRCALPHA)
|
|
result.blit(self.fontmap,[-width*column,-height*line])
|
|
return result
|
|
|
|
def getchars(self,chars):
|
|
result = []
|
|
for i in chars:
|
|
result.append(self.getchar(i))
|
|
surface = pygame.Surface((len(result)*9,9),flags=pygame.SRCALPHA)
|
|
for i in range(len(result)):
|
|
if result[i]:
|
|
surface.blit(result[i],(i*9,0))
|
|
return surface
|