Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add some more features to when_touching and some fixes #44

Merged
merged 3 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions play/callback/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ class CallbackType(Enum):
WHEN_CLICK_RELEASED = 5
WHEN_CLICKED_SPRITE = 6
WHEN_TOUCHING = 7
WHEN_CONTROLLER_BUTTON_PRESSED = 8
WHEN_CONTROLLER_BUTTON_RELEASED = 9
WHEN_CONTROLLER_AXIS_MOVED = 10
WHEN_STOPPED_TOUCHING = 8
WHEN_TOUCHING_WALL = 9
WHEN_STOPPED_TOUCHING_WALL = 10
WHEN_CONTROLLER_BUTTON_PRESSED = 11
WHEN_CONTROLLER_BUTTON_RELEASED = 12
WHEN_CONTROLLER_AXIS_MOVED = 13


class CallbackManager:
Expand Down
144 changes: 22 additions & 122 deletions play/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Core game loop and event handling functions."""

import math as _math

import pygame # pylint: disable=import-error

from .game_loop_wrapper import listen_to_failure
from .mouse_loop import _handle_mouse_loop, mouse_state
from .sprites_loop import _update_sprites
from ..callback import callback_manager, CallbackType
from ..callback.callback_helpers import run_callback
from ..globals import backdrop, FRAME_RATE, sprites_group
from ..globals import backdrop, FRAME_RATE, _walls
from ..io import screen, PYGAME_DISPLAY, convert_pos
from ..io.keypress import (
key_num_to_name as _pygame_key_to_name,
Expand All @@ -16,9 +16,7 @@
_pressed_keys,
) # don't pollute user-facing namespace with library internals
from ..io.mouse import mouse
from ..objects.line import Line
from ..objects.sprite import point_touching_sprite
from ..physics import simulate_physics
from .physics_loop import simulate_physics
from ..utils import color_name_to_rgb as _color_name_to_rgb
from ..loop import loop as _loop
from .controller_loop import (
Expand All @@ -31,15 +29,9 @@

_clock = pygame.time.Clock()

click_happened_this_frame = False # pylint: disable=invalid-name
click_release_happened_this_frame = False # pylint: disable=invalid-name


def _handle_pygame_events():
"""Handle pygame events in the game loop."""
global click_happened_this_frame
global click_release_happened_this_frame

for event in pygame.event.get():
if event.type == pygame.QUIT or ( # pylint: disable=no-member
event.type == pygame.KEYDOWN # pylint: disable=no-member
Expand All @@ -53,10 +45,10 @@ def _handle_pygame_events():
_loop.stop()
return False
if event.type == pygame.MOUSEBUTTONDOWN: # pylint: disable=no-member
click_happened_this_frame = True
mouse_state.click_happened_this_frame = True
mouse._is_clicked = True
if event.type == pygame.MOUSEBUTTONUP: # pylint: disable=no-member
click_release_happened_this_frame = True
mouse_state.click_release_happened_this_frame = True
mouse._is_clicked = False
if event.type == pygame.MOUSEMOTION: # pylint: disable=no-member
mouse.x, mouse.y = (event.pos[0] - screen.width / 2.0), (
Expand Down Expand Up @@ -147,118 +139,13 @@ def _handle_keyboard():
)


def _handle_mouse_loop():
"""Handle mouse events in the game loop."""
####################################
# @mouse.when_clicked callbacks
####################################
if (
click_happened_this_frame
and callback_manager.get_callbacks(CallbackType.WHEN_CLICKED) is not None
):
for callback in callback_manager.get_callbacks(CallbackType.WHEN_CLICKED):
run_callback(
callback,
[],
[],
)

########################################
# @mouse.when_click_released callbacks
########################################
if (
click_release_happened_this_frame
and callback_manager.get_callbacks(CallbackType.WHEN_CLICK_RELEASED) is not None
):
for callback in callback_manager.get_callbacks(
CallbackType.WHEN_CLICK_RELEASED
):
run_callback(
callback,
[],
[],
)


def _update_sprites():
# pylint: disable=too-many-nested-blocks
sprites_group.update()
for sprite in sprites_group.sprites():
sprite._is_clicked = False
if sprite.is_hidden:
continue

######################################################
# update sprites with results of physics simulation
######################################################
if sprite.physics and sprite.physics.can_move:

body = sprite.physics._pymunk_body
angle = _math.degrees(body.angle)
if isinstance(sprite, Line):
sprite._x = body.position.x - (sprite.length / 2) * _math.cos(angle)
sprite._y = body.position.y - (sprite.length / 2) * _math.sin(angle)
sprite._x1 = body.position.x + (sprite.length / 2) * _math.cos(angle)
sprite._y1 = body.position.y + (sprite.length / 2) * _math.sin(angle)
# sprite._length, sprite._angle = sprite._calc_length_angle()
else:
if (
str(body.position.x) != "nan"
): # this condition can happen when changing sprite.physics.can_move
sprite._x = body.position.x
if str(body.position.y) != "nan":
sprite._y = body.position.y

sprite.angle = (
angle # needs to be .angle, not ._angle so surface gets recalculated
)
sprite.physics._x_speed, sprite.physics._y_speed = body.velocity

#################################
# @sprite.when_clicked events
#################################
if mouse.is_clicked:
if (
point_touching_sprite(convert_pos(mouse.x, mouse.y), sprite)
and click_happened_this_frame
):
# only run sprite clicks on the frame the mouse was clicked
sprite._is_clicked = True
if callback_manager.get_callback(
CallbackType.WHEN_CLICKED_SPRITE, id(sprite)
):
for callback in callback_manager.get_callback(
CallbackType.WHEN_CLICKED_SPRITE, id(sprite)
):
if not callback.is_running:
run_callback(
callback,
[],
[],
)

#################################
# @sprite.when_touching events
#################################
if sprite._active_callbacks:
for cb in sprite._active_callbacks:
run_callback(
cb,
[],
[],
)

sprites_group.draw(PYGAME_DISPLAY)


# pylint: disable=too-many-branches, too-many-statements
@listen_to_failure()
def game_loop():
"""The main game loop."""
_keys_released_this_frame.clear()
global click_happened_this_frame, click_release_happened_this_frame
click_happened_this_frame = False
click_release_happened_this_frame = False
mouse_state.click_happened_this_frame = False
mouse_state.click_release_happened_this_frame = False

_clock.tick(FRAME_RATE)

Expand All @@ -267,7 +154,10 @@ def game_loop():

_handle_keyboard()

if click_happened_this_frame or click_release_happened_this_frame:
if (
mouse_state.click_happened_this_frame
or mouse_state.click_release_happened_this_frame
):
_handle_mouse_loop()

_handle_controller()
Expand All @@ -294,5 +184,15 @@ def game_loop():
_update_sprites()

_loop.call_soon(game_loop)
for wall in _walls:
actual_rect = [*convert_pos(*wall.a), *convert_pos(*wall.b)]

actual_rect = pygame.Rect(
min(actual_rect[0], actual_rect[2]) - 2,
min(actual_rect[1], actual_rect[3]) - 2,
abs(actual_rect[0] - actual_rect[2]) + 6,
abs(actual_rect[1] - actual_rect[3]) + 6,
)
pygame.draw.rect(PYGAME_DISPLAY, (0, 0, 0), actual_rect, 2)
pygame.display.flip()
return True
45 changes: 45 additions & 0 deletions play/core/mouse_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""This module contains the mouse loop."""

from ..callback.callback_helpers import run_callback
from ..callback import callback_manager, CallbackType


class MouseState: # pylint: disable=too-few-public-methods
click_happened_this_frame = False # pylint: disable=invalid-name
click_release_happened_this_frame = False # pylint: disable=invalid-name


mouse_state = MouseState()


def _handle_mouse_loop():
"""Handle mouse events in the game loop."""
####################################
# @mouse.when_clicked callbacks
####################################
if (
mouse_state.click_happened_this_frame
and callback_manager.get_callbacks(CallbackType.WHEN_CLICKED) is not None
):
for callback in callback_manager.get_callbacks(CallbackType.WHEN_CLICKED):
run_callback(
callback,
[],
[],
)

########################################
# @mouse.when_click_released callbacks
########################################
if (
mouse_state.click_release_happened_this_frame
and callback_manager.get_callbacks(CallbackType.WHEN_CLICK_RELEASED) is not None
):
for callback in callback_manager.get_callbacks(
CallbackType.WHEN_CLICK_RELEASED
):
run_callback(
callback,
[],
[],
)
16 changes: 16 additions & 0 deletions play/core/physics_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""This module contains the function that simulates the physics of the game"""

from ..globals import FRAME_RATE
from ..physics import physics_space, _NUM_SIMULATION_STEPS
from .sprites_loop import _update_sprites


def simulate_physics():
"""
Simulate the physics of the game
"""
# more steps means more accurate simulation, but more processing time
for _ in range(_NUM_SIMULATION_STEPS):
# the smaller the simulation step, the more accurate the simulation
physics_space.step(1 / (FRAME_RATE * _NUM_SIMULATION_STEPS))
_update_sprites()
87 changes: 87 additions & 0 deletions play/core/sprites_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""This module contains the main loop for updating sprites and running their events."""

