-
Notifications
You must be signed in to change notification settings - Fork 32
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
Sort 2D sprites in 3D space #17
Comments
@odecay @64kramsystem @edgarssilva I wanted to open discussion on this topic now because I'm wondering how far we want to go before we figure this out. It seems like it might be pretty important to do early. Things like throwing items don't really make sense with our current 2D collision system, because if you throw the bottle in an arch it can go and hit fighters that are higher on the screen, even though it should technically be going up into the sky. We'll need to position everything in 3D, and I think we will need to have 3D collision boxes for everything, including the players. The render position of the sprites on the screen will then be determined by a combination of it's I think fighter collisions will probably be simple enough, because it's just a square, but what will be more complicated is scenery. For example, if we have a car, we have to specify a multi-part 3D collision box for it, so that we have one section for the top of the car and the hood, etc. so you can jump up onto different parts of it. Specifying collision shapes for scenery might be an important role of an editor, and otherwise, we'll just have to deal with hot-reload combined with YAML definitions and debug rendering of the collision boxes. Since rapier's 3D bevy plugin uses assumes you're using a 3D camera, and we are going to be using a 2D camera, we may also have to make our own rapier debug rendering system so that we can visualize the collision boxes in the space of our 2D rendering. We can do that simply enough with Egui, using the I'm feeling like we should do this somewhat soon, before we get too stuck coding into a way that we are just going to have to un-do later, and I'd be find taking this on if nobody else wants to, but probably after getting #140 done ( which shouldn't be way to long from now ). I'm wanting to try to get the foundations of the game nailed down so we don't have as many more PRs like #203 that kind of modify everything while other people are busy working on other parts of the game that are impacted by the big PR. Or at least I'd want to get those over with as soon as possible so people can keep adding feature in parallel. But totally feel free to tell me what to do or that we have other ideas. Just let me know! |
Dropping this feature entirely is also a possibility. We are learning a lot of beatemup fundamentals in the process of building this game, and this feature is quite literally a next-gen addition that came in a much later iteration of the genre. Should we hold off on it until a potential v2.0 post-publication? LF2 and similar games have plenty of fun to offer without this extra dimension. |
I think a minimum of 3D (or 2.5, or any term 😄) implementation is necessary. This is because characters move in a 3D space. Without a third dimension, we can't perform correct collision detection, which is a problem already now. Simple example: when the flop is executed, if an enemy is a above the player, but still on the ground, it will be hit. This is because the flop is encoded as moving across the ground, rather than up (since the third coordinate doesn't exist). |
If LF2 is a beat 'em up with jumps (I need to check 😁), it must handle the third coordinate in some way (even if it fakes it, like disabling collisions while in the air). It may be simple to implement in a basic way, so it's not necessarily bad, it really depends on how one chooses to implement it. |
Yes this seems like the big topic right now, if you want to take this on go ahead. If we want to delay this off as erlend said we could probably get away with working with a virtual y value. So we have a Y-Jump that gets added to the real y before render and subtracted after render. That way we can define that collisions only happen in the same y interval. Like intervals of 10y so the fighters don't need to be exactly the same height. Not sure if that brings any unwanted problems. |
A virtual y would probably work easy enough for jumps and throws, but it might get more confusing once we add scenery that logically has a depth. Anyway, I'll check it out once I get the chance and see how an attempt at 3D goes. I don't think it will be way to hard, but I'll have a better idea after some testing. |
203: New Fighter State Design and Gameplay Refactor r=zicklag a=zicklag OK I think this is finished! This refactors almost everything related to the gameplay 😅. It does an enormous amount of re-organizing, and clean up in addition to implementing the new fighter state design I started to describe in #196. I think a lot of it turned out easier to understand and simpler to modify. I'm hoping this helps make it easier to maintain and easier to continue to add features as we continue to develop. I think the only "regression" is that enemies jump around just like the player does when you attack, instead of just standing and punching, which should probably be fixed. I want to wait to add different enemy attacks until this gets review, and we can probably do that in a separate PR. --- Honestly, I kind of got carried away and I'll leave it up to you guys whether or not we should merge this an whether or not we do it all at the same time, if we do. If it's better, I can break this into smaller pieces and merge it one piece at a time ( as much as possible ). Removing the `State` enum alone pretty much necessitates a change to everything, so it can only be broken down so far, but I did re-organize a lot of things, too, so I could break the re-organizations into separate PRs if necessary. ## Summary Of Changes `@odecay` this is mostly to help summarize the changes I made to aid in reviewing. ### Fighter State The fighter and enemy state management changed completely. `fighter_state.rs` contains the entire fighter state machine. It's absolutely possible to split this out to separate files later if it gets too big, but I think it's still pretty easy to read through right now. It uses the flow of: Collect Transition Intents ( from user input, enemy AI, or things like collisions ) → Evaluate Apply State Transitions → Run State Handlers. The enemy AI logic is placed separately in `enemy_ai.rs`. ### Movement Model I removed almost all places where we manually set an item/player translation and replaced it with a modification to a `LinearVelocity` component. The `movement.rs` file has all of the systems that modify entity transforms based on linear and angular velocities and forces. By having all our movement controlled by modifying velocities instead of translation, we are able to centralize the constraints, which are also placed in `movement.rs`. We are able to arbitrarily define new constraints on any kind of entity, be it player, fighter, item, etc. that operate on the Velocity of the entity before the the transform is applied. So we no longer have the issue of clamping fighter positions all over the place! I also removed the `move_in_arch` system in favor of using forces and velocities to create the arch. I think this is cleaner, but that's subjective, so we could add move in arch again after this PR if you wanted. Though I think we might want to wait to do any more work in that direction until we figure out how we'll handle our collisions/coordinates/y-movement: #17. ### Attack and Damage I made the damage system more generic, adding a `Health` component, a `Damageable(bool)` component and `DamageEvent` in `damage.rs`. I changed the player stats to record a `max_health`, so that we always know how much health they can go up to, and their actual health is stored in the `Health` component. This will allow us to add health to all kinds of stuff, such as destructible scenery, that can be damaged by attacks, without having to specialize the attack system for what it is attacking. The attack system doesn't change much, but is now generic over anything with `Health` and `Damageable` and attacks now have a `velocity` that can be used to figure out which direction and how hard an attack should push what was attacked, if applicable. This is used for knockback on the player, but would be ignored if a wall was attacked for instance. Damage events are emitted when an attack lands, and this event is used by the knockback collector system to put the player into a knockback state when they get attacked. ### Projectiles & Lifetime I moved the `ProjectileLifetime` component to a generic `Lifetime` component, so we can use it on anything that we want to despawn on a timer. ### Despawn I removed the `DespawnMarker` component and the `despawn_entities` system, because it's effectually exactly the same as just calling `commands.entity(entity).despawn_recursive()` which isn't really less ergonomic ( IMO ) than `commands.entity(entity).insert(DespawnMarker)` and doesn't require an extra import of the `DespawnMarker` component. ### Organization I moved almost all remaining systems that were in `main.rs` to modules that made sense for them, adding plugins as necessary to each module that had systems in it. I removed the `y_sort` module to move it's system and component in with the camera, and lots of other small organizations. I think its easier to browse the codebase and find the systems that effect different parts of the game now. ### Tweak To Item Handling Instead of matching on the item name to determine behavior, I add a `kind` field to the item YAML definition that allows you to specify what the item does. For health items you can specify how much health it refills, for throwable items you can specify how much damage it does. Eventually I think we will want a dynamic way to handle item kinds, similar to the player state, so that they can be scripted and we don't have to have a central list of what kinds of items there are in an Enum. But it's a good temporary solution. ### Enemies Attack Like the Player Right now enemies and the player share the same attack, though they have different animations. This is trivial to fix, but I didn't want to work on any more things non organizational/architectural before this is merged so that can be done afterwards. ### That's it! 🙃 Other than the massive list of changes above, the rest of the game stayed the same. Enemy AI logic is the same, the player clamping logic was adapted to a constraint so trip points and camera movement all works the same. The UI didn't change. Etc. Co-authored-by: Zicklag <zicklag@katharostech.com>
heya, for my game which was an attempted remake of LF2, I implemented this using the following method (the game used Amethyst, which used
By the way, this also means you need to track velocity and acceleration in 3 dimensions as well. I haven't looked at the code if it already does that. May be relevant, background / playable area are defined as cuboids (x, y, z, and width, height, depth), and I think I had layers that also had z coordinates, because I wanted backgrounds with sprites rendered in front of the objects, e.g. if you want to have trees that render in front of objects to block the player's view.
I'm certainly biased, but I really think this dimension contributes a lot to the fun -- especially if you come to the point of implementing flying characters (imagine having a jetpack in 2.5d). |
My comment was a bit unclear because we’re juggling a lot of subtly different dimensions here 😆 I was just saying I’m perfectly fine with whatever LF2 does to achieve ‘2.5D’ vs the additional techniques involved in the RCR: Underground example to allow for 3-dimensional height variations. The difference from one to the other is unclear to me. |
Awesome @azriel91 and thanks for the breakdown! That sounds pretty similar to what I was thinking. A cool thing about it to is that I think we can totally avoid needing to do any complicated graph sorting like in RCRU with that model, because if we translate sprites down when the Z value goes up, and we use the Z value for depth-sorting, then the player will only pass behind the other sprites once it gets all the way on the other side of it. If I'm thinking about that right. I'll need to experiment, but I think that will work well for us. |
Im still kindof suspicious about switching to 3d physics for attack collisions, I cant think of a scenario where you have an attack or projectile visually overlap with an enemy sprite where you want them to not collide, I feel like that would cause confusion to the player. But the other aspects of this sound nice, enabling jumping/nice aerial attacks/juggle combos would be really fun. We do also need some way to solve for depth of props/level and how it interacts with sprite sorting. I'm not sure, maybe I'm not thinking about the first bit from the right perspective. |
Well, right now, for instance, when you thrown a bottle, you're throwing it "forward" but it has an arch that puts it up on the screen, and if that bottle goes up on the screen and it hits an enemy that isn't actually lined up with you up-and-down on the screen, it doesn't really make sense, because you threw the bottle "forward" ( aka. to the right ) but it hit an enemy that is "back" toward the background instead. Or even the flop attack. I can hit players futher back toward the background by flopping because my player animation rises slightly, but I should only be able to hit fighters further to the right of me. |
I get that but the alternative is you have a scenario where its not clear to players when an attack should and shouldn't be able to hit enemies visually. |
That's a good point. I think shadows go a long way for that, actually. My brother had mentioned that when the player flops. It's hard to tell when the player flops if he is going towards the back of the screen, or whether he is "jumping". If we add shadows to everything it would convey the difference going toward the back of the screen or rising above the floor. Maybe we could also come up with some other visual cues for flying objects, or just try to avoid long arching projectiles where they might end up confusing. |
Hmm +1 to adding shadows in general, It will help with depth perception, but I still dont think it entirely solves this problem. Although if they were in you could track where an item/player should hit by seeing if their shadow collides with something, that seems counterintuitive to what a player will actually be looking at when performing an attack/throwing a projectile. |
This seems like a good thing to study in other similar games to see how they handle projectiles, if at all. If I remember correctly from the old river city ransom on the NES ( that's the only beat-em-up I've played through ), items were only ever thrown like a bullet in a straight line, probably for a similar reason to the issues we're having here. But as far as dimensions were concerned, there was still jumping, and you wouldn't be able to get hit by enemies further back on the screen when you were in mid-air, I don't think. |
I think that could be achieved just by having the sprite and collider separate, disabling the collider, and throwing the sprite up. |
I keep thinking in my head, and that's what made me use the small hitboxes in the poc, is that if we ignore the height in 3D we get just 2D and we could "project" it flat on the screen you would get your hitbox. Idk if you can get what I mean. |
So trying to explain myself a little better if we separate the map into a grid, with way smaller rows than this. We could define a hitbox inside 1 or multiple rows. That way we can know if the player is gonna get hit or not. So in this case, if we ignore the height of the player and the item the top one will hit and the bottom not. This way the size of the entities is how "fat" it is or a more visual way how many rows he would occupy. Not sure if this makes any sense, but been kinda stuck in my head. |
Hmm like using the projection on the ground plane instead of projection on screen plane to do collisions? |
That seems like it could work. If we want to lean away from 3D positions, I'm totally fine with that. In my head I was thinking 3D boxes for everything wouldn't be that hard, but it might be a bigger can of worms that I'm thinking. In summary, I'm not picky if it works. :D |
Doing it in rows like that seems to give us similar freedom to 3D, it just breaks the depth into bigger slices, allowing us to do 2D collision detection instead of 3D by grouping things into a small set of layers. Seems like a reasonable proposal. 👍 And visually, we could still have arbitrarily varied depth, it would just effect collisions. |
I think I prefer going full 3d over using ground plane projection to determine collisions. |
It was mostly as a placeholder suggestion since we were kinda going towards it with the idea of setting the sprites to the bottom center anchor. |
http://andrewrussell.net/2016/06/how-2-5d-sorting-works-in-river-city-ransom-underground/
https://www.youtube.com/playlist?list=PLgPMWQb41awm62M5YE3zzAKyw5Odka6JR
https://github.com/conatuscreative
The text was updated successfully, but these errors were encountered: