瀏覽代碼

Updated HTML5 behavior of the samples, mouse modes and UI cursor.

Updates:
- Exposed a new method to determine whether the mouse pointer is confined to window.
- E_MOUSEMODECHANGED now has a new parameter, P_MOUSELOCK, indicating whether the mouse pointer is locked.
- MM_ABSOLUTE now behaves more closely to the desktop equivalent (web)
- Changed some samples to use different mouse movement methods as test cases.
- Input class: suppressNextMouseMove_ now suppresses all movements within the tick.
- Added script bindings for the Input class changes.
- On Web platform, removed event polling and instead use event callbacks, allowing pointer-lock requests to occur in browser events.

Bug fixes:
- Fixed mouse jumps on entering and exiting pointer-lock (web)
- Fixed a bug that caused the UI cursor and OS cursor to fall out of sync (web)
- Fixed a bug that could allow pointer-lock to be acquired after a different mouse mode was requested. (web)
- Prevent most samples from exiting on Web platform
- Reverted previous fix to web platform touch events, as upstream SDL has corrected the corresponding bug.
hdunderscore 9 年之前
父節點
當前提交
54565005e2

+ 11 - 0
Source/Samples/Sample.h

@@ -23,6 +23,7 @@
 #pragma once
 
 #include <Urho3D/Engine/Application.h>
+#include <Urho3D/Input/Input.h>
 
 namespace Urho3D
 {
@@ -69,6 +70,8 @@ protected:
     virtual String GetScreenJoystickPatchString() const { return String::EMPTY; }
     /// Initialize touch input on mobile platform.
     void InitTouchInput();
+    /// Initialize mouse mode on non-web platform.
+    void InitMouseMode();
     /// Control logo visibility.
     void SetLogoVisible(bool enable);
 
@@ -84,6 +87,8 @@ protected:
     float pitch_;
     /// Flag to indicate whether touch input has been enabled.
     bool touchEnabled_;
+    /// Mouse mode option to use in the sample.
+    MouseMode useMouseMode_;
 
 private:
     /// Create logo.
@@ -92,8 +97,14 @@ private:
     void SetWindowTitleAndIcon();
     /// Create console and debug HUD.
     void CreateConsoleAndDebugHud();
+    /// Handle request for mouse mode on web platform.
+    void HandleMouseModeRequest(StringHash eventType, VariantMap& eventData);
+    /// Handle request for mouse mode change on web platform.
+    void HandleMouseModeChange(StringHash eventType, VariantMap& eventData);
     /// Handle key down event to process key controls common to all samples.
     void HandleKeyDown(StringHash eventType, VariantMap& eventData);
+    /// Handle key up event to process key controls common to all samples.
+    void HandleKeyUp(StringHash eventType, VariantMap& eventData);
     /// Handle scene update event to control camera's pitch and yaw for all samples.
     void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle touch begin event to initialize touch input on desktop platform.

+ 74 - 6
Source/Samples/Sample.inl

@@ -39,6 +39,7 @@
 #include <Urho3D/Core/Timer.h>
 #include <Urho3D/UI/UI.h>
 #include <Urho3D/Resource/XMLFile.h>
+#include <Urho3D/IO/Log.h>
 
 Sample::Sample(Context* context) :
     Application(context),
@@ -47,7 +48,8 @@ Sample::Sample(Context* context) :
     touchEnabled_(false),
     screenJoystickIndex_(M_MAX_UNSIGNED),
     screenJoystickSettingsIndex_(M_MAX_UNSIGNED),
-    paused_(false)
+    paused_(false),
+    useMouseMode_(MM_ABSOLUTE)
 {
 }
 
@@ -85,8 +87,13 @@ void Sample::Start()
     // Create console and debug HUD
     CreateConsoleAndDebugHud();
 
+    // Initiate the mouse mode settings
+    InitMouseMode();
+
     // Subscribe key down event
     SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(Sample, HandleKeyDown));
+    // Subscribe key up event
+    SubscribeToEvent(E_KEYUP, URHO3D_HANDLER(Sample, HandleKeyUp));
     // Subscribe scene update event
     SubscribeToEvent(E_SCENEUPDATE, URHO3D_HANDLER(Sample, HandleSceneUpdate));
 }
@@ -115,6 +122,29 @@ void Sample::InitTouchInput()
     input->SetScreenJoystickVisible(screenJoystickSettingsIndex_, true);
 }
 
