mirror of
https://github.com/BreizhHardware/project_sanic.git
synced 2026-01-18 16:47:25 +01:00
BIN
assets/map/background/forest_bg.jpg
Normal file
BIN
assets/map/background/forest_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 501 KiB |
BIN
assets/map/platform/grass_texture.jpg
Normal file
BIN
assets/map/platform/grass_texture.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
74
main.py
74
main.py
@@ -1,20 +1,30 @@
|
||||
import pygame
|
||||
import sys
|
||||
from pygame.locals import *
|
||||
|
||||
# Import from pygame_basics
|
||||
from src.game import (
|
||||
initialize_game,
|
||||
from src.game import initialize_game
|
||||
from src.constant import (
|
||||
displaysurface,
|
||||
FramePerSec,
|
||||
font,
|
||||
FPS,
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
ORIGINAL_WIDTH,
|
||||
ORIGINAL_HEIGHT,
|
||||
fullscreen,
|
||||
)
|
||||
|
||||
from src.constant import displaysurface, FramePerSec, font, FPS, WIDTH, HEIGHT
|
||||
|
||||
# Import from menu
|
||||
from src.Menu.Menu import Menu
|
||||
from src.Menu.Leaderboard import Leaderboard
|
||||
from src.Camera import Camera
|
||||
|
||||
|
||||
def main():
|
||||
# Declare globals that we'll modify
|
||||
global displaysurface, fullscreen, ORIGINAL_WIDTH, ORIGINAL_HEIGHT
|
||||
|
||||
# Add camera initialization
|
||||
camera = Camera(WIDTH, HEIGHT)
|
||||
|
||||
# Game states
|
||||
MENU = 0
|
||||
PLAYING = 1
|
||||
@@ -27,7 +37,7 @@ def main():
|
||||
leaderboard = Leaderboard()
|
||||
|
||||
# Initialize game components
|
||||
P1, PT1, platforms, all_sprites = initialize_game()
|
||||
P1, PT1, platforms, all_sprites, background = initialize_game("map_test.json")
|
||||
|
||||
# Main game loop
|
||||
while True:
|
||||
@@ -42,6 +52,28 @@ def main():
|
||||
else:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
elif event.key == K_F11:
|
||||
fullscreen = not fullscreen
|
||||
if fullscreen:
|
||||
# Store current window size before going fullscreen
|
||||
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = displaysurface.get_size()
|
||||
displaysurface = pygame.display.set_mode(
|
||||
(0, 0), pygame.FULLSCREEN
|
||||
)
|
||||
else:
|
||||
# Return to windowed mode with previous size
|
||||
displaysurface = pygame.display.set_mode(
|
||||
(ORIGINAL_WIDTH, ORIGINAL_HEIGHT), pygame.RESIZABLE
|
||||
)
|
||||
elif (
|
||||
event.type == VIDEORESIZE
|
||||
): # Fixed indentation - moved out of K_F11 condition
|
||||
if not fullscreen:
|
||||
displaysurface = pygame.display.set_mode(
|
||||
(event.w, event.h), pygame.RESIZABLE
|
||||
)
|
||||
# Update window dimensions
|
||||
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
|
||||
|
||||
# Handle menu events
|
||||
if current_state == MENU:
|
||||
@@ -73,10 +105,28 @@ def main():
|
||||
# Regular game code
|
||||
P1.move()
|
||||
P1.update()
|
||||
for entity in all_sprites:
|
||||
displaysurface.blit(entity.surf, entity.rect)
|
||||
|
||||
# Display FPS and coordinates
|
||||
# Update camera to follow player
|
||||
camera.update(P1)
|
||||
|
||||
# Clear screen
|
||||
displaysurface.fill((0, 0, 0))
|
||||
|
||||
if background:
|
||||
parallax_factor = 0.3
|
||||
bg_x = camera.camera.x * parallax_factor
|
||||
bg_y = camera.camera.y * parallax_factor
|
||||
displaysurface.blit(background, (bg_x, bg_y))
|
||||
|
||||
# Draw all sprites with camera offset applied
|
||||
for entity in all_sprites:
|
||||
# Calculate position adjusted for camera
|
||||
camera_adjusted_rect = entity.rect.copy()
|
||||
camera_adjusted_rect.x += camera.camera.x
|
||||
camera_adjusted_rect.y += camera.camera.y
|
||||
displaysurface.blit(entity.surf, camera_adjusted_rect)
|
||||
|
||||
# Display FPS and coordinates (fixed position UI elements)
|
||||
fps = int(FramePerSec.get_fps())
|
||||
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
|
||||
displaysurface.blit(fps_text, (10, 10))
|
||||
|
||||
166
map_test.json
Normal file
166
map_test.json
Normal file
@@ -0,0 +1,166 @@
|
||||
{
|
||||
"name": "Level 1",
|
||||
"width": 2400,
|
||||
"height": 800,
|
||||
"background": "assets/map/background/forest_bg.jpg",
|
||||
"gravity": 1.0,
|
||||
|
||||
"ground": [
|
||||
{
|
||||
"id": "main_ground",
|
||||
"x": -1000,
|
||||
"y": 780,
|
||||
"width": 1800,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/grass_texture.jpg"
|
||||
},
|
||||
{
|
||||
"id": "pit",
|
||||
"x": 800,
|
||||
"y": 780,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"is_hole": true
|
||||
},
|
||||
{
|
||||
"id": "main_ground_2",
|
||||
"x": 1000,
|
||||
"y": 900,
|
||||
"width": 1800,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/grass_texture.jpg"
|
||||
}
|
||||
],
|
||||
|
||||
"platforms": [
|
||||
{
|
||||
"id": "platform1",
|
||||
"x": 300,
|
||||
"y": 600,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/grass_texture.jpg",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform2",
|
||||
"x": 700,
|
||||
"y": 500,
|
||||
"width": 150,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/grass_texture.jpg",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 700, "y": 500},
|
||||
{"x": 700, "y": 300}
|
||||
],
|
||||
"speed": 2.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform3",
|
||||
"x": 1200,
|
||||
"y": 400,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/grass_texture.jpg",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "circular",
|
||||
"center": {"x": 1200, "y": 400},
|
||||
"radius": 100,
|
||||
"speed": 0.02,
|
||||
"clockwise": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"enemies": [
|
||||
{
|
||||
"id": "enemy1",
|
||||
"type": "walker",
|
||||
"x": 500,
|
||||
"y": 760,
|
||||
"health": 100,
|
||||
"damage": 20,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 400, "y": 760},
|
||||
{"x": 600, "y": 760}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png"
|
||||
},
|
||||
{
|
||||
"id": "enemy2",
|
||||
"type": "flyer",
|
||||
"x": 1000,
|
||||
"y": 400,
|
||||
"health": 50,
|
||||
"damage": 10,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png"
|
||||
},
|
||||
{
|
||||
"id": "enemy3",
|
||||
"type": "turret",
|
||||
"x": 1500,
|
||||
"y": 700,
|
||||
"health": 200,
|
||||
"damage": 30,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret_enemy.png"
|
||||
}
|
||||
],
|
||||
|
||||
"collectibles": [
|
||||
{
|
||||
"id": "coin1",
|
||||
"type": "coin",
|
||||
"x": 350,
|
||||
"y": 550,
|
||||
"value": 10,
|
||||
"sprite": "assets/map/collectibles/coin.png"
|
||||
},
|
||||
{
|
||||
"id": "power_up1",
|
||||
"type": "speed_boost",
|
||||
"x": 900,
|
||||
"y": 450,
|
||||
"duration": 5.0,
|
||||
"sprite": "assets/map/collectibles/speed_boost.png"
|
||||
}
|
||||
],
|
||||
|
||||
"checkpoints": [
|
||||
{
|
||||
"id": "checkpoint1",
|
||||
"x": 1200,
|
||||
"y": 760,
|
||||
"width": 50,
|
||||
"height": 50,
|
||||
"sprite": "assets/map/checkpoints/checkpoint.png"
|
||||
}
|
||||
],
|
||||
|
||||
"spawn_point": {
|
||||
"x": 50,
|
||||
"y": 700
|
||||
},
|
||||
|
||||
"exit": {
|
||||
"x": 2300,
|
||||
"y": 700,
|
||||
"width": 50,
|
||||
"height": 80,
|
||||
"next_level": "Level 2",
|
||||
"sprite": "assets/map/exit/door.png"
|
||||
}
|
||||
}
|
||||
18
src/Camera.py
Normal file
18
src/Camera.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# src/Camera.py
|
||||
import pygame
|
||||
from src.constant import WIDTH, HEIGHT
|
||||
|
||||
|
||||
class Camera:
|
||||
def __init__(self, width, height):
|
||||
self.camera = pygame.Rect(0, 0, width, height)
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def update(self, target):
|
||||
# Center the target in the camera view
|
||||
x = -target.rect.centerx + WIDTH // 2
|
||||
y = -target.rect.centery + HEIGHT // 2
|
||||
|
||||
# Update camera position
|
||||
self.camera = pygame.Rect(x, y, self.width, self.height)
|
||||
@@ -1,9 +1,12 @@
|
||||
import pygame
|
||||
import os
|
||||
from pygame.math import Vector2 as vec
|
||||
|
||||
|
||||
class Entity(pygame.sprite.Sprite):
|
||||
def __init__(self, pos=(0, 0), size=(30, 30), color=(255, 255, 255)):
|
||||
def __init__(
|
||||
self, pos=(0, 0), size=(30, 30), color=(255, 255, 255), texturePath=""
|
||||
):
|
||||
super().__init__()
|
||||
self.pos = vec(pos)
|
||||
self.vel = vec(0, 0)
|
||||
@@ -14,6 +17,13 @@ class Entity(pygame.sprite.Sprite):
|
||||
self.surf.fill(color)
|
||||
self.rect = self.surf.get_rect()
|
||||
self.update_rect()
|
||||
if os.path.isfile(texturePath):
|
||||
self.surf = pygame.image.load(texturePath).convert_alpha()
|
||||
self.surf = pygame.transform.scale(self.surf, size)
|
||||
self.rect = self.surf.get_rect()
|
||||
else:
|
||||
print("Texture path not found")
|
||||
print(texturePath)
|
||||
|
||||
def update_rect(self):
|
||||
"""Update rect position based on entity position"""
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import pygame
|
||||
from src.Entity.Entity import Entity
|
||||
from src.constant import WIDTH, HEIGHT
|
||||
|
||||
|
||||
class Platform(Entity):
|
||||
def __init__(self, width, height, x, y, color=(255, 0, 0)):
|
||||
super().__init__(pos=(x, y), size=(width, height), color=color)
|
||||
def __init__(self, width, height, x, y, color=(255, 0, 0), texturePath=""):
|
||||
super().__init__(
|
||||
pos=(x, y), size=(width, height), color=color, texturePath=texturePath
|
||||
)
|
||||
# Override rect setting for platforms if needed
|
||||
self.rect = self.surf.get_rect(center=(x, y))
|
||||
|
||||
@@ -124,10 +124,12 @@ class Player(Entity):
|
||||
|
||||
pressed_keys = pygame.key.get_pressed()
|
||||
if pressed_keys[K_q]:
|
||||
self.acc.x = -ACC
|
||||
self.moving = True
|
||||
if pressed_keys[K_a]:
|
||||
self.dash(-ACC)
|
||||
# Check if X is > 0 to prevent player from going off screen
|
||||
if self.pos.x > 0:
|
||||
self.acc.x = -ACC
|
||||
self.moving = True
|
||||
if pressed_keys[K_a]:
|
||||
self.dash(-ACC)
|
||||
if pressed_keys[K_d]:
|
||||
self.acc.x = ACC
|
||||
self.moving = True
|
||||
@@ -149,13 +151,13 @@ class Player(Entity):
|
||||
self.vel += self.acc
|
||||
self.pos += self.vel + 0.5 * self.acc
|
||||
|
||||
# Prevent the player from moving off-screen horizontally
|
||||
if self.pos.x > WIDTH - self.rect.width / 2:
|
||||
self.pos.x = WIDTH - self.rect.width / 2
|
||||
self.vel.x = 0
|
||||
if self.pos.x < self.rect.width / 2:
|
||||
self.pos.x = self.rect.width / 2
|
||||
self.vel.x = 0
|
||||
# Remove screen boundary restrictions
|
||||
# if self.pos.x > WIDTH - self.rect.width / 2:
|
||||
# self.pos.x = WIDTH - self.rect.width / 2
|
||||
# self.vel.x = 0
|
||||
# if self.pos.x < self.rect.width / 2:
|
||||
# self.pos.x = self.rect.width / 2
|
||||
# self.vel.x = 0
|
||||
|
||||
self.rect.midbottom = self.pos
|
||||
|
||||
|
||||
126
src/Map/parser.py
Normal file
126
src/Map/parser.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import json
|
||||
import pygame
|
||||
import os
|
||||
from src.Entity.Platform import Platform
|
||||
from src.Entity.Player import Player
|
||||
from src.constant import WIDTH, HEIGHT, all_sprites, platforms
|
||||
|
||||
|
||||
class MapParser:
|
||||
def __init__(self):
|
||||
self.all_sprites = all_sprites
|
||||
self.platforms = platforms
|
||||
self.enemies = pygame.sprite.Group()
|
||||
self.collectibles = pygame.sprite.Group()
|
||||
self.player = None
|
||||
|
||||
def load_map(self, map_file):
|
||||
"""Load and parse a map from JSON file"""
|
||||
try:
|
||||
with open(map_file, "r") as file:
|
||||
map_data = json.load(file)
|
||||
|
||||
# Create all game objects from map data
|
||||
self.create_map_objects(map_data)
|
||||
|
||||
return {
|
||||
"player": self.player,
|
||||
"all_sprites": self.all_sprites,
|
||||
"platforms": self.platforms,
|
||||
"enemies": self.enemies,
|
||||
"collectibles": self.collectibles,
|
||||
"map_properties": {
|
||||
"name": map_data.get("name", "Unnamed Level"),
|
||||
"width": map_data.get("width", WIDTH),
|
||||
"height": map_data.get("height", HEIGHT),
|
||||
},
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error loading map: {e}")
|
||||
return None
|
||||
|
||||
def create_map_objects(self, map_data):
|
||||
"""Create all game objects from map data"""
|
||||
# Clear existing sprites
|
||||
self.all_sprites.empty()
|
||||
self.platforms.empty()
|
||||
self.enemies.empty()
|
||||
self.collectibles.empty()
|
||||
|
||||
# Create ground elements
|
||||
if "ground" in map_data:
|
||||
for ground in map_data["ground"]:
|
||||
if not ground.get("is_hole", False):
|
||||
platform = Platform(
|
||||
ground["width"],
|
||||
ground["height"],
|
||||
ground["x"] + ground["width"] / 2,
|
||||
ground["y"],
|
||||
(255, 0, 0),
|
||||
ground["texture"],
|
||||
)
|
||||
self.platforms.add(platform)
|
||||
self.all_sprites.add(platform)
|
||||
|
||||
# Create platforms
|
||||
if "platforms" in map_data:
|
||||
for platform_data in map_data["platforms"]:
|
||||
platform = Platform(
|
||||
platform_data["width"],
|
||||
platform_data["height"],
|
||||
platform_data["x"] + platform_data["width"] / 2,
|
||||
platform_data["y"],
|
||||
(255, 0, 0),
|
||||
platform_data["texture"],
|
||||
)
|
||||
|
||||
# Add moving platform properties if needed
|
||||
if platform_data.get("is_moving", False):
|
||||
platform.is_moving = True
|
||||
movement = platform_data["movement"]
|
||||
platform.movement_type = movement["type"]
|
||||
|
||||
if movement["type"] == "linear":
|
||||
platform.movement_points = movement["points"]
|
||||
platform.movement_speed = movement["speed"]
|
||||
platform.wait_time = movement.get("wait_time", 0)
|
||||
platform.current_point = 0
|
||||
platform.current_direction = 1
|
||||
platform.wait_counter = 0
|
||||
|
||||
elif movement["type"] == "circular":
|
||||
platform.center = movement["center"]
|
||||
platform.radius = movement["radius"]
|
||||
platform.angle = 0
|
||||
platform.angular_speed = movement["speed"]
|
||||
platform.clockwise = movement.get("clockwise", True)
|
||||
|
||||
self.platforms.add(platform)
|
||||
self.all_sprites.add(platform)
|
||||
|
||||
# Create player at spawn point
|
||||
spawn = map_data.get("spawn_point", {"x": 50, "y": 700})
|
||||
self.player = Player()
|
||||
self.player.pos.x = spawn["x"]
|
||||
self.player.pos.y = spawn["y"]
|
||||
self.all_sprites.add(self.player)
|
||||
|
||||
# Create enemies (requires Enemy class implementation)
|
||||
if "enemies" in map_data:
|
||||
pass # You'll need to implement enemy creation
|
||||
|
||||
# Create collectibles (requires Collectible class implementation)
|
||||
if "collectibles" in map_data:
|
||||
pass # You'll need to implement collectible creation
|
||||
|
||||
# Create background image
|
||||
if "background" in map_data:
|
||||
print(f"Loading background image: {map_data['background']}")
|
||||
if os.path.isfile(map_data["background"]):
|
||||
background = pygame.image.load(map_data["background"]).convert_alpha()
|
||||
background = pygame.transform.scale(background, (WIDTH, HEIGHT))
|
||||
self.background = background
|
||||
else:
|
||||
print(f"Background image not found: {map_data['background']}")
|
||||
else:
|
||||
self.background = None
|
||||
@@ -5,14 +5,17 @@ pygame.init()
|
||||
FPS = 60
|
||||
ACC = 0.5
|
||||
FRIC = -0.12
|
||||
WIDTH = 800
|
||||
HEIGHT = 600
|
||||
WIDTH = 1200
|
||||
HEIGHT = 800
|
||||
platforms = pygame.sprite.Group()
|
||||
vec = pygame.math.Vector2
|
||||
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT), pygame.RESIZABLE)
|
||||
pygame.display.set_caption("Game")
|
||||
FramePerSec = pygame.time.Clock()
|
||||
all_sprites = pygame.sprite.Group()
|
||||
fullscreen = False
|
||||
ORIGINAL_WIDTH = WIDTH
|
||||
ORIGINAL_HEIGHT = HEIGHT
|
||||
|
||||
try:
|
||||
font = pygame.font.SysFont("Arial", 20)
|
||||
|
||||
36
src/game.py
36
src/game.py
@@ -4,23 +4,35 @@ from pygame.locals import *
|
||||
from src.Entity.Platform import Platform
|
||||
from src.Entity.Player import Player
|
||||
from src.constant import displaysurface, FramePerSec, font, FPS, platforms, all_sprites
|
||||
from src.Map.parser import MapParser
|
||||
|
||||
|
||||
def initialize_game():
|
||||
# Clear previous sprites if any
|
||||
platforms.empty()
|
||||
all_sprites.empty()
|
||||
def initialize_game(map_file="map_test.json"):
|
||||
"""Initialize game with map from JSON file"""
|
||||
parser = MapParser()
|
||||
map_objects = parser.load_map(map_file)
|
||||
|
||||
# Create new game objects
|
||||
PT1 = Platform(1200, 20, 200, 400)
|
||||
P1 = Player()
|
||||
if not map_objects:
|
||||
# Fallback to default setup if map loading fails
|
||||
platforms.empty()
|
||||
all_sprites.empty()
|
||||
|
||||
# Add them to the groups
|
||||
platforms.add(PT1)
|
||||
all_sprites.add(PT1)
|
||||
all_sprites.add(P1)
|
||||
PT1 = Platform(1200, 20, 600, 400)
|
||||
P1 = Player()
|
||||
|
||||
return P1, PT1, platforms, all_sprites
|
||||
platforms.add(PT1)
|
||||
all_sprites.add(PT1)
|
||||
all_sprites.add(P1)
|
||||
|
||||
return P1, PT1, platforms, all_sprites, None # Return None for background
|
||||
|
||||
return (
|
||||
map_objects["player"],
|
||||
None, # No specific platform reference needed
|
||||
map_objects["platforms"],
|
||||
map_objects["all_sprites"],
|
||||
parser.background, # Return the loaded background
|
||||
)
|
||||
|
||||
|
||||
def run_game(P1, all_sprites):
|
||||
|
||||
Reference in New Issue
Block a user