Skip to content

Commit

Permalink
Add mouse input translations (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
jthomperoo committed Dec 19, 2021
1 parent 0962807 commit 3f71a70
Show file tree
Hide file tree
Showing 27 changed files with 769 additions and 132 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).
- Sprite rendering.
- Image loading from filesystem (virtual or otherwise).
- Input using SDL2 events (keyboard and mouse).
- Utility methods around translating input events to useful world information.
- Box2D based Physics system.
- Collision detection using Box2D.
- Window management system.
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ endif()

add_subdirectory (examples/Collision)
add_subdirectory (examples/Fullscreen)
add_subdirectory (examples/MouseInput)
add_subdirectory (examples/Sprites)
add_subdirectory (examples/Primitives)
add_subdirectory (examples/Physics)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ JamJar is a 2D, C++ game engine that primarily targets running in the browser th
- Sprites.
- Image loading from filesystem (virtual or otherwise).
- Input using SDL2 events (keyboard and mouse).
- Utility methods around translating input events to useful world information.
- Box2D based Physics system.
- Collision detection using Box2D.
- Window management system.
Expand Down
2 changes: 1 addition & 1 deletion docs/Documentation/Box2D/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The `Box2DPhysicsSystem` manages Box2D and acts as a bridge between Box2D and Ja

## Setting up the Box2D Physics System

To set up a `Box2DPhysicsSystem`, you need to provide a message bus alongside a `Vector2D` representing the gravity
To set up a `Box2DPhysicsSystem` you need to provide a message bus alongside a `Vector2D` representing the gravity
to apply to Box2D objects.

