diff --git a/assets/images/A_pixel_art_digital_illustration_features_a_broken.png b/assets/images/A_pixel_art_digital_illustration_features_a_broken.png new file mode 100644 index 0000000..0992258 Binary files /dev/null and b/assets/images/A_pixel_art_digital_illustration_features_a_broken.png differ diff --git a/assets/images/broken_bone.png b/assets/images/broken_bone.png new file mode 100644 index 0000000..5bb8807 Binary files /dev/null and b/assets/images/broken_bone.png differ diff --git a/assets/obstacles/stone.png b/assets/obstacles/stone.png new file mode 100644 index 0000000..d07b623 Binary files /dev/null and b/assets/obstacles/stone.png differ diff --git a/assets/sounds/coin.mp3 b/assets/sounds/coin.mp3 new file mode 100644 index 0000000..a60b7f0 Binary files /dev/null and b/assets/sounds/coin.mp3 differ diff --git a/assets/sounds/gameover.mp3 b/assets/sounds/gameover.mp3 new file mode 100644 index 0000000..1d70fa9 Binary files /dev/null and b/assets/sounds/gameover.mp3 differ diff --git a/assets/sounds/party.wav b/assets/sounds/party.wav new file mode 100644 index 0000000..52d8821 Binary files /dev/null and b/assets/sounds/party.wav differ diff --git a/src/gameover.py b/src/gameover.py new file mode 100644 index 0000000..02c8772 --- /dev/null +++ b/src/gameover.py @@ -0,0 +1,68 @@ +import pygame +import settings +from settings import MyColors + +class GameOver: + def __init__(self, screen): + self.screen = screen + self.background = pygame.image.load(settings.BACKGROUND_IMG_COMP).convert_alpha() + icon_controller_retry = pygame.image.load(settings.icons.controller.viereck).convert_alpha() + self.icon_controller_retry = pygame.transform.scale(icon_controller_retry, (50, 48)) + icon_controller_menu = pygame.image.load(settings.icons.controller.dreieck).convert_alpha() + self.icon_controller_menu = pygame.transform.scale(icon_controller_menu, (50, 48)) + + self.font = pygame.font.Font(settings.fonts.PressStart2P, 60) + self.small_font = pygame.font.Font(settings.fonts.PressStart2P, 24) + + self.retry_button = pygame.Rect(390, 400, 200, 50) + self.menu_button = pygame.Rect(690, 400, 200, 50) + + + def draw(self): + # self.screen.blit(self.background, (0, 0)) + overlay = pygame.Surface((settings.SCREEN_WIDTH, settings.SCREEN_HEIGHT)) # Neues Surface in Bildschirmgröße + overlay.set_alpha(50) # 0 = komplett durchsichtig, 255 = komplett undurchsichtig + overlay.fill((0, 0, 0)) # Schwarz färben + self.screen.blit(overlay, (0, 0)) # Overlay auf + + # Score-Text + score_text = self.font.render(f"GAME OVER", True, (255, 255, 255)) # z.B. Gold + self.screen.blit(score_text, (settings.SCREEN_WIDTH // 2 - score_text.get_width() // 2, 200)) + + time_text = self.small_font.render(f"Das wird schon wieder", True, (255, 255, 255)) + self.screen.blit(time_text, (settings.SCREEN_WIDTH // 2 - time_text.get_width() // 2, 300)) + + + pygame.draw.rect(self.screen, MyColors.yellow, self.retry_button) + pygame.draw.rect(self.screen, MyColors.yellow, self.menu_button) + + nochmal_text = self.small_font.render("Nochmal", True, MyColors.brown) + menu_text = self.small_font.render("Menü", True, MyColors.brown) + + self.screen.blit(nochmal_text, (self.retry_button.x + 15, self.retry_button.y + 10)) + self.screen.blit(menu_text, (self.menu_button.x + 50, self.menu_button.y + 10)) + + self.screen.blit(self.icon_controller_retry, (self.retry_button.x + 70, self.retry_button.y + 40)) + self.screen.blit(self.icon_controller_menu, ((self.menu_button.x + 70), self.menu_button.y + 40)) + + return self.retry_button, self.menu_button + + + def handle_event(self, event): + if event.type == pygame.MOUSEBUTTONDOWN: + print("MOUSEBUTTONDOWN") if settings.DEBUG else None + if self.retry_button.collidepoint(event.pos): + return "retry" + elif self.menu_button.collidepoint(event.pos): + return "menu" + if event.type == pygame.MOUSEBUTTONUP: + if self.retry_button.collidepoint(event.pos): + return "retry" + elif self.menu_button.collidepoint(event.pos): + return "menu" + if event.type == pygame.JOYBUTTONDOWN: + if event.button == 3: + return "menu" + elif event.button == 2: + return "retry" + return None \ No newline at end of file diff --git a/src/highscores.db b/src/highscores.db new file mode 100644 index 0000000..dae8738 Binary files /dev/null and b/src/highscores.db differ diff --git a/src/level_complete_screen.py b/src/level_complete_screen.py index 8c03477..e09427c 100644 --- a/src/level_complete_screen.py +++ b/src/level_complete_screen.py @@ -1,11 +1,13 @@ import pygame import settings from settings import MyColors +import sqlite3 class Endscreen: - def __init__(self, screen, score, time_passed=0): + def __init__(self, screen, score=0, time_passed=0, player_name="Spieler1"): self.screen = screen self.score = score + self.player_name = player_name self.background = pygame.image.load(settings.BACKGROUND_IMG_COMP).convert_alpha() icon_controller_retry = pygame.image.load(settings.icons.controller.viereck).convert_alpha() @@ -21,6 +23,29 @@ class Endscreen: self.time_passed = time_passed + self.save_score_to_db() + + def save_score_to_db(self): + connection = sqlite3.connect('highscores.db') + cursor = connection.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS highscores ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + score INTEGER NOT NULL, + time REAL NOT NULL + ) + ''') + + cursor.execute(''' + INSERT INTO highscores (name, score, time) + VALUES (?, ?, ?) + ''', (self.player_name, self.score, self.time_passed)) + + connection.commit() + connection.close() + def draw(self): self.screen.blit(self.background, (0, 0)) diff --git a/src/main.py b/src/main.py index d5efda9..a780694 100644 --- a/src/main.py +++ b/src/main.py @@ -5,6 +5,7 @@ from menu import Menu # Menü importieren import settings from world import World from level_complete_screen import Endscreen +from gameover import GameOver def main(): pygame.joystick.init() @@ -57,20 +58,38 @@ def main(): menu.show_main_buttons = True menu.ready_to_start_game = False menu.player_name = "" + + if current_scene == "gameover": + action = endscreen.handle_event(event) + if action == "retry": + world = World(screen, menu.player_name) + current_scene = "world" + elif action == "menu": + current_scene = "menu" + menu.input_active = False + menu.show_main_buttons = True + menu.ready_to_start_game = False + menu.player_name = "" if current_scene == "menu": menu.draw() elif current_scene == "world": - if not world.player.game_paused: + if not world.player.game_paused and not world.player.gameOver: world.draw() + elif world.player.gameOver: + gameover = GameOver(screen) + current_scene = "gameover" else: - endscreen = Endscreen(screen, world.save_score, world.time_passed) + endscreen = Endscreen(screen, world.save_score, world.time_passed, world.player_name) current_scene = "endscreen" if settings.DEBUG and not hasattr(world, 'level_end_printed'): print("Level beendet!") world.level_end_printed = True elif current_scene == "endscreen": endscreen.draw() + + elif current_scene == "gameover": + gameover.draw() pygame.display.flip() clock.tick(60) diff --git a/src/menu.py b/src/menu.py index 93bba04..394eb19 100644 --- a/src/menu.py +++ b/src/menu.py @@ -85,6 +85,21 @@ class Menu: if settings.DEBUG: print(f"Spielstart mit Spielername: {self.player_name}") + if event.type == pygame.JOYBUTTONDOWN: + if event.button == 2: # Button 2 auf den meisten Controllern ist Index 1 + if self.show_main_buttons: + self.input_active = True + self.show_main_buttons = False + if settings.DEBUG: + print("Start gedrückt mit Controller") + elif self.input_active: + if self.player_name: # Name muss eingegeben sein + self.input_active = False + self.show_main_buttons = False + self.ready_to_start_game = True + if settings.DEBUG: + print(f"Spielstart mit Controller: {self.player_name}") + if event.type == pygame.KEYDOWN and self.input_active: if event.key == pygame.K_RETURN: if self.player_name: # Nur wenn ein Name eingegeben wurde diff --git a/src/obstacle.py b/src/obstacle.py index 1774fd9..088422e 100644 --- a/src/obstacle.py +++ b/src/obstacle.py @@ -1,10 +1,18 @@ import pygame import settings +import random class Obstacle: - def __init__(self, x, y, image_path): - self.image = pygame.image.load(image_path).convert_alpha() - self.image = pygame.transform.scale(self.image, (400, 150)) # Größe anpassen + def __init__(self, x): + type = random.randint(1,2) + if type == 1: + self.image = pygame.image.load(settings.world.obstacle.box).convert_alpha() + self.image = pygame.transform.scale(self.image, (400, 120)) # Größe anpassen + y = 530 + if type == 2: + self.image = pygame.image.load(settings.world.obstacle.stone).convert_alpha() + self.image = pygame.transform.scale(self.image, (90, 80)) # Größe anpassen + y = 575 self.rect = self.image.get_rect(topleft=(x, y)) def draw(self, screen, camera_offset): diff --git a/src/player.py b/src/player.py index aa0ee39..48df3af 100644 --- a/src/player.py +++ b/src/player.py @@ -1,6 +1,11 @@ import pygame import settings +# Sound für das Einsammeln von Items laden +pygame.mixer.init() +pickup_sound = pygame.mixer.Sound("../assets/sounds/coin.mp3") +pickup_sound.set_volume(0.5) + class Player: def __init__(self, x, y): # PLAYER BILDER @@ -21,6 +26,7 @@ class Player: self.on_ground = False self.game_paused = False + self.gameOver = False self.broken_bones = 0 self.last_collision_obstacle = None @@ -35,61 +41,61 @@ class Player: # Controller-Eingaben prüfen for joystick in joysticks: - if joystick.get_axis(0) < -0.5: # Linker Stick links + if joystick.get_axis(0) < -0.5: move_left = True - if joystick.get_axis(0) > 0.5: # Linker Stick rechts + if joystick.get_axis(0) > 0.5: move_right = True - if joystick.get_button(0): # X-Button + if joystick.get_button(0): jump = True - # Bewegung seitlich + # --- Horizontale Bewegung + dx = 0 if move_left and input_allowed: - self.rect.x -= settings.player.speed + dx = -settings.player.speed if move_right and input_allowed: - self.rect.x += settings.player.speed + dx = settings.player.speed + + self.rect.x += dx - # Springen + for obs in obstacles: + if self.rect.colliderect(obs.get_rect()): + if dx > 0: # Bewegung nach rechts + self.rect.right = obs.get_rect().left - 10 + if self.last_collision_obstacle != obs: + self.broken_bones += 1 + self.last_collision_obstacle = obs + elif dx < 0: # Bewegung nach links + self.rect.left = obs.get_rect().right - 10 + if self.last_collision_obstacle != obs: + self.broken_bones += 1 + self.last_collision_obstacle = obs + + # --- Vertikale Bewegung if jump and self.on_ground and input_allowed: self.velocity_y = self.jump_strength self.on_ground = False - # Gravitation - self.velocity_y += settings.player.gravity + self.velocity_y += self.gravity self.rect.y += self.velocity_y - self.on_ground = False # zurücksetzen + self.on_ground = False - # HINDERNISSE-KOLLISION for obs in obstacles: if self.rect.colliderect(obs.get_rect()): - if self.velocity_y > 0 and self.rect.bottom - self.velocity_y < obs.get_rect().top: - # Fred landet oben auf der Kiste + if self.velocity_y > 0: # Fallend self.rect.bottom = obs.get_rect().top self.velocity_y = 0 self.on_ground = True self.last_collision_obstacle = None - else: - # Seitenkollision - Fred wird blockiert UND Knochenbruch zählen - if abs(self.rect.right - obs.get_rect().left) < abs(settings.player.speed): - if self.last_collision_obstacle != obs: - self.broken_bones += 1 - self.last_collision_obstacle = obs - self.rect.right = obs.get_rect().left # WICHTIG: stoppen! - elif abs(self.rect.left - obs.get_rect().right) < abs(settings.player.speed): - if self.last_collision_obstacle != obs: - self.broken_bones += 1 - self.last_collision_obstacle = obs - self.rect.left = obs.get_rect().right # WICHTIG: stoppen! - else: - # Keine Kollision mehr → Reset - if self.last_collision_obstacle == obs: - self.last_collision_obstacle = None + elif self.velocity_y < 0: # Aufsteigend + self.rect.top = obs.get_rect().bottom + self.velocity_y = 0 # ITEM-KOLLISION for item in items[:]: if self.rect.colliderect(item.get_rect()): items.remove(item) - score += 10 # Beispiel: 10 Punkte pro eingesammeltem Item - + score += 10 + pickup_sound.play() # Boden-Kollision if self.rect.bottom >= ground_y: @@ -97,16 +103,20 @@ class Player: self.velocity_y = 0 self.on_ground = True - # ZIEL FLAGGEN ACTION + # Ziel-Flagge erreicht if not self.game_paused and self.rect.left > settings.world.goal.position_x: self.game_paused = True - # Bildlogik + # CANCEL nach zu vielen Brüche + if self.broken_bones == 5: + self.gameOver = True + + # Bild-Logik if not self.on_ground: self.image = self.image_jump - elif keys[pygame.K_LEFT]: + elif keys[pygame.K_LEFT] or move_left: self.image = self.image_left - elif keys[pygame.K_RIGHT]: + elif keys[pygame.K_RIGHT] or move_right: self.image = self.image_normal else: self.image = self.image_normal diff --git a/src/settings.py b/src/settings.py index 9d7e48a..2561475 100644 --- a/src/settings.py +++ b/src/settings.py @@ -4,11 +4,11 @@ import random SCREEN_WIDTH = 1280 SCREEN_HEIGHT = 720 FPS = 60 -DEBUG = True +DEBUG = False DEBUG_KEY_INPUT = False DEBUG_ENDSCREEN = False Mute = True -START_POINT = "endscreen" # world, menu +START_POINT = "menu" # world, menu BACKGROUND_IMG = "../assets/backgrounds/background_III.png" BACKGROUND_IMG_COMP = "../assets/backgrounds/background-complete.png" @@ -18,8 +18,8 @@ class fonts: class player: size = (110, 200) - speed = 25 if DEBUG else 18 - gravity = 1.5 + speed = 25 if DEBUG else 17 + gravity = 1.7 jump_strength = -30 class fred: @@ -29,6 +29,7 @@ class player: class world: countdown = 3 #sekunden + brokenBone_image = "../assets/images/broken_bone.png" class ground: img = "../assets/world/ground_II.png" crop_top = 500 @@ -38,11 +39,12 @@ class world: crop_scale_right = 1400 class obstacle: - amount = random.randint(4, 8) + amount = 15 box = "../assets/obstacles/box.png" + stone = "../assets/obstacles/stone.png" class items: - amount = 15 + amount = 23 class beer: img = "../assets/images/items/bier.png" size = (70, 85) @@ -50,7 +52,7 @@ class world: class goal: img = "../assets/world/GoalFlag.png" size = (110,160) - position_x = 13130 + position_x = 29800 class icons: diff --git a/src/world.py b/src/world.py index 3954b93..287be37 100644 --- a/src/world.py +++ b/src/world.py @@ -17,18 +17,18 @@ class World: self.obstacles = [] while len(self.obstacles) < settings.world.obstacle.amount: - x = random.randint(500, 12500) + x = random.randint(500, 29500) # Prüfen, ob der neue x weit genug entfernt ist von bestehenden Hindernissen if all(abs(x - obs.rect.x) > 800 for obs in self.obstacles): - self.obstacles.append(Obstacle(x, 500, settings.world.obstacle.box)) + self.obstacles.append(Obstacle(x)) # ITEMS self.items = [] while len(self.items) < settings.world.items.amount: - x = random.randint(200, 12800) - y = random.randint(100, 500) + x = random.randint(200, 29500) + y = random.randint(100, 480) # Prüfen, ob der neue x weit genug entfernt ist von bestehenden Hindernissen if all(abs(x - item.rect.x) > 200 for item in self.items): self.items.append(Item(x, y)) @@ -39,7 +39,7 @@ class World: self.bg_width = self.background.get_width() # Boden - self.ground_rect = pygame.Rect(0, 650, 5000 * 2.7, 80) # Boden + self.ground_rect = pygame.Rect(0, 650, 5000 * 6, 80) # Boden full_image = pygame.image.load(settings.world.ground.img).convert_alpha() # --- Ausschneiden des Bereichs cropped = pygame.Surface((settings.world.ground.crop_scale_right, settings.world.ground.crop_height), pygame.SRCALPHA) @@ -52,6 +52,9 @@ class World: goal_flag_image = pygame.image.load(settings.world.goal.img).convert_alpha() self.goal_flag = pygame.transform.scale(goal_flag_image, settings.world.goal.size) + # Knochenbrüche + brokenBone_image = pygame.image.load(settings.world.brokenBone_image).convert_alpha() + self.brokeBoneImage = pygame.transform.scale(brokenBone_image, (50,50)) # Spieler self.player = Player(200, 500) # Spieler @@ -80,7 +83,7 @@ class World: self.save_score += 10 print(self.save_score) if settings.DEBUG else None - bg_offset = int(camera_offset * 0.3) # Parallax: langsamer bewegen + bg_offset = int(camera_offset * 0.125) # Parallax: langsamer bewegen self.screen.blit(self.background, (-bg_offset, 0)) # Boden kacheln ohne Parallax start_tile = (camera_offset // self.tile_width) * self.tile_width @@ -106,13 +109,24 @@ class World: self.screen.blit(self.player.image, (player_screen_x, self.player.rect.y)) # ANZEIGE - font = pygame.font.Font(settings.fonts.PressStart2P, 24) + font = pygame.font.Font(settings.fonts.PressStart2P, 32) name_surface = font.render(self.player_name, True, (255, 255, 255)) + score_surface = font.render(f"{self.save_score}", True, (255,255,0)) self.screen.blit(name_surface, (20, 20)) + self.screen.blit(score_surface, (20, 120)) # KNOCHENBRÜCHE - bones_text = font.render(f"Schaden: {self.player.broken_bones}", True, (255, 255, 255)) - self.screen.blit(bones_text, (20, 60)) + if self.player.broken_bones < 5: + self.screen.blit(self.brokeBoneImage, (20, 50)) + if self.player.broken_bones < 4: + self.screen.blit(self.brokeBoneImage, (70, 50)) + if self.player.broken_bones < 3: + self.screen.blit(self.brokeBoneImage, (120, 50)) + if self.player.broken_bones < 2: + self.screen.blit(self.brokeBoneImage, (170, 50)) + if self.player.broken_bones < 1: + self.screen.blit(self.brokeBoneImage, (220, 50)) + current_ticks = pygame.time.get_ticks() elapsed_seconds = (current_ticks - self.start_ticks) // 1000