瀏覽代碼

Refactor samples to use patched screen joystick layout. Closes #321.
Enhance screen joystick to also support mouse button binding. It can be used in combination with key binding, e.g. to bind SHIFT+LMB.

Yao Wei Tjong 姚伟忠 11 年之前
父節點
當前提交
125000454c
共有 41 個文件被更改,包括 861 次插入435 次删除
  1. 1 0
      Bin/Data/LuaScripts/26_ConsoleInput.lua
  2. 14 13
      Bin/Data/Scripts/26_ConsoleInput.as
  3. 2 0
      Source/Engine/Engine/Console.cpp
  4. 2 0
      Source/Engine/Engine/Console.h
  5. 59 16
      Source/Engine/Input/Input.cpp
  6. 6 3
      Source/Engine/Input/InputEvents.h
  7. 2 0
      Source/Engine/LuaScript/pkgs/Engine/Console.pkg
  8. 2 1
      Source/Engine/Script/EngineAPI.cpp
  9. 10 0
      Source/Samples/01_HelloWorld/HelloWorld.h
  10. 11 1
      Source/Samples/02_HelloGUI/HelloGUI.h
  11. 10 0
      Source/Samples/03_Sprites/Sprites.h
  12. 15 0
      Source/Samples/06_SkeletalAnimation/SkeletalAnimation.h
  13. 15 0
      Source/Samples/07_Billboards/Billboards.h
  14. 23 0
      Source/Samples/08_Decals/Decals.h
  15. 48 1
      Source/Samples/09_MultipleViewports/MultipleViewports.h
  16. 24 1
      Source/Samples/11_Physics/Physics.h
  17. 24 1
      Source/Samples/12_PhysicsStressTest/PhysicsStressTest.h
  18. 24 1
      Source/Samples/13_Ragdolls/Ragdolls.h
  19. 13 0
      Source/Samples/14_SoundEffects/SoundEffects.h
  20. 74 1
      Source/Samples/15_Navigation/Navigation.h
  21. 13 0
      Source/Samples/16_Chat/Chat.h
  22. 11 1
      Source/Samples/17_SceneReplication/SceneReplication.h
  23. 96 81
      Source/Samples/18_CharacterDemo/CharacterDemo.cpp
  24. 51 2
      Source/Samples/18_CharacterDemo/CharacterDemo.h
  25. 24 183
      Source/Samples/18_CharacterDemo/Touch.cpp
  26. 2 47
      Source/Samples/18_CharacterDemo/Touch.h
  27. 50 27
      Source/Samples/19_VehicleDemo/VehicleDemo.cpp
  28. 23 0
      Source/Samples/20_HugeObjectCount/HugeObjectCount.h
  29. 6 3
      Source/Samples/24_Urho2DSprite/Urho2DSprite.cpp
  30. 23 0
      Source/Samples/24_Urho2DSprite/Urho2DSprite.h
  31. 5 0
      Source/Samples/25_Urho2DParticle/Urho2DParticle.cpp
  32. 10 0
      Source/Samples/25_Urho2DParticle/Urho2DParticle.h
  33. 19 17
      Source/Samples/26_ConsoleInput/ConsoleInput.cpp
  34. 14 1
      Source/Samples/26_ConsoleInput/ConsoleInput.h
  35. 8 5
      Source/Samples/27_Urho2DPhysics/Urho2DPhysics.cpp
  36. 23 0
      Source/Samples/27_Urho2DPhysics/Urho2DPhysics.h
  37. 5 2
      Source/Samples/28_Urho2DPhysicsRope/Urho2DPhysicsRope.cpp
  38. 23 0
      Source/Samples/28_Urho2DPhysicsRope/Urho2DPhysicsRope.h
  39. 30 1
      Source/Samples/29_SoundSynthesis/SoundSynthesis.h
  40. 8 4
      Source/Samples/Sample.h
  41. 38 22
      Source/Samples/Sample.inl

+ 1 - 0
Bin/Data/LuaScripts/26_ConsoleInput.lua

@@ -52,6 +52,7 @@ function Start()
     console.numBufferedRows = 2 * console.numRows;
     console.commandInterpreter = "LuaScript";
     console.visible = true
+    console.closeButton.visible = false;
     
     -- Show OS mouse cursor
     input.mouseVisible = true

+ 14 - 13
Bin/Data/Scripts/26_ConsoleInput.as

@@ -34,7 +34,7 @@ void Start()
 {
     // Execute the common startup for samples
     SampleStart();
-    
+
     // Disable default execution of AngelScript from the console
     script.executeConsoleCommands = false;
 
@@ -44,7 +44,7 @@ void Start()
 
     // Subscribe key down event
     SubscribeToEvent("KeyDown", "HandleEscKeyDown");
-    
+
     // Hide logo to make room for the console
     SetLogoVisible(false);
 
@@ -53,7 +53,8 @@ void Start()
     console.numBufferedRows = 2 * console.numRows;
     console.commandInterpreter = "ScriptEventInvoker";
     console.visible = true;
-    
+    console.closeButton.visible = false;
+
     // Show OS mouse cursor
     input.mouseVisible = true;
 
@@ -96,7 +97,7 @@ void StartGame()
           "objective is to survive as long as possible. Beware of hunger and the merciless\n"
           "predator cichlid Urho, who appears from time to time. Evading Urho is easier\n"
           "with an empty stomach. Type 'help' for available commands.");
-    
+
     gameOn = true;
     foodAvailable = false;
     eatenLastTurn = false;
@@ -110,7 +111,7 @@ void EndGame(const String&in message)
     Print(message);
     Print("Game over! You survived " + String(numTurns) + " turns.\n"
           "Do you want to play again (Y/N)?");
-    
+
     gameOn = false;
 }
 
@@ -129,10 +130,10 @@ void Advance()
         ++urhoThreat;
     if (urhoThreat == 0 && Random() < 0.2f)
         ++urhoThreat;
-    
+
     if (urhoThreat > 0)
         Print(urhoThreatLevels[urhoThreat - 1] + ".");
-    
+
     if ((numTurns & 3) == 0 && !eatenLastTurn)
     {
         ++hunger;
@@ -144,9 +145,9 @@ void Advance()
         else
             Print("You are " + hungerLevels[hunger] + ".");
     }
-    
+
     eatenLastTurn = false;
-    
+
     if (foodAvailable)
     {
         Print("The floating pieces of fish food are quickly eaten by other fish in the tank.");
@@ -157,7 +158,7 @@ void Advance()
         Print("The overhead dispenser drops pieces of delicious fish food to the water!");
         foodAvailable = true;
     }
-    
+
     ++numTurns;
 }
 
@@ -169,7 +170,7 @@ void HandleInput(const String&in input)
         Print("Empty input given!");
         return;
     }
-    
+
     if (inputLower == "quit" || inputLower == "exit")
         engine.Exit();
     else if (gameOn)
@@ -197,7 +198,7 @@ void HandleInput(const String&in input)
             }
             else
                 Print("There is no food available.");
-            
+
             Advance();
         }
         else if (inputLower == "wait")
@@ -220,7 +221,7 @@ void HandleInput(const String&in input)
             }
             else
                 Print("There is nothing to hide from.");
-            
+
             Advance();
         }
         else

+ 2 - 0
Source/Engine/Engine/Console.cpp

@@ -118,6 +118,8 @@ void Console::SetDefaultStyle(XMLFile* style)
     for (unsigned i = 0; i < interpreters_->GetNumItems(); ++i)
         interpreters_->GetItem(i)->SetStyle("ConsoleText");
     lineEdit_->SetStyle("ConsoleLineEdit");
+
+    closeButton_->SetDefaultStyle(style);
     closeButton_->SetStyle("CloseButton");
     
     UpdateElements();

+ 2 - 0
Source/Engine/Engine/Console.h

@@ -76,6 +76,8 @@ public:
     BorderImage* GetBackground() const { return background_; }
     /// Return the line edit element.
     LineEdit* GetLineEdit() const { return lineEdit_; }
+    /// Return the close butoon element.
+    Button* GetCloseButton() const { return closeButton_; }
     /// Return whether is visible.
     bool IsVisible() const;
     /// Return true when console is set to automatically visible when receiving an error log message.

+ 59 - 16
Source/Engine/Input/Input.cpp

@@ -55,6 +55,7 @@ namespace Urho3D
 
 const int SCREEN_JOYSTICK_START_ID = 0x40000000;
 const ShortStringHash VAR_BUTTON_KEY_BINDING("VAR_BUTTON_KEY_BINDING");
+const ShortStringHash VAR_BUTTON_MOUSE_BUTTON_BINDING("VAR_BUTTON_MOUSE_BUTTON_BINDING");
 const ShortStringHash VAR_LAST_KEYSYM("VAR_LAST_KEYSYM");
 const ShortStringHash VAR_SCREEN_JOYSTICK_ID("VAR_SCREEN_JOYSTICK_ID");
 
@@ -258,6 +259,8 @@ static void PopulateKeyBindingMap(HashMap<String, int>& keyBindingMap)
         keyBindingMap.Insert(MakePair<String, int>("RIGHT", KEY_RIGHT));
         keyBindingMap.Insert(MakePair<String, int>("UP", KEY_UP));
         keyBindingMap.Insert(MakePair<String, int>("DOWN", KEY_DOWN));
+        keyBindingMap.Insert(MakePair<String, int>("PAGEUP", KEY_PAGEUP));
+        keyBindingMap.Insert(MakePair<String, int>("PAGEDOWN", KEY_PAGEDOWN));
         keyBindingMap.Insert(MakePair<String, int>("F1", KEY_F1));
         keyBindingMap.Insert(MakePair<String, int>("F2", KEY_F2));
         keyBindingMap.Insert(MakePair<String, int>("F3", KEY_F3));