+void Sample::InitMouseMode()
+{
+    Input* input = GetSubsystem<Input>();
+
+    if (GetPlatform() != "Web")
+    {
+        if (useMouseMode_ == MM_FREE)
+            input->SetMouseVisible(true);
+
+        Console* console = GetSubsystem<Console>();
+        if (useMouseMode_ != MM_ABSOLUTE && (!console || !console->IsVisible()))
+        {
+            input->SetMouseMode(useMouseMode_);
+        }
+    }
+    else
+    {
+        input->SetMouseVisible(true);
+         SubscribeToEvent(E_MOUSEBUTTONDOWN, URHO3D_HANDLER(Sample, HandleMouseModeRequest));
+         SubscribeToEvent(E_MOUSEMODECHANGED, URHO3D_HANDLER(Sample, HandleMouseModeChange));
+    }
+}
+
 void Sample::SetLogoVisible(bool enable)
 {
     if (logoSprite_)
@@ -183,9 +213,10 @@ void Sample::CreateConsoleAndDebugHud()
     debugHud->SetDefaultStyle(xmlFile);
 }
 
-void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
+
+void Sample::HandleKeyUp(StringHash eventType, VariantMap& eventData)
 {
-    using namespace KeyDown;
+    using namespace KeyUp;
 
     int key = eventData[P_KEY].GetInt();
 
@@ -196,11 +227,27 @@ void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
         if (console->IsVisible())
             console->SetVisible(false);
         else
-            engine_->Exit();
+        {
+            if (GetPlatform() == "Web")
+            {
+                GetSubsystem<Input>()->SetMouseVisible(true);
+                if (useMouseMode_ != MM_ABSOLUTE)
+                    GetSubsystem<Input>()->SetMouseMode(MM_FREE);
+            }
+            else
+                engine_->Exit();
+        }
     }
+}
+
+void Sample::HandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace KeyDown;
+
+    int key = eventData[P_KEY].GetInt();
 
-    // Toggle console with F1
-    else if (key == KEY_F1)
+    // Toggle console with F1 or Z
+    if (key == KEY_F1 || key == 'Z')
         GetSubsystem<Console>()->Toggle();
 
     // Toggle debug HUD with F2
@@ -357,3 +404,24 @@ void Sample::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
     InitTouchInput();
     UnsubscribeFromEvent("TouchBegin");
 }
+
+// If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
+void Sample::HandleMouseModeRequest(StringHash eventType, VariantMap& eventData)
+{
+    Console* console = GetSubsystem<Console>();
+    if (console && console->IsVisible())
+        return;
+    Input* input = GetSubsystem<Input>();
+    if (useMouseMode_ == MM_ABSOLUTE)
+        input->SetMouseVisible(false);
+    else if (useMouseMode_ == MM_FREE)
+        input->SetMouseVisible(true);
+    input->SetMouseMode(useMouseMode_);
+}
+
+void Sample::HandleMouseModeChange(StringHash eventType, VariantMap& eventData)
+{
+    Input* input = GetSubsystem<Input>();
+    bool mouseLocked = eventData[MouseModeChanged::P_MOUSELOCK].GetBool();
+    input->SetMouseVisible(!mouseLocked);
+}

+ 17 - 2
Source/Urho3D/AngelScript/InputAPI.cpp

@@ -497,6 +497,16 @@ static void InputSetMouseVisible(bool enable, Input* ptr)
     ptr->SetMouseVisible(enable, false);
 }
 
