1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use bevy::{core::FixedTimestep, prelude::*};
use bevy_retrograde::prelude::*;
fn main() {
App::build()
.insert_resource(WindowDescriptor {
title: "Bevy Retrograde Physics Character".into(),
..Default::default()
})
.add_plugins(RetroPlugins)
.add_startup_system(setup.system())
.add_stage(
"game_stage",
SystemStage::parallel()
.with_run_criteria(FixedTimestep::step(0.015))
.with_system(move_player.system()),
)
.run();
}
struct Player;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn the camera
commands.spawn_bundle(CameraBundle {
camera: Camera {
size: CameraSize::FixedHeight(100),
background_color: Color::new(0.2, 0.2, 0.2, 1.0),
..Default::default()
},
transform: Transform::from_xyz(0., -50., 0.),
..Default::default()
});
// Load our images
let block = asset_server.load("block.png");
let triangle = asset_server.load("triangle.png");
let red_radish = asset_server.load("redRadish.png");
// Spawn a collider block that will just sit there and be an obstacle
commands
// First we spawn a sprite bundle like normal
.spawn_bundle(SpriteBundle {
image: block.clone(),
..Default::default()
})
// Then we add a tesselated collider component. This will create a convex collision shape
// from the provided image automatically.
.insert(TesselatedCollider {
// We want to use the same block we use for the visual for the collider shape
image: block.clone(),
..Default::default()
})
// Make it a static body
.insert(RigidBody::Static);
// Spawn a couple more blocks at different positions
commands
.spawn_bundle(SpriteBundle {
image: block.clone(),
transform: Transform::from_xyz(200., -24., 0.),
..Default::default()
})
.insert(TesselatedCollider {
image: block.clone(),
..Default::default()
})
.insert(RigidBody::Static);
commands
.spawn_bundle(SpriteBundle {
image: block.clone(),
transform: Transform::from_xyz(-200., -24., 0.),
..Default::default()
})
.insert(TesselatedCollider {
image: block.clone(),
..Default::default()
})
.insert(RigidBody::Static);
// Spawn a triangle obstacle
commands
.spawn_bundle(SpriteBundle {
image: triangle.clone(),
transform: Transform::from_xyz(-50., -60., 0.),
..Default::default()
})
.insert(RigidBody::Static)
.with_children(|parent| {
parent.spawn().insert_bundle((
Transform::default(),
GlobalTransform::default(),
TesselatedCollider {
image: triangle,
// For this obstacle we provide a custom configuration for the tesselator
tesselator_config: TesselatedColliderConfig {
// This vertice separation value sets the closest that any two tesselated vertices
// are allowed to be to each-other. In other words, the higher this value, the less
// acurate your collision box will be, but less compute expensive the collisions
// will be.
//
// By setting the separation to 0., the collision shape should be as close as
// possible to the actual pixel shape.
//
// The default value is 10.
vertice_separation: 30.,
..Default::default()
},
..Default::default()
},
));
});
// Spawn the player
commands
.spawn_bundle(SpriteBundle {
image: red_radish.clone(),
sprite: Sprite {
pixel_perfect: false,
..Default::default()
},
transform: Transform::from_xyz(0., -50., 0.),
..Default::default()
})
.insert(TesselatedCollider {
image: red_radish.clone(),
tesselator_config: TesselatedColliderConfig {
// We want the collision shape for the player to be highly accurate
vertice_separation: 0.,
..Default::default()
},
..Default::default()
})
// The player is also a dynamic body with rotations locked
.insert(RigidBody::Dynamic)
.insert(RotationConstraints::lock())
// Disable friction and bounciness
.insert(PhysicMaterial {
friction: 0.,
restitution: 0.,
..Default::default()
})
// Set the player speed to 0 initially
.insert(Velocity::from_linear(Vec3::default()))
.insert(Player);
}
/// Set's the player speed based on input from the keyboard arrow keys
fn move_player(keyboard_input: Res<Input<KeyCode>>, mut query: Query<&mut Velocity, With<Player>>) {
for mut velocity in query.iter_mut() {
const SPEED: f32 = 30.;
let mut direction = Vec3::new(0., 0., 0.);
if keyboard_input.pressed(KeyCode::Left) {
direction += Vec3::new(-SPEED, 0., 0.);
}
if keyboard_input.pressed(KeyCode::Right) {
direction += Vec3::new(SPEED, 0., 0.);
}
if keyboard_input.pressed(KeyCode::Up) {
direction += Vec3::new(0., -SPEED, 0.);
}
if keyboard_input.pressed(KeyCode::Down) {
direction += Vec3::new(0., SPEED, 0.);
}
if direction.length() > f32::EPSILON {
*velocity = Velocity::from_linear(direction);
} else {
*velocity = Velocity::from_linear(direction);
}
}
}