@@ -273,9 +276,22 @@ static void PopulateKeyBindingMap(HashMap<String, int>& keyBindingMap)
     }
 }
 
+static void PopulateMouseButtonBindingMap(HashMap<String, int>& mouseButtonBindingMap)
+{
+    if (mouseButtonBindingMap.Empty())
+    {
+        mouseButtonBindingMap.Insert(MakePair<String, int>("LEFT", SDL_BUTTON_LEFT));
+        mouseButtonBindingMap.Insert(MakePair<String, int>("MIDDLE", SDL_BUTTON_MIDDLE));
+        mouseButtonBindingMap.Insert(MakePair<String, int>("RIGHT", SDL_BUTTON_RIGHT));
+        mouseButtonBindingMap.Insert(MakePair<String, int>("X1", SDL_BUTTON_X1));
+        mouseButtonBindingMap.Insert(MakePair<String, int>("X2", SDL_BUTTON_X2));
+    }
+}
+
 SDL_JoystickID Input::AddScreenJoystick(XMLFile* layoutFile, XMLFile* styleFile)
 {
     static HashMap<String, int> keyBindingMap;
+    static HashMap<String, int> mouseButtonBindingMap;
 
     if (!graphics_)
     {
@@ -305,7 +321,7 @@ SDL_JoystickID Input::AddScreenJoystick(XMLFile* layoutFile, XMLFile* styleFile)
     SDL_JoystickID joystickID = SCREEN_JOYSTICK_START_ID;
     while (joysticks_.Contains(joystickID))
         ++joystickID;
-    
+
     JoystickState& state = joysticks_[joystickID];
     state.joystickID_ = joystickID;
     state.name_ = screenJoystick->GetName();
@@ -349,6 +365,21 @@ SDL_JoystickID Input::AddScreenJoystick(XMLFile* layoutFile, XMLFile* styleFile)
                 if (keyBinding != M_MAX_INT)
                     element->SetVar(VAR_BUTTON_KEY_BINDING, keyBinding);
             }
+
+            // Check whether the button has mouse binding
+            text = dynamic_cast<Text*>(element->GetChild("MouseButtonBinding", false));
+            if (text)
+            {
+                text->SetVisible(false);
+                const String& mouseButton = text->GetText();
+                PopulateMouseButtonBindingMap(mouseButtonBindingMap);
+
+                HashMap<String, int>::Iterator i = mouseButtonBindingMap.Find(mouseButton);
+                if (i != mouseButtonBindingMap.End())
+                    element->SetVar(VAR_BUTTON_MOUSE_BUTTON_BINDING, i->second_);
+                else
+                    LOGERRORF("Unsupported mouse button binding: %s", mouseButton.CString());
+            }
         }
         else if (name.StartsWith("Axis"))
         {
@@ -437,7 +468,7 @@ bool Input::RemoveScreenJoystick(SDL_JoystickID id)
         LOGERRORF("Failed to remove non-existing screen joystick ID #%d", id);
         return false;
     }
-    
+
     JoystickState& state = joysticks_[id];
     if (!state.screenJoystick_)
     {
@@ -533,7 +564,7 @@ SDL_JoystickID Input::OpenJoystick(unsigned index)
 
     state.buttons_.Resize(SDL_JoystickNumButtons(joystick));
     state.axes_.Resize(SDL_JoystickNumAxes(joystick));
-    
+
     // When the joystick is a controller, make sure there's enough axes & buttons for the standard controller mappings
     if (state.controller_)
     {
@@ -542,7 +573,7 @@ SDL_JoystickID Input::OpenJoystick(unsigned index)
         if (state.axes_.Size() < SDL_CONTROLLER_AXIS_MAX)
             state.axes_.Resize(SDL_CONTROLLER_AXIS_MAX);
     }
-    
+
     state.buttonPress_.Resize(state.buttons_.Size());
     state.hats_.Resize(SDL_JoystickNumHats(joystick));
 
@@ -688,7 +719,7 @@ JoystickState* Input::GetJoystickByIndex(unsigned index)
         if (compare++ == index)
             return &(i->second_);
     }
-    
+
     return 0;
 }
 
@@ -1159,7 +1190,7 @@ void Input::HandleSDLEvent(void* sdlEvent)
             unsigned button = evt.jbutton.button;
             SDL_JoystickID joystickID = evt.jbutton.which;
             JoystickState& state = joysticks_[joystickID];