+static void InputSetMouseMode(MouseMode mode, Input* ptr)
+{
+    ptr->SetMouseMode(mode, false);
+}
+
+static void InputSetMouseGrabbed(bool enable, Input* ptr)
+{
+    ptr->SetMouseGrabbed(enable, false);
+}
+
 static void RegisterInput(asIScriptEngine* engine)
 {
     engine->RegisterEnum("MouseMode");
@@ -550,10 +560,15 @@ static void RegisterInput(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Input", "void set_mouseVisible(bool)", asFUNCTION(InputSetMouseVisible), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Input", "void ResetMouseVisible()", asMETHOD(Input, ResetMouseVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool get_mouseVisible() const", asMETHOD(Input, IsMouseVisible), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Input", "void set_mouseGrabbed(bool)", asMETHOD(Input, SetMouseGrabbed), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void SetMouseGrabbed(bool, bool = false)", asMETHOD(Input, SetMouseGrabbed), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void set_mouseGrabbed(bool)", asFUNCTION(InputSetMouseGrabbed), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Input", "bool get_mouseGrabbed() const", asMETHOD(Input, IsMouseGrabbed), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Input", "void set_mouseMode(MouseMode) const", asMETHOD(Input, SetMouseMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void ResetMouseGrabbed()", asMETHOD(Input, ResetMouseGrabbed), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void SetMouseMode(MouseMode, bool = false)", asMETHOD(Input, SetMouseMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void set_mouseMode(MouseMode)", asFUNCTION(InputSetMouseMode), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Input", "void ResetMouseMode()", asMETHOD(Input, ResetMouseMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "MouseMode get_mouseMode() const", asMETHOD(Input, GetMouseMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "bool get_mouseLocked() const", asMETHOD(Input, IsMouseLocked), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "void set_screenJoystickVisible(int, bool)", asMETHOD(Input, SetScreenJoystickVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool get_screenJoystickVisible(int)", asMETHOD(Input, IsScreenJoystickVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "void set_screenKeyboardVisible(bool)", asMETHOD(Input, SetScreenKeyboardVisible), asCALL_THISCALL);

+ 21 - 5
Source/Urho3D/Engine/Console.cpp

@@ -127,21 +127,31 @@ void Console::SetDefaultStyle(XMLFile* style)
 void Console::SetVisible(bool enable)
 {
     Input* input = GetSubsystem<Input>();
+    UI* ui = GetSubsystem<UI>();
+    Cursor* cursor = ui->GetCursor();
+
     background_->SetVisible(enable);
     closeButton_->SetVisible(enable);
+
     if (enable)
     {
         // Check if we have receivers for E_CONSOLECOMMAND every time here in case the handler is being added later dynamically
         bool hasInterpreter = PopulateInterpreter();
         commandLine_->SetVisible(hasInterpreter);
         if (hasInterpreter && focusOnShow_)
-            GetSubsystem<UI>()->SetFocusElement(lineEdit_);
+            ui->SetFocusElement(lineEdit_);
 
         // Ensure the background has no empty space when shown without the lineedit
         background_->SetHeight(background_->GetMinHeight());
 
-        // Show OS mouse
-        input->SetMouseVisible(true, true);
+        if (!cursor)
+        {
+            // Show OS mouse
+            input->SetMouseMode(MM_FREE, true);
+            input->SetMouseVisible(true, true);
+        }
+
+        input->SetMouseGrabbed(false, true);
     }
     else
     {
@@ -149,8 +159,14 @@ void Console::SetVisible(bool enable)
         interpreters_->SetFocus(false);
         lineEdit_->SetFocus(false);
 
-        // Restore OS mouse visibility
-        input->ResetMouseVisible();
+        if (!cursor)
+        {
+            // Restore OS mouse visibility
+            input->ResetMouseMode();
+            input->ResetMouseVisible();
+        }
+
+        input->ResetMouseGrabbed();
     }
 }
 

文件差異過大導致無法顯示
+ 447 - 201
Source/Urho3D/Input/Input.cpp


+ 33 - 21
Source/Urho3D/Input/Input.h

@@ -38,7 +38,8 @@ enum MouseMode
     MM_ABSOLUTE = 0,
     MM_RELATIVE,
     MM_WRAP,
-    MM_FREE
+    MM_FREE,
+    MM_INVALID
 };
 
 class Deserializer;
@@ -155,7 +156,9 @@ public:
     /// Reset last mouse visibility that was not suppressed in SetMouseVisible.
     void ResetMouseVisible();
     /// Set whether the mouse is currently being grabbed by an operation.
-    void SetMouseGrabbed(bool grab);
+    void SetMouseGrabbed(bool grab, bool suppressEvent = false);
+    /// Reset the mouse grabbed to the last unsuppressed SetMouseGrabbed call
+    void ResetMouseGrabbed();
     /// Set the mouse mode.
     /** Set the mouse mode behaviour.
      *  MM_ABSOLUTE is the default behaviour, allowing the toggling of operating system cursor visibility and allowing the cursor to escape the window when visible.
@@ -174,7 +177,9 @@ public:
      *  MM_FREE does not grab/confine the mouse cursor even when it is hidden. This can be used for cases where the cursor should render using the operating system
      *  outside the window, and perform custom rendering (with SetMouseVisible(false)) inside.
     */
-    void SetMouseMode(MouseMode mode);
+    void SetMouseMode(MouseMode mode, bool suppressEvent = false);
+    /// Reset the last mouse mode that wasn't suppressed in SetMouseMode
+    void ResetMouseMode();
     /// Add screen joystick.
     /** Return the joystick instance ID when successful or negative on error.
      *  If layout file is not given, use the default screen joystick layout.
@@ -240,28 +245,22 @@ public:
     int GetQualifiers() const;
     /// Return mouse position within window. Should only be used with a visible mouse cursor.
     IntVector2 GetMousePosition() const;
-
     /// Return mouse movement since last frame.
-    const IntVector2& GetMouseMove() const { return mouseMove_; }
-
+    const IntVector2& GetMouseMove() const;
     /// Return horizontal mouse movement since last frame.
-    int GetMouseMoveX() const { return mouseMove_.x_; }
-
+    int GetMouseMoveX() const;
     /// Return vertical mouse movement since last frame.
-    int GetMouseMoveY() const { return mouseMove_.y_; }
-
+    int GetMouseMoveY() const;
     /// Return mouse wheel movement since last frame.
     int GetMouseMoveWheel() const { return mouseMoveWheel_; }
 
     /// Return number of active finger touches.
     unsigned GetNumTouches() const { return touches_.Size(); }
-
     /// Return active finger touch by index.
     TouchState* GetTouch(unsigned index) const;
 
     /// Return number of connected joysticks.
     unsigned GetNumJoysticks() const { return joysticks_.Size(); }
-
     /// Return joystick state by ID, or null if does not exist.
     JoystickState* GetJoystick(SDL_JoystickID id);
     /// Return joystick state by index, or null if does not exist. 0 = first connected joystick.
@@ -282,9 +281,10 @@ public:
 
     /// Return whether the operating system mouse cursor is visible.
     bool IsMouseVisible() const { return mouseVisible_; }
-
     /// Return whether the mouse is currently being grabbed by an operation.
     bool IsMouseGrabbed() const { return mouseGrabbed_; }
+    /// Return whether the mouse is locked to the window
+    bool IsMouseLocked() const;
 
     /// Return the mouse mode.
     MouseMode GetMouseMode() const { return mouseMode_; }
@@ -310,6 +310,8 @@ private:
     void ResetState();
     /// Clear touch states and send touch end events.
     void ResetTouches();
+    /// Reset input accumulation.
+    void ResetInputAccumulation();
     /// Get the index of a touch based on the touch ID.
     unsigned GetTouchIndexFromID(int touchID);
     /// Used internally to return and remove the next available touch index.
@@ -322,12 +324,6 @@ private:
     void SetMouseButton(int button, bool newState);
     /// Handle a key change.
     void SetKey(int key, int scancode, bool newState);
-#ifdef __EMSCRIPTEN__
-    /// Set whether the operating system mouse cursor is visible (Emscripten platform only).
-    void SetMouseVisibleEmscripten(bool enable);
-    /// Set mouse mode (Emscripten platform only).
-    void SetMouseModeEmscripten(MouseMode mode);
-#endif
     /// Handle mouse wheel change.
     void SetMouseWheel(int delta);
     /// Internal function to set the mouse cursor position.
@@ -341,6 +337,15 @@ private:
     /// Handle SDL event.
     void HandleSDLEvent(void* sdlEvent);
 
+#ifdef __EMSCRIPTEN__
+    /// Set whether the operating system mouse cursor is visible (Emscripten platform only).
+    void SetMouseVisibleEmscripten(bool enable, bool suppressEvent = false);
+    /// Set mouse mode (Emscripten platform only).
+    void SetMouseModeEmscripten(MouseMode mode, bool suppressEvent = false);
+    /// Handle frame end event.
+    void HandleEndFrame(StringHash eventType, VariantMap& eventData);
+#endif
+
     /// Graphics subsystem.
     WeakPtr<Graphics> graphics_;
     /// Key down state.
@@ -383,8 +388,12 @@ private:
     bool lastMouseVisible_;
     /// Flag to indicate the mouse is being grabbed by an operation. Subsystems like UI that uses mouse should temporarily ignore the mouse hover or click events.
     bool mouseGrabbed_;
+    /// The last mouse grabbed set by SetMouseGrabbed.
+    bool lastMouseGrabbed_;
     /// Determines the mode of mouse behaviour.
     MouseMode mouseMode_;
+    /// The last mouse mode set by SetMouseMode.
+    MouseMode lastMouseMode_;
     /// Touch emulation mode flag.
     bool touchEmulation_;
     /// Input focus flag.
@@ -401,13 +410,16 @@ private:
     bool screenModeChanged_;
     /// Initialized flag.
     bool initialized_;
+
 #ifdef __EMSCRIPTEN__
     /// Emscripten Input glue instance.
     EmscriptenInput* emscriptenInput_;
-    /// Flag used to detect mouse jump when exiting pointer lock.
+    /// Flag used to detect mouse jump when exiting pointer-lock.
     bool emscriptenExitingPointerLock_;
-    /// Flag used to detect mouse jump on initial mouse click when entering pointer lock.
+    /// Flag used to detect mouse jump on initial mouse click when entering pointer-lock.
     bool emscriptenEnteredPointerLock_;
+    /// Flag indicating current pointer-lock status.
+    bool emscriptenPointerLock_;
 #endif
 };
 

+ 1 - 0
Source/Urho3D/Input/InputEvents.h

@@ -213,6 +213,7 @@ URHO3D_EVENT(E_MOUSEVISIBLECHANGED, MouseVisibleChanged)
 URHO3D_EVENT(E_MOUSEMODECHANGED, MouseModeChanged)
 {
     URHO3D_PARAM(P_MODE, Mode);                    // MouseMode
+    URHO3D_PARAM(P_MOUSELOCK, PointerLock);      // bool
 }
 
 /// Application exit requested.

+ 4 - 2
Source/Urho3D/LuaScript/pkgs/Input/Input.pkg

@@ -47,8 +47,9 @@ class Input : public Object
 {
     void SetToggleFullscreen(bool enable);
     void SetMouseVisible(bool enable, bool suppressEvent = false);
-    void SetMouseGrabbed(bool grab);
-    void SetMouseMode(MouseMode mode);
+    void SetMouseGrabbed(bool grab, bool suppressEvent = false);
+    void SetMouseMode(MouseMode mode, bool suppressEvent = false);
+    bool IsMouseLocked();
     int AddScreenJoystick(XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
     bool RemoveScreenJoystick(int id);
     void SetScreenJoystickVisible(int id, bool enable);
@@ -115,6 +116,7 @@ class Input : public Object
     tolua_property__get_set bool touchEmulation;
     tolua_property__is_set bool mouseVisible;
     tolua_property__is_set bool mouseGrabbed;
+    tolua_readonly tolua_property__is_set bool mouseLocked;
     tolua_readonly tolua_property__has_set bool focus;
     tolua_readonly tolua_property__is_set bool minimized;
 };

+ 4 - 2
Source/Urho3D/UI/UI.cpp

@@ -1438,9 +1438,11 @@ void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
     {
         if (!input->IsMouseVisible())
         {
-            // Relative mouse motion: move cursor only when visible
-            if (cursor_->IsVisible())
+            if (!input->IsMouseLocked())
+                cursor_->SetPosition(IntVector2(eventData[P_X].GetInt(), eventData[P_Y].GetInt()));
+            else if (cursor_->IsVisible())
             {
+                // Relative mouse motion: move cursor only when visible
                 IntVector2 pos = cursor_->GetPosition();
                 pos.x_ += eventData[P_DX].GetInt();
                 pos.y_ += eventData[P_DY].GetInt();

+ 33 - 3
bin/Data/LuaScripts/Utilities/Sample.lua

@@ -19,6 +19,7 @@ yaw = 0 -- Camera yaw angle
 pitch = 0 -- Camera pitch angle
 TOUCH_SENSITIVITY = 2
 debugHudMode = 0
+useMouseMode_ = MM_ABSOLUTE
 
 function SampleStart()
     if GetPlatform() == "Android" or GetPlatform() == "iOS" or input.touchEmulation then
@@ -29,6 +30,9 @@ function SampleStart()
         SubscribeToEvent("TouchBegin", "HandleTouchBegin")
     end
 
+    -- Initiate the mouse mode settings
+    InitMouseMode()
+
     -- Create logo
     CreateLogo()
 
@@ -58,6 +62,17 @@ function InitTouchInput()
     input:SetScreenJoystickVisible(screenJoystickSettingsIndex, true)
 end
 
+function InitMouseMode()
+    if GetPlatform() ~= "Web" then
+        if useMouseMode_ ~= MM_ABSOLUTE and (console ~= nil or not console.visible) then
+            input.mouseMode = useMouseMode_
+        end
+    else
+        input.mouseVisible = true
+        SubscribeToEvent("MouseDown", "HandleMouseModeRequest")
+    end
+end
+
 function SetLogoVisible(enable)
     if logoSprite ~= nil then
         logoSprite.visible = enable
@@ -126,10 +141,14 @@ function HandleKeyDown(eventType, eventData)
     local key = eventData["Key"]:GetInt()
     -- Close console (if open) or exit when ESC is pressed
     if key == KEY_ESC then
-        if not console:IsVisible() then
-            engine:Exit()
-        else
+        if console:IsVisible() then
             console:SetVisible(false)
+        else
+            if GetPlatform() == "Web" then
+                input.mouseMode = MM_FREE;
+            else
+                engine:Exit();
+            end
         end
 
     elseif key == KEY_F1 then
@@ -261,6 +280,17 @@ function HandleTouchBegin(eventType, eventData)
     UnsubscribeFromEvent("TouchBegin")
 end
 
+-- If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
+function HandleMouseModeRequest(eventType, eventData)
+    if console ~= nil or console.visible or useMouseMode_ ~= MM_RELATIVE then
+        return
+    end
+
+    if input.mouseMode ~= MM_RELATIVE then
+        input.mouseMode = MM_RELATIVE
+    end
+end
+
 -- Create empty XML patch instructions for screen joystick layout if none defined
 function GetScreenJoystickPatchString()
     return ""

+ 37 - 3
bin/Data/Scripts/Utilities/Sample.as

@@ -18,6 +18,7 @@ Node@ cameraNode; // Camera scene node
 float yaw = 0.0f; // Camera yaw angle
 float pitch = 0.0f; // Camera pitch angle
 const float TOUCH_SENSITIVITY = 2;
+MouseMode useMouseMode_ = MM_ABSOLUTE;
 
 void SampleStart()
 {
@@ -28,6 +29,9 @@ void SampleStart()
         // On desktop platform, do not detect touch when we already got a joystick
         SubscribeToEvent("TouchBegin", "HandleTouchBegin");
 
+    // Initiate the mouse mode settings
+    InitMouseMode();
+
     // Create logo
     CreateLogo();
 
@@ -60,6 +64,22 @@ void InitTouchInput()
     input.screenJoystickVisible[0] = true;
 }
 
+void InitMouseMode()
+{
+    if (GetPlatform() != "Web")
+    {
+        if (useMouseMode_ != MM_ABSOLUTE && (console !is null || !console.visible))
+        {
+            input.mouseMode = useMouseMode_;
+        }
+    }
+    else
+    {
+        input.mouseVisible = true;
+        SubscribeToEvent("MouseDown", "HandleMouseModeRequest");
+    }
+}
+
 void SetLogoVisible(bool enable)
 {
     if (logoSprite !is null)
@@ -132,10 +152,15 @@ void HandleKeyDown(StringHash eventType, VariantMap& eventData)
     // Close console (if open) or exit when ESC is pressed
     if (key == KEY_ESC)
     {
-        if (!console.visible)
-            engine.Exit();
-        else
+        if (console.visible)
             console.visible = false;
+        else
+        {
+            if (GetPlatform() == "Web")
+                input.mouseMode = MM_FREE;
+            else
+                engine.Exit();
+        }
     }
 
     // Toggle console with F1
@@ -287,3 +312,12 @@ void HandleTouchBegin(StringHash eventType, VariantMap& eventData)
     InitTouchInput();
     UnsubscribeFromEvent("TouchBegin");
 }
+
+// If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
+void HandleMouseModeRequest(StringHash eventType, VariantMap& eventData)
+{
+    if (console !is null || console.visible || useMouseMode_ != MM_RELATIVE)
+        return;
+    if (input.mouseMode != MM_RELATIVE)
+        input.mouseMode = MM_RELATIVE;
+}

部分文件因文件數量過多而無法顯示