TetrArcade/arcade/physics_engines.py

308 lines
12 KiB
Python

"""
Physics engines for top-down or platformers.
"""
# pylint: disable=too-many-arguments, too-many-locals, too-few-public-methods
from arcade.geometry import check_for_collision_with_list
from arcade.geometry import check_for_collision
from arcade.sprite import Sprite
from arcade.sprite_list import SpriteList
class PhysicsEngineSimple:
"""
This class will move everything, and take care of collisions.
"""
def __init__(self, player_sprite: Sprite, walls: SpriteList):
"""
Constructor.
"""
assert(isinstance(player_sprite, Sprite))
assert(isinstance(walls, SpriteList))
self.player_sprite = player_sprite
self.walls = walls
def update(self):
"""
Move everything and resolve collisions.
"""
# --- Move in the x direction
self.player_sprite.center_x += self.player_sprite.change_x
# Check for wall hit
hit_list = \
check_for_collision_with_list(self.player_sprite,
self.walls)
# If we hit a wall, move so the edges are at the same point
if len(hit_list) > 0:
if self.player_sprite.change_x > 0:
for item in hit_list:
self.player_sprite.right = min(item.left,
self.player_sprite.right)
elif self.player_sprite.change_x < 0:
for item in hit_list:
self.player_sprite.left = max(item.right,
self.player_sprite.left)
else:
print("Error, collision while player wasn't moving.")
# --- Move in the y direction
self.player_sprite.center_y += self.player_sprite.change_y
# Check for wall hit
hit_list = \
check_for_collision_with_list(self.player_sprite,
self.walls)
# If we hit a wall, move so the edges are at the same point
if len(hit_list) > 0:
if self.player_sprite.change_y > 0:
for item in hit_list:
self.player_sprite.top = min(item.bottom,
self.player_sprite.top)
elif self.player_sprite.change_y < 0:
for item in hit_list:
self.player_sprite.bottom = max(item.top,
self.player_sprite.bottom)
else:
print("Error, collision while player wasn't moving.")
class PhysicsEnginePlatformer:
"""
This class will move everything, and take care of collisions.
"""
def __init__(self,
player_sprite: Sprite,
platforms: SpriteList,
gravity_constant: float = 0.5,
ladders: SpriteList = None,
):
"""
Constructor.
"""
if ladders is not None and not isinstance(ladders, SpriteList):
raise TypeError("Fourth parameter should be a SpriteList of ladders")
self.player_sprite = player_sprite
self.platforms = platforms
self.gravity_constant = gravity_constant
self.jumps_since_ground = 0
self.allowed_jumps = 1
self.allow_multi_jump = False
self.ladders = ladders
def is_on_ladder(self):
# Check for touching a ladder
if self.ladders:
hit_list = check_for_collision_with_list(self.player_sprite, self.ladders)
if len(hit_list) > 0:
return True
return False
def can_jump(self, y_distance=5) -> bool:
"""
Method that looks to see if there is a floor under
the player_sprite. If there is a floor, the player can jump
and we return a True.
:returns: True if there is a platform below us
:rtype: bool
"""
# Check for touching a ladder
if self.is_on_ladder():
return False
# Move down to see if we are on a platform
self.player_sprite.center_y -= y_distance
# Check for wall hit
hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
self.player_sprite.center_y += y_distance
if len(hit_list) > 0:
self.jumps_since_ground = 0
if len(hit_list) > 0 or self.allow_multi_jump and self.jumps_since_ground < self.allowed_jumps:
return True
else:
return False
def enable_multi_jump(self, allowed_jumps: int):
"""
Enables multi-jump.
allowed_jumps should include the initial jump.
(1 allows only a single jump, 2 enables double-jump, etc)
If you enable multi-jump, you MUST call increment_jump_counter()
every time the player jumps. Otherwise they can jump infinitely.
:param int allowed_jumps:
"""
self.allowed_jumps = allowed_jumps
self.allow_multi_jump = True
def disable_multi_jump(self):
"""
Disables multi-jump.
Calling this function also removes the requirement to
call increment_jump_counter() every time the player jumps.
"""
self.allow_multi_jump = False
self.allowed_jumps = 1
self.jumps_since_ground = 0
def jump(self, velocity: int):
self.player_sprite.change_y = velocity
self.increment_jump_counter()
def increment_jump_counter(self):
"""
Updates the jump counter for multi-jump tracking
"""
if self.allow_multi_jump:
self.jumps_since_ground += 1
def update(self):
"""
Move everything and resolve collisions.
"""
# print(f"Spot A ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
# --- Add gravity if we aren't on a ladder
if not self.is_on_ladder():
self.player_sprite.change_y -= self.gravity_constant
# --- Move in the y direction
self.player_sprite.center_y += self.player_sprite.change_y
# Check for wall hit
hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
# If we hit a wall, move so the edges are at the same point
if len(hit_list) > 0:
if self.player_sprite.change_y > 0:
for item in hit_list:
self.player_sprite.top = min(item.bottom,
self.player_sprite.top)
# print(f"Spot X ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
elif self.player_sprite.change_y < 0:
# Reset number of jumps
for item in hit_list:
while check_for_collision(self.player_sprite, item):
# self.player_sprite.bottom = item.top <- Doesn't work for ramps
self.player_sprite.bottom += 0.25
if item.change_x != 0:
self.player_sprite.center_x += item.change_x
# print(f"Spot Y ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
else:
pass
# TODO: The code below can't execute, as "item" doesn't
# exist. In theory, this condition should never be arrived at.
# Collision while player wasn't moving, most likely
# moving platform.
# if self.player_sprite.center_y >= item.center_y:
# self.player_sprite.bottom = item.top
# else:
# self.player_sprite.top = item.bottom
self.player_sprite.change_y = min(0.0, hit_list[0].change_y)
# print(f"Spot B ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
self.player_sprite.center_y = round(self.player_sprite.center_y, 2)
# print(f"Spot Q ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
# --- Move in the x direction
self.player_sprite.center_x += self.player_sprite.change_x
check_again = True
while check_again:
check_again = False
# Check for wall hit
hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
# If we hit a wall, move so the edges are at the same point
if len(hit_list) > 0:
change_x = self.player_sprite.change_x
if change_x > 0:
for item in hit_list:
# print(f"Spot 1 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
# See if we can "run up" a ramp
self.player_sprite.center_y += change_x
if len(check_for_collision_with_list(self.player_sprite, self.platforms)) > 0:
self.player_sprite.center_y -= change_x
self.player_sprite.right = min(item.left, self.player_sprite.right)
# print(f"Spot R ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
check_again = True
break
# else:
# print("Run up ok 1")
# print(f"Spot 2 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
elif change_x < 0:
for item in hit_list:
# See if we can "run up" a ramp
self.player_sprite.center_y -= change_x
if len(check_for_collision_with_list(self.player_sprite, self.platforms)) > 0:
# Can't run up the ramp, reverse
self.player_sprite.center_y += change_x
self.player_sprite.left = max(item.right, self.player_sprite.left)
# print(f"Reverse 1 {item.right}, {self.player_sprite.left}")
# Ok, if we were shoved back to the right, we need to check this whole thing again.
check_again = True
break
# print(f"Spot 4 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
else:
print("Error, collision while player wasn't moving.\n"
"Make sure you aren't calling multiple updates, like "
"a physics engine update and an all sprites list update.")
# print(f"Spot E ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
for platform in self.platforms:
if platform.change_x != 0 or platform.change_y != 0:
platform.center_x += platform.change_x
if platform.boundary_left is not None \
and platform.left <= platform.boundary_left:
platform.left = platform.boundary_left
if platform.change_x < 0:
platform.change_x *= -1
if platform.boundary_right is not None \
and platform.right >= platform.boundary_right:
platform.right = platform.boundary_right
if platform.change_x > 0:
platform.change_x *= -1
if check_for_collision(self.player_sprite, platform):
if platform.change_x < 0:
self.player_sprite.right = platform.left
if platform.change_x > 0:
self.player_sprite.left = platform.right
platform.center_y += platform.change_y
if platform.boundary_top is not None \
and platform.top >= platform.boundary_top:
platform.top = platform.boundary_top
if platform.change_y > 0:
platform.change_y *= -1
if platform.boundary_bottom is not None \
and platform.bottom <= platform.boundary_bottom:
platform.bottom = platform.boundary_bottom
if platform.change_y < 0:
platform.change_y *= -1
# self.player_sprite.center_x = round(self.player_sprite.center_x, 2)
# print(f"Spot C ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
# print()