Ninja-Jump-and-run/scripts/entities.py
Benjamin 24418b851c END
2026-07-03 17:38:29 +02:00

351 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]
)
self.game.sfx["shoot"].play()
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]
)
self.game.sfx["shoot"].play()
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)
self.game.sfx["hit"].play()
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 and not self.wall_slide:
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.air_time = 5
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:
self.game.sfx["dash"].play()
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)