Skip to content

Commit d909466

Browse files
FyorMachaMeijeroxkitsune
authored
Lost ball behavior (#651)
Co-authored-by: machameijer <machameijer@gmail.com> Co-authored-by: Gijs de Jong <berichtaangijs@gmail.com>
1 parent 5ac337f commit d909466

5 files changed

Lines changed: 239 additions & 11 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use bevy::prelude::*;
2+
use serde::{Deserialize, Serialize};
3+
use serde_with::serde_as;
4+
use std::time::Instant;
5+
6+
use crate::{
7+
behavior::{
8+
BehaviorConfig,
9+
engine::{Behavior, BehaviorState, in_behavior},
10+
},
11+
motion::walking_engine::{StandingHeight, step::Step, step_context::StepContext},
12+
nao::{NaoManager, Priority},
13+
};
14+
use nidhogg::types::{FillExt, HeadJoints};
15+
16+
const ROTATION_STIFFNESS: f32 = 0.3;
17+
18+
/// Config struct containing parameters for the initial behavior.
19+
#[serde_as]
20+
#[derive(Resource, Serialize, Deserialize, Debug, Clone)]
21+
#[serde(deny_unknown_fields)]
22+
pub struct LostBallSearchBehaviorConfig {
23+
// Controls how fast the robot moves its head back and forth while looking around
24+
pub head_rotation_speed: f32,
25+
// Controls how far to the left and right the robot looks while looking around, in radians.
26+
// If this value is one, the robot will look one radian to the left and one radian to the
27+
// right.
28+
pub head_pitch_max: f32,
29+
// Controls how far to the bottom the robot looks while looking around, in radians
30+
pub head_yaw_max: f32,
31+
}
32+
33+
#[derive(Resource, Deref)]
34+
struct LostBallSearchStartingTime(Instant);
35+
36+
/// This behavior makes the robot look around with a sinusoidal head movement with an optional step.
37+
/// With this behavior, the robot can observe its surroundings while standing still or turning.
38+
#[derive(Resource, Default)]
39+
pub struct LostBallSearch {
40+
pub step: Option<Step>,
41+
}
42+
43+
impl LostBallSearch {
44+
#[must_use]
45+
pub fn with_turning(turn: f32) -> Self {
46+
LostBallSearch {
47+
step: Some(Step {
48+
turn,
49+
..Default::default()
50+
}),
51+
}
52+
}
53+
}
54+
55+
impl Behavior for LostBallSearch {
56+
const STATE: BehaviorState = BehaviorState::LostBallSearch;
57+
}
58+
59+
pub struct LostBallSearchBehaviorPlugin;
60+
61+
impl Plugin for LostBallSearchBehaviorPlugin {
62+
fn build(&self, app: &mut App) {
63+
app.add_systems(Update, observe.run_if(in_behavior::<LostBallSearch>))
64+
.add_systems(
65+
OnEnter(BehaviorState::Observe),
66+
reset_lost_ball_search_starting_time,
67+
)
68+
.insert_resource(LostBallSearchStartingTime(Instant::now()));
69+
}
70+
}
71+
72+
fn reset_lost_ball_search_starting_time(
73+
mut lost_ball_search_starting_time: ResMut<LostBallSearchStartingTime>,
74+
) {
75+
lost_ball_search_starting_time.0 = Instant::now();
76+
}
77+
78+
fn observe(
79+
mut nao_manager: ResMut<NaoManager>,
80+
behavior_config: Res<BehaviorConfig>,
81+
observe: Res<LostBallSearch>,
82+
observe_starting_time: Res<LostBallSearchStartingTime>,
83+
mut step_context: ResMut<StepContext>,
84+
) {
85+
let observe_config = &behavior_config.observe;
86+
look_around(
87+
&mut nao_manager,
88+
**observe_starting_time,
89+
observe_config.head_rotation_speed,
90+
observe_config.head_yaw_max,
91+
observe_config.head_pitch_max,
92+
);
93+
94+
if let Some(step) = observe.step {
95+
step_context.request_walk(step);
96+
} else {
97+
step_context.request_stand_with_height(StandingHeight::MAX);
98+
}
99+
}
100+
101+
fn look_around(
102+
nao_manager: &mut NaoManager,
103+
starting_time: Instant,
104+
rotation_speed: f32,
105+
yaw_multiplier: f32,
106+
pitch_multiplier: f32,
107+
) {
108+
// Used to parameterize the yaw and pitch angles, multiplying with a large
109+
// rotation speed will make the rotation go faster.
110+
let movement_progress = starting_time.elapsed().as_secs_f32() * rotation_speed;
111+
let yaw = (movement_progress).sin() * yaw_multiplier;
112+
let pitch = (movement_progress * 2.0 + std::f32::consts::FRAC_PI_2)
113+
.sin()
114+
.max(0.0)
115+
* pitch_multiplier;
116+
117+
let position = HeadJoints { yaw, pitch };
118+
let stiffness = HeadJoints::fill(ROTATION_STIFFNESS);
119+
120+
nao_manager.set_head(position, stiffness, Priority::default());
121+
}

yggdrasil/src/behavior/behaviors/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod catchfall;
2+
mod lost_ball_search;
23
mod observe;
34
mod rl_striker_search;
45
mod sitting;
@@ -13,6 +14,7 @@ mod walk_to_ball;
1314
mod walk_to_set;
1415

1516
pub use catchfall::*;
17+
pub use lost_ball_search::*;
1618
pub use observe::*;
1719
pub use rl_striker_search::*;
1820
pub use sitting::*;

yggdrasil/src/behavior/engine.rs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use ml::{
88
use nalgebra::Point2;
99

1010
use crate::{
11+
behavior::roles::LostBallSearchTimer,
1112
core::config::showtime::PlayerConfig,
1213
motion::walking_engine::Gait,
1314
nao::{NaoManager, Priority, RobotInfo},
@@ -17,18 +18,20 @@ use crate::{
1718

1819
use super::{
1920
behaviors::{
20-
CatchFall, CatchFallBehaviorPlugin, ObserveBehaviorPlugin, RlStrikerSearchBehaviorPlugin,
21-
Sitting, SittingBehaviorPlugin, Stand, StandBehaviorPlugin, StandLookAt,
22-
StandLookAtBehaviorPlugin, Standup, StandupBehaviorPlugin, StartUpBehaviorPlugin,
23-
VisualReferee, VisualRefereeBehaviorPlugin, WalkBehaviorPlugin, WalkToBallBehaviorPlugin,
24-
WalkToBehaviorPlugin, WalkToSet, WalkToSetBehaviorPlugin,
21+
CatchFall, CatchFallBehaviorPlugin, LostBallSearchBehaviorPlugin, ObserveBehaviorPlugin,
22+
RlStrikerSearchBehaviorPlugin, Sitting, SittingBehaviorPlugin, Stand, StandBehaviorPlugin,
23+
StandLookAt, StandLookAtBehaviorPlugin, Standup, StandupBehaviorPlugin,
24+
StartUpBehaviorPlugin, VisualReferee, VisualRefereeBehaviorPlugin, WalkBehaviorPlugin,
25+
WalkToBallBehaviorPlugin, WalkToBehaviorPlugin, WalkToSet, WalkToSetBehaviorPlugin,
2526
},
2627
primary_state::PrimaryState,
2728
roles::{
2829
Defender, DefenderRolePlugin, Goalkeeper, GoalkeeperRolePlugin, Striker, StrikerRolePlugin,
2930
},
3031
};
3132

33+
use std::time::Duration;
34+
3235
const FORWARD_LEANING_THRESHOLD: f32 = 0.2;
3336
const BACKWARD_LEANING_THRESHOLD: f32 = -0.2;
3437

@@ -57,6 +60,7 @@ impl Plugin for BehaviorEnginePlugin {
5760
WalkToBallBehaviorPlugin,
5861
WalkToBehaviorPlugin,
5962
WalkToSetBehaviorPlugin,
63+
LostBallSearchBehaviorPlugin,
6064
))
6165
.add_systems(PostUpdate, role_base);
6266
}
@@ -70,6 +74,20 @@ fn broken_robot_behavior_override(robot_info: Res<RobotInfo>, mut nao_manager: R
7074
}
7175
}
7276

77+
#[derive(Resource)]
78+
pub struct DefenderSwitchTimer {
79+
timer: Timer,
80+
}
81+
82+
impl DefenderSwitchTimer {
83+
#[must_use]
84+
pub fn new(duration: Duration) -> Self {
85+
DefenderSwitchTimer {
86+
timer: Timer::new(duration, TimerMode::Once),
87+
}
88+
}
89+
}
90+
7391
pub trait RlBehaviorInput<T> {
7492
fn to_input(&self) -> T;
7593
}
@@ -111,6 +129,7 @@ pub enum BehaviorState {
111129
WalkToSet,
112130
WalkToBall,
113131
RlStrikerSearchBehavior,
132+
LostBallSearch,
114133
}
115134

116135
#[must_use]
@@ -173,13 +192,38 @@ impl RoleState {
173192
commands: &mut Commands,
174193
player_number: u8,
175194
possible_ball_distance: Option<f32>,
195+
role_state: Res<State<RoleState>>,
196+
defender_switch_timer: Option<ResMut<DefenderSwitchTimer>>,
197+
time: Res<Time>,
176198
) {
177199
if let Some(distance) = possible_ball_distance {
178200
if distance < 3.0 {
179201
commands.set_role(Striker);
180202
return;
181203
}
182204
}
205+
206+
// check if the current role is striker
207+
if *role_state == RoleState::Striker && (player_number != 4 && player_number != 5) {
208+
if let Some(mut timer) = defender_switch_timer {
209+
timer.timer.tick(time.delta());
210+
if timer.timer.finished() {
211+
commands.remove_resource::<DefenderSwitchTimer>();
212+
if player_number == 1 {
213+
commands.set_role(Goalkeeper);
214+
} else {
215+
commands.set_role(Defender);
216+
}
217+
} else {
218+
commands.set_role(Striker);
219+
}
220+
return;
221+
}
222+
commands.insert_resource(DefenderSwitchTimer::new(Duration::from_secs(9)));
223+
commands.set_role(Striker);
224+
return;
225+
}
226+
183227
// TODO: Check if robots have been penalized, or which robot is closed to the ball etc.
184228
Self::by_player_number(commands, player_number);
185229
}
@@ -216,6 +260,9 @@ pub fn role_base(
216260
game_controller_message: Option<Res<GameControllerMessage>>,
217261
imu_values: Res<IMUValues>,
218262
ball_tracker: Res<BallTracker>,
263+
role_state: Res<State<RoleState>>,
264+
defender_switch_timer: Option<ResMut<DefenderSwitchTimer>>,
265+
time: Res<Time>,
219266
) {
220267
commands.disable_role();
221268
let behavior = behavior_state.get();
@@ -278,6 +325,9 @@ pub fn role_base(
278325
match *primary_state {
279326
PrimaryState::Sitting => commands.set_behavior(Sitting),
280327
PrimaryState::Penalized => {
328+
// reset all timers
329+
commands.remove_resource::<DefenderSwitchTimer>();
330+
commands.remove_resource::<LostBallSearchTimer>();
281331
commands.set_behavior(Stand);
282332
}
283333
PrimaryState::Standby => {
@@ -308,6 +358,9 @@ pub fn role_base(
308358
&mut commands,
309359
player_config.player_number,
310360
possible_ball_distance,
361+
role_state,
362+
defender_switch_timer,
363+
time,
311364
);
312365
}
313366
}

yggdrasil/src/behavior/roles/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ mod striker;
66

77
pub use defender::{Defender, DefenderRolePlugin};
88
pub use goalkeeper::{Goalkeeper, GoalkeeperRolePlugin};
9-
pub use striker::{Striker, StrikerRolePlugin};
9+
pub use striker::{LostBallSearchTimer, Striker, StrikerRolePlugin};

yggdrasil/src/behavior/roles/striker.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use nidhogg::types::{FillExt, RightEye, color};
55

66
use crate::{
77
behavior::{
8-
behaviors::{LookMode, RlStrikerSearchBehavior, StandLookAt, Walk, WalkTo, WalkToBall},
8+
behaviors::{
9+
LookMode, LostBallSearch, RlStrikerSearchBehavior, StandLookAt, Walk, WalkTo,
10+
WalkToBall,
11+
},
912
engine::{BehaviorState, CommandsBehaviorExt, RoleState, Roles, in_role},
1013
primary_state::PrimaryState,
1114
},
@@ -19,6 +22,8 @@ use crate::{
1922
vision::ball_detection::ball_tracker::BallTracker,
2023
};
2124

25+
use std::time::Duration;
26+
2227
const WALK_WITH_BALL_ANGLE: f32 = 0.3;
2328
const ALIGN_WITH_BALL_DISTANCE: f32 = 0.3;
2429

@@ -69,6 +74,22 @@ fn in_set_play(
6974
#[derive(Resource, Default, Debug)]
7075
pub struct Striker;
7176

77+
#[derive(Resource)]
78+
pub struct LostBallSearchTimer {
79+
timer: Timer,
80+
last_ball: Point2<f32>,
81+
}
82+
83+
impl LostBallSearchTimer {
84+
#[must_use]
85+
pub fn new(duration: Duration, last_ball: Point2<f32>) -> Self {
86+
LostBallSearchTimer {
87+
timer: Timer::new(duration, TimerMode::Once),
88+
last_ball,
89+
}
90+
}
91+
}
92+
7293
impl Roles for Striker {
7394
const STATE: RoleState = RoleState::Striker;
7495
}
@@ -83,18 +104,49 @@ pub fn striker_role(
83104
layout_config: Res<LayoutConfig>,
84105
ball_tracker: Res<BallTracker>,
85106
mut nao_manager: ResMut<NaoManager>,
107+
lost_ball_timer: Option<ResMut<LostBallSearchTimer>>,
108+
time: Res<Time>,
86109
) {
87110
let Some(relative_ball) = ball_tracker.stationary_ball() else {
88-
nao_manager.set_right_eye_led(RightEye::fill(color::f32::GREEN), Priority::default());
111+
if let Some(mut timer) = lost_ball_timer {
112+
timer.timer.tick(time.delta()); // <- tick the timer
89113

90-
commands.set_behavior(RlStrikerSearchBehavior);
114+
if timer.timer.finished() {
115+
commands.remove_resource::<LostBallSearchTimer>();
116+
} else {
117+
nao_manager
118+
.set_right_eye_led(RightEye::fill(color::f32::BLUE), Priority::default()); // TEMP LED
119+
120+
// determine the side we need to turn to by using timer.last_ball
121+
let relative_last_ball = &timer.last_ball;
122+
commands.set_behavior(LostBallSearch::with_turning(
123+
relative_last_ball.y.signum() * 0.6, //TODO test
124+
));
125+
}
126+
} else {
127+
nao_manager.set_right_eye_led(RightEye::fill(color::f32::GREEN), Priority::default());
128+
commands.set_behavior(RlStrikerSearchBehavior);
129+
}
91130
return;
92131
};
132+
let absolute_ball: nalgebra::OPoint<f32, nalgebra::Const<2>> =
133+
pose.robot_to_world(&relative_ball);
134+
135+
if ball_tracker.timestamp.elapsed().as_secs_f32() > 0.5 {
136+
if lost_ball_timer.is_none() {
137+
commands.insert_resource(LostBallSearchTimer::new(
138+
Duration::from_secs(9),
139+
relative_ball,
140+
));
141+
}
142+
} else {
143+
commands.remove_resource::<LostBallSearchTimer>();
144+
}
93145

94-
let absolute_ball = pose.robot_to_world(&relative_ball);
95146
let ball_angle = pose.angle_to(&absolute_ball);
96147
let ball_distance = relative_ball.coords.norm();
97-
let ball_target = Point3::new(absolute_ball.x, absolute_ball.y, 0.2);
148+
let ball_target: nalgebra::OPoint<f32, nalgebra::Const<3>> =
149+
Point3::new(absolute_ball.x, absolute_ball.y, 0.2);
98150

99151
let relative_goalpost_left =
100152
pose.world_to_robot(&Point2::new(layout_config.field.length / 2., 0.8));

0 commit comments

Comments
 (0)