344 lines
12 KiB
Python
344 lines
12 KiB
Python
import pygame
|
|
import math
|
|
import random
|
|
from dataclasses import dataclass
|
|
from scripts.animation import Animation
|
|
from scripts.particle import Particle
|
|
from scripts.spark import Spark
|
|
from scripts.tilemap import Tilemap
|
|
|
|
|
|
@dataclass
|
|
class Collisions:
|
|
top: bool = False
|
|
right: bool = False
|
|
bottom: bool = False
|
|
left: bool = False
|
|
|
|
|
|
class PhysicsEntity:
|
|
def __init__(
|
|
self, game, e_type: str, pos: tuple[float, float], size: tuple[int, int]
|
|
):
|
|
self.game = game
|
|
self.e_type: str = e_type
|
|
self.pos: list[float] = list(pos)
|
|
self.size: tuple[int, int] = size
|
|
self.velocity: list[float] = [0, 0]
|
|
self.collisions: Collisions = Collisions()
|
|
self.speed: int = 3
|
|
|
|
# Animationen
|
|
self.action: str = ""
|
|
self.flip: bool = False
|
|
self.animation_offset: tuple[int, int] = (-3, -3)
|
|
self.animation: Animation
|
|
self.set_action("idle")
|
|
self.last_movement = [0, 0]
|
|
|
|
def update(self, tilemap: Tilemap, movement: tuple[float, float] = (0, 0)) -> None:
|
|
self.collisions = Collisions()
|
|
frame_movement: tuple[float, float] = (
|
|
(movement[0] + self.velocity[0]),
|
|
movement[1] + self.velocity[1],
|
|
)
|
|
self.pos[0] += frame_movement[0]
|
|
|
|
entity_rect = self.rect()
|
|
for recto in tilemap.physics_rects_around(self.pos):
|
|
if entity_rect.colliderect(recto):
|
|
if frame_movement[0] > 0:
|
|
entity_rect.right = recto.left
|
|
self.collisions.right = True
|
|
self.game.isJumping = False
|
|
|
|
if frame_movement[0] < 0:
|
|
entity_rect.left = recto.right
|
|
self.collisions.left = True
|
|
self.game.isJumping = False
|
|
|
|
self.pos[0] = entity_rect.x
|
|
|
|
self.pos[1] += frame_movement[1]
|
|
entity_rect = self.rect()
|
|
|
|
for rectolino in tilemap.physics_rects_around(self.pos):
|
|
if entity_rect.colliderect(rectolino):
|
|
if frame_movement[1] > 0:
|
|
entity_rect.bottom = rectolino.top
|
|
self.collisions.bottom = True
|
|
self.game.isJumping = False
|
|
|
|
if frame_movement[1] < 0:
|
|
entity_rect.top = rectolino.bottom
|
|
self.collisions.top = True
|
|
|
|
self.pos[1] = entity_rect.y
|
|
|
|
if movement[0] > 0:
|
|
self.flip = False
|
|
if movement[0] < 0:
|
|
self.flip = True
|
|
self.last_movement = movement
|
|
|
|
if self.collisions.bottom or self.collisions.top:
|
|
self.velocity[1] = 0
|
|
|
|
self.velocity[1] = min(5, self.velocity[1] + 0.1)
|
|
|
|
self.animation.update()
|
|
|
|
def render(self, surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None:
|
|
surface.blit(
|
|
pygame.transform.flip(self.animation.img(), self.flip, False),
|
|
(
|
|
self.pos[0] - offset[0] + self.animation_offset[0],
|
|
self.pos[1] - offset[1] + self.animation_offset[1],
|
|
),
|
|
)
|
|
|
|
def rect(self) -> pygame.Rect:
|
|
return pygame.Rect(self.pos[0], self.pos[1], self.size[0], self.size[1])
|
|
|
|
def set_action(self, action: str) -> None:
|
|
if action != self.action:
|
|
self.action = action
|
|
image = self.e_type + "/" + self.action
|
|
self.animation = self.game.assets[image].copy()
|
|
|
|
|
|
class Enemy(PhysicsEntity):
|
|
def __init__(self, game, pos: tuple[float, float], size: tuple[int, int]):
|
|
super().__init__(game, "enemy", pos, size)
|
|
|
|
self.walking = 0
|
|
|
|
def update(self, tilemap, movement: tuple[float, float] = (0, 0)):
|
|
if self.walking:
|
|
if tilemap.solid_check(
|
|
(self.rect().centerx + (-7 if self.flip else 7), self.pos[1] + 23)
|
|
):
|
|
if self.collisions.right or self.collisions.left:
|
|
self.flip = not self.flip
|
|
else:
|
|
movement = (movement[0] - 0.5 if self.flip else 0.5, movement[1])
|
|
else:
|
|
self.flip = not self.flip
|
|
|
|
self.walking = max(0, self.walking - 1)
|
|
|
|
if not self.walking:
|
|
distance = (
|
|
self.game.player.pos[0] - self.pos[0],
|
|
self.game.player.pos[1] - self.pos[1],
|
|
)
|
|
if abs(distance[1]) < 16:
|
|
if self.flip and distance[0] < 0:
|
|
self.game.projectiles.append(
|
|
[[self.rect().centerx - 7, self.rect().centery], -1.5, 0]
|
|
)
|
|
for _ in range(4):
|
|
self.game.sparks.append(
|
|
Spark(
|
|
self.game.projectiles[-1][0],
|
|
random.random() - 0.5 + math.pi,
|
|
random.random(),
|
|
)
|
|
)
|
|
if not self.flip and distance[0] > 0:
|
|
self.game.projectiles.append(
|
|
[[self.rect().centerx + 7, self.rect().centery], 1.5, 0]
|
|
)
|
|
for _ in range(4):
|
|
self.game.sparks.append(
|
|
Spark(
|
|
self.game.projectiles[-1][0],
|
|
random.random() - 0.5,
|
|
random.random(),
|
|
)
|
|
)
|
|
|
|
elif random.random() < 0.02:
|
|
self.walking = random.randint(30, 120)
|
|
|
|
super().update(tilemap, movement)
|
|
|
|
if movement[0] != 0:
|
|
self.set_action("run")
|
|
else:
|
|
self.set_action("idle")
|
|
|
|
if abs(self.game.player.dashing) >= 50:
|
|
if self.rect().colliderect(self.game.player.rect()):
|
|
self.game.screenshake = max(16, self.game.screenshake)
|
|
for _ in range(30):
|
|
angle = random.random() * math.pi * 2
|
|
speed = random.random() * 5
|
|
self.game.sparks.append(Spark(self.rect().center, angle, 2 + random.random()))
|
|
self.game.particles.append(Particle(self.game, 'particle', self.rect().center, velocity=[math.cos(angle + math.pi) * speed * 0.5, math.sin(angle + math.pi) * speed * 0.5], frame=random.randint(0, 7)))
|
|
|
|
self.game.sparks.append(Spark(self.rect().center, 0, 5 + random.random()))
|
|
self.game.sparks.append(Spark(self.rect().center, math.pi, 5 + random.random()))
|
|
return True
|
|
|
|
def render(self, surface: pygame.Surface, offset: tuple = (0, 0)):
|
|
super().render(surface, offset)
|
|
|
|
if self.flip:
|
|
surface.blit(
|
|
pygame.transform.flip(self.game.assets["gun"], True, False),
|
|
(
|
|
self.rect().centerx
|
|
- 4
|
|
- self.game.assets["gun"].get_width()
|
|
- offset[0],
|
|
self.rect().centery - offset[1],
|
|
),
|
|
)
|
|
else:
|
|
surface.blit(
|
|
self.game.assets["gun"],
|
|
(self.rect().centerx + 4 - offset[0], self.rect().centery - offset[1]),
|
|
)
|
|
|
|
class Player(PhysicsEntity):
|
|
def __init__(self, game, pos: tuple[float, float], size: tuple[int, int]):
|
|
super().__init__(game, "player", pos, size)
|
|
self.air_time: int = 0 # Neu! Zählt, wie lange wir fallen/springen
|
|
self.jumps: int = 0
|
|
self.wall_slide: bool = False
|
|
self.dashing: int = 0
|
|
|
|
def update(self, tilemap: Tilemap, movement: tuple[float, float] = (0, 0)) -> None:
|
|
super().update(tilemap, movement)
|
|
|
|
self.air_time += 1
|
|
|
|
if self.air_time > 120:
|
|
if not self.game.dead:
|
|
self.game.screenshake = max(16, self.game.screenshake)
|
|
self.game.dead += 1
|
|
|
|
if self.collisions.bottom:
|
|
self.air_time = 0
|
|
self.jumps = 1
|
|
# Wenn wir länger als 4 Frames in der Luft sind -> Springen!
|
|
self.wall_slide = False
|
|
|
|
if (self.collisions.right or self.collisions.left) and self.air_time > 4:
|
|
self.wall_slide = True
|
|
self.velocity[1] = min(self.velocity[1], 0.5)
|
|
|
|
if self.collisions.right:
|
|
self.flip = False
|
|
else:
|
|
self.flip = True
|
|
|
|
self.set_action("wall_slide")
|
|
|
|
if not self.wall_slide:
|
|
if self.air_time > 4:
|
|
self.set_action("jump")
|
|
|
|
# Wenn wir nicht in der Luft sind, aber uns bewegen -> Rennen!
|
|
elif movement[0] != 0:
|
|
self.set_action("run")
|
|
|
|
# Sonst -> Rumstehen!
|
|
else:
|
|
self.set_action("idle")
|
|
|
|
if abs(self.dashing) in {60, 50}:
|
|
for _ in range(20):
|
|
particle_angle = random.random() * math.pi * 2
|
|
particle_speed = random.random() * 0.5 + 0.5
|
|
particle_velocity = [
|
|
math.cos(particle_angle) * particle_speed,
|
|
math.sin(particle_angle) * particle_speed,
|
|
]
|
|
particle_pos = self.rect().center
|
|
particle_startframe = random.randint(0, 7)
|
|
self.game.particles.append(
|
|
Particle(
|
|
self.game,
|
|
"particle",
|
|
particle_pos,
|
|
particle_velocity,
|
|
particle_startframe,
|
|
)
|
|
)
|
|
|
|
if self.dashing > 0:
|
|
self.dashing = max(0, self.dashing - 1) # 60 → 59 → ... → 0
|
|
if self.dashing < 0:
|
|
self.dashing = min(0, self.dashing + 1) # -60 → -59 → ... → 0
|
|
|
|
if abs(self.dashing) > 50:
|
|
self.velocity[0] = abs(self.dashing) / self.dashing * 8
|
|
|
|
if abs(self.dashing) == 51:
|
|
self.velocity[0] *= 0.1
|
|
|
|
particle_velocity = [
|
|
abs(self.dashing) / self.dashing * random.random() * 3,
|
|
0,
|
|
]
|
|
|
|
self.game.particles.append(
|
|
Particle(
|
|
self.game,
|
|
"particle",
|
|
self.rect().center,
|
|
particle_velocity,
|
|
random.randint(0, 7),
|
|
)
|
|
)
|
|
|
|
if self.velocity[0] > 0:
|
|
self.velocity[0] = max(self.velocity[0] - 0.1, 0)
|
|
else:
|
|
self.velocity[0] = min(self.velocity[0] + 0.1, 0)
|
|
|
|
def jump(self) -> None | bool:
|
|
if self.wall_slide:
|
|
if self.flip and self.last_movement[0] < 0:
|
|
self.velocity[0] = 3.5
|
|
self.velocity[1] = -2.5
|
|
self.air_time = 5
|
|
self.jumps = max(0, self.jumps - 1)
|
|
|
|
elif not self.flip and self.last_movement[0] > 0:
|
|
self.velocity[0] = -3.5
|
|
self.velocity[1] = -2.5
|
|
self.air_time = 5
|
|
self.jumps = max(0, self.jumps - 1)
|
|
|
|
elif self.jumps > 0: # and not self.dashing
|
|
self.velocity[1] = -3
|
|
self.jumps -= 1
|
|
self.air_time = 5
|
|
return True
|
|
|
|
def boost(self, boosting: bool = False):
|
|
# if boosting:
|
|
# self.velocity[1] -= 5
|
|
# boost_particle = Particle(self.game, "boost", (self.pos[0] + self.size[0]//2, self.pos[1] + self.size[1]//2))
|
|
# self.game.particles.append(boost_particle)
|
|
self.velocity[1] -= 5
|
|
boost_particle = Particle(
|
|
self.game,
|
|
"boost",
|
|
(self.pos[0] + self.size[0] // 2, self.pos[1] + self.size[1] // 2),
|
|
)
|
|
self.game.particles.append(boost_particle)
|
|
|
|
def dash(self) -> None:
|
|
if not self.dashing:
|
|
if self.flip:
|
|
self.dashing = -60
|
|
else:
|
|
self.dashing = 60
|
|
|
|
def render(self, surface, offset=(0, 0)):
|
|
if abs(self.dashing) <= 50:
|
|
super().render(surface, offset)
|