-            
+
             // Skip ordinary joystick event for a controller
             if (!state.controller_)
             {
@@ -1254,7 +1285,7 @@ void Input::HandleSDLEvent(void* sdlEvent)
             unsigned button = evt.cbutton.button;
             SDL_JoystickID joystickID = evt.cbutton.which;
             JoystickState& state = joysticks_[joystickID];
-            
+
             VariantMap& eventData = GetEventDataMap();
             eventData[P_JOYSTICKID] = joystickID;
             eventData[P_BUTTON] = button;
@@ -1421,9 +1452,10 @@ void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventDat
         if (eventType == E_TOUCHMOVE)
             return;
 
-        // Determine whether to inject a joystick event or keyboard event
-        Variant variant = element->GetVar(VAR_BUTTON_KEY_BINDING);
-        if (variant.IsEmpty())
+        // Determine whether to inject a joystick event or keyboard/mouse event
+        Variant keyBindingVar = element->GetVar(VAR_BUTTON_KEY_BINDING);
+        Variant mouseButtonBindingVar = element->GetVar(VAR_BUTTON_MOUSE_BUTTON_BINDING);
+        if (keyBindingVar.IsEmpty() && mouseButtonBindingVar.IsEmpty())
         {
             evt.type = eventType == E_TOUCHBEGIN ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
             evt.jbutton.which = joystickID;
@@ -1431,15 +1463,26 @@ void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventDat
         }
         else
         {
-            evt.type = eventType == E_TOUCHBEGIN ? SDL_KEYDOWN : SDL_KEYUP;
-            evt.key.keysym.sym = variant.GetInt();
-            evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
+            if (!keyBindingVar.IsEmpty())
+            {
+                evt.type = eventType == E_TOUCHBEGIN ? SDL_KEYDOWN : SDL_KEYUP;
+                evt.key.keysym.sym = keyBindingVar.GetInt();
+                evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
+            }
+            if (!mouseButtonBindingVar.IsEmpty())
+            {
+                // Mouse button are sent as extra events besides key events
+                SDL_Event evt;
+                evt.type = eventType == E_TOUCHBEGIN ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
+                evt.button.button = mouseButtonBindingVar.GetInt();
+                HandleSDLEvent(&evt);
+            }
         }
     }
     else if (name.StartsWith("Hat"))
     {
-        Variant variant = element->GetVar(VAR_BUTTON_KEY_BINDING);
-        if (variant.IsEmpty())
+        Variant keyBindingVar = element->GetVar(VAR_BUTTON_KEY_BINDING);
+        if (keyBindingVar.IsEmpty())
         {
             evt.type = SDL_JOYHATMOTION;
             evt.jaxis.which = joystickID;
@@ -1461,7 +1504,7 @@ void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventDat
         else
         {
             // Hat is binded by 4 keys, like 'WASD'
-            String keyBinding = variant.GetString();
+            String keyBinding = keyBindingVar.GetString();
 
             if (eventType == E_TOUCHEND)
             {

+ 6 - 3
Source/Engine/Input/InputEvents.h

@@ -27,6 +27,7 @@
 #include <SDL_joystick.h>
 #include <SDL_gamecontroller.h>
 #include <SDL_keycode.h>
+#include <SDL_mouse.h>
 
 namespace Urho3D
 {
@@ -215,9 +216,11 @@ EVENT(E_EXITREQUESTED, ExitRequested)
 {
 }
 
-static const int MOUSEB_LEFT = 1;
-static const int MOUSEB_MIDDLE = 2;
-static const int MOUSEB_RIGHT = 4;
+static const int MOUSEB_LEFT = SDL_BUTTON_LMASK;
+static const int MOUSEB_MIDDLE = SDL_BUTTON_MMASK;
+static const int MOUSEB_RIGHT = SDL_BUTTON_RMASK;
+static const int MOUSEB_X1 = SDL_BUTTON_X1MASK;
+static const int MOUSEB_X2 = SDL_BUTTON_X2MASK;
 
 static const int QUAL_SHIFT = 1;
 static const int QUAL_CTRL = 2;

+ 2 - 0
Source/Engine/LuaScript/pkgs/Engine/Console.pkg

@@ -16,6 +16,7 @@ class Console : public Object
     XMLFile* GetDefaultStyle() const;
     BorderImage* GetBackground() const;
     LineEdit* GetLineEdit() const;
+    Button* GetCloseButton() const;
     bool IsVisible() const;
     bool IsAutoVisibleOnError() const;
     const String& GetCommandInterpreter() const;
@@ -30,6 +31,7 @@ class Console : public Object
     tolua_property__get_set XMLFile* defaultStyle;
     tolua_readonly tolua_property__get_set BorderImage* background;
     tolua_readonly tolua_property__get_set LineEdit* lineEdit;
+    tolua_readonly tolua_property__get_set Button* closeButton;
     tolua_property__is_set bool visible;
     tolua_property__is_set bool autoVisibleOnError;
     tolua_property__get_set String commandInterpreter;

+ 2 - 1
Source/Engine/Script/EngineAPI.cpp

@@ -60,6 +60,7 @@ static void RegisterConsole(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Console", "const String& get_historyRow(uint) const", asMETHOD(Console, GetHistoryRow), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "BorderImage@+ get_background() const", asMETHOD(Console, GetBackground), asCALL_THISCALL);
     engine->RegisterObjectMethod("Console", "LineEdit@+ get_lineEdit() const", asMETHOD(Console, GetLineEdit), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Console", "Button@+ get_closeButton() const", asMETHOD(Console, GetCloseButton), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Console@+ get_console()", asFUNCTION(GetConsole), asCALL_CDECL);
 }
 
@@ -75,7 +76,7 @@ static void RegisterDebugHud(asIScriptEngine* engine)
     engine->RegisterGlobalProperty("const uint DEBUGHUD_SHOW_MODE", (void*)&DEBUGHUD_SHOW_MODE);
     engine->RegisterGlobalProperty("const uint DEBUGHUD_SHOW_PROFILER", (void*)&DEBUGHUD_SHOW_PROFILER);
     engine->RegisterGlobalProperty("const uint DEBUGHUD_SHOW_ALL", (void*)&DEBUGHUD_SHOW_ALL);
-    
+
     RegisterObject<Console>(engine, "DebugHud");
     engine->RegisterObjectMethod("DebugHud", "void Toggle(uint)", asMETHOD(DebugHud, Toggle), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugHud", "void ToggleAll()", asMETHOD(DebugHud, ToggleAll), asCALL_THISCALL);

+ 10 - 0
Source/Samples/01_HelloWorld/HelloWorld.h

@@ -40,6 +40,16 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct a new Text instance, containing the 'Hello World' String, and add it to the UI root element.
     void CreateText();

+ 11 - 1
Source/Samples/02_HelloGUI/HelloGUI.h

@@ -51,6 +51,16 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Create and initialize a Window control.
     void InitWindow();
@@ -68,7 +78,7 @@ private:
     void HandleControlClicked(StringHash eventType, VariantMap& eventData);
     /// Handle close button pressed and released.
     void HandleClosePressed(StringHash eventType, VariantMap& eventData);
-    
+
     /// The Window.
     SharedPtr<Window> window_;
     /// The UI's root UIElement.

+ 10 - 0
Source/Samples/03_Sprites/Sprites.h

@@ -41,6 +41,16 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the sprites.
     void CreateSprites();

+ 15 - 0
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.h

@@ -50,6 +50,21 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 15 - 0
Source/Samples/07_Billboards/Billboards.h

@@ -48,6 +48,21 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 23 - 0
Source/Samples/08_Decals/Decals.h

@@ -50,6 +50,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Paint</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 48 - 1
Source/Samples/09_MultipleViewports/MultipleViewports.h

@@ -47,6 +47,53 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element\">"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button3\" />"
+        "            <attribute name=\"Position\" value=\"-120 -120\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"FXAA\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "                <attribute name=\"Text\" value=\"F\" />"
+        "            </element>"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Bloom</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"B\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -62,7 +109,7 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Rear-facing camera scene node.
     SharedPtr<Node> rearCameraNode_;
     /// Flag for drawing debug geometry.

+ 24 - 1
Source/Samples/11_Physics/Physics.h

@@ -49,6 +49,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Spawn</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -66,7 +89,7 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Flag for drawing debug geometry.
     bool drawDebug_;
 };

+ 24 - 1
Source/Samples/12_PhysicsStressTest/PhysicsStressTest.h

@@ -48,6 +48,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Spawn</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -65,7 +88,7 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Flag for drawing debug geometry.
     bool drawDebug_;
 };

+ 24 - 1
Source/Samples/13_Ragdolls/Ragdolls.h

@@ -48,6 +48,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Spawn</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -65,7 +88,7 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Flag for drawing debug geometry.
     bool drawDebug_;
 };

+ 13 - 0
Source/Samples/14_SoundEffects/SoundEffects.h

@@ -48,6 +48,19 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button2']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Create the UI and subscribes to UI events.
     void CreateUI();

+ 74 - 1
Source/Samples/15_Navigation/Navigation.h

@@ -52,6 +52,79 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element\">"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button3\" />"
+        "            <attribute name=\"Position\" value=\"-120 -120\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"Teleport\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "                <attribute name=\"Text\" value=\"LSHIFT\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "                <attribute name=\"Text\" value=\"LEFT\" />"
+        "            </element>"
+        "        </element>"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button4\" />"
+        "            <attribute name=\"Position\" value=\"-120 -12\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"Obstacles\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "                <attribute name=\"Text\" value=\"MIDDLE\" />"
+        "            </element>"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Set</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -77,7 +150,7 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle the post-render update event.
     void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Last calculated path.
     PODVector<Vector3> currentPath_;
     /// Path end position.

+ 13 - 0
Source/Samples/16_Chat/Chat.h

@@ -49,6 +49,19 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button2']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Create the UI.
     void CreateUI();

+ 11 - 1
Source/Samples/17_SceneReplication/SceneReplication.h

@@ -52,6 +52,16 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();
@@ -87,7 +97,7 @@ private:
     void HandleClientDisconnected(StringHash eventType, VariantMap& eventData);
     /// Handle remote event from server which tells our controlled object node ID.
     void HandleClientObjectID(StringHash eventType, VariantMap& eventData);
-    
+
     /// Mapping from client connections to controllable objects.
     HashMap<Connection*, WeakPtr<Node> > serverObjects_;
     /// Button container element.

+ 96 - 81
Source/Samples/18_CharacterDemo/CharacterDemo.cpp

@@ -55,7 +55,7 @@ DEFINE_APPLICATION_MAIN(CharacterDemo)
 
 CharacterDemo::CharacterDemo(Context* context) :
     Sample(context),
-    touch_(new Touch(context))
+    firstPerson_(false)
 {
     // Register factory and attributes for the Character component so it can be created via CreateComponent, and loaded / saved
     Character::RegisterObject(context);
@@ -69,44 +69,39 @@ void CharacterDemo::Start()
 {
     // Execute base class startup
     Sample::Start();
-    
+    if (touchEnabled_)
+        touch_ = new Touch(context_);
+
     // Create static scene content
     CreateScene();
-    
+
     // Create the controllable character
     CreateCharacter();
-    
+
     // Create the UI content
     CreateInstructions();
-    
+
     // Subscribe to necessary events
     SubscribeToEvents();
-
-    // Initialize touch input on Android & iOS
-    if (GetPlatform() == "Android" || GetPlatform() == "iOS")
-    {
-        SetLogoVisible(false);
-        touch_->InitTouchInput();
-    }
 }
 
 void CharacterDemo::CreateScene()
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
+
     scene_ = new Scene(context_);
-    
+
     // Create scene subsystem components
     scene_->CreateComponent<Octree>();
     scene_->CreateComponent<PhysicsWorld>();
-    
+
     // Create camera and define viewport. We will be doing load / save, so it's convenient to create the camera outside the scene,
     // so that it won't be destroyed and recreated, and we don't have to redefine the viewport on load
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(300.0f);
     GetSubsystem<Renderer>()->SetViewport(0, new Viewport(context_, scene_, camera));
-    
+
     // Create static scene content. First create a zone for ambient lighting and fog control
     Node* zoneNode = scene_->CreateChild("Zone");
     Zone* zone = zoneNode->CreateComponent<Zone>();
@@ -115,7 +110,7 @@ void CharacterDemo::CreateScene()
     zone->SetFogStart(100.0f);
     zone->SetFogEnd(300.0f);
     zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.0f));
-    
+
     // Create a directional light with cascaded shadow mapping
     Node* lightNode = scene_->CreateChild("DirectionalLight");
     lightNode->SetDirection(Vector3(0.3f, -0.5f, 0.425f));
@@ -133,14 +128,14 @@ void CharacterDemo::CreateScene()
     StaticModel* object = floorNode->CreateComponent<StaticModel>();
     object->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
     object->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
-    
+
     RigidBody* body = floorNode->CreateComponent<RigidBody>();
     // Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
     // inside geometry
     body->SetCollisionLayer(2);
     CollisionShape* shape = floorNode->CreateComponent<CollisionShape>();
     shape->SetBox(Vector3::ONE);
-    
+
     // Create mushrooms of varying sizes
     const unsigned NUM_MUSHROOMS = 60;
     for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
@@ -153,19 +148,19 @@ void CharacterDemo::CreateScene()
         object->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
         object->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
         object->SetCastShadows(true);
-        
+
         RigidBody* body = objectNode->CreateComponent<RigidBody>();
         body->SetCollisionLayer(2);
         CollisionShape* shape = objectNode->CreateComponent<CollisionShape>();
         shape->SetTriangleMesh(object->GetModel(), 0);
     }
-    
+
     // Create movable boxes. Let them fall from the sky at first
     const unsigned NUM_BOXES = 100;
     for (unsigned i = 0; i < NUM_BOXES; ++i)
     {
         float scale = Random(2.0f) + 0.5f;
-        
+
         Node* objectNode = scene_->CreateChild("Box");
         objectNode->SetPosition(Vector3(Random(180.0f) - 90.0f, Random(10.0f) + 10.0f, Random(180.0f) - 90.0f));
         objectNode->SetRotation(Quaternion(Random(360.0f), Random(360.0f), Random(360.0f)));
@@ -174,7 +169,7 @@ void CharacterDemo::CreateScene()
         object->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
         object->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
         object->SetCastShadows(true);
-        
+
         RigidBody* body = objectNode->CreateComponent<RigidBody>();
         body->SetCollisionLayer(2);
         // Bigger boxes will be heavier and harder to move
@@ -182,26 +177,22 @@ void CharacterDemo::CreateScene()
         CollisionShape* shape = objectNode->CreateComponent<CollisionShape>();
         shape->SetBox(Vector3::ONE);
     }
-
-    // Pass knowledge of the scene & camera node to the Touch helper object
-    touch_->scene_ = scene_;
-    touch_->cameraNode_ = cameraNode_;
 }
 
 void CharacterDemo::CreateCharacter()
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
+
     Node* objectNode = scene_->CreateChild("Jack");
     objectNode->SetPosition(Vector3(0.0f, 1.0f, 0.0f));
