Conversation
|
Play this branch at https://play.threadbare.game/branches/endlessm/abilities-prototypes/. (This launches the game from the start, not directly at the change(s) in this pull request.) |
31d9bd2 to
2b85c96
Compare
12a60c1 to
fd2f9c8
Compare
fd2f9c8 to
a7e99a6
Compare
Similar to the "hookable" layer. To make the repel mechanic a bit more generic. Update the enum and the references in eldrune StoryQuest.
Otherwise adding a RigidBody2D to the scene, the bodies go down until they hit a wall. Setting the default gravity to zero, the object looks like it's on the ground.
That has a got_repelled() method. So far we've been assuming a Projectile node for the on_body_entered handler of the Repel area. But the Area2D.body_entered signal is sent with the body: Node2D parameter (which in reality can be a PhysicsBody2D or a TileMap). So use duck typing and also rename the expected method to "got_repelled". Also, call it with the direction of the repel. Previously the Player was passed and the repel direction was calculated on the called node. This makes the repel ability more generic. Also adapt the eldrune_projectile.gd script.
So the action can be used in other entities of the game, like in NPCs. Export a player controlled boolean for that same reason. Move all animation tracks related to the repel action, except the player animation, to its own AnimationPlayer. These two animations (the player moving the button and the repel air stream growing) should be kept in sync.
Add a test gym scene with multiple things to repel: - A box that can be pushed along a grid. - Bouncy ball. - A lever that can be swithed in the repelled direction.
a7e99a6 to
62a9980
Compare
|
This makes the repel a separate component that is also more generic. Any physic body can become repellable, using duck-typing for the called method and being in the corresponding layer. This PR comes with a gym scene and some custom repellable objects. But is up to the learners to come up with more interesting ones. recording.webm |
|
|
||
| [physics] | ||
|
|
||
| 2d/default_gravity=0.0 |
There was a problem hiding this comment.
Without changing this default, here is what happens when RigidBody2D are added to a scene:
recording.webm
| func got_repelled(direction: Vector2) -> void: | ||
| var sign_x := signf(direction.x) | ||
| var new_side: Enums.LookAtSide | ||
| if sign_x == 1: | ||
| new_side = Enums.LookAtSide.RIGHT | ||
| elif sign_x == -1: | ||
| new_side = Enums.LookAtSide.LEFT | ||
| if new_side == lever_side: | ||
| shaker.shake() | ||
| else: | ||
| lever_side = new_side |
There was a problem hiding this comment.
It's very unlikely but possible that sign_x could be 0. I don't think the handling is correct in this case: everything else in this file assumes that the only possibilities are LEFT and RIGHT but here it will be set to UNSPECIFIED in that unlikely case.
Since there are only 2 valid positions for the lever - left and right - and we hardcode that right is on, I think it would be simpler & less bug-prone to instead store a bool. What do you think?
| # SPDX-License-Identifier: MPL-2.0 | ||
| extends StaticBody2D | ||
|
|
||
| ## Emitted when the scene starts, indicating the initial state of this unlocker. |
There was a problem hiding this comment.
| ## Emitted when the scene starts, indicating the initial state of this unlocker. | |
| ## Emitted when the scene starts, indicating the initial state of this lever. |
| var axis := get_closest_axis(direction) | ||
| var neighbor := NEIGHBORS_FOR_AXIS[axis] |
There was a problem hiding this comment.
Same bug here - it's unlikely but possible that axis is Vector2i(0, 0) in which case this line will crash because there is no such key in NEIGHBORS_FOR_AXIS. You could just return early in that case.
| Vector2i.RIGHT: TileSet.CELL_NEIGHBOR_RIGHT_SIDE, | ||
| } | ||
|
|
||
| @export var constrain_layer: TileMapLayer |
There was a problem hiding this comment.
This is a really clever design. I wish I'd thought of this!
| func tile_coordinate_to_global_position(coord: Vector2i) -> Vector2: | ||
| return constrain_layer.map_to_local(coord) |
There was a problem hiding this comment.
This doesn't return a global position, it returns a local position relative to constrain_layer. Either rename the function or change this to:
| func tile_coordinate_to_global_position(coord: Vector2i) -> Vector2: | |
| return constrain_layer.map_to_local(coord) | |
| func tile_coordinate_to_global_position(coord: Vector2i) -> Vector2: | |
| return constrain_layer.to_global(constrain_layer.map_to_local(coord)) |
| func get_closest_axis(vector: Vector2) -> Vector2i: | ||
| if abs(vector.x) > abs(vector.y): | ||
| # Closer to Horizontal (X-axis) | ||
| return Vector2i(sign(vector.x), 0) | ||
|
|
||
| # Closer to Vertical (Y-axis) | ||
| return Vector2i(0, sign(vector.y)) |
There was a problem hiding this comment.
I always feel like there must be a built-in way to do this but I can never find it.
| var data := constrain_layer.get_cell_tile_data(new_coord) | ||
|
|
||
| if not data: |
There was a problem hiding this comment.
You could also spell this as:
if constrain_layer.get_cell_source_id(new_coord) == -1:This has a very slightly different meaning because in the case where TileMapLayer.tile_map_data specifies a tile for the cell, but the source ID doesn't exist in the TileSet, using get_cell_source_id will give you a non-negative source ID and so the code would allow the box to move there, while get_cell_tile_data will log a warning then return null and so the code would not allow the box to move there. Kind of moot.
|
|
||
| var tween: Tween | ||
|
|
||
| @onready var shaker: Shaker = $Shaker |


The repel mechanic was initially intended for the ink blobs, then to any projectile. But we can now consider a repel action that affects more game elements.
Physics layers: Rename projectile to repellable
Similar to the "hookable" layer. To make the repel mechanic a bit more generic.
Update the enum and the references in eldrune StoryQuest.
Project: Set default gravity to zero
Otherwise adding a RigidBody2D to the scene, the bodies go down
until they hit a wall.
Setting the default gravity to zero, the object looks like it's on
the ground.
Repel mechanic: Make it work with any Node2D
That has a got_repelled() method.
So far we've been assuming a Projectile node for the on_body_entered handler of the Repel area.
But the Area2D.body_entered signal is sent with the body: Node2D parameter (which in reality
can be a PhysicsBody2D or a TileMap).
So use duck typing and also rename the expected method to "got_repelled". Also, call it
with the direction of the repel. Previously the Player was passed and the repel direction
was calculated on the called node.
This makes the repel ability more generic.
Also adapt the eldrune_projectile.gd script.
Player repel: Split to its own scene
So the action can be used in other entities of the game, like in NPCs. Export a player controlled boolean
for that same reason.
Move all animation tracks related to the repel action, except the player animation, to its own AnimationPlayer.
These two animations (the player moving the button and the repel air stream growing) should be kept in sync.
Add examples of custom repellable objects
Add a test gym scene with multiple things to repel:
Resolve #2059