diff --git a/README.md b/README.md
index 604d49e6e0cb202164e59090f7822c7c1366c3d2..faee707787a9df78bc9e8b44e32e7a843a791d72 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # MyLittleZombie
 
-This project is created with the main purpose of learning (and perhaps teaching) a little bit of game development, the tools used and the methods applied. This crate is a living document and a platform for experimentation, see sections Background and Progress for the journey. The Tricks and Tips section elaborates on related tools and libraries used. Everything is WIP, expect no release until 2077.
+This project is created with the main purpose of learning (and perhaps teaching) a little bit of game development, the tools used and the methods applied. This crate is a living document and a platform for experimentation, see sections Background and Progress for the journey. The Tricks and Tips section elaborates on related tools and libraries used, I wrote it mainly as a note to self, but others might find the information useful too. I'm open to all kinds of discussions and suggestions. Everything is WIP, expect no release until 2077.
 
 ## Prerequisites
 
@@ -26,6 +26,7 @@ Tested mainly under win10.
 
 - [Tiled](https://www.mapeditor.org/)
 - [Piskel](https://www.piskelapp.com)
+- [DS4Windows](https://github.com/Ryochan7/DS4Windows), optional if you want to use a PS4 or PS5 controller with your game under windows. See e.g., [How to Connect PS5 Dualsense Controller to PC with DS4 Windows Driver] (https://www.youtube.com/watch?v=C_gFAe7cbNo) for setting up your PS5 controller. 
 
 ## Related Documentation
 
@@ -39,10 +40,10 @@ Tested mainly under win10.
 
 Typical workflow:
 - Tile sheets: 
-  - Decide on the size of each tile, e.g. 16*16 pixels, 32*32, 64*64 (I believe this is the current maximum for use in `bevy_ecs_tiledmap`).
-  - Select "Resize" to set the size of the canvas, e.g. 10 * 10 tiles (in the case each tile size is 16, this means 160 * 160). There are some options here to resize, re-position the current canvas if you already started out painting. You may store the default canvas size as well, so a new instance will automatically inherit the setting (new Canvas "+" upper right corner).
+  - Decide on the size of each tile, e.g. 16 * 16 pixels, 32 * 32, 64 * 64 (I believe this is the current maximum for use in `bevy_ecs_tiledmap`).
+  - Select "Resize" to set the size of the canvas, e.g. 10 * 10 tiles (in the case each tile size is 16, this means 160 * 160). There are some options here to resize, re-position the current canvas if you already started out painting. You may store the default canvas size as well, so a new instance will automatically inherit the setting (new Canvas "+" upper right corner to create a new Piskel instance).
   - Select "Setting/Grid" grid size "1", and grid setting "16" (matching the size you chose for your tiles). (Grid size is currently limited to 2^n, e.g., 8, 16, 32, 64).
-  - When working with your tile tile sheet you may want to preview how a tile would look when "tiled"
+  - When working with your tile sheet you may want to preview how a tile would look when "tiled" together. Under "Setting/Tile Mode" you can enable tiled mode, however the complete canvas will be tiled (which might not be what you want). You can work around this problem by creating a new instance with a canvas size set to match your tile size, select "Tiled mode" for that canvas and use this Piskel instance to preview the tiled appearance. From your original tile sheet, copy ("Rectangle Selection" and Ctrl-C) the area of the tile. Paste the tile to the top left corner (if not already working on the top left tile that is). Now the top left corner area and paste it in the tiled mode preview instance (tick the "Settings/Tiled Mode" for the preview instance). The reason for this somewhat cumbersome workflow is that copy/paste in Piskel operates relative to the top left corner of both instances (as the target instance is only one tile, it corresponds to the top left corner of the source instance). If you find a better way to do this please make an "Issue" on this repo (or even a PR).
 
 
 ---
@@ -76,13 +77,27 @@ When playing with Tiled, we encountered some shortcomings of the crates at hand.
 
 StarAwan date 2021-08-10 (approximately).
 
-### Tiled animation mini-boss.
+### Tiled animation
 
+### Tiled rotation and flipping 
 
+### Tiled parallax
 
+### Controller input
 
+## Some platform specific tips
 
+### Win10
 
+When developing under Win10, you need git [https://git-scm.com/downloads](https://git-scm.com/downloads). Gitbash comes with a shell/terminal. Alacritty is a another (fast) terminal, that uses the GPU for rendering. Under the hood it defaults to powershell.
+
+In powershell you set environment variables through `$Env:<NAME> = "<VALUE>"`. To get a full call stack trace on panic:
+
+```shell
+> $Env:RUST_BACKTRACE = "full"
+```
+
+You may also use this to set log level.
 
 
 ## Thanks
diff --git a/examples/input.rs b/examples/input.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c514ab4bb0ddb90e77cfe24544e10b24c148ca52
--- /dev/null
+++ b/examples/input.rs
@@ -0,0 +1,265 @@
+// Example based on https://bevy-cheatbook.github.io/features/input-handling.html
+use bevy::input::keyboard::KeyboardInput;
+use bevy::input::mouse::{MouseButtonInput, MouseMotion, MouseWheel};
+use bevy::prelude::*;
+
+// ANCHOR: keyboard-input
+fn keyboard_input(keys: Res<Input<KeyCode>>) {
+    if keys.just_pressed(KeyCode::Space) {
+        // Space was pressed
+    }
+    if keys.just_released(KeyCode::LControl) {
+        // Left Ctrl was released
+    }
+    if keys.pressed(KeyCode::W) {
+        // W is being held down
+    }
+}
+// ANCHOR_END: keyboard-input
+
+// ANCHOR: keyboard-events
+fn keyboard_events(mut key_evr: EventReader<KeyboardInput>) {
+    use bevy::input::ElementState;
+
+    for ev in key_evr.iter() {
+        match ev.state {
+            ElementState::Pressed => {
+                println!("Key press: {:?} ({})", ev.key_code, ev.scan_code);
+            }
+            ElementState::Released => {
+                println!("Key release: {:?} ({})", ev.key_code, ev.scan_code);
+            }
+        }
+    }
+}
+// ANCHOR_END: keyboard-events
+
+// ANCHOR: mouse-button-input
+fn mouse_button_input(buttons: Res<Input<MouseButton>>) {
+    if buttons.just_pressed(MouseButton::Left) {
+        // Left button was pressed
+    }
+    if buttons.just_released(MouseButton::Left) {
+        // Left Button was released
+    }
+    if buttons.pressed(MouseButton::Right) {
+        // Right Button is being held down
+    }
+}
+// ANCHOR_END: mouse-button-input
+
+// ANCHOR: mouse-button-events
+fn mouse_button_events(mut mousebtn_evr: EventReader<MouseButtonInput>) {
+    use bevy::input::ElementState;
+
+    for ev in mousebtn_evr.iter() {
+        match ev.state {
+            ElementState::Pressed => {
+                println!("Mouse button press: {:?}", ev.button);
+            }
+            ElementState::Released => {
+                println!("Mouse button release: {:?}", ev.button);
+            }
+        }
+    }
+}
+// ANCHOR_END: mouse-button-events
+
+// ANCHOR: mouse-motion
+fn mouse_motion(mut motion_evr: EventReader<MouseMotion>) {
+    for ev in motion_evr.iter() {
+        println!("Mouse moved: X: {} px, Y: {} px", ev.delta.x, ev.delta.y);
+    }
+}
+// ANCHOR_END: mouse-motion
+
+// ANCHOR: cursor-events
+fn cursor_events(mut cursor_evr: EventReader<CursorMoved>) {
+    for ev in cursor_evr.iter() {
+        println!(
+            "New cursor position: X: {}, Y: {}, in Window ID: {:?}",
+            ev.position.x, ev.position.y, ev.id
+        );
+    }
+}
+// ANCHOR_END: cursor-events
+
+// ANCHOR: cursor-position
+fn cursor_position(windows: Res<Windows>) {
+    // Games typically only have one window (the primary window).
+    // For multi-window applications, you need to use a specific window ID here.
+    let window = windows.get_primary().unwrap();
+
+    if let Some(_position) = window.cursor_position() {
+        // cursor is inside the window, position given
+    } else {
+        // cursor is not inside the window
+    }
+}
+// ANCHOR_END: cursor-position
+
+// ANCHOR: scroll-events
+fn scroll_events(mut scroll_evr: EventReader<MouseWheel>) {
+    use bevy::input::mouse::MouseScrollUnit;
+    for ev in scroll_evr.iter() {
+        match ev.unit {
+            MouseScrollUnit::Line => {
+                println!(
+                    "Scroll (line units): vertical: {}, horizontal: {}",
+                    ev.y, ev.x
+                );
+            }
+            MouseScrollUnit::Pixel => {
+                println!(
+                    "Scroll (pixel units): vertical: {}, horizontal: {}",
+                    ev.y, ev.x
+                );
+            }
+        }
+    }
+}
+// ANCHOR_END: scroll-events
+
+// ANCHOR: gamepad-connect-disconnect
+/// Simple resource to store the ID of the connected gamepad.
+/// We need to know which gamepad to use for player input.
+struct MyGamepad(Gamepad);
+
+fn gamepad_connections(
+    mut commands: Commands,
+    my_gamepad: Option<Res<MyGamepad>>,
+    mut gamepad_evr: EventReader<GamepadEvent>,
+) {
+    for GamepadEvent(id, kind) in gamepad_evr.iter() {
+        match kind {
+            GamepadEventType::Connected => {
+                println!("New gamepad connected with ID: {:?}", id);
+
+                // if we don't have any gamepad yet, use this one
+                if my_gamepad.is_none() {
+                    commands.insert_resource(MyGamepad(*id));
+                }
+            }
+            GamepadEventType::Disconnected => {
+                println!("Lost gamepad connection with ID: {:?}", id);
+
+                // if it's the one we previously associated with the player,
+                // disassociate it:
+                if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
+                    if old_id == id {
+                        commands.remove_resource::<MyGamepad>();
+                    }
+                }
+            }
+            // other events are irrelevant
+            _ => {}
+        }
+    }
+}
+// ANCHOR_END: gamepad-connect-disconnect
+
+// ANCHOR: gamepad-input
+fn gamepad_input(
+    axes: Res<Axis<GamepadAxis>>,
+    buttons: Res<Input<GamepadButton>>,
+    my_gamepad: Option<Res<MyGamepad>>,
+) {
+    let gamepad = if let Some(gp) = my_gamepad {
+        // a gamepad is connected, we have the id
+        gp.0
+    } else {
+        // no gamepad is connected
+        return;
+    };
+
+    // The joysticks are represented using a separate axis for X and Y
+
+    let axis_lx = GamepadAxis(gamepad, GamepadAxisType::LeftStickX);
+    let axis_ly = GamepadAxis(gamepad, GamepadAxisType::LeftStickY);
+
+    if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
+        // combine X and Y into one vector
+        let left_stick_pos = Vec2::new(x, y);
+
+        // implement a dead-zone to ignore small inputs
+        if left_stick_pos.length() > 0.1 {
+            // do something with the position of the left stick
+            println!("left stick {:?}", left_stick_pos);
+        }
+    }
+
+    let jump_button = GamepadButton(gamepad, GamepadButtonType::South);
+    let heal_button = GamepadButton(gamepad, GamepadButtonType::East);
+
+    if buttons.just_pressed(jump_button) {
+        // button pressed: make the player jump
+        println!("jump");
+    }
+
+    if buttons.pressed(heal_button) {
+        println!("heal");
+        // button being held down: heal the player
+    }
+}
+// ANCHOR_END: gamepad-input
+
+// ANCHOR: touches
+fn touches(touches: Res<Touches>) {
+    // There is a lot more information available, see the API docs.
+    // This example only shows some very basic things.
+
+    for finger in touches.iter() {
+        if touches.just_pressed(finger.id()) {
+            println!("A new touch with ID {} just began.", finger.id());
+        }
+        println!(
+            "Finger {} is at position ({},{}), started from ({},{}).",
+            finger.id(),
+            finger.position().x,
+            finger.position().y,
+            finger.start_position().x,
+            finger.start_position().y,
+        );
+    }
+}
+// ANCHOR_END: touches
+
+// ANCHOR: touch-events
+fn touch_events(mut touch_evr: EventReader<TouchInput>) {
+    use bevy::input::touch::TouchPhase;
+    for ev in touch_evr.iter() {
+        match ev.phase {
+            TouchPhase::Started => {
+                println!("Touch {} started at: {:?}", ev.id, ev.position);
+            }
+            TouchPhase::Moved => {
+                println!("Touch {} moved to: {:?}", ev.id, ev.position);
+            }
+            TouchPhase::Ended => {
+                println!("Touch {} ended at: {:?}", ev.id, ev.position);
+            }
+            TouchPhase::Cancelled => {
+                println!("Touch {} cancelled at: {:?}", ev.id, ev.position);
+            }
+        }
+    }
+}
+// ANCHOR_END: touch-events
+
+fn main() {
+    App::build()
+        .add_plugins(DefaultPlugins)
+        .add_system(keyboard_input.system())
+        .add_system(keyboard_events.system())
+        .add_system(mouse_button_input.system())
+        .add_system(mouse_button_events.system())
+        .add_system(mouse_motion.system())
+        .add_system(cursor_events.system())
+        .add_system(cursor_position.system())
+        .add_system(scroll_events.system())
+        .add_system(gamepad_connections.system())
+        .add_system(gamepad_input.system())
+        .add_system(touches.system())
+        .add_system(touch_events.system())
+        .run();
+}
diff --git a/src/main.rs b/src/main.rs
index 95251e2f89b4ac4e850a8889954286fad1722e36..2fccf00a85dd55c65cbcce9d9bc6b1faa4ee457f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,9 +28,12 @@ fn main() {
         .add_plugin(TilemapPlugin)
         .add_plugin(TiledMapPlugin)
         .add_startup_system(setup.system())
+        //.add_startup_system(gamepad_connections.system())
         .add_system_set(
             SystemSet::new()
                 //.with_run_criteria(FixedTimestep::step(1.0 / 60.0))
+                .with_system(gamepad_connections.system())
+                .with_system(gamepad_input.system())
                 .with_system(player_movement.system())
                 .with_system(camera.system()), // .with_system(parallax.system()),
         )
@@ -56,6 +59,14 @@ fn set_texture_filters_to_nearest(
 }
 
 const MAP_SCALE: f32 = 4.0;
+// use gilrs::{Button, Event, EventType, Gilrs};
+
+// struct Gamepad {
+//     gilrs: Gilrs,
+// }
+
+// unsafe impl Sync for Gamepad {}
+
 fn setup(
     mut commands: Commands,
     asset_server: Res<AssetServer>,
@@ -63,6 +74,19 @@ fn setup(
     mut texture_atlases: ResMut<Assets<TextureAtlas>>,
     mut windows: ResMut<Windows>,
 ) {
+    // let mut gilrs = Gilrs::new().unwrap();
+
+    // get the first gamepad
+    // if let Some(gamepad) = gilrs.gamepads().next() {
+    //     println!("a gamepad found");
+    //     // command.insert_resource(Gampad { : false, bonus: false });
+    // }
+
+    // Get the first gamepad and store it in a resource
+    // commands.insert_resource(Gamepad {
+    //     gilrs: Gilrs::new().unwrap(),
+    // });
+
     let mut window = windows.get_primary_mut().unwrap();
     commands.spawn_bundle(OrthographicCameraBundle::new_2d());
     let bottom = -window.height() / 2.0;
@@ -144,7 +168,28 @@ impl Default for Player {
 fn player_movement(
     mut query: Query<(&mut Player, &mut Transform, &mut TextureAtlasSprite)>,
     keyboard_input: Res<Input<KeyCode>>,
+    //  mut gilrs_input: ResMut<Gamepad>,
 ) {
+    // while let Some(event) = gilrs_input.gilrs.next_event() {
+    //     match event {
+    //         Event {
+    //             id,
+    //             event: EventType::ButtonPressed(Button::South, _),
+    //             ..
+    //         } => {
+    //             println!("Player {}: jump!", id)
+    //         }
+    //         Event {
+    //             id,
+    //             event: EventType::Disconnected,
+    //             ..
+    //         } => {
+    //             println!("We lost player {}", id)
+    //         }
+    //         _ => (),
+    //     };
+    // }
+    // println!("gilrs_input {}", gilrs_input.gilrs);
     if let Ok((mut player, mut transform, mut sprite)) = query.single_mut() {
         let (index, state, dir) = if player.player_jump {
             // already in jump
@@ -254,3 +299,83 @@ pub fn camera(
         transform.translation.x = player.player_x * (1. - layer.settings.parallax_x) / MAP_SCALE;
     }
 }
+
+// Gamepad related
+
+/// Simple resource to store the ID of the connected gamepad.
+/// We need to know which gamepad to use for player input.
+struct MyGamepad(Gamepad);
+
+fn gamepad_connections(
+    mut commands: Commands,
+    my_gamepad: Option<Res<MyGamepad>>,
+    mut gamepad_evr: EventReader<GamepadEvent>,
+) {
+    for GamepadEvent(id, kind) in gamepad_evr.iter() {
+        match kind {
+            GamepadEventType::Connected => {
+                println!("New gamepad connected with ID: {:?}", id);
+
+                // if we don't have any gamepad yet, use this one
+                if my_gamepad.is_none() {
+                    commands.insert_resource(MyGamepad(*id));
+                }
+            }
+            GamepadEventType::Disconnected => {
+                println!("Lost gamepad connection with ID: {:?}", id);
+
+                // if it's the one we previously associated with the player,
+                // disassociate it:
+                if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
+                    if old_id == id {
+                        commands.remove_resource::<MyGamepad>();
+                    }
+                }
+            }
+            // other events are irrelevant
+            _ => {}
+        }
+    }
+}
+
+fn gamepad_input(
+    axes: Res<Axis<GamepadAxis>>,
+    buttons: Res<Input<GamepadButton>>,
+    my_gamepad: Option<Res<MyGamepad>>,
+) {
+    let gamepad = if let Some(gp) = my_gamepad {
+        // a gamepad is connected, we have the id
+        gp.0
+    } else {
+        // no gamepad is connected
+        return;
+    };
+
+    // The joysticks are represented using a separate axis for X and Y
+
+    let axis_lx = GamepadAxis(gamepad, GamepadAxisType::LeftStickX);
+    let axis_ly = GamepadAxis(gamepad, GamepadAxisType::LeftStickY);
+
+    if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
+        // combine X and Y into one vector
+        let left_stick_pos = Vec2::new(x, y);
+
+        // implement a dead-zone to ignore small inputs
+        if left_stick_pos.length() > 0.1 {
+            // do something with the position of the left stick
+        }
+    }
+
+    let jump_button = GamepadButton(gamepad, GamepadButtonType::South);
+    let heal_button = GamepadButton(gamepad, GamepadButtonType::East);
+
+    if buttons.just_pressed(jump_button) {
+        // button pressed: make the player jump
+        println!("jump");
+    }
+
+    if buttons.pressed(heal_button) {
+        // button being held down: heal the player
+        println!("heal");
+    }
+}