fishfolk / jumpy

Tactical 2D shooter in fishy pixels style. Made with Rust-lang 🦀 and Bevy 🪶
https://fishfolk.org/games/jumpy/
Other
1.64k stars 118 forks source link

feat: non-tile solids #903

Closed nelson137 closed 7 months ago

nelson137 commented 7 months ago

Implement collisions for non-tile solids and a way to define them for elements in a map's YAML. This makes the CollisionWorld account for entities with the Solid component.

I started out trying to implement a door for #43 but quickly realized I couldn't make a closed door solid. This is its own PR because I want to make sure I'm going in the right direction here and discuss the implementation.

https://github.com/fishfolk/jumpy/assets/25290530/3851d2b3-8f58-41c0-8462-9b641334d63a

Changes

Defining solids

Elements can be defined in each layer of a map's YAML file. Solids can now be defined on an element with a solid object.

Example element in a map layer:

  - pos: [630.0, 365.0]
    element: core:/elements/environment/demo_solid_box/demo_solid_box.element.yaml

Now, with a solid:

  - pos: [630.0, 365.0]
    element: core:/elements/environment/demo_solid_box/demo_solid_box.element.yaml
    solid:
      enabled: true
      pos: [630.0, 365.0]
      size: [16.0, 16.0]

The solid has its own position and size since a collider may need to be separate from the sprite. For example, the door sprite will need to be very wide for when it's open but the collider for a closed door will be very thin.

This was done by adding the new struct ElementSolidMeta to ElementSpawn. I wanted it to be a Maybe<ElementSolidMeta> but got an error from bones. My next best option was letting it be default-initialized for all elements but provide an enabled boolean that must be true for the solid to be created. Not ideal but it works.

Another downside is that the size of the solid collider has to be defined in the map, whereas the size of the KinematicBody is often defined in the element's .yaml data file and the size of the sprite is defined in the element's .atlast.yaml. Maybe this can eventually be moved into an element's asset configs.

Collision detection

Solids get a Collider component like kinematic bodies, which are also synced to the rapier colliders. This allows game code to easily control the collider. e.g. the door will want to disable the collider when it's open, which can be done by setting Collider.disabled to true.

I'm not convinced my changes to CollisionWorld::move_{horizontal,vertical} are correct, but this seems to mostly work.

Since the CollisionWorld::tile_collision{,_filtered} methods now detect any collision they should be renamed to CollisionWorld::collision{,_filtered}. If this is looking good so far I will make that change.

Game behavior

I added a few demo boxes to dev level 1 for testing. It works great from the testing I did except for a couple minor bugs. If you slide into the box to the left of where you spawn you stop short of the box. But if you walk up to the box you collide with it as expected. Sliding into the one in the far bottom right of the map behaves like you would expect. This is all shown in the video.

Additionally, critters cause a lot of collision warnings:

2024-01-16T22:02:52.902798Z WARN jumpy::core::physics: Collision test error resulting in physics body stuck in wall at Rect { min: Vec2(712.0, 96.1), max: Vec2(722.0, 106.1) }

Snails walk straight through solids, causing these warnings. Crabs may attempt and fail to surface under a solid after burrowing, also causing these warnings. I'm not sure what to do about this yet.

zicklag commented 7 months ago

Cool, thanks for checking this out! I'm going to do a proper review of this as soon as I can, probably over the week-end if I can't find time at the end of the next couple days.

nelson137 commented 7 months ago

Great! No rush, I already added 1 thing I missed, and now I think I need to support multiple solids per element for the door 😅

nelson137 commented 7 months ago

That's actually a longstanding bug, not introduced here

I can open an issue for that and then pick it up sometime after this

Instead we should put all of the metadata for the solids in whatever kind of elements that we want to spawn...

That makes a lot of sense, and I like that way better than putting solids in the map.

nelson137 commented 7 months ago

I've finished making those changes.

I also realized that I was only using Collider for its rapier_handle and disabled. I decided to move all of this functionality into Solid, so they no longer need a Collider. I don't believe using a Collider for these kinds of entities is appropriate since it seems tailored for things that have more normal physics (gravity, friction, velocity, interaction with jump-through platforms like wood, etc.).

Another option would've been to go full KinematicBody + Collider and make Solid just a marker component with no data. But IMO solids are fundamentally different kinds of entities whose only overlap with other phsyics objects is collision detection, and therefore they merit their own special treatment. Let me know if you disagree / have any thoughts on this.

This change means that a Solid has full control over the rapier collider, making it a nice interface to update the size, position, and disabled of the collider in-game. I updated the demo to show this.

Anyway, if everything looks good I can rebase onto master and drop those DELETEME commits.

nelson137 commented 7 months ago

And it was moving, but not the sprite, just the collision shape

Sorry, my last message wasn't very clear. I did that on purpose to show that changing the properties of Solid syncs with the collider. So for that one box I change the position, size, and toggle disabled every second.

And the change to DebugSettings should be in one of the DELETEME commits, and I will drop those before rebasing

zicklag commented 7 months ago

Oh, OK, you're right, I forgot about that in your comment. Cool, that's actually great! So then I'm all good with this, you can drop those commits and I'll merge it!