-    
+
     // Create the rendering component + animation controller
     AnimatedModel* object = objectNode->CreateComponent<AnimatedModel>();
     object->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
     object->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
     object->SetCastShadows(true);
     objectNode->CreateComponent<AnimationController>();
-    
+
     // Set the head bone for manual control
     object->GetSkeleton().GetBone("Bip01_Head")->animated_ = false;
 
@@ -209,18 +200,18 @@ void CharacterDemo::CreateCharacter()
     RigidBody* body = objectNode->CreateComponent<RigidBody>();
     body->SetCollisionLayer(1);
     body->SetMass(1.0f);
-    
+
     // Set zero angular factor so that physics doesn't turn the character on its own.
     // Instead we will control the character yaw manually
     body->SetAngularFactor(Vector3::ZERO);
-    
+
     // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
     body->SetCollisionEventMode(COLLISION_ALWAYS);
-    
+
     // Set a capsule shape for collision
     CollisionShape* shape = objectNode->CreateComponent<CollisionShape>();
     shape->SetCapsule(0.7f, 1.8f, Vector3(0.0f, 0.9f, 0.0f));
-    
+
     // Create the character logic component, which takes care of steering the rigidbody
     // Remember it so that we can set the controls. Use a WeakPtr because the scene hierarchy already owns it
     // and keeps it alive as long as it's not removed from the hierarchy
