PortalPlugin¶
Handles map transitions through an event-driven system integrated with the script plugin.
Location¶
- Implementation: src/pedre/plugins/portal/plugin.py
- Base class: src/pedre/plugins/portal/base.py
- Events: src/pedre/plugins/portal/events.py
Configuration¶
The PortalPlugin uses the following settings from pedre.conf.settings:
Portal Settings¶
PORTAL_INTERACTION_DISTANCE- Maximum distance in pixels for player to activate portals (default: 50)
This can be overridden in your project's settings.py:
Notes:
PORTAL_INTERACTION_DISTANCEdetermines how close the player must be to trigger a portal- Common values: 32 (1 tile), 50 (default, ~1.5 tiles), 64 (2 tiles), 96 (3 tiles)
- Uses Euclidean distance from player center to portal center
- Creates a circular activation zone around each portal
- Only fires when player enters the zone (transitions from outside to inside)
- Won't re-fire while player remains standing in the zone
Overview¶
The portal plugin uses an event-driven architecture where:
- Portals are registered from Tiled map data during map loading
- When the player enters a portal zone,
PortalEnteredEventis published - Scripts respond to the event and handle transitions via
change_sceneaction
This approach allows full flexibility: conditional portals, cutscenes before transitions, failure messages, and complex multi-step sequences.
Public API¶
Portal Registration¶
register_portal¶
register_portal(sprite: arcade.Sprite, name: str) -> None
Register a portal from Tiled map data.
Parameters:
sprite- The arcade Sprite representing the portal's location and collision areaname- Unique portal identifier (used in script triggers)
Example:
Notes:
- Portals must be registered before they can be activated
- Portal name should be unique within the map
- Usually called automatically by
load_from_tiled() - The sprite defines the physical location and activation area
Portal Checking¶
check_portals¶
check_portals(player_sprite: arcade.Sprite | None) -> None
Check if player is near any portal and publish events on entry.
Parameters:
player_sprite- The player's arcade Sprite for position checking
Example:
Notes:
- Called automatically by the plugin each frame via
update() - Events only fire when player enters a portal zone (transitions from outside to inside)
- Won't re-fire while player remains standing in the portal
- Uses
PORTAL_INTERACTION_DISTANCEsetting - Distance calculation uses Euclidean distance (straight-line)
- Publishes
PortalEnteredEventwhen player enters
clear¶
clear() -> None
Clear all registered portals.
Notes:
- Removes all portals from the plugin's registry
- Called automatically when changing maps
- Also clears the internal tracking of which portals player is inside
Plugin Lifecycle¶
setup¶
setup(context: GameContext) -> None
Initialize the portal plugin with game context.
Parameters:
context- Game context providing access to other plugins
Notes:
- Called automatically by PluginLoader
- Configures the plugin with event bus and settings
- Stores reference to game context
update¶
update(delta_time: float) -> None
Update portal plugin, checking for player entry.
Parameters:
delta_time- Time since last update in seconds
Notes:
- Called automatically by PluginLoader each frame
- Calls
check_portals()with current player sprite - Handles portal entry detection
cleanup¶
cleanup() -> None
Clean up portal resources when the scene unloads.
Notes:
- Clears all registered portals
- Resets tracking state
- Called automatically by PluginLoader
load_from_tiled¶
load_from_tiled(tile_map: arcade.TileMap, arcade_scene: arcade.Scene) -> None
Load portals from Tiled map object layer.
Parameters:
tile_map- The loaded Tiled maparcade_scene- The arcade Scene to add portals to
Notes:
- Called automatically by PluginLoader
- Looks for "Portals" object layer
- Creates portal sprites from object shapes
- Clears old portals before loading new ones
- Portal objects need a
nameproperty
Data Structures¶
Portal¶
Runtime data for a single portal.
Location: src/pedre/plugins/portal/base.py
Attributes:
sprite: arcade.Sprite- The portal's sprite representing location and collision areaname: str- Unique identifier for this portal (used in script triggers)
Example:
from pedre.plugins.portal.base import Portal
portal = Portal(
sprite=portal_sprite,
name="forest_entrance"
)
Notes:
- Portals are typically created automatically from Tiled map data
- The sprite defines the physical location and activation zone
- Portal name is used in script triggers to match specific portals
Events¶
PortalEnteredEvent¶
Published when player enters a portal zone.
Location: src/pedre/plugins/portal/events.py
Attributes:
portal_name: str- Name of the portal that was entered
Script Trigger Example:
{
"trigger": {
"event": "portal_entered",
"portal": "forest_gate"
},
"actions": [
{"type": "change_scene", "target_map": "Forest.tmx", "spawn_waypoint": "entrance"}
]
}
Notes:
- Fires when player enters the portal zone (not while standing in it)
- The
portalfilter is optional (omit to trigger for any portal) - Only triggers on entry (transitions from outside to inside)
- Won't re-fire until player leaves and re-enters the zone
- Uses Euclidean distance calculation with
PORTAL_INTERACTION_DISTANCE
Use Cases:
- Map transitions
- Conditional portal access (with conditions)
- Cutscenes before transitions
- Locked doors with failure messages
- Multi-step portal sequences
Script Integration¶
Portals are handled through scripts using the portal_entered event trigger and change_scene action.
Simple Portal¶
Tiled properties:
Script JSON:
{
"forest_entrance_portal": {
"trigger": {"event": "portal_entered", "portal": "forest_entrance"},
"actions": [
{"type": "change_scene", "target_map": "Forest.tmx", "spawn_waypoint": "forest_start"}
]
}
}
Conditional Portal¶
Portal that requires a condition to be met:
{
"tower_gate_open": {
"trigger": {"event": "portal_entered", "portal": "tower_gate"},
"conditions": [{"check": "npc_dialog_level", "npc": "guard", "gte": 2}],
"actions": [
{"type": "change_scene", "target_map": "Tower.tmx", "spawn_waypoint": "tower_entrance"}
]
},
"tower_gate_locked": {
"trigger": {"event": "portal_entered", "portal": "tower_gate"},
"conditions": [{"check": "npc_dialog_level", "npc": "guard", "lt": 2}],
"actions": [
{"type": "dialog", "speaker": "Narrator", "text": ["The gate is sealed. Perhaps the guard knows something..."]}
]
}
}
Portal with Cutscene¶
Portal that plays a cutscene on first entry:
{
"dungeon_cutscene": {
"trigger": {"event": "portal_entered", "portal": "dungeon_portal"},
"run_once": true,
"actions": [
{"type": "dialog", "speaker": "Narrator", "text": ["A cold wind blows from the depths..."]},
{"type": "wait_for_dialog_close"},
{"type": "play_sfx", "file": "wind.wav"},
{"type": "change_scene", "target_map": "Dungeon.tmx", "spawn_waypoint": "dungeon_entrance"}
]
},
"dungeon_return": {
"trigger": {"event": "portal_entered", "portal": "dungeon_portal"},
"conditions": [{"check": "script_completed", "script": "dungeon_cutscene"}],
"actions": [
{"type": "change_scene", "target_map": "Dungeon.tmx", "spawn_waypoint": "dungeon_entrance"}
]
}
}
Tiled Setup¶
- Create a "Portals" object layer in your Tiled map
- Add rectangle objects where you want portals
- Set the
nameproperty on each portal object
The portal name is used in script triggers to match specific portals:
Portal Behavior¶
Entry Detection¶
The portal plugin uses zone-based detection:
- Distance Check - Calculates Euclidean distance from player center to portal center
- Zone Entry - Player enters when distance <
PORTAL_INTERACTION_DISTANCE - Event Publishing -
PortalEnteredEventpublished on zone entry - Entry Tracking - Tracks which portals player is currently inside
- Exit Detection - Player exits when distance >=
PORTAL_INTERACTION_DISTANCE - Re-entry - Can re-fire if player leaves and returns
Distance Calculation¶
distance = arcade.get_distance_between_sprites(player_sprite, portal_sprite)
if distance < PORTAL_INTERACTION_DISTANCE:
# Player is in portal zone
Notes:
- Uses center points of both sprites
- Creates circular activation zone
- Works with portals of any size
- Distance is in pixels
Portal Lifecycle¶
- Registration - Portals loaded from Tiled during scene setup
- Checking - Distance checked every frame in
update() - Event Publishing - Events published on zone entry
- Script Execution - Scripts respond to events
- Cleanup - Portals cleared when scene unloads
Implementation Details¶
Portal Tracking¶
The plugin tracks portal entry state to prevent duplicate events:
# Internal tracking set
self._player_in_portals = set()
# Check if player just entered
if portal.name not in self._player_in_portals:
self._player_in_portals.add(portal.name)
# Publish event only on entry
Benefits:
- Events fire once per entry
- No spam while standing in portal
- Clean re-entry detection
- Minimal state tracking
Portal Loading from Tiled¶
Portals are registered during map loading:
for portal_object in portal_layer:
portal_sprite = arcade.Sprite()
portal_sprite.center_x = portal_object.shape.x
portal_sprite.center_y = portal_object.shape.y
self.register_portal(
sprite=portal_sprite,
name=portal_object.properties.get("name")
)
Requirements:
- Portal object must have
nameproperty - Portal sprite defines activation zone
- Usually rectangular objects in Tiled
Usage Examples¶
Basic Portal Transition¶
{
"go_to_forest": {
"trigger": {
"event": "portal_entered",
"portal": "forest_entrance"
},
"actions": [
{"type": "change_scene", "target_map": "forest.tmx", "spawn_waypoint": "entrance"}
]
}
}
Two-Way Portal¶
Create matching portals in both maps:
village.tmx:
{
"to_forest": {
"trigger": {"event": "portal_entered", "portal": "forest_gate"},
"actions": [
{"type": "change_scene", "target_map": "forest.tmx", "spawn_waypoint": "from_village"}
]
}
}
forest.tmx:
{
"to_village": {
"trigger": {"event": "portal_entered", "portal": "village_gate"},
"actions": [
{"type": "change_scene", "target_map": "village.tmx", "spawn_waypoint": "from_forest"}
]
}
}
Quest-Locked Portal¶
{
"castle_open": {
"trigger": {"event": "portal_entered", "portal": "castle_gate"},
"conditions": [
{"check": "npc_interacted", "npc": "king", "scene": "throne_room"}
],
"actions": [
{"type": "change_scene", "target_map": "castle_interior.tmx", "spawn_waypoint": "entrance"}
]
},
"castle_locked": {
"trigger": {"event": "portal_entered", "portal": "castle_gate"},
"conditions": [
{"check": "npc_interacted", "npc": "king", "scene": "throne_room", "equals": false}
],
"actions": [
{"type": "dialog", "speaker": "Guard", "text": ["The castle is closed to visitors."]}
]
}
}
Portal with Sound Effect¶
{
"enter_cave": {
"trigger": {"event": "portal_entered", "portal": "cave_entrance"},
"actions": [
{"type": "play_sfx", "file": "cave_echo.wav"},
{"type": "wait", "duration": 0.5},
{"type": "change_scene", "target_map": "cave.tmx", "spawn_waypoint": "entrance"}
]
}
}
Integration with Other Plugins¶
ScenePlugin Integration¶
The ScenePlugin handles map transitions:
# Portal triggers scene change via script action
context.scene_plugin.request_transition(
map_file="forest.tmx",
spawn_waypoint="entrance"
)
Notes:
- Portal event triggers script
- Script executes
change_sceneaction - ScenePlugin handles the actual transition
- Player spawns at specified waypoint
PlayerPlugin Integration¶
The PlayerPlugin provides player position:
# Portal plugin checks player position
player_sprite = context.player_plugin.get_player_sprite()
if player_sprite:
portal_plugin.check_portals(player_sprite)
Notes:
- Player must exist for portal checking
- Uses player sprite center position
- Checked every frame in update loop
ScriptPlugin Integration¶
Scripts handle portal responses:
# PortalEnteredEvent published
event = PortalEnteredEvent(portal_name="forest_gate")
context.event_bus.publish(event)
# Scripts listen and respond
# Execute actions (change_scene, dialog, etc.)
Notes:
- Portal plugin only publishes events
- Scripts define what happens
- Full flexibility for conditional behavior
- Can chain multiple actions
WaypointPlugin Integration¶
Waypoints define spawn positions:
# Scene transition with waypoint
{"type": "change_scene", "target_map": "castle.tmx", "spawn_waypoint": "main_gate"}
# Player spawns at waypoint location
waypoint = context.waypoint_plugin.get_waypoint("main_gate")
player.center_x = waypoint.x
player.center_y = waypoint.y
Notes:
- Waypoints must exist in target scene
- Falls back to default position if waypoint missing
- Enables precise spawn positioning
Troubleshooting¶
Portal Not Triggering¶
If portals don't activate when player walks over them:
- Check portal layer - Ensure Tiled map has "Portals" object layer
- Verify name property - Each portal object must have unique
nameproperty - Check distance setting - Increase
PORTAL_INTERACTION_DISTANCEif needed - Review scripts - Ensure script exists with matching portal name
- Test player position - Verify player sprite exists and has valid position
Portal Triggers Multiple Times¶
If portal events fire repeatedly:
- Check script conditions - Ensure conditions prevent re-triggering
- Use run_once - Add
"run_once": trueto script if it should only run once - Review exit detection - Player must fully exit zone before re-entry triggers
Portal Activates from Too Far¶
If portals trigger from unexpected distance:
- Reduce distance - Lower
PORTAL_INTERACTION_DISTANCEvalue - Check portal size - Portal sprite size affects center point
- Verify collision - Ensure portal sprite position is correct in Tiled
Scene Transition Not Working¶
If portal event fires but scene doesn't change:
- Check action - Verify script has
change_sceneaction - Verify map file - Ensure
target_mappath is correct - Check waypoint - Verify
spawn_waypointexists in target scene - Review logs - Look for scene loading errors
Custom Portal Implementation¶
If you need to replace the portal plugin with a custom implementation, you can extend the PortalBasePlugin abstract base class.
PortalBasePlugin¶
Location: src/pedre/plugins/portal/base.py
The PortalBasePlugin class defines the minimum interface that any portal plugin must implement.
Required Methods¶
Your custom portal plugin must implement these abstract methods:
from pedre.plugins.portal.base import PortalBasePlugin
class CustomPortalPlugin(PortalBasePlugin):
"""Custom portal implementation."""
name = "portal"
def register_portal(self, sprite: arcade.Sprite, name: str) -> None:
"""Register a portal."""
...
def check_portals(self, player_sprite: arcade.Sprite | None) -> None:
"""Check for portal activation."""
...
def clear(self) -> None:
"""Clear all portals."""
...
Registration¶
Register your custom portal plugin using the @PluginRegistry.register decorator:
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.portal.base import PortalBasePlugin
@PluginRegistry.register
class TriggerPortalPlugin(PortalBasePlugin):
"""Portal plugin with touch triggers instead of zones."""
name = "portal"
# ... implement all abstract methods ...
Notes on Custom Implementation¶
- Your custom plugin inherits from
BasePlugin(viaPortalBasePlugin), so you must implement the standard plugin lifecycle methods:setup(),cleanup(), andreset() - The
roleattribute is set to"portal_plugin"in the base class - Your implementation can use any detection system (zones, triggers, collisions)
- Register your custom portal plugin in your project's
INSTALLED_PLUGINSsetting before the default"pedre.plugins.portal"to replace it
Example Custom Implementation:
# In myproject/plugins/custom_portal.py
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.portal.base import PortalBasePlugin
@PluginRegistry.register
class CollisionPortalPlugin(PortalBasePlugin):
"""Portal plugin using collision detection instead of distance."""
name = "portal"
def __init__(self):
self.portals = []
# ... rest of initialization ...
def check_portals(self, player_sprite: arcade.Sprite | None) -> None:
if not player_sprite:
return
# Use collision detection instead of distance
for portal in self.portals:
if arcade.check_for_collision(player_sprite, portal.sprite):
# Publish event
event = PortalEnteredEvent(portal_name=portal.name)
self.context.event_bus.publish(event)
# ... implement other abstract methods ...
# In myproject/settings.py
INSTALLED_PLUGINS = [
"myproject.plugins.custom_portal", # Load custom portal first
"pedre.plugins.camera",
"pedre.plugins.audio",
# ... rest of plugins (omit "pedre.plugins.portal") ...
]
See Also¶
- ScenePlugin - Map loading and transitions
- ScriptPlugin - Event-driven scripting
- Configuration Guide
- Events Reference
- Actions Reference
- Conditions Reference
- Tiled Integration