Added a jumping trigger and state
[kaka/rust-sdl-test.git] / src / core / object / character.rs
... / ...
CommitLineData
1use core::controller::Controller;
2use core::object::boll::Boll;
3use core::level::{Level, Wall, IntersectResult::Intersection};
4use core::object::{Object, Objects, ObjectState};
5use core::render::Renderer;
6use geometry::Point;
7use point;
8use sdl2::rect::Rect;
9use sprites::SpriteManager;
10use std::cell::RefCell;
11use std::rc::Rc;
12use time::Duration;
13
14////////// TRIGGER /////////////////////////////////////////////////////////////
15
16trait Trigger {
17 fn update(&mut self, body: &Body, ctrl: &Controller, dt: Duration) -> Option<Box<dyn State>>;
18}
19
20////////// STATE ///////////////////////////////////////////////////////////////
21
22trait State {
23 fn enter(&mut self, _body: &mut Body, _ctrl: &Controller, _objects: &mut Objects) {}
24 fn exit(&self) {}
25 fn update(&mut self, body: &mut Body, ctrl: &Controller, objects: &mut Objects, lvl: &Level, dt: Duration) -> Option<Box<dyn State>>;
26}
27
28////////// CHARACTER ///////////////////////////////////////////////////////////
29
30struct Body {
31 pos: Point<f64>,
32 vel: Point<f64>,
33 standing_on: Option<Wall>,
34}
35
36pub struct Character {
37 ctrl: Rc<RefCell<Controller>>,
38 body: Body,
39 triggers: Vec<Box<dyn Trigger>>,
40 state: Box<dyn State>,
41}
42
43impl Character {
44 pub fn new(ctrl: Rc<RefCell<Controller>>) -> Self {
45 Character {
46 ctrl,
47 body: Body {
48 pos: point!(300.0, 300.0),
49 vel: point!(0.0, 0.0),
50 standing_on: None,
51 },
52 triggers: vec!(Box::new(JumpTrigger)),
53 state: Box::new(FallState),
54 }
55 }
56}
57
58impl Object for Character {
59 fn update(&mut self, objects: &mut Objects, lvl: &Level, dt: Duration) -> ObjectState {
60 let ctrl = self.ctrl.borrow();
61
62 if let Some(state) = self.state.update(&mut self.body, &ctrl, objects, lvl, dt) {
63 self.state.exit();
64 self.state = state;
65 self.state.enter(&mut self.body, &ctrl, objects);
66 }
67
68 match &self.body.standing_on {
69 Some(_wall) => {
70 },
71 None => { // in air
72 if let Intersection(wall, pos) = lvl.intersect_walls(self.body.pos - self.body.vel, self.body.pos) {
73 self.body.standing_on = Some(wall);
74 self.body.pos = pos;
75 self.body.vel = point!(0.0, 0.0);
76
77 self.state.exit();
78 self.state = Box::new(StandState);
79 self.state.enter(&mut self.body, &ctrl, objects);
80 }
81 }
82 }
83
84 for trigger in &mut self.triggers {
85 if let Some(state) = trigger.update(&self.body, &ctrl, dt) {
86 self.state.exit();
87 self.state = state;
88 self.state.enter(&mut self.body, &ctrl, objects);
89 }
90 }
91
92 if ctrl.shoot.is_pressed {
93 use rand::distributions::{Distribution, Normal};
94 let normal = Normal::new(0.0, 0.1);
95 let direction = if ctrl.aim.to_point().length() > 0.1 { ctrl.aim.to_point() } else { ctrl.mov.to_point() };
96 for _i in 0..100 {
97 objects.push(Box::new(Boll::new(
98 self.body.pos + point!(0.0, -16.0), // half the height of mario
99 direction * (10.0 + rand::random::<f64>()) + point!(normal.sample(&mut rand::thread_rng()), normal.sample(&mut rand::thread_rng())) + self.body.vel,
100 2,
101 )));
102 }
103 ctrl.rumble(1.0, dt);
104 self.body.vel -= direction * 0.1;
105 }
106
107 ObjectState::Alive
108 }
109
110 fn render(&self, renderer: &mut Renderer, sprites: &SpriteManager) {
111 let block = sprites.get("mario");
112 let size = 32;
113 renderer.blit(block, None, Rect::new(self.body.pos.x as i32 - size as i32 / 2, self.body.pos.y as i32 - size as i32, size, size));
114
115 let ctrl = &self.ctrl.borrow();
116 let l = 300.0;
117 let pos = (self.body.pos.x as i32, self.body.pos.y as i32);
118 // // axis values
119 // let p = (self.body.pos + ctrl.aim.to_axis_point() * l).to_i32().into();
120 // renderer.draw_line(pos, p, (0, 255, 0));
121 // draw_cross(renderer, p);
122 // values limited to unit vector
123 let p = (self.body.pos + ctrl.aim.to_point() * l).to_i32().into();
124 renderer.draw_line(pos, p, (255, 0, 0));
125 draw_cross(renderer, p);
126 let p = (self.body.pos + ctrl.mov.to_point() * l).to_i32().into();
127 renderer.draw_line(pos, p, (0, 255, 0));
128 draw_cross(renderer, p);
129 // // circle values
130 // let p = (self.body.pos + Point::from(ctrl.aim.a) * l).to_i32().into();
131 // renderer.draw_line(pos, p, (0, 0, 255));
132 // draw_cross(renderer, p);
133 }
134}
135
136fn draw_cross(renderer: &mut Renderer, p: (i32, i32)) {
137 renderer.canvas().draw_line((p.0 - 5, p.1), (p.0 + 5, p.1)).unwrap();
138 renderer.canvas().draw_line((p.0, p.1 - 5), (p.0, p.1 + 5)).unwrap();
139}
140
141////////// FALLING /////////////////////////////////////////////////////////////
142
143struct FallState;
144
145impl State for FallState {
146 fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
147 body.vel += lvl.gravity;
148 body.pos += body.vel;
149
150 match ctrl.mov.x {
151 v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 }
152 v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 }
153 _ => {}
154 }
155
156 None
157 }
158}
159
160////////// STANDING ////////////////////////////////////////////////////////////
161
162struct StandState;
163
164impl State for StandState {
165 fn update(&mut self, body: &mut Body, _ctrl: &Controller, _objects: &mut Objects, _lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
166 if let Some(_wall) = &body.standing_on {
167 body.vel *= 0.9;
168 }
169
170 None
171 }
172}
173
174////////// JUMPING /////////////////////////////////////////////////////////////
175
176struct JumpTrigger;
177
178impl Trigger for JumpTrigger {
179 fn update(&mut self, body: &Body, ctrl: &Controller, _dt: Duration) -> Option<Box<dyn State>> {
180 if body.standing_on.is_some() && ctrl.jump.is_pressed && !ctrl.jump.was_pressed { // this is redundant now because JumpState needs a wall to get starting velocity, but that will probably change
181 Some(Box::new(JumpState))
182 } else {
183 None
184 }
185 }
186}
187
188struct JumpState;
189
190impl State for JumpState {
191 fn enter(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects) {
192 if let Some(wall) = &body.standing_on {
193 if ctrl.mov.to_point().length() < 0.1 {
194 body.vel = wall.normal().into();
195 } else {
196 body.vel = ctrl.mov.to_point();
197 }
198 body.vel *= 5.0;
199 body.pos += body.vel * 0.1;
200 body.standing_on = None;
201 }
202 }
203
204 fn update(&mut self, body: &mut Body, ctrl: &Controller, _objects: &mut Objects, lvl: &Level, _dt: Duration) -> Option<Box<dyn State>> {
205 body.vel += lvl.gravity;
206 body.pos += body.vel;
207
208 match ctrl.mov.x {
209 v if v < -0.9 && body.vel.x > -5.0 => { body.vel.x -= 0.5 }
210 v if v > 0.9 && body.vel.x < 5.0 => { body.vel.x += 0.5 }
211 _ => {}
212 }
213
214 None
215 }
216}