@@ -231,7 +222,7 @@ void CharacterDemo::CreateInstructions()
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     UI* ui = GetSubsystem<UI>();
-    
+
     // Construct new Text object, set string to display and font to use
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     instructionText->SetText(
@@ -242,7 +233,7 @@ void CharacterDemo::CreateInstructions()
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     instructionText->SetTextAlignment(HA_CENTER);
-    
+
     // Position the text relative to the screen center
     instructionText->SetHorizontalAlignment(HA_CENTER);
     instructionText->SetVerticalAlignment(VA_CENTER);
@@ -253,15 +244,18 @@ void CharacterDemo::SubscribeToEvents()
 {
     // Subscribe to Update event for setting the character controls before physics simulation
     SubscribeToEvent(E_UPDATE, HANDLER(CharacterDemo, HandleUpdate));
-    
+
     // Subscribe to PostUpdate event for updating the camera position after physics simulation
     SubscribeToEvent(E_POSTUPDATE, HANDLER(CharacterDemo, HandlePostUpdate));
+
+    // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace Update;
-    
+
     float timeStep = eventData[P_TIMESTEP].GetFloat();
     Input* input = GetSubsystem<Input>();
 
@@ -270,52 +264,73 @@ void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
         // Clear previous controls
         character_->controls_.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
 
-        if (touch_->touchEnabled_)
-        {
-            // Update controls using touch (mobile)
+        // Update controls using touch utility class
+        if (touch_)
             touch_->UpdateTouches(character_->controls_);
-        }
-        else
+
+        // Update controls using keys
+        UI* ui = GetSubsystem<UI>();
+        if (!ui->GetFocusElement())
         {
-            // Update controls using keys (desktop)
-            UI* ui = GetSubsystem<UI>();
-             
-            if (!ui->GetFocusElement())
+            if (!touch_ || !touch_->useGyroscope_)
             {
                 character_->controls_.Set(CTRL_FORWARD, input->GetKeyDown('W'));
                 character_->controls_.Set(CTRL_BACK, input->GetKeyDown('S'));
                 character_->controls_.Set(CTRL_LEFT, input->GetKeyDown('A'));
                 character_->controls_.Set(CTRL_RIGHT, input->GetKeyDown('D'));
-                character_->controls_.Set(CTRL_JUMP, input->GetKeyDown(KEY_SPACE));
-                
-                // Add character yaw & pitch from the mouse motion
-                character_->controls_.yaw_ += (float)input->GetMouseMoveX() * YAW_SENSITIVITY;
-                character_->controls_.pitch_ += (float)input->GetMouseMoveY() * YAW_SENSITIVITY;
-                // Limit pitch
-                character_->controls_.pitch_ = Clamp(character_->controls_.pitch_, -80.0f, 80.0f);
-                
-                // Switch between 1st and 3rd person
-                if (input->GetKeyPress('F'))
-                    touch_->firstPerson_ = touch_->newFirstPerson_ = !touch_->firstPerson_;
-
-                // Check for loading / saving the scene
-                if (input->GetKeyPress(KEY_F5))
-                {
-                    File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml",
-                        FILE_WRITE);
-                    scene_->SaveXML(saveFile);
-                }
-                if (input->GetKeyPress(KEY_F7))
+            }
+            character_->controls_.Set(CTRL_JUMP, input->GetKeyDown(KEY_SPACE));
+
+            // Add character yaw & pitch from the mouse motion or touch input
+            if (touchEnabled_)
+            {
+                for (unsigned i = 0; i < input->GetNumTouches(); ++i)
                 {
-                    File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml", FILE_READ);
-                    scene_->LoadXML(loadFile);
-                    // After loading we have to reacquire the weak pointer to the Character component, as it has been recreated
-                    // Simply find the character's scene node by name as there's only one of them
-                    Node* characterNode = scene_->GetChild("Jack", true);
-                    if (characterNode)
-                        character_ = characterNode->GetComponent<Character>();
+                    TouchState* state = input->GetTouch(i);
+                    if (!state->touchedElement_)    // Touch on empty space
+                    {
+                        Camera* camera = cameraNode_->GetComponent<Camera>();
+                        if (!camera)
+                            return;
+
+                        Graphics* graphics = GetSubsystem<Graphics>();
+                        character_->controls_.yaw_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.x_;
+                        character_->controls_.pitch_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.y_;
+                    }
                 }
             }
+            else
+            {
+                character_->controls_.yaw_ += (float)input->GetMouseMoveX() * YAW_SENSITIVITY;
+                character_->controls_.pitch_ += (float)input->GetMouseMoveY() * YAW_SENSITIVITY;
+            }
+            // Limit pitch
+            character_->controls_.pitch_ = Clamp(character_->controls_.pitch_, -80.0f, 80.0f);
+
+            // Switch between 1st and 3rd person
+            if (input->GetKeyPress('F'))
+                firstPerson_ = !firstPerson_;
+
+            // Turn on/off gyroscope on mobile platform
+            if (touch_ && input->GetKeyPress('G'))
+                touch_->useGyroscope_ = !touch_->useGyroscope_;
+
+            // Check for loading / saving the scene
+            if (input->GetKeyPress(KEY_F5))
+            {
+                File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml", FILE_WRITE);
+                scene_->SaveXML(saveFile);
+            }
+            if (input->GetKeyPress(KEY_F7))
+            {
+                File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml", FILE_READ);
+                scene_->LoadXML(loadFile);
+                // After loading we have to reacquire the weak pointer to the Character component, as it has been recreated
+                // Simply find the character's scene node by name as there's only one of them
+                Node* characterNode = scene_->GetChild("Jack", true);
+                if (characterNode)
+                    character_ = characterNode->GetComponent<Character>();
+            }
         }
 
         // Set rotation already here so that it's updated every rendering frame instead of every physics frame
@@ -327,13 +342,13 @@ void CharacterDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData
 {
     if (!character_)
         return;
-    
+
     Node* characterNode = character_->GetNode();
-    
+
     // Get camera lookat dir from character yaw + pitch
     Quaternion rot = characterNode->GetRotation();
     Quaternion dir = rot * Quaternion(character_->controls_.pitch_, Vector3::RIGHT);
-    
+
     // Turn head to camera pitch, but limit to avoid unnatural animation
     Node* headNode = characterNode->GetChild("Bip01_Head", true);
     float limitPitch = Clamp(character_->controls_.pitch_, -45.0f, 45.0f);
@@ -344,7 +359,7 @@ void CharacterDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData
     // Correct head orientation because LookAt assumes Z = forward, but the bone has been authored differently (Y = forward)
     headNode->Rotate(Quaternion(0.0f, 90.0f, 90.0f));
 
-    if (touch_->firstPerson_)
+    if (firstPerson_)
     {
         cameraNode_->SetPosition(headNode->GetWorldPosition() + rot * Vector3(0.0f, 0.15f, 0.2f));
         cameraNode_->SetRotation(dir);
@@ -353,16 +368,16 @@ void CharacterDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData
     {
         // Third person camera: position behind the character
         Vector3 aimPoint = characterNode->GetPosition() + rot * Vector3(0.0f, 1.7f, 0.0f);
-        
+
         // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
         Vector3 rayDir = dir * Vector3::BACK;
-        float rayDistance = touch_->cameraDistance_;
+        float rayDistance = touch_ ? touch_->cameraDistance_ : CAMERA_INITIAL_DIST;
         PhysicsRaycastResult result;
         scene_->GetComponent<PhysicsWorld>()->RaycastSingle(result, Ray(aimPoint, rayDir), rayDistance, 2);
         if (result.body_)
             rayDistance = Min(rayDistance, result.distance_);
         rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
-        
+
         cameraNode_->SetPosition(aimPoint + rayDir * rayDistance);
         cameraNode_->SetRotation(dir);
     }

+ 51 - 2
Source/Samples/18_CharacterDemo/CharacterDemo.h

@@ -55,7 +55,54 @@ public:
 
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
-    
+
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element\">"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button3\" />"
+        "            <attribute name=\"Position\" value=\"-120 -120\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"Gyroscope\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "                <attribute name=\"Text\" value=\"G\" />"
+        "            </element>"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">1st/3rd</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"F\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Jump</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Create static scene content.
     void CreateScene();
@@ -69,9 +116,11 @@ private:
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle application post-update. Update camera position after character has moved.
     void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Touch utility object.
     SharedPtr<Touch> touch_;
     /// The controllable character component.
     WeakPtr<Character> character_;
+    /// First person camera flag.
+    bool firstPerson_;
 };

+ 24 - 183
Source/Samples/18_CharacterDemo/Touch.cpp

@@ -20,38 +20,22 @@
 // THE SOFTWARE.
 //
 
-#include "BorderImage.h"
-#include "Camera.h"
 #include "Character.h"
 #include "Controls.h"
-#include "Drawable.h"
 #include "Graphics.h"
 #include "Input.h"
-#include "InputEvents.h"
-#include "Log.h"
-#include "Octree.h"
-#include "PhysicsWorld.h"
 #include "Renderer.h"
-#include "ResourceCache.h"
-#include "RigidBody.h"
-#include "Scene.h"
-#include "Texture2D.h"
 #include "Touch.h"
-#include "UI.h"
+#include "UIElement.h"
+
+const float TOUCH_SENSITIVITY = 5.0f;
+const float GYROSCOPE_THRESHOLD = 0.1f;
 
 Touch::Touch(Context* context) :
     Object(context),
     cameraDistance_(CAMERA_INITIAL_DIST),
-    touchButtonSize_(96),
-    touchButtonBorder_(12),
-    moveTouchID_(-1),
-    rotateTouchID_(-1),
-    fireTouchID_(-1),
-    firstPerson_(false),
-    newFirstPerson_(false),
-    shadowMode_(false),
     zoom_(false),
-    touchEnabled_(false)
+    useGyroscope_(false)
 {
 }
 
@@ -59,122 +43,40 @@ Touch::~Touch()
 {
 }
 
-void Touch::InitTouchInput()
-{
-    UI* ui = GetSubsystem<UI>();
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-
-    moveButton_ = ui->GetRoot()->CreateChild<BorderImage>();
-    moveButton_->SetTexture(cache->GetResource<Texture2D>("Textures/TouchInput.png"));
-    moveButton_->SetImageRect(IntRect(0, 0, 96, 96));
-    moveButton_->SetAlignment(HA_LEFT, VA_BOTTOM);
-    moveButton_->SetPosition(touchButtonBorder_, -touchButtonBorder_);
-    moveButton_->SetSize(touchButtonSize_, touchButtonSize_);
-    moveButton_->SetOpacity(0.25f);
-
-    fireButton_ = ui->GetRoot()->CreateChild<BorderImage>();
-    fireButton_->SetTexture(cache->GetResource<Texture2D>("Textures/TouchInput.png"));
-    fireButton_->SetImageRect(IntRect(96, 0, 192, 96));
-    fireButton_->SetAlignment(HA_RIGHT, VA_BOTTOM);
-    fireButton_->SetPosition(-touchButtonBorder_, -touchButtonBorder_);
-    fireButton_->SetSize(touchButtonSize_, touchButtonSize_);
-    fireButton_->SetOpacity(0.25f);
-    
-    touchEnabled_ = true;
-}
-
-void Touch::SubscribeToTouchEvents()
-{
-    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(Touch, HandleTouchBegin));
-    SubscribeToEvent(E_TOUCHEND, HANDLER(Touch, HandleTouchEnd));
-}
-
 void Touch::UpdateTouches(Controls& controls) // Called from HandleUpdate
 {
-    if (!scene_ || !cameraNode_)
-        return;
-
-    Camera* camera = cameraNode_->GetComponent<Camera>();
-    if (!camera)
-        return;
-    
     zoom_ = false; // reset bool
 
     Graphics* graphics = GetSubsystem<Graphics>();
     Input* input = GetSubsystem<Input>();
 
-    // Touch Inputs
-    if (touchEnabled_)
+    // Zoom in/out
+    if (input->GetNumTouches() == 2)
     {
-        // Zoom in/out
-        if (input->GetNumTouches() == 2)
-        {
-            TouchState* touch1 = input->GetTouch(0);
-            TouchState* touch2 = input->GetTouch(1);
-
-            // Check for zoom pattern (touches moving in opposite directions)
-            if ((touch1->delta_.y_ > 0 && touch2->delta_.y_ < 0) || (touch1->delta_.y_ < 0 && touch2->delta_.y_ > 0)) 
-                zoom_ = true;
-            else 
-                zoom_ = false;
-
-            if (zoom_)
-            {
-                int sens = 0;
-                // Check for zoom direction (in/out)
-                if (Abs(touch1->position_.y_ - touch2->position_.y_) > Abs(touch1->lastPosition_.y_ - touch2->lastPosition_.y_))
-                    sens = -1;
-                else
-                    sens = 1;
-                cameraDistance_ += Abs(touch1->delta_.y_ - touch2->delta_.y_) * sens * TOUCH_SENSITIVITY_TODO / 50.0f;
-                cameraDistance_ = Clamp(cameraDistance_, CAMERA_MIN_DIST, CAMERA_MAX_DIST); // Restrict zoom range to [1;20]
-            }
-        }
+        TouchState* touch1 = input->GetTouch(0);
+        TouchState* touch2 = input->GetTouch(1);
 
-        // Switch 1st/3rd person mode
-        if (input->GetNumTouches() == 3)
-            newFirstPerson_ = !firstPerson_;
+        // Check for zoom pattern (touches moving in opposite directions)
+        if ((touch1->delta_.y_ > 0 && touch2->delta_.y_ < 0) || (touch1->delta_.y_ < 0 && touch2->delta_.y_ > 0))
+            zoom_ = true;
+        else
+            zoom_ = false;
 
-        // Switch draw debug
-        if (input->GetNumTouches() == 4)
-            shadowMode_ = !GetSubsystem<Renderer>()->GetDrawShadows();
-
-        // Rotate and Move
-        if (!zoom_)
+        if (zoom_)
         {
-            for (unsigned i = 0; i < input->GetNumTouches(); ++i)
-            {
-                TouchState* touch = input->GetTouch(i);
-
-                if (touch->touchID_ == rotateTouchID_)
-                {
-                    controls.yaw_ += TOUCH_SENSITIVITY_TODO * camera->GetFov() / (float)graphics->GetHeight() * touch->delta_.x_;
-                    controls.pitch_ += TOUCH_SENSITIVITY_TODO * camera->GetFov() / (float)graphics->GetHeight() * touch->delta_.y_;
-                    controls.pitch_ = Clamp(controls.pitch_, -80.0f, 80.0f); // Limit pitch
-                }
-
-                if (touch->touchID_ == moveTouchID_)
-                {
-                    int relX = touch->position_.x_ - moveButton_->GetScreenPosition().x_ - touchButtonSize_ / 2;
-                    int relY = touch->position_.y_ - moveButton_->GetScreenPosition().y_ - touchButtonSize_ / 2;
-                    if (relY < 0 && Abs(relX * 3 / 2) < Abs(relY))
-                        controls.Set(CTRL_FORWARD, true);
-                    if (relY > 0 && Abs(relX * 3 / 2) < Abs(relY))
-                        controls.Set(CTRL_BACK, true);
-                    if (relX < 0 && Abs(relY * 3 / 2) < Abs(relX))
-                        controls.Set(CTRL_LEFT, true);
-                    if (relX > 0 && Abs(relY * 3 / 2) < Abs(relX))
-                        controls.Set(CTRL_RIGHT, true);
-                }
-            }
+            int sens = 0;
+            // Check for zoom direction (in/out)
+            if (Abs(touch1->position_.y_ - touch2->position_.y_) > Abs(touch1->lastPosition_.y_ - touch2->lastPosition_.y_))
+                sens = -1;
+            else
+                sens = 1;
+            cameraDistance_ += Abs(touch1->delta_.y_ - touch2->delta_.y_) * sens * TOUCH_SENSITIVITY / 50.0f;
+            cameraDistance_ = Clamp(cameraDistance_, CAMERA_MIN_DIST, CAMERA_MAX_DIST); // Restrict zoom range to [1;20]
         }
-
-        if (fireTouchID_ >= 0)
-            controls.Set(CTRL_JUMP, true);
     }
 
     // Gyroscope (emulated by SDL through a virtual joystick)
-    if (input->GetNumJoysticks() > 0) // numJoysticks = 1 on iOS & Android
+    if (useGyroscope_ && input->GetNumJoysticks() > 0)  // numJoysticks = 1 on iOS & Android
     {
         JoystickState* joystick = input->GetJoystickByIndex(0);
         if (joystick->GetNumAxes() >= 2)
@@ -190,64 +92,3 @@ void Touch::UpdateTouches(Controls& controls) // Called from HandleUpdate
         }
     }
 }
-
-void Touch::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
-{
-    using namespace TouchBegin;
-
-    int touchID = eventData[P_TOUCHID].GetInt(); // Get #touches or dragging value
-    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt()); // Get touch coordinates
-
-    UI* ui = GetSubsystem<UI>();
-    UIElement* element = ui->GetElementAt(pos, false); // Get gamepad UIElement touched (if any)
-
-    // Check for gamepad button touched. If none, rotate
-    if (element == moveButton_)
-        moveTouchID_ = touchID;
-    else if (element == fireButton_)
-        fireTouchID_ = touchID;
-    else
-        rotateTouchID_ = touchID;
-
-    // Init Raycast (for example to acquire a target)
-    if (!cameraNode_ || !scene_)
-        return;
-    Camera* camera = cameraNode_->GetComponent<Camera>();
-    if (!camera)
-        return;
-
-    Graphics* graphics = GetSubsystem<Graphics>();
-    Ray cameraRay = camera->GetScreenRay((float)pos.x_ / (float)graphics->GetWidth(), (float)pos.y_ / (float)graphics->GetHeight());
-
-    // Raycast of RigidBodies
-    PhysicsRaycastResult result;
-    scene_->GetComponent<PhysicsWorld>()->RaycastSingle(result, cameraRay, camera->GetFarClip(), 2); // NB: here we restrict targets to layer 2
-    if (result.body_)
-        LOGINFO("Physics raycast hit " + result.body_->GetNode()->GetName());
-
-    // Raycast of drawable components (for targets without physics)
-    PODVector<RayQueryResult> result2;
-    RayOctreeQuery rayQuery(result2, cameraRay, RAY_TRIANGLE, camera->GetFarClip(), DRAWABLE_GEOMETRY);
-    scene_->GetComponent<Octree>()->RaycastSingle(rayQuery);
-    if (result2.Size())
-        LOGINFO("Drawable raycast hit " + result2[0].drawable_->GetNode()->GetName());
-}
-
-void Touch::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
-{
-    using namespace TouchBegin;
-
-    int touchID = eventData[P_TOUCHID].GetInt();
-
-    if (touchID == moveTouchID_)
-        moveTouchID_ = -1;
-    if (touchID == rotateTouchID_)
-        rotateTouchID_ = -1;
-    if (touchID == fireTouchID_)
-        fireTouchID_ = -1;
-
-    // On-release Update
-    firstPerson_ = newFirstPerson_;
-    GetSubsystem<Renderer>()->SetDrawShadows(shadowMode_);
-}
-

