mirror of
https://github.com/BreizhHardware/project_sanic.git
synced 2026-01-18 16:47:25 +01:00
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,4 +15,5 @@ map/infinite/*
|
||||
temp_audio.mp3
|
||||
output.prof
|
||||
|
||||
**/*.pyc
|
||||
**/*.pyc
|
||||
__pycache__/
|
||||
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/map/.DS_Store
vendored
Normal file
BIN
assets/map/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/map/background/.DS_Store
vendored
Normal file
BIN
assets/map/background/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/map/enemy/.DS_Store
vendored
Normal file
BIN
assets/map/enemy/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/map/platform/.DS_Store
vendored
Normal file
BIN
assets/map/platform/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/player/.DS_Store
vendored
Normal file
BIN
assets/player/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/sound/execuse_me.mp3
Normal file
BIN
assets/sound/execuse_me.mp3
Normal file
Binary file not shown.
@@ -177,7 +177,7 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [50,100]
|
||||
"size": [80,80]
|
||||
},
|
||||
{
|
||||
"id": "enemy3_01",
|
||||
@@ -206,7 +206,7 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [50,100]
|
||||
"size": [80,80]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [50,100]
|
||||
"size": [80,80]
|
||||
},
|
||||
{
|
||||
"id": "enemy2_02",
|
||||
@@ -343,7 +343,7 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [50,100]
|
||||
"size": [80,80]
|
||||
},
|
||||
{
|
||||
"id": "enemy1_05",
|
||||
@@ -356,7 +356,7 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [50,100]
|
||||
"size": [80,80]
|
||||
},
|
||||
{
|
||||
"id": "enemy1_06",
|
||||
|
||||
@@ -1,44 +1,895 @@
|
||||
{
|
||||
"name": "Level 3",
|
||||
"width": 2400,
|
||||
"height": 800,
|
||||
"background": "assets/map/background/forest_bg.jpg",
|
||||
"width": 10500,
|
||||
"height": 1500,
|
||||
"background": "assets/map/background/desert_bg.jpg",
|
||||
"gravity": 1.0,
|
||||
"platforms": [
|
||||
{
|
||||
"id": "platform1",
|
||||
"x": 100,
|
||||
"y": 140,
|
||||
"width": 540,
|
||||
"height": 160,
|
||||
"texture": "assets/map/platform/grass_texture.png",
|
||||
"id": "main_ground",
|
||||
"x": -1000,
|
||||
"y": 800,
|
||||
"width": 1500,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform1_01",
|
||||
"x": 500,
|
||||
"y": 550,
|
||||
"width": 300,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform2",
|
||||
"x": 320,
|
||||
"y": 40,
|
||||
"id": "platform1_02",
|
||||
"x": 900,
|
||||
"y": 400,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/grass_texture.png",
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 900, "y": 100},
|
||||
{"x": 900, "y": 400}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_ground_2",
|
||||
"x": 1200,
|
||||
"y": 200,
|
||||
"width": 1400,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform2_01",
|
||||
"x": 2100,
|
||||
"y": 0,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "main_ground_3",
|
||||
"x": 2900,
|
||||
"y": 400,
|
||||
"width": 600,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform3_01",
|
||||
"x": 3800,
|
||||
"y": 600,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "circular",
|
||||
"center": {"x": 3800, "y": 600},
|
||||
"radius": 2,
|
||||
"speed": 0.02,
|
||||
"clockwise": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform3_02",
|
||||
"x": 4100,
|
||||
"y": 300,
|
||||
"width": 300,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform3_03",
|
||||
"x": 4400,
|
||||
"y": 200,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 4400, "y": 200},
|
||||
{"x": 4900, "y": 200}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform3_04",
|
||||
"x": 5100,
|
||||
"y": 100,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform3_05",
|
||||
"x": 3600,
|
||||
"y": 800,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "main_ground_4",
|
||||
"x": 2900,
|
||||
"y": 1000,
|
||||
"width": 700,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform4_01",
|
||||
"x": 2600,
|
||||
"y": 1200,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "circular",
|
||||
"center": {"x": 2600, "y": 1200},
|
||||
"radius": 2,
|
||||
"speed": 0.02,
|
||||
"clockwise": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform4_02",
|
||||
"x": 2000,
|
||||
"y": 900,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform4_03",
|
||||
"x": 2100,
|
||||
"y": 1300,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform4_04",
|
||||
"x": 2400,
|
||||
"y": 1400,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 2400, "y": 1400},
|
||||
{"x": 2400, "y": 1800}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform4_05",
|
||||
"x": 3000,
|
||||
"y": 1800,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 2600, "y": 1800},
|
||||
{"x": 3000, "y": 1800}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform4_06",
|
||||
"x": 3200,
|
||||
"y": 1800,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 3200, "y": 1800},
|
||||
{"x": 3600, "y": 1800}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_ground_5",
|
||||
"x": 3800,
|
||||
"y": 1900,
|
||||
"width": 1000,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform5_01",
|
||||
"x": 4900,
|
||||
"y": 1900,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 4900, "y": 1900},
|
||||
{"x": 5400, "y": 1900}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform5_02",
|
||||
"x": 5600,
|
||||
"y": 1900,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform5_03",
|
||||
"x": 5900,
|
||||
"y": 1900,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 5900, "y": 1100},
|
||||
{"x": 5900, "y": 1900}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_ground_6",
|
||||
"x": 3900,
|
||||
"y": 1000,
|
||||
"width": 900,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform6_01",
|
||||
"x": 4900,
|
||||
"y": 1000,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 4900, "y": 1000},
|
||||
{"x": 5400, "y": 1000}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform6_02",
|
||||
"x": 5600,
|
||||
"y": 1100,
|
||||
"width": 500,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "main_ground_7",
|
||||
"x": 6300,
|
||||
"y": 1100,
|
||||
"width": 800,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform7_01",
|
||||
"x": 6500,
|
||||
"y": 900,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_02",
|
||||
"x": 6700,
|
||||
"y": 800,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_03",
|
||||
"x": 6300,
|
||||
"y": 700,
|
||||
"width": 400,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_04",
|
||||
"x": 7400,
|
||||
"y": 800,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "circular",
|
||||
"center": {"x": 7400, "y": 800},
|
||||
"radius": 2,
|
||||
"speed": 0.02,
|
||||
"clockwise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform7_05",
|
||||
"x": 7700,
|
||||
"y": 800,
|
||||
"width": 300,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_06",
|
||||
"x": 8100,
|
||||
"y": 800,
|
||||
"width": 400,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_07",
|
||||
"x": 8500,
|
||||
"y": 700,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 8500, "y": 700},
|
||||
{"x": 8900, "y": 700}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "platform7_08",
|
||||
"x": 7700,
|
||||
"y": 1200,
|
||||
"width": 300,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform7_09",
|
||||
"x": 8100,
|
||||
"y": 1200,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 8100, "y": 1200},
|
||||
{"x": 8600, "y": 1200}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_ground_8",
|
||||
"x": 9100,
|
||||
"y": 800,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform8_01",
|
||||
"x": 9600,
|
||||
"y": 700,
|
||||
"width": 200,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": true,
|
||||
"movement": {
|
||||
"type": "linear",
|
||||
"points": [
|
||||
{"x": 9600, "y": 700},
|
||||
{"x": 9600, "y": 1100}
|
||||
],
|
||||
"speed": 3.0,
|
||||
"wait_time": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_ground_9",
|
||||
"x": 8900,
|
||||
"y": 1300,
|
||||
"width": 2600,
|
||||
"height": 200,
|
||||
"texture": "assets/map/platform/stone_texture.png"
|
||||
},
|
||||
{
|
||||
"id": "platform9_01",
|
||||
"x": 10300,
|
||||
"y": 1100,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform9_02",
|
||||
"x": 10400,
|
||||
"y": 1000,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
},
|
||||
{
|
||||
"id": "platform9_03",
|
||||
"x": 10500,
|
||||
"y": 1100,
|
||||
"width": 100,
|
||||
"height": 20,
|
||||
"texture": "assets/map/platform/wood_texture.png",
|
||||
"is_moving": false
|
||||
}
|
||||
],
|
||||
"enemies": [],
|
||||
"checkpoints": [],
|
||||
"exits": [
|
||||
|
||||
"enemies": [
|
||||
{
|
||||
"x": 355,
|
||||
"y": -760,
|
||||
"width": 50,
|
||||
"height": 80,
|
||||
"next_level": "map/levels/1.json",
|
||||
"sprite": "assets/map/exit/Zeldo.png"
|
||||
"id": "enemy_01",
|
||||
"type": "flyer",
|
||||
"x": 1200,
|
||||
"y": 0,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_02",
|
||||
"type": "turret",
|
||||
"x": 1400,
|
||||
"y": 75,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_03",
|
||||
"type": "walker",
|
||||
"x": 1600,
|
||||
"y": 75,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 1600, "y": 75},
|
||||
{"x": 1900, "y": 75}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_04",
|
||||
"type": "flyer",
|
||||
"x": 2000,
|
||||
"y": -100,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_05",
|
||||
"type": "walker",
|
||||
"x": 2900,
|
||||
"y": 275,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 2900, "y": 275},
|
||||
{"x": 3300, "y": 275}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_06",
|
||||
"type": "flyer",
|
||||
"x": 3400,
|
||||
"y": 800,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_07",
|
||||
"type": "turret",
|
||||
"x": 3000,
|
||||
"y": 875,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_08",
|
||||
"type": "turret",
|
||||
"x": 2100,
|
||||
"y": 875,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_09",
|
||||
"type": "flyer",
|
||||
"x": 3100,
|
||||
"y": 1600,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_10",
|
||||
"type": "walker",
|
||||
"x": 3900,
|
||||
"y": 1775,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 3900, "y": 1775},
|
||||
{"x": 4200, "y": 1775}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_11",
|
||||
"type": "turret",
|
||||
"x": 4500,
|
||||
"y": 1775,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_12",
|
||||
"type": "walker",
|
||||
"x": 3900,
|
||||
"y": 875,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 3900, "y": 875},
|
||||
{"x": 4200, "y": 875}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_13",
|
||||
"type": "flyer",
|
||||
"x": 4100,
|
||||
"y": 800,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_14",
|
||||
"type": "walker",
|
||||
"x": 4200,
|
||||
"y": 875,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 4200, "y": 875},
|
||||
{"x": 4500, "y": 875}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_15",
|
||||
"type": "turret",
|
||||
"x": 4300,
|
||||
"y": 875,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_16",
|
||||
"type": "turret",
|
||||
"x": 6300,
|
||||
"y": 675,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_17",
|
||||
"type": "flyer",
|
||||
"x": 7900,
|
||||
"y": 1100,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_18",
|
||||
"type": "flyer",
|
||||
"x": 7900,
|
||||
"y": 700,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_19",
|
||||
"type": "turret",
|
||||
"x": 9400,
|
||||
"y": 675,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "enemy_20",
|
||||
"type": "walker",
|
||||
"x": 8900,
|
||||
"y": 1175,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "patrol",
|
||||
"patrol_points": [
|
||||
{"x": 8900, "y": 1175},
|
||||
{"x": 9200, "y": 1175}
|
||||
],
|
||||
"speed": 1.5,
|
||||
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_21",
|
||||
"type": "flyer",
|
||||
"x": 9100,
|
||||
"y": 1100,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "chase",
|
||||
"detection_radius": 200,
|
||||
"speed": 2.0,
|
||||
"sprite_sheet": "assets/map/enemy/flying_enemy.png",
|
||||
"size": [50,50]
|
||||
},
|
||||
{
|
||||
"id": "enemy_22",
|
||||
"type": "turret",
|
||||
"x": 9800,
|
||||
"y": 1175,
|
||||
"health": 1,
|
||||
"damage": 1,
|
||||
"behavior": "stationary",
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret.gif",
|
||||
"size": [75,100]
|
||||
},
|
||||
{
|
||||
"id": "boss",
|
||||
"type": "boss",
|
||||
"x": 10000,
|
||||
"y": 1200,
|
||||
"health": 3,
|
||||
"damage": 1,
|
||||
"behavior": "boss",
|
||||
"attack_interval": 1.0,
|
||||
"attack_range": 1000,
|
||||
"sprite_sheet": "assets/map/enemy/boss.gif",
|
||||
"size": [200,200]
|
||||
}
|
||||
],
|
||||
"collectibles": [],
|
||||
|
||||
"collectibles": [
|
||||
{
|
||||
"id": "coin1",
|
||||
"type": "coin",
|
||||
"x": 2200,
|
||||
"y": -50,
|
||||
"sprite": "assets/map/collectibles/Sanic_Coin.png"
|
||||
},
|
||||
{
|
||||
"id": "coin2",
|
||||
"type": "coin",
|
||||
"x": 5100,
|
||||
"y": 50,
|
||||
"sprite": "assets/map/collectibles/Sanic_Coin.png"
|
||||
},
|
||||
{
|
||||
"id": "coin3",
|
||||
"type": "coin",
|
||||
"x": 4700,
|
||||
"y": 1750,
|
||||
"sprite": "assets/map/collectibles/Sanic_Coin.png"
|
||||
},
|
||||
{
|
||||
"id": "coin4",
|
||||
"type": "coin",
|
||||
"x": 6500,
|
||||
"y": 650,
|
||||
"sprite": "assets/map/collectibles/Sanic_Coin.png"
|
||||
},
|
||||
{
|
||||
"id": "coin5",
|
||||
"type": "coin",
|
||||
"x": 9200,
|
||||
"y": 650,
|
||||
"sprite": "assets/map/collectibles/Sanic_Coin.png"
|
||||
},
|
||||
{
|
||||
"id": "jump1",
|
||||
"type": "jump",
|
||||
"x": 400,
|
||||
"y": 650,
|
||||
"sprite": "assets/map/collectibles/jump.png"
|
||||
},
|
||||
{
|
||||
"id": "jump2",
|
||||
"type": "jump",
|
||||
"x": 700,
|
||||
"y": 500,
|
||||
"sprite": "assets/map/collectibles/jump.png"
|
||||
},
|
||||
{
|
||||
"id": "speed1",
|
||||
"type": "speed",
|
||||
"x": 2300,
|
||||
"y": -100,
|
||||
"sprite": "assets/map/collectibles/speed.png"
|
||||
}
|
||||
],
|
||||
|
||||
"checkpoints": [
|
||||
{
|
||||
"id": "checkpoint1",
|
||||
"x": 6000,
|
||||
"y": 975,
|
||||
"width": 50,
|
||||
"height": 125,
|
||||
"sprite": "assets/map/checkpoints/checkpoint.png"
|
||||
}
|
||||
],
|
||||
|
||||
"spawn_point": {
|
||||
"x": 260.0,
|
||||
"y": 0.0
|
||||
}
|
||||
"x": 50,
|
||||
"y": 650
|
||||
},
|
||||
|
||||
"exits": [
|
||||
{
|
||||
"x": 11300,
|
||||
"y": 1150,
|
||||
"width": 50,
|
||||
"height": 80,
|
||||
"next_level": "Level 2",
|
||||
"sprite": "assets/map/exit/Zeldo.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -135,6 +135,19 @@
|
||||
"attack_interval": 2.0,
|
||||
"attack_range": 300,
|
||||
"sprite_sheet": "assets/map/enemy/turret_enemy.png"
|
||||
},
|
||||
{
|
||||
"id": "boss",
|
||||
"type": "boss",
|
||||
"x": 2000,
|
||||
"y": 0,
|
||||
"health": 3,
|
||||
"damage": 1,
|
||||
"behavior": "boss",
|
||||
"attack_interval": 1.0,
|
||||
"attack_range": 1000,
|
||||
"sprite_sheet": "assets/map/enemy/boss.gif",
|
||||
"size": [200,200]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -45,7 +45,7 @@ class CheckpointDB:
|
||||
try:
|
||||
self.cursor.execute(
|
||||
"INSERT OR REPLACE INTO checkpoints (map_name, pos_x, pos_y, timestamp) VALUES (?, ?, ?, strftime('%s'))",
|
||||
(map_name, pos_x, pos_y),
|
||||
(map_name, pos_x, pos_y + 100),
|
||||
)
|
||||
self.conn.commit()
|
||||
except Exception as e:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
import pygame
|
||||
import os
|
||||
from PIL import Image, ImageSequence
|
||||
import random
|
||||
from src.Entity.Entity import Entity
|
||||
@@ -31,13 +30,13 @@ class Enemy(Entity):
|
||||
sprite_path = enemy_data.get("sprite_sheet", "assets/enemy/default_enemy.png")
|
||||
|
||||
# Load sprite sheet or GIF depending on enemy type and file extension
|
||||
if sprite_path.lower().endswith(".gif") and self.enemy_type == "turret":
|
||||
self.load_gif_frames(sprite_path)
|
||||
if sprite_path.lower().endswith(".gif"):
|
||||
self.load_gif_frames(sprite_path, self.size)
|
||||
if self.frames:
|
||||
self.surf = self.frames[0]
|
||||
else:
|
||||
# Default sprite
|
||||
self.surf = pygame.Surface((40, 40))
|
||||
self.surf = pygame.Surface(self.size)
|
||||
self.surf.fill((255, 0, 0))
|
||||
else:
|
||||
try:
|
||||
@@ -45,7 +44,7 @@ class Enemy(Entity):
|
||||
self.surf = pygame.transform.scale(self.surf, self.size)
|
||||
except:
|
||||
# Default sprite
|
||||
self.surf = pygame.Surface((40, 40))
|
||||
self.surf = pygame.Surface(self.size)
|
||||
self.surf.fill((255, 0, 0))
|
||||
|
||||
# Initial rectangle
|
||||
@@ -64,7 +63,7 @@ class Enemy(Entity):
|
||||
self.is_attacking = False
|
||||
self.detected_player = False
|
||||
|
||||
def load_gif_frames(self, gif_path):
|
||||
def load_gif_frames(self, gif_path, size=(80, 80)):
|
||||
"""Load frames from a GIF file"""
|
||||
try:
|
||||
gif = Image.open(gif_path)
|
||||
@@ -74,7 +73,7 @@ class Enemy(Entity):
|
||||
frame_surface = pygame.image.fromstring(
|
||||
frame.convert("RGBA").tobytes(), frame.size, "RGBA"
|
||||
)
|
||||
frame_surface = pygame.transform.scale(frame_surface, (80, 80))
|
||||
frame_surface = pygame.transform.scale(frame_surface, size)
|
||||
self.frames.append(frame_surface)
|
||||
frame_count += 1
|
||||
|
||||
@@ -93,9 +92,11 @@ class Enemy(Entity):
|
||||
self.chase(player)
|
||||
elif self.behavior == "stationary" and player:
|
||||
self.stationary_attack(player)
|
||||
elif self.behavior == "boss" and player:
|
||||
self.boss(player)
|
||||
|
||||
# Animation management for turret enemies
|
||||
if self.enemy_type == "turret" and self.frames:
|
||||
if (self.enemy_type == "turret" or self.enemy_type == "boss") and self.frames:
|
||||
self.animation_timer += dt
|
||||
if self.animation_timer >= self.animation_speed:
|
||||
self.animation_timer = 0
|
||||
@@ -153,6 +154,8 @@ class Enemy(Entity):
|
||||
|
||||
if self.attack_timer >= self.attack_interval:
|
||||
self.attack_timer = 0
|
||||
# Easter egg sound
|
||||
pygame.mixer.Sound("assets/sound/execuse_me.mp3").play()
|
||||
self.attack(player)
|
||||
|
||||
def attack(self, player):
|
||||
@@ -160,7 +163,7 @@ class Enemy(Entity):
|
||||
self.is_attacking = True
|
||||
|
||||
# For turret-type enemies, create a projectile
|
||||
if self.enemy_type == "turret":
|
||||
if self.enemy_type == "turret" or self.enemy_type == "boss":
|
||||
# Calculate direction to player
|
||||
direction = vec(player.pos.x - self.pos.x, player.pos.y - self.pos.y)
|
||||
|
||||
@@ -210,3 +213,27 @@ class Enemy(Entity):
|
||||
# KB LOL MINECRAFT
|
||||
knockback_direction = 1 if player.pos.x > self.pos.x else -1
|
||||
player.vel.x = knockback_direction * 8
|
||||
|
||||
def boss(self, player, FPS=60):
|
||||
"""
|
||||
Boss behavior: combine horizontal chase with turret-like attacks
|
||||
"""
|
||||
# Follow the player horizontally (x axis)
|
||||
if abs(player.pos.x - self.pos.x) > 50:
|
||||
direction = 1 if player.pos.x > self.pos.x else -1
|
||||
self.pos.x += direction * self.speed
|
||||
self.direction = direction
|
||||
|
||||
# Attack the player if within range
|
||||
distance_to_player = vec(
|
||||
player.pos.x - self.pos.x, player.pos.y - self.pos.y
|
||||
).length()
|
||||
|
||||
if distance_to_player <= self.attack_range:
|
||||
self.attack_timer += 1 / FPS
|
||||
|
||||
if self.attack_timer >= self.attack_interval:
|
||||
self.attack_timer = 0
|
||||
self.attack(player)
|
||||
|
||||
self.detected_player = distance_to_player <= self.detection_radius
|
||||
|
||||
@@ -11,6 +11,7 @@ class Entity(pygame.sprite.Sprite):
|
||||
self.pos = vec(pos)
|
||||
self.vel = vec(0, 0)
|
||||
self.acc = vec(0, 0)
|
||||
self.alive = True
|
||||
|
||||
# Default surface
|
||||
self.surf = pygame.Surface(size)
|
||||
|
||||
@@ -24,9 +24,11 @@ class Exit(Entity):
|
||||
sprite_path (str, optional): Path to the sprite image for the exit
|
||||
"""
|
||||
super().__init__(pos=(x, y), size=(width, height), color=(0, 255, 0))
|
||||
self.next_level = next_level # Store the next level to load
|
||||
self.next_level = next_level
|
||||
self.active = True # Flag to prevent multiple triggers
|
||||
self.player = None # Will store the player reference
|
||||
self.player = None
|
||||
self.boss = None
|
||||
self.locked = False
|
||||
|
||||
# Load sprite if provided
|
||||
if sprite_path:
|
||||
@@ -46,16 +48,33 @@ class Exit(Entity):
|
||||
"""
|
||||
self.player = player
|
||||
|
||||
def set_boss(self, boss):
|
||||
"""
|
||||
Set the boss to this exit. The exit will be locked until the boss is defeated.
|
||||
|
||||
Args:
|
||||
boss: The boss entity to check defeat status with
|
||||
"""
|
||||
self.boss = boss
|
||||
self.locked = True
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Check for collision with the player and trigger level completion.
|
||||
"""
|
||||
# Check if the boss is defeated
|
||||
if self.boss:
|
||||
if hasattr(self.boss, "alive") and not self.boss.alive:
|
||||
self.locked = False
|
||||
elif not hasattr(self.boss, "alive"):
|
||||
self.locked = False
|
||||
|
||||
# Skip collision check if player reference is not set
|
||||
if not self.player or not self.active:
|
||||
return
|
||||
|
||||
# Check if player is colliding with exit
|
||||
if self.rect.colliderect(self.player.rect):
|
||||
if self.rect.colliderect(self.player.rect) and not self.locked:
|
||||
# Play the video and return to menu
|
||||
self.play_video_and_return_to_menu("assets/map/exit/Zeldo Motus.mp4")
|
||||
self.active = False # Prevent multiple triggers
|
||||
|
||||
44
src/Entity/FloatingText.py
Normal file
44
src/Entity/FloatingText.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import pygame
|
||||
|
||||
|
||||
class FloatingText:
|
||||
def __init__(self, text, player, game_resources, duration=2000):
|
||||
self.text = text
|
||||
self.player = player
|
||||
self.game_resources = game_resources
|
||||
self.creation_time = pygame.time.get_ticks()
|
||||
self.duration = duration
|
||||
self.font = pygame.font.SysFont("Arial", 24, bold=True)
|
||||
self.color = (255, 50, 0)
|
||||
self.offset_y = -50
|
||||
self.alpha = 255
|
||||
|
||||
def update(self):
|
||||
current_time = pygame.time.get_ticks()
|
||||
elapsed = current_time - self.creation_time
|
||||
|
||||
# Fade out
|
||||
if elapsed > self.duration * 0.7:
|
||||
fade_time = self.duration * 0.3
|
||||
self.alpha = max(0, 255 * (1 - (elapsed - self.duration * 0.7) / fade_time))
|
||||
|
||||
return elapsed < self.duration
|
||||
|
||||
def draw(self, surface, camera_offset=None):
|
||||
if camera_offset is None:
|
||||
camera_offset = pygame.math.Vector2(0, 0)
|
||||
|
||||
pos_x = self.player.rect.centerx - camera_offset.x
|
||||
pos_y = self.player.rect.top - camera_offset.y + self.offset_y
|
||||
|
||||
text_surface = self.font.render(self.text, True, self.color)
|
||||
text_surface.set_alpha(self.alpha)
|
||||
text_rect = text_surface.get_rect(center=(pos_x, pos_y))
|
||||
|
||||
bg_rect = text_rect.inflate(20, 10)
|
||||
bg_surface = pygame.Surface((bg_rect.width, bg_rect.height), pygame.SRCALPHA)
|
||||
bg_color = (0, 0, 0, int(self.alpha * 0.7))
|
||||
pygame.draw.rect(bg_surface, bg_color, bg_surface.get_rect(), border_radius=5)
|
||||
|
||||
surface.blit(bg_surface, bg_rect)
|
||||
surface.blit(text_surface, text_rect)
|
||||
@@ -4,6 +4,8 @@ import pygame
|
||||
import os
|
||||
from PIL import Image, ImageSequence
|
||||
from pygame.math import Vector2 as vec
|
||||
|
||||
from src.Entity.FloatingText import FloatingText
|
||||
from src.Entity.Projectile import Projectile
|
||||
|
||||
|
||||
@@ -19,6 +21,8 @@ class Player(Entity):
|
||||
|
||||
self.jump_button = 0
|
||||
self.dash_button = 1
|
||||
self.attack_button = 2
|
||||
self.menu_button = 3
|
||||
|
||||
try:
|
||||
if pygame.joystick.get_count() > 0:
|
||||
@@ -63,6 +67,7 @@ class Player(Entity):
|
||||
self.life_icon = None
|
||||
|
||||
self.rect = self.surf.get_rect()
|
||||
self.floating_texts = []
|
||||
|
||||
# Load images
|
||||
self.load_images()
|
||||
@@ -435,6 +440,8 @@ class Player(Entity):
|
||||
elif self.vel.x < 0:
|
||||
self.facing_right = False
|
||||
|
||||
self.floating_texts = [text for text in self.floating_texts if text.update()]
|
||||
|
||||
def take_damage(self, amount=1):
|
||||
"""Reduce life number if not invulnerable"""
|
||||
if not self.invulnerable:
|
||||
@@ -535,156 +542,177 @@ class Player(Entity):
|
||||
self.is_attacking = False
|
||||
current_time = pygame.time.get_ticks()
|
||||
pressed_keys = pygame.key.get_pressed()
|
||||
if pressed_keys[K_q] and pressed_keys[K_c]:
|
||||
if current_time - self.last_attack_time >= self.attack_cooldown:
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, self.pos.y)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x, self.pos.y),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
|
||||
if pressed_keys[K_d] and pressed_keys[K_c]:
|
||||
if current_time - self.last_attack_time >= self.attack_cooldown:
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, self.pos.y)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x, self.pos.y),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
joystick_attack = False
|
||||
if self.has_joystick and self.joystick:
|
||||
try:
|
||||
if self.joystick.get_numbuttons() > self.attack_button:
|
||||
joystick_attack = self.joystick.get_button(self.attack_button)
|
||||
except pygame.error:
|
||||
pass
|
||||
|
||||
if pressed_keys[K_q] and pressed_keys[K_v]:
|
||||
if current_time - self.last_attack_time >= self.attack_cooldown:
|
||||
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
|
||||
attack_sound.set_volume(0.4)
|
||||
attack_sound.play()
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(-self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x - 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
size=(50, 50),
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
if self.projectiles > 0:
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(-self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x - 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
self.projectiles -= 1
|
||||
if (
|
||||
joystick_attack
|
||||
and current_time - self.last_attack_time >= self.attack_cooldown
|
||||
and self.projectiles > 0
|
||||
):
|
||||
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
|
||||
attack_sound.set_volume(0.4)
|
||||
attack_sound.play()
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
|
||||
if pressed_keys[K_d] and pressed_keys[K_v]:
|
||||
if current_time - self.last_attack_time >= self.attack_cooldown:
|
||||
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
|
||||
attack_sound.set_volume(0.4)
|
||||
attack_sound.play()
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
# Direction en fonction de où le personnage est tourné
|
||||
if self.facing_right:
|
||||
direction = vec(self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x + 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
size=(50, 50),
|
||||
position = vec(self.pos.x + 50, self.pos.y - 50)
|
||||
else:
|
||||
direction = vec(-self.pos.x, 0)
|
||||
position = vec(self.pos.x - 50, self.pos.y - 50)
|
||||
|
||||
projectile = Projectile(
|
||||
pos=position,
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
size=(50, 50),
|
||||
)
|
||||
|
||||
# Ajouter le projectile au groupe de sprites
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
|
||||
self.projectiles -= 1
|
||||
|
||||
if (
|
||||
pressed_keys[K_q]
|
||||
and pressed_keys[K_c]
|
||||
and current_time - self.last_attack_time >= self.attack_cooldown
|
||||
):
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, self.pos.y)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x, self.pos.y),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
if self.projectiles > 0:
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x + 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
self.projectiles -= 1
|
||||
)
|
||||
|
||||
if (
|
||||
pressed_keys[K_d]
|
||||
and pressed_keys[K_c]
|
||||
and current_time - self.last_attack_time >= self.attack_cooldown
|
||||
):
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, self.pos.y)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x, self.pos.y),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
pressed_keys[K_q]
|
||||
and pressed_keys[K_v]
|
||||
and current_time - self.last_attack_time >= self.attack_cooldown
|
||||
and self.projectiles > 0
|
||||
):
|
||||
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
|
||||
attack_sound.set_volume(0.4)
|
||||
attack_sound.play()
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(-self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x - 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
)
|
||||
# Add projectile to the sprite group (to be placed in main.py)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
self.projectiles -= 1
|
||||
|
||||
if (
|
||||
pressed_keys[K_d]
|
||||
and pressed_keys[K_v]
|
||||
and current_time - self.last_attack_time >= self.attack_cooldown
|
||||
and self.projectiles > 0
|
||||
):
|
||||
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
|
||||
attack_sound.set_volume(0.4)
|
||||
attack_sound.play()
|
||||
self.is_attacking = True
|
||||
self.attack_start_time = current_time
|
||||
self.last_attack_time = current_time
|
||||
# Calculate direction to player
|
||||
direction = vec(self.pos.x, 0)
|
||||
projectile = Projectile(
|
||||
pos=vec(self.pos.x + 50, self.pos.y - 50),
|
||||
direction=direction,
|
||||
speed=2,
|
||||
damage=1,
|
||||
color=(165, 42, 42),
|
||||
enemy_proj=False,
|
||||
texturePath="assets/player/Boule de feu.png",
|
||||
)
|
||||
pygame.event.post(
|
||||
pygame.event.Event(
|
||||
pygame.USEREVENT,
|
||||
{"action": "create_projectile", "projectile": projectile},
|
||||
)
|
||||
)
|
||||
self.projectiles -= 1
|
||||
|
||||
def add_projectiles(self):
|
||||
"""Set player projectiles to 3"""
|
||||
"""Set player projectiles to 3 and show floating text"""
|
||||
self.projectiles = 3
|
||||
self.floating_texts.append(
|
||||
FloatingText("+3 fireball", self, self.game_resources)
|
||||
)
|
||||
|
||||
def draw_projectiles_amount(self, surface):
|
||||
"""Draws the projectiles counter with icon in the top left corner"""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
136
src/Map/cinematic.py
Normal file
136
src/Map/cinematic.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import pygame
|
||||
from PIL import Image, ImageSequence
|
||||
|
||||
|
||||
class Cinematic:
|
||||
"""Class to handle cinematics in the game"""
|
||||
|
||||
# Class variable to track if cinematics have been played (shared across all instances)
|
||||
played_cinematics = {"Level 1": False, "Level 2": False, "Level 3": False}
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize cinematic resources"""
|
||||
# Load resources
|
||||
self.player_image = pygame.image.load(
|
||||
"assets/player/Sanic Base.png"
|
||||
).convert_alpha()
|
||||
self.princess_image = pygame.image.load(
|
||||
"assets/map/exit/Zeldo.png"
|
||||
).convert_alpha()
|
||||
|
||||
# Prepare the boss GIF
|
||||
self.boss_gif = Image.open("assets/map/enemy/boss.gif")
|
||||
self.boss_frames = [
|
||||
frame.copy() for frame in ImageSequence.Iterator(self.boss_gif)
|
||||
]
|
||||
self.boss_frame_index = 0
|
||||
|
||||
self.player_image = pygame.transform.scale(self.player_image, (200, 200))
|
||||
self.princess_image = pygame.transform.scale(self.princess_image, (200, 200))
|
||||
|
||||
def _create_gradient_background(
|
||||
self, screen, start_color=(0, 0, 128), end_color=(0, 0, 0)
|
||||
):
|
||||
"""Create a gradient background for the cinematic"""
|
||||
width, height = screen.get_size()
|
||||
background = pygame.Surface((width, height))
|
||||
|
||||
for y in range(height):
|
||||
ratio = y / height
|
||||
|
||||
r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
|
||||
g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
|
||||
b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
|
||||
|
||||
pygame.draw.line(background, (r, g, b), (0, y), (width, y))
|
||||
|
||||
return background
|
||||
|
||||
def _display_cinematic_text(self, screen, lore_text, level_name):
|
||||
"""Helper function to display cinematic text with animations"""
|
||||
font = pygame.font.Font(None, 36)
|
||||
pygame.mixer.init()
|
||||
cinematic_voice = pygame.mixer.Sound("assets/sound/cinematic_voice.mp3")
|
||||
|
||||
gradient_bg = self._create_gradient_background(screen)
|
||||
screen.blit(gradient_bg, (0, 0))
|
||||
|
||||
for i, line in enumerate(lore_text):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
return False # Skip the cinematic if any key is pressed
|
||||
|
||||
# Play the voice audio
|
||||
cinematic_voice.play()
|
||||
|
||||
# Display character images based on text content
|
||||
if "Sanic" in line:
|
||||
screen.blit(self.player_image, (100, 400))
|
||||
if "Zeldo" in line:
|
||||
screen.blit(self.princess_image, (700, 400))
|
||||
if "Wheatly" in line or "Wheatley" in line:
|
||||
for _ in range(46):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
return False # Skip the cinematic if any key is pressed
|
||||
|
||||
boss_frame = self.boss_frames[self.boss_frame_index]
|
||||
boss_frame = boss_frame.convert("RGBA")
|
||||
boss_frame = pygame.image.fromstring(
|
||||
boss_frame.tobytes(), boss_frame.size, boss_frame.mode
|
||||
)
|
||||
boss_frame = pygame.transform.scale(boss_frame, (200, 200))
|
||||
screen.blit(boss_frame, (400, 400))
|
||||
pygame.display.flip()
|
||||
pygame.time.wait(100)
|
||||
self.boss_frame_index = (self.boss_frame_index + 1) % len(
|
||||
self.boss_frames
|
||||
)
|
||||
|
||||
text_surface = font.render(line, True, (255, 255, 255))
|
||||
screen.blit(text_surface, (50, 50 + i * 40))
|
||||
pygame.display.flip()
|
||||
pygame.time.wait(2000)
|
||||
cinematic_voice.stop()
|
||||
|
||||
# Mark this cinematic as played
|
||||
Cinematic.played_cinematics[level_name] = True
|
||||
return True
|
||||
|
||||
def play_cinematic(self, game_resources, level_name):
|
||||
"""Play the cinematic for levels"""
|
||||
# Check if this cinematic has already been played
|
||||
if Cinematic.played_cinematics.get(level_name, False):
|
||||
return
|
||||
|
||||
screen = game_resources.displaysurface
|
||||
|
||||
if level_name == "Level 1":
|
||||
lore_text = [
|
||||
"Once upon a time in a land far away...",
|
||||
"A brave hero named Sanic...",
|
||||
"And a beautiful princess named Zeldo...",
|
||||
"Has been captured by the evil boss...",
|
||||
"Wheatly !!!",
|
||||
"Sanic must rescue Zeldo...",
|
||||
]
|
||||
self._display_cinematic_text(screen, lore_text, level_name)
|
||||
|
||||
elif level_name == "Level 2":
|
||||
lore_text = [
|
||||
"When Sanic arrives at Zeldo's position...",
|
||||
"He realizes that it's a trap...",
|
||||
"Zeldo is actually a fake...",
|
||||
"And the real Zeldo is in another castle...",
|
||||
]
|
||||
self._display_cinematic_text(screen, lore_text, level_name)
|
||||
|
||||
elif level_name == "Level 3":
|
||||
lore_text = [
|
||||
"Sanic must face the evil boss Wheatley...",
|
||||
"To rescue the real princess Zeldo...",
|
||||
"Will Sanic succeed?",
|
||||
]
|
||||
self._display_cinematic_text(screen, lore_text, level_name)
|
||||
else:
|
||||
print(f"No cinematic available for {level_name}.")
|
||||
@@ -10,6 +10,7 @@ from src.Entity.Exit import Exit
|
||||
from src.Entity.Coin import Coin
|
||||
from src.Entity.JumpBoost import JumpBoost
|
||||
from src.Entity.SpeedBoost import SpeedBoost
|
||||
from src.Map.cinematic import Cinematic
|
||||
|
||||
|
||||
class MapParser:
|
||||
@@ -34,8 +35,7 @@ class MapParser:
|
||||
self.princess_image = pygame.image.load(
|
||||
"assets/map/exit/Zeldo.png"
|
||||
).convert_alpha()
|
||||
|
||||
self.cinematic_played = False
|
||||
self.cinematic = Cinematic()
|
||||
|
||||
def load_map(self, map_file):
|
||||
"""Load and parse a map from JSON file"""
|
||||
@@ -44,9 +44,8 @@ class MapParser:
|
||||
map_data = json.load(file)
|
||||
|
||||
# If it's level 1, play the cinematic
|
||||
if map_data.get("name") == "Level 1" and not self.cinematic_played:
|
||||
self.play_cinematic(self.game_resources)
|
||||
self.cinematic_played = True
|
||||
if map_data.get("name"):
|
||||
self.cinematic.play_cinematic(self.game_resources, map_data.get("name"))
|
||||
|
||||
# Create all game objects from map data
|
||||
self.create_map_objects(map_data, map_file)
|
||||
@@ -198,61 +197,3 @@ class MapParser:
|
||||
self.player.pos.x = spawn["x"]
|
||||
self.player.pos.y = spawn["y"]
|
||||
self.all_sprites.add(self.player)
|
||||
|
||||
def play_cinematic(self, game_resources):
|
||||
"""Play the cinematic for level 1"""
|
||||
screen = game_resources.displaysurface
|
||||
font = pygame.font.Font(None, 36)
|
||||
lore_text = [
|
||||
"Once upon a time in a land far away...",
|
||||
"A brave hero named Sanic...",
|
||||
"And a beautiful princess named Zeldo...",
|
||||
"Has been captured by the evil boss...",
|
||||
"Wheatly !!!",
|
||||
"Sanic must rescue Zeldo...",
|
||||
]
|
||||
|
||||
self.player_image = pygame.transform.scale(self.player_image, (200, 200))
|
||||
self.princess_image = pygame.transform.scale(self.princess_image, (200, 200))
|
||||
|
||||
# Initialize the mixer
|
||||
pygame.mixer.init()
|
||||
cinematic_voice = pygame.mixer.Sound("assets/sound/cinematic_voice.mp3")
|
||||
|
||||
screen.fill((0, 0, 0))
|
||||
for i, line in enumerate(lore_text):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
return # Skip the cinematic if any key is pressed
|
||||
|
||||
# Play the voice audio
|
||||
cinematic_voice.play()
|
||||
|
||||
if "Sanic" in line:
|
||||
screen.blit(self.player_image, (100, 400))
|
||||
if "Zeldo" in line:
|
||||
screen.blit(self.princess_image, (700, 400))
|
||||
if "Wheatly" in line:
|
||||
for _ in range(46):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
return # Skip the cinematic if any key is pressed
|
||||
|
||||
boss_frame = self.boss_frames[self.boss_frame_index]
|
||||
boss_frame = boss_frame.convert("RGBA")
|
||||
boss_frame = pygame.image.fromstring(
|
||||
boss_frame.tobytes(), boss_frame.size, boss_frame.mode
|
||||
)
|
||||
boss_frame = pygame.transform.scale(boss_frame, (200, 200))
|
||||
screen.blit(boss_frame, (400, 400))
|
||||
pygame.display.flip()
|
||||
pygame.time.wait(100)
|
||||
self.boss_frame_index = (self.boss_frame_index + 1) % len(
|
||||
self.boss_frames
|
||||
)
|
||||
|
||||
text_surface = font.render(line, True, (255, 255, 255))
|
||||
screen.blit(text_surface, (50, 50 + i * 40))
|
||||
pygame.display.flip()
|
||||
pygame.time.wait(2000)
|
||||
cinematic_voice.stop()
|
||||
|
||||
97
src/Menu/InstructionsScreen.py
Normal file
97
src/Menu/InstructionsScreen.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import pygame
|
||||
import math
|
||||
from src.Menu.BackgroundManager import BackgroundManager
|
||||
|
||||
|
||||
class InstructionsScreen:
|
||||
def __init__(self, game_resources):
|
||||
self.game_resources = game_resources
|
||||
self.bg_manager = BackgroundManager(game_resources.WIDTH, game_resources.HEIGHT)
|
||||
|
||||
self.title_font = pygame.font.SysFont("Arial", 72)
|
||||
self.text_font = pygame.font.SysFont("Arial", 32)
|
||||
|
||||
self.blink_timer = 0
|
||||
self.blink_speed = 0.5
|
||||
|
||||
def draw(self, surface):
|
||||
self.bg_manager.draw(surface)
|
||||
|
||||
def render_text_with_outline(text, font, text_color, outline_color):
|
||||
text_surface = font.render(text, True, text_color)
|
||||
outline_surface = font.render(text, True, outline_color)
|
||||
|
||||
w, h = text_surface.get_size()
|
||||
outline_surf = pygame.Surface((w + 2, h + 2), pygame.SRCALPHA)
|
||||
|
||||
# Dessiner le contour en décalant le texte
|
||||
offsets = [
|
||||
(1, 1),
|
||||
(1, -1),
|
||||
(-1, 1),
|
||||
(-1, -1),
|
||||
(1, 0),
|
||||
(-1, 0),
|
||||
(0, 1),
|
||||
(0, -1),
|
||||
]
|
||||
for dx, dy in offsets:
|
||||
outline_surf.blit(outline_surface, (dx + 1, dy + 1))
|
||||
|
||||
# Dessiner le texte principal au centre
|
||||
outline_surf.blit(text_surface, (1, 1))
|
||||
return outline_surf
|
||||
|
||||
title_surf = render_text_with_outline(
|
||||
"Game control", self.title_font, (255, 255, 255), (0, 0, 0)
|
||||
)
|
||||
title_rect = title_surf.get_rect(center=(self.game_resources.WIDTH // 2, 100))
|
||||
surface.blit(title_surf, title_rect)
|
||||
|
||||
instructions = [
|
||||
"Q : Move left",
|
||||
"D : Move right",
|
||||
"A : Dash",
|
||||
"Espace : Jump",
|
||||
"V: Attack",
|
||||
"Escape : Pause / Menu",
|
||||
"Controller : Use the left joystick to move",
|
||||
"B: Dash",
|
||||
"A: Jump",
|
||||
"X: Attack",
|
||||
"Y: Pause / Menu",
|
||||
]
|
||||
|
||||
y_offset = 180
|
||||
line_spacing = 40
|
||||
|
||||
for line in instructions:
|
||||
text_surf = render_text_with_outline(
|
||||
line, self.text_font, (255, 255, 255), (0, 0, 0)
|
||||
)
|
||||
text_rect = text_surf.get_rect(
|
||||
center=(self.game_resources.WIDTH // 2, y_offset)
|
||||
)
|
||||
surface.blit(text_surf, text_rect)
|
||||
y_offset += line_spacing
|
||||
|
||||
self.blink_timer += 0.01
|
||||
alpha = abs(math.sin(self.blink_timer * self.blink_speed)) * 255
|
||||
|
||||
skip_text = render_text_with_outline(
|
||||
"Press any key to continue", self.text_font, (255, 220, 0), (0, 0, 0)
|
||||
)
|
||||
skip_text.set_alpha(int(alpha))
|
||||
skip_rect = skip_text.get_rect(
|
||||
center=(self.game_resources.WIDTH // 2, self.game_resources.HEIGHT - 100)
|
||||
)
|
||||
surface.blit(skip_text, skip_rect)
|
||||
|
||||
def handle_event(self, event):
|
||||
if (
|
||||
event.type == pygame.KEYDOWN
|
||||
or event.type == pygame.MOUSEBUTTONDOWN
|
||||
or event.type == pygame.JOYBUTTONDOWN
|
||||
):
|
||||
return "menu"
|
||||
return None
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -20,7 +20,7 @@ class GameResources:
|
||||
icon = pygame.image.load("assets/player/Sanic Head.png")
|
||||
pygame.display.set_icon(icon)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du chargement de l'icône: {e}")
|
||||
print(f"Error loading icons: {e}")
|
||||
|
||||
# Ressources
|
||||
self.platforms = pygame.sprite.Group()
|
||||
@@ -28,7 +28,7 @@ class GameResources:
|
||||
self.exits = pygame.sprite.Group()
|
||||
self.vec = pygame.math.Vector2
|
||||
self.displaysurface = pygame.display.set_mode(
|
||||
(self.WIDTH, self.HEIGHT), pygame.RESIZABLE
|
||||
(self.WIDTH, self.HEIGHT), pygame.RESIZABLE, vsync=1
|
||||
)
|
||||
pygame.display.set_caption("Project Sanic")
|
||||
self.FramePerSec = pygame.time.Clock()
|
||||
|
||||
@@ -61,6 +61,14 @@ def initialize_game(game_resources, map_file="map/levels/1.json"):
|
||||
for exit_obj in exits:
|
||||
exit_obj.set_player(map_objects["player"])
|
||||
|
||||
enemies = map_objects.get("enemies", None)
|
||||
if exits and enemies:
|
||||
for enemy in enemies:
|
||||
if hasattr(enemy, "enemy_type") and enemy.enemy_type == "boss":
|
||||
for exit_obj in exits:
|
||||
exit_obj.set_boss(enemy)
|
||||
break
|
||||
|
||||
background = map_objects.get("background", None)
|
||||
|
||||
# If no background is found, use a default black background
|
||||
|
||||
116
src/handler.py
116
src/handler.py
@@ -23,6 +23,7 @@ from src.Database.CheckpointDB import CheckpointDB
|
||||
from src.Map.Editor.LevelEditor import LevelEditor
|
||||
from src.Menu.LevelEditorSelectionMenu import LevelEditorSelectionMenu
|
||||
from src.Map.Speedrun.SpeedrunTimer import SpeedrunTimer
|
||||
from src.Menu.InstructionsScreen import InstructionsScreen
|
||||
|
||||
|
||||
def initialize_game_resources():
|
||||
@@ -63,8 +64,9 @@ def initialize_game_resources():
|
||||
projectiles = pygame.sprite.Group()
|
||||
|
||||
# Game states initialization
|
||||
current_state = 0 # MENU
|
||||
current_state = 5 # INSTRUCTIONS
|
||||
current_menu = "main"
|
||||
instructions_screen = InstructionsScreen(game_resources)
|
||||
main_menu = Menu(game_resources)
|
||||
level_select_menu = None
|
||||
editor_select_menu = None
|
||||
@@ -99,6 +101,7 @@ def initialize_game_resources():
|
||||
joysticks,
|
||||
editor_select_menu,
|
||||
leaderboard_db,
|
||||
instructions_screen,
|
||||
)
|
||||
|
||||
|
||||
@@ -134,6 +137,16 @@ def handle_system_events(
|
||||
)
|
||||
# Update window dimensions
|
||||
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
|
||||
elif event.type == pygame.JOYBUTTONDOWN:
|
||||
try:
|
||||
if event.button == 4: # Triangle sur la plupart des manettes
|
||||
if current_state in [1, 2]: # PLAYING, INFINITE
|
||||
current_state = 0 # MENU
|
||||
else:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
except Exception as e:
|
||||
print(f"Error while handling joystick button: {e}")
|
||||
|
||||
return current_state, fullscreen, displaysurface, ORIGINAL_WIDTH, ORIGINAL_HEIGHT
|
||||
|
||||
@@ -393,10 +406,16 @@ def draw_background(displaysurface, background, camera, WIDTH, HEIGHT):
|
||||
if background.get_width() != bg_width or background.get_height() != bg_height:
|
||||
background = pygame.transform.scale(background, (bg_width, bg_height))
|
||||
|
||||
# Parallax effect (smaller factor makes background move slower)
|
||||
# Parallax effect
|
||||
parallax_factor = 0.3
|
||||
bg_x = -(camera.camera.x * parallax_factor) % bg_width
|
||||
bg_y = -(camera.camera.y * parallax_factor) % bg_height
|
||||
|
||||
bg_x = (camera.camera.x * parallax_factor) % bg_width
|
||||
bg_y = (camera.camera.y * parallax_factor) % bg_height
|
||||
|
||||
if bg_x > 0:
|
||||
bg_x -= bg_width
|
||||
if bg_y > 0:
|
||||
bg_y -= bg_height
|
||||
|
||||
# Draw background in all directions to create seamless effect
|
||||
displaysurface.blit(background, (bg_x, bg_y))
|
||||
@@ -486,6 +505,9 @@ def draw_playing_state(
|
||||
collectible.on_collision()
|
||||
P1.collect_coin(displaysurface, speedrun_timer)
|
||||
|
||||
for text in P1.floating_texts:
|
||||
text.draw(displaysurface)
|
||||
|
||||
# Draw UI elements
|
||||
draw_ui_elements(displaysurface, P1, FramePerSec, font, speedrun_timer)
|
||||
|
||||
@@ -496,39 +518,43 @@ def handle_exits(P1, exits, game_resources, level_file, speedrun_timer=None):
|
||||
"""Handle collisions with level exits"""
|
||||
exits_hit = pygame.sprite.spritecollide(P1, exits, False) if exits else []
|
||||
for exit in exits_hit:
|
||||
if speedrun_timer and speedrun_timer.is_running:
|
||||
collected_coins = speedrun_timer.collected_items
|
||||
total_coins = speedrun_timer.total_items
|
||||
if not exit.locked:
|
||||
if speedrun_timer and speedrun_timer.is_running:
|
||||
collected_coins = speedrun_timer.collected_items
|
||||
total_coins = speedrun_timer.total_items
|
||||
|
||||
speedrun_timer.stop()
|
||||
speedrun_timer.save_time(collected_coins, total_coins)
|
||||
if hasattr(game_resources, "infinite_mode") and game_resources.infinite_mode:
|
||||
# Infinite mode: load the next level without going back to menu
|
||||
if hasattr(game_resources, "infinite_mode_db"):
|
||||
# Zeldo : add 100 points
|
||||
game_resources.infinite_mode_db.add_score("player", 100)
|
||||
# Add coins points also
|
||||
game_resources.infinite_mode_db.add_score("player", P1.coins * 10)
|
||||
result = handle_exit_collision(exit, game_resources, level_file)
|
||||
return {"action": "continue_infinite", "result": result}
|
||||
else:
|
||||
# Normal mode: unlock the next level and return to menu
|
||||
current_level_match = re.search(r"(\d+)\.json$", level_file)
|
||||
if current_level_match:
|
||||
current_level = int(current_level_match.group(1))
|
||||
next_level = current_level + 1
|
||||
speedrun_timer.stop()
|
||||
speedrun_timer.save_time(collected_coins, total_coins)
|
||||
if (
|
||||
hasattr(game_resources, "infinite_mode")
|
||||
and game_resources.infinite_mode
|
||||
):
|
||||
# Infinite mode: load the next level without going back to menu
|
||||
if hasattr(game_resources, "infinite_mode_db"):
|
||||
# Zeldo : add 100 points
|
||||
game_resources.infinite_mode_db.add_score("player", 100)
|
||||
# Add coins points also
|
||||
game_resources.infinite_mode_db.add_score("player", P1.coins * 10)
|
||||
result = handle_exit_collision(exit, game_resources, level_file)
|
||||
return {"action": "continue_infinite", "result": result}
|
||||
else:
|
||||
# Normal mode: unlock the next level and return to menu
|
||||
current_level_match = re.search(r"(\d+)\.json$", level_file)
|
||||
if current_level_match:
|
||||
current_level = int(current_level_match.group(1))
|
||||
next_level = current_level + 1
|
||||
|
||||
# Unlock next level
|
||||
db = LevelDB()
|
||||
db.unlock_level(next_level)
|
||||
db.close()
|
||||
# Unlock next level
|
||||
db = LevelDB()
|
||||
db.unlock_level(next_level)
|
||||
db.close()
|
||||
|
||||
# Return to level select menu
|
||||
return {
|
||||
"action": "return_to_level_select",
|
||||
"current_state": 0, # MENU
|
||||
"current_menu": "level_select",
|
||||
}
|
||||
# Return to level select menu
|
||||
return {
|
||||
"action": "return_to_level_select",
|
||||
"current_state": 0, # MENU
|
||||
"current_menu": "level_select",
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
@@ -611,7 +637,7 @@ def handle_death_screen(
|
||||
game_resources.infinite_mode_db.clear_InfiniteModeDB()
|
||||
game_resources.infinite_mode_db.close()
|
||||
# Calculate total points, add them to leaderboard table
|
||||
if(leaderboard_db):
|
||||
if leaderboard_db:
|
||||
total = 0
|
||||
for i in range(len(all_scores)):
|
||||
total += all_scores[i][1]
|
||||
@@ -632,7 +658,7 @@ def handle_death_screen(
|
||||
def handler():
|
||||
"""Main function that handles the game flow"""
|
||||
# Game state constants
|
||||
MENU, PLAYING, INFINITE, LEADERBOARD, DEATH_SCREEN = 0, 1, 2, 3, 4
|
||||
MENU, PLAYING, INFINITE, LEADERBOARD, DEATH_SCREEN, INSTRUCTIONS = 0, 1, 2, 3, 4, 5
|
||||
previous_state = None
|
||||
|
||||
# Initialize game resources and states
|
||||
@@ -654,6 +680,7 @@ def handler():
|
||||
joysticks,
|
||||
editor_select_menu,
|
||||
leaderboard_db,
|
||||
instructions_screen,
|
||||
) = initialize_game_resources()
|
||||
|
||||
# Initialize editor variables
|
||||
@@ -674,6 +701,7 @@ def handler():
|
||||
print(f"Error while getting events: {e}")
|
||||
pygame.joystick.quit()
|
||||
pygame.joystick.init()
|
||||
events = []
|
||||
continue
|
||||
|
||||
# Process events
|
||||
@@ -753,6 +781,13 @@ def handler():
|
||||
game_resources
|
||||
)
|
||||
|
||||
elif current_state == INSTRUCTIONS:
|
||||
for event in events:
|
||||
result = instructions_screen.handle_event(event)
|
||||
if result == "menu":
|
||||
current_state = MENU
|
||||
instructions_screen.draw(displaysurface)
|
||||
|
||||
# Process general game events (player death, projectiles, etc.)
|
||||
if event.type == USEREVENT:
|
||||
current_state, death_timer, checkpoint_data, projectiles = (
|
||||
@@ -898,7 +933,7 @@ def handler():
|
||||
game_resources,
|
||||
game_resources.WIDTH,
|
||||
game_resources.HEIGHT,
|
||||
leaderboard_db
|
||||
leaderboard_db,
|
||||
)
|
||||
|
||||
death_timer = death_result["death_timer"]
|
||||
@@ -916,6 +951,13 @@ def handler():
|
||||
elif death_result["action"] == "return_to_menu":
|
||||
current_state = death_result["current_state"]
|
||||
|
||||
elif current_state == INSTRUCTIONS:
|
||||
for event in events:
|
||||
result = instructions_screen.handle_event(event)
|
||||
if result == "menu":
|
||||
current_state = MENU
|
||||
instructions_screen.draw(displaysurface)
|
||||
|
||||
# Update display
|
||||
pygame.display.update()
|
||||
game_resources.FramePerSec.tick(game_resources.FPS)
|
||||
|
||||
Reference in New Issue
Block a user