```c++
Expand Down
19 changes: 19 additions & 0 deletions docs/Documentation/SDL2/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SDL2

JamJar includes the [SDL2 library](https://github.com/libsdl-org/SDL) to handle user input, window management, and
fullscreen functionality. There is a wrapper around the SDL2 APIs to make it integrate better with the JamJar
architecture using entities, components, and systems.

The `SDL2InputSystem` handles converting SDL2 user input into a JamJar ECS friendly format through messages.

The `WindowSystem` makes use of SDL2 to help when calculating screen sizes for fullscreen.

## Setting up the SDL2 Input System

To set up a `SDL2InputSystem` you only need to provide a message bus:

```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
...
new JamJar::Standard::SDL2InputSystem(messageBus);
```
59 changes: 59 additions & 0 deletions docs/Documentation/SDL2/keyboard_input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Keyboard Input

The `SDL2InputSystem` broadcasts keyboard events, which can be listened to and parsed to handle keyboard input.

## Subscribing to Key Events

There are two keyboard events that can be subscribed to, `MESSAGE_KEY_DOWN_EVENT` and `MESSAGE_KEY_UP_EVENT`.

```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
...
YourSystem::YourSystem(JamJar::MessageBus *messageBus) : JamJar::System(messageBus) {
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_DOWN_EVENT);
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_UP_EVENT);
}
```
## Parsing Key Events
Each key event message has a payload which is of type `SDL2KeyEvent` which contains some useful quick access
information, alongside the raw `SDL_Event` that gives full advanced key event information.
The quick access information in the `SDL2KeyEvent` is:
- Key name as a character array.
- The key event type, expressed as an `SDL2KeyEventType` enum.
- If the key event is a repeat (holding down a key will generate multiple key down events, the duplicates are marked as
repeat, this is useful for handling user text input).
See the SDL2 documentation for a full reference of the raw [SDL_Event](https://wiki.libsdl.org/SDL_Event).
```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
...
void YourSystem::OnMessage(JamJar::Message *message) {
System::OnMessage(message);
switch (message->type) {
case JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_DOWN_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2KeyEvent> *>(message);
auto event = eventMessage->payload;
if (event.repeat) {
// Ignore repeat keypresses
break;
}
std::cout << "listener got key down: " << event.key << std::endl;
break;
}
case JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_UP_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2KeyEvent> *>(message);
auto event = eventMessage->payload;
if (event.repeat) {
break;
}
std::cout << "listener got key up: " << event.key << std::endl;
break;
}
}
}
```
128 changes: 128 additions & 0 deletions docs/Documentation/SDL2/mouse_input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Mouse Input

The `SDL2InputSystem` broadcasts mouse events, which can be listened to and parsed to handle mouse input.

## Subscribing to Mouse Events

There are four mouse events that can be subscribed to, `MESSAGE_MOUSE_BUTTON_UP_EVENT`,
`MESSAGE_MOUSE_BUTTON_DOWN_EVENT`, `MESSAGE_MOUSE_MOTION_EVENT`, and `MESSAGE_MOUSE_WHEEL_EVENT`.

```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
...
YourSystem::YourSystem(JamJar::MessageBus *messageBus) : JamJar::System(messageBus) {
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_BUTTON_UP_EVENT);
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_BUTTON_DOWN_EVENT);
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_MOTION_EVENT);
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_WHEEL_EVENT);
}
```
## Parsing Mouse Events
Each mouse event message has a payload which is of type `SDL2MouseEvent` which contains some useful quick access
information, alongside the raw `SDL_Event` that gives full advanced key event information.
The quick access information in the `SDL2MouseEvent` is:
- The mouse event type, expressed as an `SDL2MouseEventType` enum.
- An optional value containing the mouse button used, expressed as an optional `SDL2MouseButton` (this is not present
for mouse motion or mouse wheel events).
- The position of the mouse during the event expressed in terms of the canvas (top left canvas is `x:0, y:0`).
See the SDL2 documentation for a full reference of the raw [SDL_Event](https://wiki.libsdl.org/SDL_Event).
```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
...
void YourSystem::OnMessage(JamJar::Message *message) {
System::OnMessage(message);
switch (message->type) {
case JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_BUTTON_DOWN_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2MouseEvent> *>(message);
auto event = eventMessage->payload;
if (event.button == JamJar::Standard::SDL2MouseButton::LEFT) {
std::cout << "listener got left mouse button down" << std::endl;
} else if (event.button == JamJar::Standard::SDL2MouseButton::RIGHT) {
std::cout << "listener got right mouse button down" << std::endl;
} else {
std::cout << "listener got a different mouse button down" << std::endl;
}
break;
}
case JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_BUTTON_UP_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2MouseEvent> *>(message);
auto event = eventMessage->payload;
if (event.button == JamJar::Standard::SDL2MouseButton::LEFT) {
std::cout << "listener got left mouse button up" << std::endl;
} else if (event.button == JamJar::Standard::SDL2MouseButton::RIGHT) {
std::cout << "listener got right mouse button up" << std::endl;
} else {
std::cout << "listener got a different mouse button up" << std::endl;
}
break;
}
case JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_WHEEL_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2MouseEvent> *>(message);
auto event = eventMessage->payload;
if (event.event.wheel.y > 0) {
std::cout << "mouse wheel up" << std::endl;
} else if (event.event.wheel.y < 0) {
std::cout << "mouse wheel down" << std::endl;
}
break;
}
}
}
```

### Getting the World Position of a Mouse Event

You can convert the mouse canvas position included in the event information to a world event if you have the camera
that the click occurred in and the SDL2 window of the game canvas.

There are three utility functions provided to calculate the world position:

- `MousePositionToCanvasPosition`
- `CanvasPositionToWorldPosition`
- `MousePositionToWorldPosition`

They exist in the same import as the `Camera` component.

```c++
#include "standard/sdl2_input/sdl2_input_system.hpp"
#include "standard/2d/camera/camera.hpp"
#include "standard/2d/transform/transform.hpp"
#include <SDL2/SDL.h>
...
void YourSystem::OnMessage(JamJar::Message *message) {
MapSystem::OnMessage(message);
switch (message->type) {
case JamJar::Standard::SDL2InputSystem::MESSAGE_MOUSE_BUTTON_DOWN_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2MouseEvent> *>(message);
auto event = eventMessage->payload;

if (this->entities.size() <= 0) {
break;
}

// Get the first camera that is registered, assume that it is the one that is clicked (this holds true if
// you only have one camera, if you have multiple more complex logic is required)
auto cameraEntity = this->entities.begin()->second;
auto transform =
static_cast<JamJar::Standard::_2D::Transform *>(cameraEntity.Get(JamJar::Standard::_2D::Transform::KEY));
auto camera =
static_cast<JamJar::Standard::_2D::Camera *>(cameraEntity.Get(JamJar::Standard::_2D::Camera::KEY));

// In this instance the SDL2_Window is stored in the YourSystem object as a member variable, allowing it to
// be accessed. The SDL2_Window is not normally stored in a System and this needs to be set up by providing it
// to the constructor.
auto worldPos = JamJar::Standard::_2D::MousePositionToWorldPosition(event.position, transform->position, camera,
this->window);

std::cout << "listener got mouse down on world position x: " << worldPos.x << ", y:" << worldPos.y << std::endl;
break;
}
}
}
```
32 changes: 14 additions & 18 deletions examples/Fullscreen/src/input_listener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,29 @@
#include "message/message_payload.hpp"
#include "standard/sdl2_input/sdl2_input_system.hpp"
#include "standard/window/window_system.hpp"
#include <SDL2/SDL.h>
#include <iostream>

InputListener::InputListener(JamJar::MessageBus *messageBus) : JamJar::System(messageBus) {
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_KEYBOARD_EVENT);
this->messageBus->Subscribe(this, JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_UP_EVENT);
}

void InputListener::OnMessage(JamJar::Message *message) {
System::OnMessage(message);
switch (message->type) {
case JamJar::Standard::SDL2InputSystem::MESSAGE_KEYBOARD_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<SDL_Event> *>(message);
case JamJar::Standard::SDL2InputSystem::MESSAGE_KEY_DOWN_EVENT: {
auto *eventMessage = static_cast<JamJar::MessagePayload<JamJar::Standard::SDL2KeyEvent> *>(message);
auto event = eventMessage->payload;
if (event.key.type == SDL_KEYDOWN) {
auto key = SDL_GetKeyName(event.key.keysym.sym);
if (std::strcmp(key, "F") == 0) {
auto msg =
std::make_unique<JamJar::Message>(JamJar::Standard::WindowSystem::MESSAGE_REQUEST_ENTER_FULLSCREEN);
this->messageBus->Publish(std::move(msg));
break;
}
if (std::strcmp(key, "E") == 0) {
auto msg =
std::make_unique<JamJar::Message>(JamJar::Standard::WindowSystem::MESSAGE_REQUEST_EXIT_FULLSCREEN);
this->messageBus->Publish(std::move(msg));
break;
}
if (std::strcmp(event.key, "F") == 0) {
auto msg =
std::make_unique<JamJar::Message>(JamJar::Standard::WindowSystem::MESSAGE_REQUEST_ENTER_FULLSCREEN);
this->messageBus->Publish(std::move(msg));
break;
}
if (std::strcmp(event.key, "E") == 0) {
auto msg =
std::make_unique<JamJar::Message>(JamJar::Standard::WindowSystem::MESSAGE_REQUEST_EXIT_FULLSCREEN);
this->messageBus->Publish(std::move(msg));
break;
}
break;
}
Expand Down
47 changes: 47 additions & 0 deletions examples/MouseInput/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.14)

project(MouseInput VERSION 0.0.0)

### ===== dependencies ===== ###

# We don't need to compile the unit tests to use the library
set(JAMJAR_COMPILE_UNIT_TESTS OFF CACHE BOOL "Compile JamJar unit tests")

include(FetchContent)

# This example relies directly on the latest code in the parent directory, if using the library externally instead
# this should be fetched from over the internet, e.g.:
# FetchContent_Declare(JamJar
# GIT_REPOSITORY "git@github.com:jamjarlabs/JamJar.git"
# GIT_TAG "<library version>"
# )

set(JAMJAR_NATIVE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/../../")

FetchContent_Declare(JamJar
SOURCE_DIR ${JAMJAR_NATIVE_SOURCE_DIR}
)

FetchContent_MakeAvailable(JamJar)

### ===== core ===== ###

set(CMAKE_CXX_FLAGS "-s USE_SDL=2 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 --preload-file ${PROJECT_SOURCE_DIR}/assets@/assets")
SET(CMAKE_EXE_LINKER_FLAGS "-s LLD_REPORT_UNDEFINED")

set(EXE_SOURCES
src/main.cpp
src/mouse_input.cpp
src/input_controller.cpp
)

add_executable(MouseInput ${EXE_SOURCES})

target_include_directories(MouseInput
PRIVATE
src
)

target_link_libraries(MouseInput PRIVATE JamJar)

configure_file(${PROJECT_SOURCE_DIR}/index.html ${PROJECT_BINARY_DIR}/index.html COPYONLY)
54 changes: 54 additions & 0 deletions examples/MouseInput/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Mouse Input

This is an example that demonstrates how to

The code is designed to be simple and easy to follow.

## Overview

This example allows left clicking to place a smiley face sprite on the position you have clicked.

## Dependencies

See the dependencies for [JamJar here](../../README.md#Dependencies).

## Running this example

1. Clone down the JamJar project:

```bash
git clone git@github.com:jamjarlabs/JamJar.git
```

2. Navigate the cloned directory:

```bash
cd JamJar
```

3. Use `cmake` to generate the build system and pull down any dependencies:

```bash
emcmake cmake -D CMAKE_EXPORT_COMPILE_COMMANDS=ON . -B build
```

4. Navigate to the generated example build directory:

```bash
cd build/examples/MouseInput
```

5. Build the project:

```bash
emmake make
```

6. If you want to view the example, you can now access the generated HTML, if you have Python 3 installed a handy way
to do this is to run:

```bash
python -m http.server
```

Then you should be able to access the game at <http://127.0.0.1:8000/>.
Binary file added examples/MouseInput/assets/texture.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3f71a70

Please sign in to comment.