+ 2 - 47
Source/Samples/18_CharacterDemo/Touch.h

@@ -28,34 +28,21 @@ using namespace Urho3D;
 
 namespace Urho3D
 {
-    class BorderImage;
     class Controls;
-    class Node;
-    class Scene;
 }
 
-const float TOUCH_SENSITIVITY_TODO = 5.0f;
-const float GYROSCOPE_THRESHOLD = 0.1f;
 const float CAMERA_MIN_DIST = 1.0f;
 const float CAMERA_INITIAL_DIST = 5.0f;
 const float CAMERA_MAX_DIST = 20.0f;
 
 /// Mobile framework for Android/iOS
 /// Gamepad from NinjaSnowWar
-/// Gyroscope (activated by default)
 /// Touches patterns:
 ///     - 1 finger touch  = pick object through raycast
 ///     - 1 or 2 fingers drag  = rotate camera
-///     - 3 fingers touch = switch between first/third person view
-///     - 4 fingers touch = switch shadows on/off
 ///     - 2 fingers sliding in opposite direction (up/down) = zoom in/out
 ///
-/// 3 fingers touch & 4 fingers touch could be used to switch gyroscope on/off, activate/deactivate secondary viewport, activate a panel GUI, switch debug HUD/geometry, toggle console, switch the gyroscope...
-///
 /// Setup:
-/// - On init, call 'InitTouchInput()' on mobile platforms and also set the scene_ & cameraNode_ member variables
-///   -> to detect platform, use 'if (GetPlatform() == "Android" || GetPlatform() == "iOS")' from ProcessUtils.h
-/// - Subscribe to touch events (Begin, Move, End) using 'SubscribeToTouchEvents()'
 /// - Call the update function 'UpdateTouches()' from HandleUpdate or equivalent update handler function
 class Touch : public Object
 {
@@ -67,46 +54,14 @@ public:
     /// Destruct.
     ~Touch();
 
-    /// Initialize the touch UI.
-    void InitTouchInput();
-    /// Start responding to touch events.
-    void SubscribeToTouchEvents();
     /// Update touch controls for the current frame.
     void UpdateTouches(Controls& controls);
-    /// Handle finger touch begin.
-    void HandleTouchBegin(StringHash eventType, VariantMap& eventData);
-    /// Handle finger touch end.
-    void HandleTouchEnd(StringHash eventType, VariantMap& eventData);
 
-    /// Scene. Needs to be assigned by the application.
-    WeakPtr<Scene> scene_;
-    /// Camera node. Needs to be assigned by the application.
-    WeakPtr<Node> cameraNode_;
-    /// On-screen gamepad move button (left side.)
-    WeakPtr<BorderImage> moveButton_;
-    /// On-screen gamepad fire/jump button (right side.)
-    WeakPtr<BorderImage> fireButton_;
-    /// Size of gamepad buttons.
-    int touchButtonSize_;
-    /// Distance of gamepad buttons from the screen corners.
-    int touchButtonBorder_;
-    /// Current ID of the move touch, or -1 for none.
-    int moveTouchID_;
-    /// Current ID of the rotate touch, or -1 for none.
-    int rotateTouchID_;
-    /// Current ID of the fire/jump touch, or -1 for none.
-    int fireTouchID_;
     /// Current camera zoom distance.
     float cameraDistance_;
-    /// Current first person mode flag.
-    bool firstPerson_;
-    /// Pending new first person mode flag.
-    bool newFirstPerson_;
-    /// Pending new shadow mode flag.
-    bool shadowMode_;
     /// Zoom flag.
     bool zoom_;
-    /// Touch input initialized flag.
-    bool touchEnabled_;
+    /// Gyroscope on/off flag.
+    bool useGyroscope_;
 };
 

+ 50 - 27
Source/Samples/19_VehicleDemo/VehicleDemo.cpp

@@ -64,16 +64,16 @@ void VehicleDemo::Start()
 {
     // Execute base class startup
     Sample::Start();
-    
+
     // Create static scene content
     CreateScene();
-    
+
     // Create the controllable vehicle
     CreateVehicle();
-    
+
     // Create the UI content
     CreateInstructions();
-    
+
     // Subscribe to necessary events
     SubscribeToEvents();
 }
@@ -81,20 +81,20 @@ void VehicleDemo::Start()
 void VehicleDemo::CreateScene()
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    
+
     scene_ = new Scene(context_);
-    
+
     // Create scene subsystem components
     scene_->CreateComponent<Octree>();
     scene_->CreateComponent<PhysicsWorld>();
-    
+
     // Create camera and define viewport. We will be doing load / save, so it's convenient to create the camera outside the scene,
     // so that it won't be destroyed and recreated, and we don't have to redefine the viewport on load
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(500.0f);
     GetSubsystem<Renderer>()->SetViewport(0, new Viewport(context_, scene_, camera));
-    
+
     // Create static scene content. First create a zone for ambient lighting and fog control
     Node* zoneNode = scene_->CreateChild("Zone");
     Zone* zone = zoneNode->CreateComponent<Zone>();
@@ -103,7 +103,7 @@ void VehicleDemo::CreateScene()
     zone->SetFogStart(300.0f);
     zone->SetFogEnd(500.0f);
     zone->SetBoundingBox(BoundingBox(-2000.0f, 2000.0f));
-    
+
     // Create a directional light with cascaded shadow mapping
     Node* lightNode = scene_->CreateChild("DirectionalLight");
     lightNode->SetDirection(Vector3(0.3f, -0.5f, 0.425f));
@@ -113,7 +113,7 @@ void VehicleDemo::CreateScene()
     light->SetShadowBias(BiasParameters(0.00025f, 0.5f));
     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
     light->SetSpecularIntensity(0.5f);
-    
+
     // Create heightmap terrain with collision
     Node* terrainNode = scene_->CreateChild("Terrain");
     terrainNode->SetPosition(Vector3::ZERO);
@@ -126,12 +126,12 @@ void VehicleDemo::CreateScene()
     // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
     // terrain patches and other objects behind it
     terrain->SetOccluder(true);
-    
+
     RigidBody* body = terrainNode->CreateComponent<RigidBody>();
     body->SetCollisionLayer(2); // Use layer bitmask 2 for static geometry
     CollisionShape* shape = terrainNode->CreateComponent<CollisionShape>();
     shape->SetTerrain();
-    
+
     // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
     const unsigned NUM_MUSHROOMS = 1000;
     for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
@@ -147,7 +147,7 @@ void VehicleDemo::CreateScene()
         object->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
         object->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
         object->SetCastShadows(true);
-        
+
         RigidBody* body = objectNode->CreateComponent<RigidBody>();
         body->SetCollisionLayer(2);
         CollisionShape* shape = objectNode->CreateComponent<CollisionShape>();
@@ -161,7 +161,7 @@ void VehicleDemo::CreateVehicle()
 
     Node* vehicleNode = scene_->CreateChild("Vehicle");
     vehicleNode->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
-    
+
     // Create the vehicle logic component
     vehicle_ = vehicleNode->CreateComponent<Vehicle>();
     // Create the rendering and physics components
@@ -172,17 +172,17 @@ void VehicleDemo::CreateInstructions()
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     UI* ui = GetSubsystem<UI>();
-    
+
     // Construct new Text object, set string to display and font to use
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     instructionText->SetText(
-        "Use WASD keys to drive, mouse to rotate camera\n"
+        "Use WASD keys to drive, mouse/touch to rotate camera\n"
         "F5 to save scene, F7 to load"
     );
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     instructionText->SetTextAlignment(HA_CENTER);
-    
+
     // Position the text relative to the screen center
     instructionText->SetHorizontalAlignment(HA_CENTER);
     instructionText->SetVerticalAlignment(VA_CENTER);