import math as _math

from play.globals import sprites_group
from .mouse_loop import mouse_state
from ..callback import callback_manager, CallbackType
from ..callback.callback_helpers import run_callback
from ..io import convert_pos, PYGAME_DISPLAY
from ..io.mouse import mouse

from ..objects.line import Line
from ..objects.sprite import point_touching_sprite


def _update_sprites(skip_user_events=False): # pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks
sprites_group.update()

for sprite in sprites_group.sprites():
#################################
# @sprite.when_touching events
#################################
if sprite._active_callbacks:
for cb in sprite._active_callbacks:
run_callback(
cb,
[],
[],
)
if skip_user_events:
continue

sprite._is_clicked = False
if sprite.is_hidden:
continue

######################################################
# update sprites with results of physics simulation
######################################################
if sprite.physics and sprite.physics.can_move:

body = sprite.physics._pymunk_body
angle = _math.degrees(body.angle)
if isinstance(sprite, Line):
sprite._x = body.position.x - (sprite.length / 2) * _math.cos(angle)
sprite._y = body.position.y - (sprite.length / 2) * _math.sin(angle)
sprite._x1 = body.position.x + (sprite.length / 2) * _math.cos(angle)
sprite._y1 = body.position.y + (sprite.length / 2) * _math.sin(angle)
# sprite._length, sprite._angle = sprite._calc_length_angle()
else:
if (
str(body.position.x) != "nan"
): # this condition can happen when changing sprite.physics.can_move
sprite._x = body.position.x
if str(body.position.y) != "nan":
sprite._y = body.position.y

sprite.angle = (
angle # needs to be .angle, not ._angle so surface gets recalculated
)
sprite.physics._x_speed, sprite.physics._y_speed = body.velocity

#################################
# @sprite.when_clicked events
#################################
if mouse.is_clicked:
if (
point_touching_sprite(convert_pos(mouse.x, mouse.y), sprite)
and mouse_state.click_happened_this_frame
):
# only run sprite clicks on the frame the mouse was clicked
sprite._is_clicked = True
if callback_manager.get_callback(
CallbackType.WHEN_CLICKED_SPRITE, id(sprite)
):
for callback in callback_manager.get_callback(
CallbackType.WHEN_CLICKED_SPRITE, id(sprite)
):
if not callback.is_running:
run_callback(
callback,
[],
[],
)

sprites_group.draw(PYGAME_DISPLAY)
Loading
Loading