├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── GETTING_STARTED.md ├── LICENSE ├── README.md ├── assets ├── capsule_r_100_h_150.png ├── capsule_r_25_h_50.png ├── circle_50.png ├── circle_50_color.png ├── fonts │ └── FiraSans-Bold.ttf └── triangle.png ├── examples ├── capsule.rs ├── convex.rs ├── nbody.rs ├── platformer.rs └── showcase.rs └── src ├── bodies ├── kinematic.rs ├── mod.rs ├── raycast.rs ├── sensor.rs └── staticbody.rs ├── broad.rs ├── common.rs ├── lib.rs ├── narrow.rs ├── normal_coll.rs ├── physics_components ├── mod.rs ├── transform2d.rs └── velocity.rs ├── plugin.rs ├── shapes ├── aabb.rs ├── capsule.rs ├── circle.rs ├── mod.rs ├── square.rs └── triangle.rs └── transform_mode.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *~ 4 | .#* 5 | #*# 6 | workspace.code-workspace 7 | .vscode/launch.json 8 | .vscode/ltex.dictionary.en-US.txt 9 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | control_brace_style = "ClosingNextLine" 2 | fn_args_layout = "Vertical" 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_physimple" 3 | description = "A simple 2d physics engine for bevy based on physme" 4 | version = "0.5.0" 5 | authors = ["Aviv 'RustyStriker' Romem "] 6 | edition = "2021" 7 | readme = "README.md" 8 | homepage = "https://github.com/RustyStriker/bevy_physimple" 9 | repository = "https://github.com/RustyStriker/bevy_physimple" 10 | license = "MIT" 11 | keywords = ["gamedev", "physics", "collision"] 12 | categories = ["game-development"] 13 | 14 | [dependencies] 15 | serde = "^1.0" 16 | bevy = "0.8.0" 17 | -------------------------------------------------------------------------------- /GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | The plugin's name is `Physics2dPlugin` so in order to use it you need to do `App.add_plugin(Physics2dPlugin);` 4 | 5 | The plugin contains the following components and bundles(with a brief explanation): 6 | 7 | - `CollisionShape`: Enum which holds the collision shape 8 | - `KinematicBundle`(bundle): Contains the needed components for a continuous collision KinematicBody 9 | - `StaticBundle`(bundle): Contains the needed components for a StaticBody 10 | - `StaticBody`: Marker component, StaticBody V StaticBody/Sensor collisions cannot occur 11 | - `SensorBundle`(bundle): Contains the needed components for a Sensor 12 | - `Sensor`: Marker component, but also holds information about the colliding bodies in a Vec(might be changed in favor of events/hash sets) 13 | - `RayCastBundle`(bundle): Contains the needed components for a RayCast 14 | - `RayCast`: Gets the closest collision occurring on a given ray 15 | - `CollisionLayer`: Which collision layer and mask the body occupies(a collision can occur only if `a.mask & b.layer | a.layer & b.mask != 0`) 16 | - `Vel`: Used for Continuous collision kinematic bodies, requires more computational power, so not a good idea for small visual particles(like debris), yet good for stuff like bullets 17 | - `Transform2D`: Used internally, if you are modifying the position/rotation of an object during a physics step, it's better to modify this component instead. 18 | 19 | You may also use the following events: 20 | 21 | - `CollisionEvent` 22 | - more will probably come in the future(feel free to suggest events) 23 | 24 | And of course, the following resource: 25 | 26 | - `TransformMode`: Allows you to pick which 2D plane you want to "project" your physics on 27 | 28 | This lib takes care of: 29 | 30 | - collision 31 | - solving said collision and providing some information about them 32 | - that is pretty much it 33 | 34 | while it also provides continuous collision, it is quite limited and works only for `With` against entities marked with `StaticBody` or `Sensor` 35 | 36 | What you need to take care of: 37 | 38 | - Gravity(If you want) 39 | - Applying movement(except for `With` entities) 40 | - Actually reacting to the collision events(solving is done automatically, and `With` will slide the movement along the collision normal) 41 | 42 | Now I know you might be asking yourself: 43 | 44 | ```plain 45 | But why do I have to take care of all those stuff? 46 | shouldn't the physics engine take care of it??? 47 | ??? 48 | ``` 49 | 50 | and for that my friend, let me inform you of my goal here, 51 | this is my attempt at creating a "minimalistic" physics engine, 52 | so technically its mostly collision detection and solving(and even that is not that good tbh) 53 | 54 | The reason is, games can have funky and different physics, 55 | whether it's a top-down shooter with rigid controls, 56 | or a game which attempts at mimicking `Titanfall 2`'s movement(please make one, even just a demo), 57 | games have a lot of unrealistic physics, because real physics ain't always fun(you can't double jump in real life). 58 | 59 | I am getting derailed here... Gonna finish this rant some when and move it to somewhere more appropriate. 60 | 61 | ## Actually using it 62 | 63 | To make a minimalist example, we first need to add the plugin in main, so make sure you are doing: 64 | 65 | ```rs 66 | app.add_plugin(Physics2dPlugin); 67 | ``` 68 | 69 | Now we can spawn some physics objects. 70 | 71 | So in our startup system we are going to add some physical bodies with sprites: 72 | 73 | ```rs 74 | fn startup( 75 | mut coms: Commands, 76 | mut materials: ResMut>, 77 | ) { 78 | // Spawn a camera in case we didnt add 1 already 79 | coms.spawn_bundle(OrthographicCameraBundle::new_2d()); 80 | 81 | // Spawn a floor 82 | coms 83 | .spawn_bundle(SpriteBundle { 84 | sprite: Sprite::new(Vec2::new(600.0, 30.0)), 85 | material: materials.add(Color::BLACK.into()), 86 | transform: Transform::from_xyz(150.0, -200.0, 0.0), 87 | ..Default::default() 88 | }) // The sprite bundle already inserts the `Global/Transform` components 89 | .insert_bundle(StaticBundle { 90 | marker: StaticBody, // This is an empty struct 91 | shape: CollisionShape::Square(Square::size(Vec2::new(600.0, 30.0))), 92 | coll_layer: CollisionLayer::default(), 93 | }) 94 | ; 95 | 96 | // And we gonna spawn a simple cube using KinematicBundle 97 | coms 98 | .spawn_bundle(SpriteBundle { 99 | sprite: Sprite::new(Vec2::splat(35.0)), 100 | material: another_color.clone(), 101 | ..Default::default() 102 | }) 103 | .insert_bundle(KinematicBundle { 104 | shape: CollisionShape::Square(Square::size(Vec2::splat(35.0))), 105 | ..Default::default() 106 | }) 107 | ; 108 | // Spawn another cube without KinematicBundle 109 | coms 110 | .spawn_bundle(SpriteBundle { 111 | sprite: Sprite::new(Vec2::splat(35.0)), 112 | material: another_color.clone(), 113 | ..Default::default() 114 | }) 115 | .insert(CollisionShape::Square(Square::size(Vec2::splat(35.0)))) // The collision shape 116 | .insert(CollisionLayer::default()) // And collision layers 117 | ; 118 | // make sure not to have `Sensor/StaticBody` as it will turn this body 119 | // into a Sensor/Staticbody instead. 120 | } 121 | ``` 122 | 123 | ### NOTE 124 | 125 | All physical bodies need both `Transform` and `GlobalTransform` to work, 126 | but they are not a part of the given bundles, 127 | as I assumed you will only use them with a `SpriteBundle` or something else which already holds a `Transform` + `GlobalTransform` with it. 128 | 129 | (You can create your own bundles quite easily, as they hold 3 components each) 130 | 131 | For a more "full" example, you can always check the examples directory. 132 | 133 | ### NOTE — 2 134 | 135 | When moving bodies/objects, you will need to read the `CollisionEvent`s 136 | and slide(or reflect/bounce/whatever) the movement along the collision normal 137 | to prevent the bodies from endlessly accelerating downwards, 138 | eventually causing them to be too fast to handle. 139 | 140 | You will also want to move them the remainder of the last movement probably(aka, the penetration value in `CollisionEvent`) 141 | but do remember to slide/reflect it along the collision normal. 142 | 143 | Lastly, if you have any questions feel free to @ me on the bevy discord. 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aviv "RustyStriker" Romem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bevy_physimple 2 | 3 | Bevy_physimple is a collision detection(and solver) plugin for the bevy game engine. 4 | 5 | ## Current state 6 | 7 | I keep updating it wwith every bevy update, and will probably continue to 8 | do so for as long as I can spare the time for it(which is for quite some time currently), 9 | but I shifted focus to making/trying to make games in my free time, 10 | so the big stuff I need/want to do will have to wait for now. 11 | 12 | ## Why? 13 | 14 | Because I love physics and I love programming, so what is better? Physics programming! 15 | Besides, simulation physics can be restricting when you want to do some weird physics behavior for games, 16 | and eventually I want people to be able to use this crate as a simple collision solver. 17 | 18 | ## What is currently working? 19 | 20 | - Square, Circle, Capsule and custom collision shapes 21 | - Sensors, Static and normal kinematic bodies 22 | - Rays 23 | 24 | ## What doesn't work/is currently buggy? 25 | 26 | - Continuous collision is now disabled and awaiting a rewrite 27 | - Scale doesn't affect the shapes 28 | - You can push objects through walls, if the wall is too thin the object might tunnel through it 29 | - Probably some more stuff, please tell me when something isn't working properly(and isn't written here, or has an issue) 30 | 31 | ## Quickstart 32 | 33 | Clone the repo, and run 34 | 35 | cargo run --example showcase --release 36 | 37 | Or check out the `GETTING_STARTED.md` file. 38 | 39 | ## Bevy — physimple versions 40 | 41 | | bevy | physimple | 42 | |------|-----------------| 43 | | 0.5 | 0.1.0 — 0.2.0 | 44 | | 0.6 | 0.3.0 | 45 | | 0.7 | 0.4.0 | 46 | | 0.8 | 0.5.0 - current | 47 | 48 | ## Features todo list 49 | 50 | - [x] Better manual ray casting support 51 | - [x] Support multiple shapes on the same object(now possible with `CollisionShape::Multiple`) 52 | - [ ] Better/Rewrite continuous collision 53 | - [ ] Make continuous collision (fully) optional 54 | - [ ] Start adding 3D support 55 | - [ ] Add bugs to fix 56 | - [ ] A new simple platformer game example 57 | - [ ] A new simple top down game example 58 | -------------------------------------------------------------------------------- /assets/capsule_r_100_h_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/capsule_r_100_h_150.png -------------------------------------------------------------------------------- /assets/capsule_r_25_h_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/capsule_r_25_h_50.png -------------------------------------------------------------------------------- /assets/circle_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/circle_50.png -------------------------------------------------------------------------------- /assets/circle_50_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/circle_50_color.png -------------------------------------------------------------------------------- /assets/fonts/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/fonts/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /assets/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyStriker/bevy_physimple/3aa9f79bd516712488643d9864bf9fc8f488e06b/assets/triangle.png -------------------------------------------------------------------------------- /examples/capsule.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_physimple::prelude::*; 3 | 4 | fn main() { 5 | let mut app = App::new(); 6 | 7 | app 8 | .add_plugins(DefaultPlugins) 9 | .add_plugin(Physics2dPlugin) 10 | .add_system(bevy::window::close_on_esc); 11 | 12 | app 13 | .add_startup_system(setup_sys) 14 | .add_system(move_controller_sys); 15 | 16 | app.run(); 17 | } 18 | 19 | #[derive(Component)] 20 | struct Controller; 21 | 22 | fn setup_sys( 23 | mut coms: Commands, 24 | asset_server: Res, 25 | ) { 26 | // camera 27 | coms.spawn_bundle(Camera2dBundle::default()); 28 | 29 | // triangle 30 | coms.spawn_bundle(SpriteBundle { 31 | texture: asset_server.load("capsule_r_100_h_150.png"), 32 | ..Default::default() 33 | }) 34 | .insert_bundle(StaticBundle { 35 | marker: StaticBody, 36 | // The texture's dimension are indeed 100x150, but the height is the distance between the 2 centers of the edge circles 37 | // thus we need to do `height = acutal_size(150) - 2 * radius(100) = 50` 38 | shape: CollisionShape::Capsule(Capsule::new(50.0,50.0)), 39 | coll_layer: CollisionLayer::default(), 40 | }); 41 | 42 | // spawn a moveable player 43 | coms.spawn_bundle(SpriteBundle { 44 | sprite: Sprite { 45 | custom_size: Some(Vec2::splat(30.0)), 46 | color: Color::MIDNIGHT_BLUE, 47 | ..Default::default() 48 | }, 49 | transform: Transform::from_xyz(150.0,150.0,0.0), 50 | ..Default::default() 51 | }) 52 | // this is pretty much how you get a non continuous collision kinematic object 53 | .insert(CollisionShape::Square(Square::size(Vec2::splat(30.0)))) 54 | .insert(CollisionLayer::default()) 55 | .insert(Controller); 56 | } 57 | 58 | fn move_controller_sys( 59 | time: Res