@@ -193,22 +193,25 @@ void VehicleDemo::SubscribeToEvents()
 {
     // Subscribe to Update event for setting the vehicle controls before physics simulation
     SubscribeToEvent(E_UPDATE, HANDLER(VehicleDemo, HandleUpdate));
-    
+
     // Subscribe to PostUpdate event for updating the camera position after physics simulation
     SubscribeToEvent(E_POSTUPDATE, HANDLER(VehicleDemo, HandlePostUpdate));
+
+    // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void VehicleDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace Update;
-    
+
     float timeStep = eventData[P_TIMESTEP].GetFloat();
     Input* input = GetSubsystem<Input>();
 
     if (vehicle_)
     {
         UI* ui = GetSubsystem<UI>();
-        
+
         // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
         if (!ui->GetFocusElement())
         {
@@ -216,10 +219,30 @@ void VehicleDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
             vehicle_->controls_.Set(CTRL_BACK, input->GetKeyDown('S'));
             vehicle_->controls_.Set(CTRL_LEFT, input->GetKeyDown('A'));
             vehicle_->controls_.Set(CTRL_RIGHT, input->GetKeyDown('D'));
-        
-            // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
-            vehicle_->controls_.yaw_ += (float)input->GetMouseMoveX() * YAW_SENSITIVITY;
-            vehicle_->controls_.pitch_ += (float)input->GetMouseMoveY() * YAW_SENSITIVITY;
+
+            // Add yaw & pitch from the mouse motion or touch input. Used only for the camera, does not affect motion
+            if (touchEnabled_)
+            {
+                for (unsigned i = 0; i < input->GetNumTouches(); ++i)
+                {
+                    TouchState* state = input->GetTouch(i);
+                    if (!state->touchedElement_)    // Touch on empty space
+                    {
+                        Camera* camera = cameraNode_->GetComponent<Camera>();
+                        if (!camera)
+                            return;
+
+                        Graphics* graphics = GetSubsystem<Graphics>();
+                        vehicle_->controls_.yaw_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.x_;
+                        vehicle_->controls_.pitch_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.y_;
+                    }
+                }
+            }
+            else
+            {
+                vehicle_->controls_.yaw_ += (float)input->GetMouseMoveX() * YAW_SENSITIVITY;
+                vehicle_->controls_.pitch_ += (float)input->GetMouseMoveY() * YAW_SENSITIVITY;
+            }
             // Limit pitch
             vehicle_->controls_.pitch_ = Clamp(vehicle_->controls_.pitch_, 0.0f, 80.0f);
 
@@ -250,14 +273,14 @@ void VehicleDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
     if (!vehicle_)
         return;
-    
+
     Node* vehicleNode = vehicle_->GetNode();
-    
+
     // Physics update has completed. Position camera behind vehicle
     Quaternion dir(vehicleNode->GetRotation().YawAngle(), Vector3::UP);
     dir = dir * Quaternion(vehicle_->controls_.yaw_, Vector3::UP);
     dir = dir * Quaternion(vehicle_->controls_.pitch_, Vector3::RIGHT);
-    
+
     Vector3 cameraTargetPos = vehicleNode->GetPosition() - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
     Vector3 cameraStartPos = vehicleNode->GetPosition();
 

+ 23 - 0
Source/Samples/20_HugeObjectCount/HugeObjectCount.h

@@ -50,6 +50,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Group</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"G\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Animation</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 6 - 3
Source/Samples/24_Urho2DSprite/Urho2DSprite.cpp

@@ -99,7 +99,7 @@ void Urho2DSprite::CreateScene()
     {
         SharedPtr<Node> spriteNode(scene_->CreateChild("StaticSprite2D"));
         spriteNode->SetPosition(Vector3(Random(-halfWidth, halfWidth), Random(-halfHeight, halfHeight), 0.0f));
-        
+
         StaticSprite2D* staticSprite = spriteNode->CreateComponent<StaticSprite2D>();
         // Set random color
         staticSprite->SetColor(Color(Random(1.0f), Random(1.0f), Random(1.0f), 1.0f));
@@ -124,7 +124,7 @@ void Urho2DSprite::CreateScene()
 
     SharedPtr<Node> spriteNode(scene_->CreateChild("AnimatedSprite2D"));
     spriteNode->SetPosition(Vector3(0.0f, 0.0f, -1.0f));
-    
+
     AnimatedSprite2D* animatedSprite = spriteNode->CreateComponent<AnimatedSprite2D>();
     // Set animation
     animatedSprite->SetAnimation(animation);
@@ -177,7 +177,7 @@ void Urho2DSprite::MoveCamera(float timeStep)
         cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
     if (input->GetKeyDown('D'))
         cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
-    
+
     if (input->GetKeyDown(KEY_PAGEUP))
     {
         Camera* camera = cameraNode_->GetComponent<Camera>();
@@ -195,6 +195,9 @@ void Urho2DSprite::SubscribeToEvents()
 {
     // Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent(E_UPDATE, HANDLER(Urho2DSprite, HandleUpdate));
+
+    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void Urho2DSprite::HandleUpdate(StringHash eventType, VariantMap& eventData)

+ 23 - 0
Source/Samples/24_Urho2DSprite/Urho2DSprite.h

@@ -46,6 +46,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEUP\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 5 - 0
Source/Samples/25_Urho2DParticle/Urho2DParticle.cpp

@@ -130,6 +130,11 @@ void Urho2DParticle::SetupViewport()
 void Urho2DParticle::SubscribeToEvents()
 {
     SubscribeToEvent(E_MOUSEMOVE, HANDLER(Urho2DParticle, HandleMouseMove));
+    if (touchEnabled_)
+        SubscribeToEvent(E_TOUCHMOVE, HANDLER(Urho2DParticle, HandleMouseMove));
+
+    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void Urho2DParticle::HandleMouseMove(StringHash eventType, VariantMap& eventData)

+ 10 - 0
Source/Samples/25_Urho2DParticle/Urho2DParticle.h

@@ -46,6 +46,16 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 19 - 17
Source/Samples/26_ConsoleInput/ConsoleInput.cpp

@@ -20,6 +20,7 @@
 // THE SOFTWARE.
 //
 
+#include "Button.h"
 #include "Console.h"
 #include "CoreEvents.h"
 #include "Engine.h"
@@ -63,17 +64,17 @@ void ConsoleInput::Start()
 {
     // Execute base class startup
     Sample::Start();
-    
+
     // Subscribe to console commands and the frame update
     SubscribeToEvent(E_CONSOLECOMMAND, HANDLER(ConsoleInput, HandleConsoleCommand));
     SubscribeToEvent(E_UPDATE, HANDLER(ConsoleInput, HandleUpdate));
 
     // Subscribe key down event
     SubscribeToEvent(E_KEYDOWN, HANDLER(ConsoleInput, HandleEscKeyDown));
-    
+
     // Hide logo to make room for the console
     SetLogoVisible(false);
-    
+
     // Show the console by default, make it large. Console will show the text edit field when there is at least one
     // subscriber for the console command event
     Console* console = GetSubsystem<Console>();
@@ -81,16 +82,17 @@ void ConsoleInput::Start()
     console->SetNumBufferedRows(2 * console->GetNumRows());
     console->SetCommandInterpreter(GetTypeName());
     console->SetVisible(true);
-    
+    console->GetCloseButton()->SetVisible(false);
+
     // Show OS mouse cursor
     GetSubsystem<Input>()->SetMouseVisible(true);
-    
+
     // Open the operating system console window (for stdin / stdout) if not open yet
     OpenConsoleWindow();
-    
+
     // Initialize game and print the welcome message
     StartGame();
-    
+
     // Randomize from system clock
     SetRandomSeed(Time::GetSystemTime());
 }
@@ -123,7 +125,7 @@ void ConsoleInput::StartGame()
           "objective is to survive as long as possible. Beware of hunger and the merciless\n"
           "predator cichlid Urho, who appears from time to time. Evading Urho is easier\n"
           "with an empty stomach. Type 'help' for available commands.");
-    
+
     gameOn_ = true;
     foodAvailable_ = false;
     eatenLastTurn_ = false;
@@ -137,7 +139,7 @@ void ConsoleInput::EndGame(const String& message)
     Print(message);
     Print("Game over! You survived " + String(numTurns_) + " turns.\n"
           "Do you want to play again (Y/N)?");
-    
+
     gameOn_ = false;
 }
 
@@ -156,10 +158,10 @@ void ConsoleInput::Advance()
         ++urhoThreat_;
     if (urhoThreat_ == 0 && Random() < 0.2f)
         ++urhoThreat_;
-    
+
     if (urhoThreat_ > 0)
         Print(urhoThreatLevels[urhoThreat_ - 1] + ".");
-    
+
     if ((numTurns_ & 3) == 0 && !eatenLastTurn_)
     {
         ++hunger_;
@@ -171,9 +173,9 @@ void ConsoleInput::Advance()
         else
             Print("You are " + hungerLevels[hunger_] + ".");
     }
-    
+
     eatenLastTurn_ = false;
-    
+
     if (foodAvailable_)
     {
         Print("The floating pieces of fish food are quickly eaten by other fish in the tank.");
@@ -184,7 +186,7 @@ void ConsoleInput::Advance()
         Print("The overhead dispenser drops pieces of delicious fish food to the water!");
         foodAvailable_ = true;
     }
-    
+
     ++numTurns_;
 }
 
@@ -196,7 +198,7 @@ void ConsoleInput::HandleInput(const String& input)
         Print("Empty input given!");
         return;
     }
-    
+
     if (inputLower == "quit" || inputLower == "exit")
         engine_->Exit();
     else if (gameOn_)
@@ -224,7 +226,7 @@ void ConsoleInput::HandleInput(const String& input)
             }
             else
                 Print("There is no food available.");
-            
+
             Advance();
         }
         else if (inputLower == "wait")
@@ -247,7 +249,7 @@ void ConsoleInput::HandleInput(const String& input)
             }
             else
                 Print("There is nothing to hide from.");
-            
+
             Advance();
         }
         else

+ 14 - 1
Source/Samples/26_ConsoleInput/ConsoleInput.h

@@ -39,6 +39,19 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button2']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Handle console command event.
     void HandleConsoleCommand(StringHash eventType, VariantMap& eventData);
@@ -56,7 +69,7 @@ private:
     void HandleInput(const String& input);
     /// Print text to the engine console and standard output.
     void Print(const String& output);
-    
+
     /// Game on flag.
     bool gameOn_;
     /// Food dispensed flag.

+ 8 - 5
Source/Samples/27_Urho2DPhysics/Urho2DPhysics.cpp

