Commit e1bfe2d8 authored by Florian Blasius's avatar Florian Blasius 🤘

Implement entity movement and animation by script.

parent e1c8a44f
......@@ -31,3 +31,4 @@ chrono = "0.4"
fps_counter = "1.0.0"
rhai = "0.8.1"
ron = "0.1.5"
regex = "0.2"
......@@ -9,6 +9,7 @@ Game (
y: 0,
entities: [
Entity (
id: "player",
layer: 1,
x: 16,
y: 16,
......
print("Hello from player");
\ No newline at end of file
let walk_speed = 250.0;
fn calculate_animation_step(step, hor, ver) {
if hor == -1.0 {
if step >= 12.0 && step <= 15.0 {
if step < 15.0 {
return step + 1.0
} else {
return 12.0
}
} else {
return 12.0
}
}
if hor == 1.0 {
if step >= 4.0 && step <= 7.0 {
if step < 7.0 {
return step + 1.0
} else {
return 4.0
}
} else {
return 4.0
}
}
if ver == -1.0 {
if step >= 8.0 && step <= 11.0 {
if step < 11.0 {
return step + 1.0
} else {
return 8.0
}
} else {
return 8.0
}
}
if ver == 1.0 {
if step >= 0.0 && step <= 3.0 {
if step < 3.0 {
return step + 1.0
} else {
return 0.0
}
} else {
return 0.0
}
}
step
}
player.animation_step(calculate_animation_step(animation_step, horizontal_direction, vertical_direction));
player.mov(delta * walk_speed * horizontal_direction, delta * walk_speed * vertical_direction);
player
\ No newline at end of file
......@@ -2,6 +2,8 @@ use std::cell::Cell;
use orbtk::{Point, Rect};
use Entity;
#[derive(Clone)]
pub struct Camera {
rect: Cell<Rect>,
......@@ -47,44 +49,44 @@ impl Camera {
self.rect().set(rect);
}
// pub fn follow(&mut self, entity: &mut Entity) {
// let mut screen_position = entity.screen_position().get();
// let entity_rect = entity.rect().get();
// let mut rect = self.rect.get();
// let maximum = self.maximum.get();
// screen_position.x = rect.width as i32 / 2;
// screen_position.y = rect.height as i32 / 2;
// // make the camera follow the sprite
// rect.x = entity_rect.x - rect.width as i32 / 2;
// rect.y = entity_rect.y - rect.height as i32 / 2;
// let zero: i32 = 0;
// // clamp values
// rect.x = zero.max(rect.x.min(maximum.x));
// rect.y = zero.max(rect.y.min(maximum.y));
// // in map corners, the sprite cannot be placed in the center of the screen
// // and we have to change its screen coordinates
// // left and right sides
// if entity_rect.x < rect.width as i32 / 2
// || entity_rect.x > maximum.x + rect.width as i32 / 2
// {
// let new_x = entity_rect.x - rect.x;
// screen_position.x = new_x;
// }
// // top and bottom sides
// if entity_rect.y < rect.height as i32 / 2
// || entity_rect.y > maximum.y + rect.height as i32 / 2
// {
// let new_y = entity_rect.y - rect.y;
// screen_position.y = new_y;
// }
// entity.screen_position().set(screen_position);
// self.rect.set(rect);
// }
pub fn follow(&mut self, entity: &mut Entity) {
let mut screen_position = entity.screen_position().get();
let entity_rect = entity.rect().get();
let mut rect = self.rect.get();
let maximum = self.maximum.get();
screen_position.x = rect.width as i32 / 2;
screen_position.y = rect.height as i32 / 2;
// make the camera follow the sprite
rect.x = entity_rect.x - rect.width as i32 / 2;
rect.y = entity_rect.y - rect.height as i32 / 2;
let zero: i32 = 0;
// clamp values
rect.x = zero.max(rect.x.min(maximum.x));
rect.y = zero.max(rect.y.min(maximum.y));
// in map corners, the sprite cannot be placed in the center of the screen
// and we have to change its screen coordinates
// left and right sides
if entity_rect.x < rect.width as i32 / 2
|| entity_rect.x > maximum.x + rect.width as i32 / 2
{
let new_x = entity_rect.x - rect.x;
screen_position.x = new_x;
}
// top and bottom sides
if entity_rect.y < rect.height as i32 / 2
|| entity_rect.y > maximum.y + rect.height as i32 / 2
{
let new_y = entity_rect.y - rect.y;
screen_position.y = new_y;
}
entity.screen_position().set(screen_position);
self.rect.set(rect);
}
}
use std::cell::{Cell, RefCell};
use orbtk::{CloneCell, Rect};
use orbtk::{Rect, Point};
use sprite::{Sprite, SpriteConfig};
use ScriptEngine;
use TileMap;
#[derive(Clone, Debug, Deserialize, Default)]
#[serde(rename = "Entity")]
pub struct EntityConfig {
pub id: String,
pub x: i32,
pub y: i32,
pub width: u32,
......@@ -17,8 +20,10 @@ pub struct EntityConfig {
#[derive(Clone)]
pub struct Entity {
id: String,
layer: i32,
rect: Cell<Rect>,
screen_position: Cell<Point>,
sprite: RefCell<Option<Sprite>>,
script: RefCell<String>,
}
......@@ -26,14 +31,19 @@ pub struct Entity {
impl Entity {
pub fn from_config(config: &EntityConfig) -> Self {
Entity {
id: config.id.clone(),
layer: config.layer,
rect: Cell::new(Rect::new(config.x, config.y, config.width, config.height)),
screen_position: Cell::new(Point::new(config.x, config.y)),
sprite: RefCell::new(Some(Sprite::from_config(&config.sprite))),
// todo: support file loading and inplace code
script: RefCell::new(config.script.clone()),
script: RefCell::new(ScriptEngine::load_script(&config.script)),
}
}
pub fn id(&self) -> String {
self.id.clone()
}
pub fn layer(&self) -> i32 {
self.layer
}
......@@ -42,6 +52,10 @@ impl Entity {
&self.rect
}
pub fn screen_position(&self) -> &Cell<Point> {
&self.screen_position
}
pub fn sprite(&self) -> &RefCell<Option<Sprite>> {
&self.sprite
}
......@@ -49,4 +63,64 @@ impl Entity {
pub fn script(&self) -> &RefCell<String> {
&self.script
}
pub fn animation_step(&mut self, animation_step: f64) {
if let Some(ref sprite) = *self.sprite.borrow_mut() {
sprite.animation_step().set(animation_step as usize);
}
}
pub fn mov(&mut self, dir_x: f64, dir_y: f64/*, map: &TileMap*/) {
let mut rect = self.rect.get();
// rect.x += (dir_x * self.speed * delta) as i32;
// self.check_tile_collison(&mut rect, dir_x, 0.0, map);
// rect.y += (dir_y * self.speed * delta) as i32;
// self.check_tile_collison(&mut rect, 0.0, dir_y, map);
rect.x += dir_x as i32;
//self.check_tile_collison(&mut rect, dir_x, 0.0, map);
rect.y += dir_y as i32;
//self.check_tile_collison(&mut rect, 0.0, dir_y, map);
// let max_x = map.column_count() * map.tile_size() as usize - rect.width as usize;
// let max_y = map.row_count() * map.tile_size() as usize - rect.height as usize;
let zero_x: i32 = 0 + rect.width as i32;
let zero_y: i32 = 0 + rect.height as i32;
// adjust to respect the render_bounds
// rect.x = zero_x.max(rect.x.min(max_x as i32));
// rect.y = zero_y.max(rect.y.min(max_y as i32));
self.rect.set(rect);
}
fn check_tile_collison(&mut self, rect: &mut Rect, dir_x: f32, dir_y: f32, map: &TileMap) {
let left = rect.x as f32 + 1.0;
let right = rect.x as f32 + rect.width as f32 - 1.0;
let top = rect.y as f32 + 1.0;
let bottom = rect.y as f32 + rect.height as f32 - 1.0;
// check for collisions on sprite sides
let collision = map.is_tile_blocked(left, top) || map.is_tile_blocked(right, top)
|| map.is_tile_blocked(right, bottom)
|| map.is_tile_blocked(left, bottom);
if !collision {
return;
}
if dir_y > 0.0 {
rect.y = map.get_y(map.get_row(bottom)) as i32 - rect.height as i32;
} else if dir_y < 0.0 {
rect.y = map.get_y(map.get_row(top) + 1.0) as i32;
} else if dir_x > 0.0 {
rect.x = map.get_x(map.get_column(right)) as i32 - rect.width as i32;
} else if dir_x < 0.0 {
rect.x = map.get_x(map.get_column(left) + 1.0) as i32;
}
}
}
use std::sync::Arc;
use std::time;
use fps_counter::FPSCounter;
use orbtk::{Rect, Window, WindowBuilder};
......@@ -17,9 +20,12 @@ pub struct GameConfig {
pub struct Game {
window: Window,
target_fps: u32,
target_fps: f64,
target_fps_nanos: f32,
script_engine: ScriptEngine,
scene: Arc<Scene>,
last_tick_time: time::Instant,
fps_counter: FPSCounter,
}
impl Game {
......@@ -33,13 +39,31 @@ impl Game {
Game {
window,
target_fps: config.target_fps,
target_fps: config.target_fps as f64,
target_fps_nanos: (1. / config.target_fps as f32) * 1_000_000_000.,
script_engine: ScriptEngine::new(),
scene: scene,
last_tick_time: time::Instant::now(),
fps_counter: FPSCounter::new(),
}
}
fn update(&mut self) {}
pub fn elapsed(&self) -> f32 {
let time = self.last_tick_time.elapsed();
let total_nanos = time.as_secs() * 1_000_000_000 + time.subsec_nanos() as u64;
self.target_fps_nanos - (total_nanos as f32)
}
fn update(&mut self) {
if self.elapsed() > 0. {
return;
}
let delta = 1.0 / self.target_fps;
self.scene.update(&mut self.script_engine, delta);
self.last_tick_time = time::Instant::now();
}
pub fn exec(&mut self) {
'event: while self.window.running.get() {
......@@ -50,87 +74,3 @@ impl Game {
}
}
}
// #[derive(Clone, Serialize, Deserialize, Debug)]
// pub struct Config {
// title: String,
// stage: String,
// target_fps: u32,
// width: u32,
// height: u32,
// ui_css: String,
// }
// impl Config {
// pub fn from_toml(path: &str) -> Self {
// let config = {
// // todo: handel result
// let mut file = File::open(path).unwrap();
// let mut buf = Vec::new();
// file.read_to_end(&mut buf).unwrap();
// toml::from_slice(&buf).unwrap()
// };
// config
// }
// }
// pub struct Game {
// title: String,
// width: u32,
// height :u32,
// target_fps: u32,
// config: Config,
// stage: Arc<Stage>,
// script_engine: ScriptEngine,
// }
// impl Game {
// pub fn from_ron(path: &str) -> Self {
// let value = super::load_ron_value(path);
// Game {
// titile: String::from("test"),
// width: 0,
// height: 0,
// }
// }
// pub fn from_toml(path: &str) -> Self {
// Game::from_config(Config::from_toml(path))
// }
// pub fn from_config(config: Config) -> Self {
// let stage = Stage::from_toml(&config.stage[..]);
// stage.size(config.width, config.height);
// let script_engine = ScriptEngine::new();
// Game {
// config,
// stage,
// script_engine,
// }
// }
// pub fn update(&mut self) {
// self.script_engine.update();
// }
// pub fn exec(&mut self) {
// let mut window = Window::new(
// Rect::new(0, 0, self.config.width, self.config.height),
// &self.config.title[..],
// );
// window.add(&self.stage);
// 'event: while window.running.get() {
// window.drain_events();
// self.update();
// window.draw();
// window.drain_orbital_events();
// }
// }
// }
......@@ -6,6 +6,7 @@ extern crate orbclient;
extern crate orbimage;
extern crate orbtk;
extern crate rhai;
extern crate regex;
extern crate chrono;
extern crate fps_counter;
......
......@@ -3,6 +3,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::cmp;
use orbclient;
use orbimage::Image;
use orbtk::{Event, Place, Point, Rect, Renderer, Widget};
use orbtk::theme::Theme;
......@@ -10,6 +11,7 @@ use orbtk::theme::Theme;
use Camera;
use Entity;
use EntityConfig;
use ScriptEngine;
use tile_map::{TileMap, TileMapConfig};
#[derive(Clone, Debug, Deserialize, Default)]
......@@ -24,14 +26,16 @@ pub struct SceneConfig {
#[derive(Clone)]
pub struct Scene {
rect: Cell<Rect>,
entities: HashMap<i32, RefCell<Vec<Entity>>>,
entities: HashMap<i32, RefCell<Vec<RefCell<Entity>>>>,
tile_map: RefCell<Option<TileMap>>,
camera: RefCell<Camera>,
vertical_direction: Cell<f64>,
horizontal_direction: Cell<f64>,
}
impl Scene {
pub fn from_config(config: &SceneConfig) -> Arc<Self> {
let mut entities: HashMap<i32, RefCell<Vec<Entity>>> = HashMap::new();
let mut entities: HashMap<i32, RefCell<Vec<RefCell<Entity>>>> = HashMap::new();
for entity in &config.entities {
let layer = entity.layer;
......@@ -44,7 +48,7 @@ impl Scene {
.get(&layer)
.unwrap()
.borrow_mut()
.push(Entity::from_config(entity));
.push(RefCell::new(Entity::from_config(entity)));
}
Arc::new(Scene {
......@@ -54,8 +58,13 @@ impl Scene {
// todo: real camera values
camera: RefCell::new(Camera::new(
Rect::new(0, 0, 800, 600),
Point::new(1000, 1000),
Point::new(
*&config.map.tile_set.tile_size as i32 * *&config.map.column_count as i32,
*&config.map.tile_set.tile_size as i32 * *&config.map.row_count as i32,
),
)),
vertical_direction: Cell::new(0.0),
horizontal_direction: Cell::new(0.0),
})
}
......@@ -64,6 +73,23 @@ impl Scene {
self
}
pub fn update(&self, script_engine: &mut ScriptEngine, delta: f64) {
script_engine.update(
self.vertical_direction.get(),
self.horizontal_direction.get(),
delta,
);
for (_, entities) in &self.entities {
for entity in &*entities.borrow_mut() {
let entity_c = script_engine.execute_script(&*entity.borrow());
*entity.borrow_mut() = entity_c;
// todo: use connect camera and entity from game.ron
self.camera.borrow_mut().follow(&mut *entity.borrow_mut());
}
}
}
pub fn draw_all_layers(&self, renderer: &mut Renderer) {
let rect = self.rect.get();
let camera_rect = self.camera.borrow().rect().get();
......@@ -108,7 +134,7 @@ impl Scene {
if let Some(entities) = self.entities.get(&layer) {
for entity in &*entities.borrow() {
self.draw_entity(renderer, entity);
self.draw_entity(renderer, &*entity.borrow());
}
}
}
......@@ -116,7 +142,8 @@ impl Scene {
}
fn draw_entity(&self, renderer: &mut Renderer, entity: &Entity) {
let rect = entity.rect().get();
//let rect = entity.rect().get();
let screen_position = entity.screen_position().get();
if let Some(ref sprite) = *entity.sprite().borrow() {
let sheet = sprite.sheet();
......@@ -126,8 +153,8 @@ impl Scene {
Scene::draw_image_part(
renderer,
sheet,
rect.x,
rect.y,
screen_position.x,
screen_position.y,
animation_rect.x as u32,
animation_rect.y as u32,
animation_rect.width,
......@@ -235,7 +262,40 @@ impl Widget for Scene {
self.draw_all_layers(renderer);
}
fn event(&self, _event: Event, _focused: bool, _redraw: &mut bool) -> bool {
fn event(&self, event: Event, _focused: bool, _redraw: &mut bool) -> bool {
match event {
Event::KeyPressed(key_event) => match key_event.scancode {
orbclient::K_UP => {
self.vertical_direction.set(-1.0);
}
orbclient::K_DOWN => {
self.vertical_direction.set(1.0);
}
orbclient::K_LEFT => {
self.horizontal_direction.set(-1.0);
}
orbclient::K_RIGHT => {
self.horizontal_direction.set(1.0);
}
_ => {}
},
Event::KeyReleased(key_event) => match key_event.scancode {
orbclient::K_UP => {
self.vertical_direction.set(0.0);
}
orbclient::K_DOWN => {
self.vertical_direction.set(0.0);
}
orbclient::K_LEFT => {
self.horizontal_direction.set(0.0);
}
orbclient::K_RIGHT => {
self.horizontal_direction.set(0.0);
}
_ => {}
},
_ => {}
}
_focused
}
}
......
use std::fs::File;
use std::io::Read;
use std::sync::Arc;
use rhai::{Engine, RegisterFn, Scope};
use sprite::Sprite;
use regex::Regex;
// use sprite::Sprite;
use entity::Entity;
use scene::Scene;
// use scene::Scene;
pub struct ScriptEngine {
inner_engine: Engine,
......@@ -14,9 +20,9 @@ impl ScriptEngine {
let mut inner_engine = Engine::new();
let scope = Scope::new();
inner_engine.register_type::<Sprite>();
inner_engine.register_type::<Entity>();
inner_engine.register_type::<Scene>();
inner_engine.register_fn("mov", Entity::mov);
inner_engine.register_fn("animation_step", Entity::animation_step);
ScriptEngine {
inner_engine,
......@@ -24,11 +30,57 @@ impl ScriptEngine {
}
}
pub fn update(&mut self) {
let result = self.inner_engine
.eval_with_scope::<i32>(&mut self.scope, "4 + 4");
if let Ok(result) = result {
println!("{}", result);
pub fn load_script(script: &str) -> String {
let re = Regex::new(r".+(.rhai){1}").unwrap();
if re.is_match(script) {
let mut f = File::open(script).expect("script file not found");
let mut contents = String::new();
f.read_to_string(&mut conten