10. Getting and Managing User Inputs
<< RETURN TO THE PREVIOUS SECTION
The majority of applications are designed to interact with the user. In UNIGINE you can manage user inputs using the following classes
The following code illustrates how to use Input class to get mouse coordinates in case if a right mouse button was clicked and to close the application if "q" key was pressed (ignoring this key if the console is opened):#include <core/unigine.h>
/* .. */
int update() {
// if right mouse button is clicked
if (engine.input.isMouseButtonDown(INPUT_MOUSE_BUTTON_RIGHT))
{
ivec2 mouse = engine.input.getMouseCoord();
// report mouse cursor coordinates to the console
log.message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (engine.input.isKeyDown(INPUT_KEY_Q) && engine.console.getActivity() == 0)
{
engine.app.exit();
}
return 1;
}
The following code illustrates how to use App class to to do the same: get mouse coordinates in case if a right mouse button was clicked and to close the application if "q" key was pressed (ignoring this key if the console is opened):
#include <core/unigine.h>
/* .. */
int update() {
// if right mouse button is clicked
if (engine.app.clearMouseButtonState(APP_BUTTON_RIGHT))
{
// report mouse cursor coordinates to the console
log.message("Right mouse button was clicked at (%d, %d)\n", engine.app.getMouseX(), engine.app.getMouseY());
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (engine.app.getKeyState('q') && engine.console.getActivity() == 0)
{
engine.app.exit();
}
return 1;
}
The following code illustrates how to use Controls class to handle keyboard input:
#include <core/unigine.h>
/* .. */
int update() {
// getting current controls
Controls controls = engine.game.getPlayer().getControls();
// checking controls states and reporting which buttons were pressed
if (controls.clearState(CONTROLS_STATE_FORWARD) || controls.clearState(CONTROLS_STATE_TURN_UP))
{
log.message("FORWARD or UP key pressed\n");
}
else if (controls.clearState(CONTROLS_STATE_BACKWARD) || controls.clearState(CONTROLS_STATE_TURN_DOWN))
{
log.message("BACKWARD or DOWN key pressed\n");
}
else if (controls.clearState(CONTROLS_STATE_MOVE_LEFT) || controls.clearState(CONTROLS_STATE_TURN_LEFT))
{
log.message("MOVE_LEFT or TURN_LEFT key pressed\n");
}
else if (controls.clearState(CONTROLS_STATE_MOVE_RIGHT) || controls.clearState(CONTROLS_STATE_TURN_RIGHT))
{
log.message("MOVE_RIGHT or TURN_RIGHT key pressed\n");
}
return 1;
}
The following code illustrates how to use ControlsApp class to map keys and buttons to states and then to handle user input:
#include <core/unigine.h>
int init() {
// remapping states to other keys and buttons
engine.controls.setStateKey(CONTROLS_STATE_FORWARD, APP_KEY_PGUP);
engine.controls.setStateKey(CONTROLS_STATE_BACKWARD, APP_KEY_PGDOWN);
engine.controls.setStateKey(CONTROLS_STATE_MOVE_LEFT, 'l');
engine.controls.setStateKey(CONTROLS_STATE_MOVE_RIGHT, 'r');
engine.controls.setStateButton(CONTROLS_STATE_JUMP, APP_BUTTON_LEFT);
return 1;
}
int update() {
if (engine.controls.clearState(CONTROLS_STATE_FORWARD))
{
log.message("FORWARD key pressed\n");
}
else if (engine.controls.clearState(CONTROLS_STATE_BACKWARD))
{
log.message("BACKWARD key pressed\n");
}
else if (engine.controls.clearState(CONTROLS_STATE_MOVE_LEFT))
{
log.message("MOVE_LEFT key pressed\n");
}
else if (engine.controls.clearState(CONTROLS_STATE_MOVE_RIGHT))
{
log.message("MOVE_RIGHT key pressed\n");
}
else if (engine.controls.clearState(CONTROLS_STATE_JUMP))
{
log.message("JUMP button pressed\n");
}
return 1;
}
Additional information:
- For more information on managing user inputs using Input class, see the Input class article.
- For more information on managing user inputs using App class, see the App class article.
- For more information on managing user inputs using Controls class, see the Controls class article.
- For more information on managing user inputs using ControlsApp class, see the ControlsApp class article.
Project Progress#
In our project we are going to handle the following user inputs:
- Pressing 'Q' key on the keyboard - in this case we close the application
- Clicking right mouse button - in this case we call getObjectUnderMouseCursor function from the previous step and report the name of the selected object using selectObject function.
In the AppWorldLogic.h file, we include UnigineApp.h, UnigineInput.h and UnigineConsole.h libraries to work with App, Input and Console classes.
// AppWorldLogic.h
#include <UnigineApp.h>
#include <UnigineConsole.h>
#include <UnigineInput.h>
/* ... */
In the AppWorldLogic.cpp file let us add the following code to the AppWorldLogic::init() method.
// AppWorldLogic.cpp
/* .. */
/// function reporting to the console the name of selected object if any
int selectObject(ObjectPtr new_selected_object)
{
// if selected object is not NULL printing its name to the console
if (new_selected_object)
{
Log::message("\n%s object selected", new_selected_object->getName());
return 1;
}
return 0;
}
/* .. */
int AppWorldLogic::update()
{
/* .. */
// if right mouse button is pressed
if (Input::isMouseButtonPressed(Input::MOUSE_BUTTON_RIGHT))
{
// get and select object under the mouse cursor
Math::ivec2 mouse = Input::getMouseCoord();
selectObject(getObjectUnderMouseCursor(Game::getPlayer(), mouse.x, mouse.y, 100.0f));
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (Input::isKeyDown(Input::KEY_Q) && !Console::getActivity())
{
App::exit();
}
/* .. */
}
/* .. */
Source Files
You can copy the code below and paste it to the corresponding source files of your project:
AppWorldLogic.h
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__
#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineGame.h>
#include <UnigineLights.h>
#include <UnigineMaterials.h>
#include <UnigineWorld.h>
#include <UnigineApp.h>
#include <UnigineConsole.h>
#include <UnigineInput.h>
using namespace Unigine;
// auxiliary constants
const float DELTA_ANGLE = 60.0f; // delta angle of objects rotation
const float MOVING_SPEED = 3.0f; // speed of objects movement
const float CHANGE_INTERVAL = 1.0f; // the interval between changes of objects' parameters, in seconds
const float SUN_ROTATION_RATE = 10.0f; // rotation rate of the sun
class AppWorldLogic : public WorldLogic {
public:
AppWorldLogic();
virtual ~AppWorldLogic();
virtual int init();
virtual int update();
virtual int postUpdate();
virtual int updatePhysics();
virtual int shutdown();
virtual int save(const StreamPtr &stream);
virtual int restore(const StreamPtr &stream);
private:
PlayerSpectatorPtr player;
// pointers to light sources
LightWorldPtr thesun;
LightOmniPtr light_omni;
LightProjPtr projector;
// auxiliary functions
int addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position);
int removeMeshFromScene(const char *node_name);
int transformNode(NodePtr node);
// initialization functions
int initObjects();
int initPlayer();
int initLights();
int initMaterials();
// update functions
int updateLights();
int updateObjects();
// shutdown functions
int clearMaterials();
int removeObjects();
// scene objects vector
Vector <ObjectMeshDynamicPtr> Objects;
Math::vec3 current_objects_scale = Math::vec3(1.0f); // current scaling vector for objects
Math::Vec3 forward_direction = Math::Vec3(0.0f, -1.0f, 0.0f); // current forward direction vector for objects
float elapsed_time = CHANGE_INTERVAL; // current time left to change current scale and forward direction of our objects
float sun_angle = 0.0f; // current sun position
};
#endif // __APP_WORLD_LOGIC_H__
AppWorldLogic.cpp
#include "AppWorldLogic.h"
// World logic, it takes effect only when the world is loaded.
// These methods are called right after corresponding world script's (UnigineScript) methods.
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- AUXILIARY FUNCTIONS AND METHODS ----------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method adding a Dynamic Mesh Object to the scene. If an empty filename is passed the method creates a default box; otherwise, loads a mesh-file with a given name.
int AppWorldLogic::addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position)
{
MeshPtr mesh = Mesh::create();
ObjectMeshDynamicPtr omd;
if (file_name){ // loading a mesh from a specified file
if (!mesh->load(file_name))
{
Log::error("\nError opening .mesh file!\n");
mesh.clear();
return 0;
}
else omd = ObjectMeshDynamic::create(mesh);
}
else // creating a default box
{
mesh->addBoxSurface("box_surface", Math::vec3(0.5f));
omd = ObjectMeshDynamic::create(mesh);
}
// setting node material, name and position
omd->setMaterial(material_name, "*");
omd->setName(mesh_name);
omd->setWorldPosition(position);
// enabling intersection detection for the first surface (0)
omd->setIntersection(1, 0);
// updating the list of scene objects
Objects.append(omd);
// reporting progress to the console
Log::message("-> Object %s added to the scene.\n", mesh_name);
// clearing the mesh
mesh.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method deleting a Dynamic Mesh Object with a given name from the scene
int AppWorldLogic::removeMeshFromScene(const char *node_name)
{
// getting a pointer to the node with a given name and downcasting it to ObjectMeshDynamicPtr
ObjectMeshDynamicPtr object = checked_ptr_cast<ObjectMeshDynamic>(World::getNodeByName(node_name));
if (object)
{
// reporting node deletion to the console
Log::message("Removing %s node named %s from the scene.\n", object->getTypeName(), node_name);
// removing the node with a given name from the list of scene objects
for (int i = 0; i < Objects.size(); i++)
{
if (strcmp(Objects[i]->getName(), node_name) == 0) {
Objects.remove(i);
break;
}
}
// removing the node from the scene
object->deleteLater();
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing node transformations
int AppWorldLogic::transformNode(NodePtr node)
{
// getting current node transformation matrix
Math::Mat4 transform = node->getTransform();
// calculating delta rotation around an arbitrary axis
Math::quat delta_rotation = Math::quat(rand() % 2, rand() % 2, rand() % 2, DELTA_ANGLE * Game::getIFps());
// setting node's scale, rotation and position
node->setWorldScale(current_objects_scale);
node->setWorldRotation(node->getWorldRotation() * delta_rotation);
node->setWorldPosition(node->getWorldPosition() + forward_direction * MOVING_SPEED * Game::getIFps());
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// function getting an object under the mouse cursor
ObjectPtr getObjectUnderMouseCursor(const PlayerPtr & player, int mouse_x, int mouse_y, float max_dist)
{
// setting start point (p0) at the center of player's screen
Math::Vec3 p0 = player->getWorldPosition();
// setting end point (p1) according to the position of the mouse cursor
Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromScreen(mouse_x, mouse_y)) * max_dist;
WorldIntersectionPtr intersection = WorldIntersection::create();
// returning the pointer to the first intersected object if any or NULL
return World::getIntersection(p0, p1, 1, intersection);
}
int selectObject(ObjectPtr new_selected_object)
{
// if selected object is not NULL printing its name to the console
if (new_selected_object)
{
Log::message("\n%s object selected", new_selected_object->getName());
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- INITIALIZATION METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the set of 4 boxes
int AppWorldLogic::initObjects()
{
int index = 0;
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
addMeshToScene(NULL, String::format("my_meshdynamic_%d", index), String::format("my_mesh_base%d", index), Math::Vec3(x, y, 1.0f));
index++;
}
}
// reporting progress to the console
Log::warning("Objects generation OK!\n\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the player
int AppWorldLogic::initPlayer()
{
// creating a new PlayerSpectator instance
player = PlayerSpectator::create();
// setting player's FOV, ZNear, ZFar
player->setFov(90.0f);
player->setZNear(0.1f);
player->setZFar(10000.0f);
// setting player's view direction vector and position
player->setPosition(Math::Vec3(3.0f));
player->setDirection(Math::vec3(-1.0f), Math::vec3(0.0f, 0.0f, -1.0f));
// setting the player as a default one via the Game singleton instance
Game::setPlayer(player);
//reporting progress to the console
Log::warning("\nPlayer initialization OK!\n\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of lights
int AppWorldLogic::initLights()
{
// creating an omni light and setting up its parameters
light_omni = LightOmni::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f), 10.0f, "");
light_omni->setWorldPosition(Math::Vec3(0.0f, 0.0f, 5.0f));
light_omni->setIntensity(0.1f);
// reporting progress to the console
Log::message("-> Created a %s light source.\n", light_omni->getName());
// creating a world light and setting up its parameters
thesun = LightWorld::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f));
thesun->setName("Sun");
thesun->setDisableAngle(90.0f);
thesun->setIntensity(1.0f);
thesun->setScattering(LightWorld::SCATTERING_SUN);
thesun->setWorldRotation(Math::quat(86.0f, 30.0f, 300.0f));
// reporting progress to the console
Log::message("-> Created a %s light source.\n", thesun->getName());
// creating a proj light and setting up its parameters
projector = LightProj::create(Math::vec4(1.0f, 1.0f, 0.5f, 1.0f), 10.0f, 60.0f, "");
projector->setWorldPosition(Math::Vec3(2.5f, 2.5f, 3.0f));
projector->setName("projector");
projector->setRotation(Math::quat(-45.0f, 45.0f, 0.0f));
projector->setPenumbra(0.425f);
projector->setIntensity(1.0f);
// reporting progress to the console
Log::message("-> Created a %s light source.\n", projector->getName());
Log::warning("Lights initialization OK!\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of materials
int AppWorldLogic::initMaterials()
{
// creating a new child material of the mesh_base and setting its color
MaterialPtr mesh_base = Materials::findMaterial("mesh_base");
MaterialPtr my_mesh_base = mesh_base->inherit("my_mesh_base0");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 0, 0, 255));
// reporting progress to the console
Log::message("\n-> Generated %s material.\n", my_mesh_base->getName());
// creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base1");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(0, 255, 0, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
//creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base2");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(0, 0, 255, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
//creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base3");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 255, 0, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
Log::warning("Material generation OK!\n\n");
// clearing material pointer
my_mesh_base.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- UPDATE METHODS ---------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the position of the sun
int AppWorldLogic::updateLights()
{
// updating the Sun rotation angle
sun_angle += SUN_ROTATION_RATE * Game::getIFps();
if (sun_angle > 360.0f) sun_angle = 0.0f;
// changing the Sun position using the new angle
checked_ptr_cast<LightWorld>(World::getNodeByName("Sun"))->setWorldRotation(Math::quat(Math::vec3(0.5f, 0.0f, 0.5f), 180.0f - sun_angle));
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the initial set of scene objects
int AppWorldLogic::updateObjects()
{
ObjectMeshDynamicPtr object;
// changing transformation for all scene objects named "my_meshdynamic_*" (initial set)
for (auto it = Objects.begin(); it != Objects.end(); it++)
{
object = it.get();
if (strstr(object->getName(), "my_meshdynamic_"))
{
// transform the node
transformNode(object);
}
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- SHUTDOWN METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created materials
int AppWorldLogic::clearMaterials()
{
Materials::removeMaterial(Materials::findMaterial("my_mesh_base0")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base1")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base2")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base3")->getGUID());
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created objects
int AppWorldLogic::removeObjects()
{
while (Objects.size() > 0)
{
removeMeshFromScene(Objects.begin()->get()->getName());
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
AppWorldLogic::AppWorldLogic() {
}
AppWorldLogic::~AppWorldLogic() {
}
int AppWorldLogic::init() {
// Write here code to be called on world initialization: initialize resources for your world scene during the world start.
// initializing pseudo-random number generator
Game::setSeed(time(NULL));
// creating materials
initMaterials();
// creating objects
initObjects();
// creating a player
initPlayer();
// creating lights
initLights();
return 1;
}
// start of the main loop
int AppWorldLogic::update() {
// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.
// checking if it's time to change current scale and forward vector direction of our objects
if (elapsed_time < 0.0f)
{
// change current scaling vector for objects
current_objects_scale = Math::vec3(Game::getRandomFloat(0.8f, 1.2f));
// change forward direction for objects
forward_direction = forward_direction * Math::rotateZ(60.0f);
// resetting elapsed time counter
elapsed_time = CHANGE_INTERVAL;
}
// decreasing the time counter to the next change of current scale and forward vector direction
elapsed_time -= Game::getIFps();
// if right mouse button is clicked
if (Input::isMouseButtonPressed(Input::MOUSE_BUTTON_RIGHT))
{
// get and select object under the mouse cursor
Math::ivec2 mouse = Input::getMouseCoord();
selectObject(getObjectUnderMouseCursor(Game::getPlayer(), mouse.x, mouse.y, 100.0f));
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (Input::isKeyDown(Input::KEY_Q) && !Console::getActivity())
{
App::exit();
}
// updating objects
updateObjects();
// updating lights
updateLights();
return 1;
}
int AppWorldLogic::postUpdate() {
// The engine calls this function before rendering each render frame: correct behavior after the state of the node has been updated.
return 1;
}
int AppWorldLogic::updatePhysics() {
// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
return 1;
}
// end of the main loop
int AppWorldLogic::shutdown() {
// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.
// deleting all created nodes
removeObjects();
// clearing the player pointer
player->deleteLater();
// clearing light sources
thesun->deleteLater();
light_omni->deleteLater();
projector->deleteLater();
// clearing all created materials
clearMaterials();
return 1;
}
int AppWorldLogic::save(const StreamPtr &stream) {
// Write here code to be called when the world is saving its state (i.e. state_save is called): save custom user data to a file.
UNIGINE_UNUSED(stream);
return 1;
}
int AppWorldLogic::restore(const StreamPtr &stream) {
// Write here code to be called when the world is restoring its state (i.e. state_restore is called): restore custom user data to a file here.
UNIGINE_UNUSED(stream);
return 1;
}