@@ -80,7 +80,7 @@ void Urho2DPhysics::CreateScene()
     cameraNode_ = scene_->CreateChild("Camera");
     // Set camera's position
     cameraNode_->SetPosition(Vector3(0.0f, 0.0f, -10.0f));
-    
+
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetOrthographic(true);
 
@@ -89,7 +89,7 @@ void Urho2DPhysics::CreateScene()
 
     // Create 2D physics world component
     PhysicsWorld2D* physicsWorld = scene_->CreateComponent<PhysicsWorld2D>();
-    
+
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     Sprite2D* boxSprite = cache->GetResource<Sprite2D>("Urho2D/Box.png");
     Sprite2D* ballSprite = cache->GetResource<Sprite2D>("Urho2D/Ball.png");
@@ -98,10 +98,10 @@ void Urho2DPhysics::CreateScene()
     Node* groundNode = scene_->CreateChild("Ground");
     groundNode->SetPosition(Vector3(0.0f, -3.0f, 0.0f));
     groundNode->SetScale(Vector3(200.0f, 1.0f, 0.0f));
-    
+
     // Create 2D rigid body for gound
     RigidBody2D* groundBody = groundNode->CreateComponent<RigidBody2D>();
-    
+
     StaticSprite2D* groundSprite = groundNode->CreateComponent<StaticSprite2D>();
     groundSprite->SetSprite(boxSprite);
 
@@ -201,7 +201,7 @@ void Urho2DPhysics::MoveCamera(float timeStep)
         cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
     if (input->GetKeyDown('D'))
         cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
-    
+
     if (input->GetKeyDown(KEY_PAGEUP))
     {
         Camera* camera = cameraNode_->GetComponent<Camera>();
@@ -219,6 +219,9 @@ void Urho2DPhysics::SubscribeToEvents()
 {
     // Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent(E_UPDATE, HANDLER(Urho2DPhysics, HandleUpdate));
+
+    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void Urho2DPhysics::HandleUpdate(StringHash eventType, VariantMap& eventData)

+ 23 - 0
Source/Samples/27_Urho2DPhysics/Urho2DPhysics.h

@@ -45,6 +45,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEUP\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 5 - 2
Source/Samples/28_Urho2DPhysicsRope/Urho2DPhysicsRope.cpp

@@ -190,7 +190,7 @@ void Urho2DPhysicsRope::MoveCamera(float timeStep)
         cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
     if (input->GetKeyDown('D'))
         cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
-    
+
     if (input->GetKeyDown(KEY_PAGEUP))
     {
         Camera* camera = cameraNode_->GetComponent<Camera>();
@@ -208,6 +208,9 @@ void Urho2DPhysicsRope::SubscribeToEvents()
 {
     // Subscribe HandleUpdate() function for processing update events
     SubscribeToEvent(E_UPDATE, HANDLER(Urho2DPhysicsRope, HandleUpdate));
+
+    // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+    UnsubscribeFromEvent(E_SCENEUPDATE);
 }
 
 void Urho2DPhysicsRope::HandleUpdate(StringHash eventType, VariantMap& eventData)
@@ -219,7 +222,7 @@ void Urho2DPhysicsRope::HandleUpdate(StringHash eventType, VariantMap& eventData
 
     // Move the camera, scale movement with time step
     MoveCamera(timeStep);
-    
+
     PhysicsWorld2D* physicsWorld = scene_->GetComponent<PhysicsWorld2D>();
     physicsWorld->DrawDebugGeometry();
 }

+ 23 - 0
Source/Samples/28_Urho2DPhysicsRope/Urho2DPhysicsRope.h

@@ -47,6 +47,29 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEUP\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the scene content.
     void CreateScene();

+ 30 - 1
Source/Samples/29_SoundSynthesis/SoundSynthesis.h

@@ -46,6 +46,35 @@ public:
     /// Setup after engine initialization and before running the main loop.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button2']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+        "        <attribute name=\"Is Visible\" value=\"false\" />"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Up</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"UP\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Down</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"DOWN\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
 private:
     /// Construct the sound stream and start playback.
     void CreateSound();
@@ -57,7 +86,7 @@ private:
     void SubscribeToEvents();
     /// Handle the logic update event.
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
-    
+
     /// Scene node for the sound component.
     SharedPtr<Node> node_;
     /// Sound stream that we update.

+ 8 - 4
Source/Samples/Sample.h

@@ -36,6 +36,8 @@ class Sprite;
 // All Urho3D classes reside in namespace Urho3D
 using namespace Urho3D;
 
+const float TOUCH_SENSITIVITY = 2.0f;
+
 /// Sample class, as framework for all samples.
 ///    - Initialization of the Urho3D engine (in Application class)
 ///    - Modify engine parameters for windowed mode and to show the class name as title
@@ -45,7 +47,7 @@ using namespace Urho3D;
 ///    - Toggle rendering options from the keys 1-8
 ///    - Take screenshot with key 9
 ///    - Handle Esc key down to hide Console or exit application
-///    - Init touch input on mobile platform using screen joystick
+///    - Init touch input on mobile platform using screen joysticks (patched for each individual sample)
 class Sample : public Application
 {
     // Enable type information.
@@ -60,12 +62,14 @@ public:
     /// Setup after engine initialization. Creates the logo, console & debug HUD.
     virtual void Start();
 
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return String::EMPTY; }
     /// Initialize touch input on mobile platform.
     void InitTouchInput();
     /// Control logo visibility.
     void SetLogoVisible(bool enable);
 
-protected:
     /// Logo sprite.
     SharedPtr<Sprite> logoSprite_;
     /// Scene.
@@ -76,6 +80,8 @@ protected:
     float yaw_;
     /// Camera pitch angle.
     float pitch_;
+    /// Flag to indicate whether touch input has been enabled.
+    bool touchEnabled_;
 
 private:
     /// Create logo.
@@ -89,8 +95,6 @@ private:
     /// Handle scene update event to control camera's pitch and yaw for all samples.
     void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
 
-    /// Flag to indicate whether touch input has been enabled.
-    bool touchEnabled_;
     /// Screen joystick index for navigational controls (mobile platforms only).
     unsigned screenJoystickIndex_;
     /// Screen joystick index for settings (mobile platforms only).

+ 38 - 22
Source/Samples/Sample.inl

@@ -23,6 +23,7 @@
 #include "Application.h"
 #include "Camera.h"
 #include "Console.h"
+#include "Cursor.h"
 #include "DebugHud.h"
 #include "Engine.h"
 #include "FileSystem.h"
@@ -39,8 +40,6 @@
 #include "UI.h"
 #include "XMLFile.h"
 
-static const float TOUCH_SENSITIVITY = 2.0f;
-
 Sample::Sample(Context* context) :
     Application(context),
     yaw_(0.0f),
@@ -89,7 +88,19 @@ void Sample::InitTouchInput()
 
         ResourceCache* cache = GetSubsystem<ResourceCache>();
         Input* input = GetSubsystem<Input>();
-        screenJoystickIndex_ = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystick_Samples.xml"), cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
+        XMLFile* layout = cache->GetResource<XMLFile>("UI/ScreenJoystick_Samples.xml");
+        const String& patchString = GetScreenJoystickPatchString();
+        if (!patchString.Empty())
+        {
+            // Patch the screen joystick layout further on demand
+            VectorBuffer buffer;
+            buffer.WriteString(patchString);
+            buffer.Seek(0);
+            SharedPtr<XMLFile> patchFile(new XMLFile(context_));
+            if (patchFile->Load(buffer))
+                layout->Patch(patchFile);
+        }
+        screenJoystickIndex_ = input->AddScreenJoystick(layout, cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
         input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, true);
     }
 }
@@ -194,22 +205,17 @@ void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
         // Preferences / Pause
         if (key == KEY_SELECT && touchEnabled_)
         {
+            paused_ = !paused_;
+
             Input* input = GetSubsystem<Input>();
             if (screenJoystickSettingsIndex_ == M_MAX_UNSIGNED)
             {
+                // Lazy initialization
                 ResourceCache* cache = GetSubsystem<ResourceCache>();
                 screenJoystickSettingsIndex_ = input->AddScreenJoystick(cache->GetResource<XMLFile>("UI/ScreenJoystickSettings_Samples.xml"), cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
-                input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, true);
-				paused_ = true;
             }
             else
-            {
-                paused_ = !paused_;
-                if (paused_)
-                    input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, false);
-                else
-                    input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, true);
-            }
+                input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, paused_);
         }
 
         // Texture quality
@@ -296,16 +302,26 @@ void Sample::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
             TouchState* state = input->GetTouch(i);
             if (!state->touchedElement_)    // Touch on empty space
             {
-                Camera* camera = cameraNode_->GetComponent<Camera>();
-                if (!camera)
-                    return;
-
-                Graphics* graphics = GetSubsystem<Graphics>();
-                yaw_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.x_;
-                pitch_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.y_;
-
-                // Construct new orientation for the camera scene node from yaw and pitch; roll is fixed to zero
-                cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+                if (state->delta_.x_ ||state->delta_.y_)
+                {
+                    Camera* camera = cameraNode_->GetComponent<Camera>();
+                    if (!camera)
+                        return;
+
+                    Graphics* graphics = GetSubsystem<Graphics>();
+                    yaw_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.x_;
+                    pitch_ += TOUCH_SENSITIVITY * camera->GetFov() / graphics->GetHeight() * state->delta_.y_;
+
+                    // Construct new orientation for the camera scene node from yaw and pitch; roll is fixed to zero
+                    cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+                }
+                else
+                {
+                    // Move the cursor to the touch position
+                    Cursor* cursor = GetSubsystem<UI>()->GetCursor();
+                    if (cursor && cursor->IsVisible())
+                        cursor->SetPosition(state->position_);
+                }
             }
         }
     }