October 19, 2021, 11:00:30

Author Topic: [bmx] Mouse as Pseudo-Proportional Controller (Smooth, Boundary-Free Mouse) by Tom Darby [ 1+ years ago ]  (Read 1363 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
Title : Mouse as Pseudo-Proportional Controller (Smooth, Boundary-Free Mouse)
Author : Tom Darby
Posted : 1+ years ago

Description : If you're using the mouse for game input in BlitzMax, you know that you can only move your mouse so far before it stops against the edge of the screen/window.  For point-and-click games, this is fine, but if you want to use a more advanced control system such as mouselook or camera pan, you won't want your mouse "hitting the edge" and not registering movement.  This can be achieved by simply re-positioning the mouse to the center of the screen every time it moves, but there are issues with this approach.  For one, you lose your mouse's sub-pixel motion whenever you call MoveMouse().  This can be noticeable if the user is making very minute mouse movements or if their mouse sensitivity is set very low; it manifests itself in "jerky" or "weird" movement.  

If the above doesn't really make sense, or if you're mathematically inclined, think of it this way: the mouse itself (and the underlying system routines) registers far more sensitive movement than actually registers on the screen, which is an integer value.  It's what allows you to adjust your mouse sensitivity; all mouse sensitivity is is a factor by which you amplify whatever input is coming in from the mouse.  The computer remembers very slight changes in mouse position, but BlitzMax only reveals the integer pixel coordinates of your cursor after the system is done factoring in sensitivity and such.

If your user is moving the mouse at a rate of 0.9 "pixels per loop", then it'll take 2 loops for the position of the mouse pointer to actually change on screen.  In a basic repositioning system, you reset your mouse position every time the mouse cursor moves, so on the second game loop, your mouse has moved 1.8 "pixels", and the on-screen cursor has accordingly moved 1 pixel (since MouseX and MouseY are ints, not floats.)  When you reposition your mouse, though, you have no way of knowing just how far your mouse is between pixels; it simply resets to a predetermined "center" point, and you lose whatever "float" data the mouse was keeping track of.  Thus, if your user moves the mouse at 0.9 pixels per loop, a basic repositioning method would re-center your mouse every 2 loops after it had moved 1 pixel--making the effective pixels-per-loop speed 0.5 instead of 0.9.

To eliminate this admittedly uncommonly-noticed but real side effect of mouse repositioning, I've created a little routine which only repositions the mouse once it has left a "safe" zone.  While you'll still lose subpixel movement information every time you reposition the mouse, calls to MoveMouse() are much less frequent when using this routine, and any lost subpixel positioning data is effectively unnoticeable, even to the most sensitive users.

This handly little bit of code allows you to mimic a proportional controller using BlitzMax's existing mouse routines.  Basically, it creates a bounding box for the mouse and two global variables to report your proportional controller's change in position, MouseXSpeed and MouseYSpeed.  If the mouse ever leaves its bounding box, it gets re-centered in the middle of the screen; thus, your mouse will never "peg" the sides of the screen area and stop reporting movement in that particular direction.  

The smaller you make the box defined by SAFEZONE, the "jerkier" movement will seem (when the mouse resets position, you lose whatever sub-pixel motion there may be.)  You'll also lose some of your faster mouse movements; set SAFEZONE to 1 to see what happens.

The larger you make the box, the smoother overall motion will be, but be careful not to set the box -too- large, as the mouse could conceivably "skip" out of a windowed-mode canvas and cease to respond to MouseX/MouseY calls properly (until the mouse returns to the window, that is.)  Careful, too, not to make the bounding box larger than the actual resolution, or your mouse will never reposition and you'll lose the "proportional controller" part of the proportional controller!  I've found that 100 is a pretty good value, but feel free to play with it.

Use is farily straightforward: call the SampleMouse() function once per game loop, and use the MouseXSpeed and MouseYSpeed global variables to calculate ALL mouse-related movement.  It is important that you NOT use the standard mouse position functions at the same time you're using the mouse as a proportional controller, as you'll get weird results (for obvious reasons.)  Checking for mouseclicks is fine.

Code :
Code: BlitzMax
  1. SuperStrict
  3. Const DEPTH:Int = 32
  4. Const WIDTH:Int = 1024
  5. Const HEIGHT:Int = 768
  6. Const SAFEZONE:Int = 100 ' set this high to disable repositioning; set to 0 to always reposition
  8. Global MouseXSpeed:Int,MouseYSpeed:Int, prevMouseX:Int, prevMouseY:Int, movementZone:Int
  9. Global controllerX#, controllerY#, slowControllerX#, slowControllerY#
  11. Function SampleMouse()
  13.         Local curMouseX:Int, curMouseY:Int
  14.         curMouseX = MouseX()
  15.         curMouseY = MouseY()
  17.         MouseXSpeed=curMouseX - prevMouseX
  18.         MouseYSpeed=curMouseY - prevMouseY
  19.         If Abs(centerX - curMouseX) > movementZone Or Abs(centerY - curMouseY) > movementZone Then
  20.                 MoveMouse centerX, centerY
  21.                 prevMouseX = centerX - MouseXSpeed
  22.                 prevMouseY = centerY - MouseYSpeed
  23.         Else
  24.                 prevMouseX = curMouseX
  25.                 prevMouseY = curMouseY
  26.         EndIf
  27. End Function
  29. Graphics WIDTH, HEIGHT, DEPTH
  31. Global centerX:Int, centerY:Int
  33. Function Reset()
  34.         centerX = GraphicsWidth() / 2
  35.         centerY = GraphicsHeight() / 2
  36.         movementZone = SAFEZONE
  37.         controllerX# = centerX
  38.         controllerY# = centerY
  39.         slowControllerX# = centerX
  40.         slowControllerY# = centerY
  41.         prevMouseX = centerX
  42.         prevMouseY = centerY
  43.         mouseXSpeed = 0
  44.         mouseYSpeed = 0
  45.         MoveMouse centerX, centerY
  46. End Function
  48. HideMouse
  50. Reset()
  52. While Not KeyHit(KEY_ESCAPE)
  54.         If MouseHit(1) Then
  55.                 Reset()
  56.         EndIf  
  57.         SampleMouse()
  59.         ' reposition "controllers"
  60.         controllerX# = controllerX# + Float(MouseXSpeed)
  61.         controllerY# = controllerY# + Float(MouseYSpeed)
  63.         slowControllerX# = slowControllerX# + (Float(MouseXSpeed) / 2)
  64.         slowControllerY# = slowControllerY# + (Float(MouseYSpeed) / 2)
  66.         Cls
  68.         ' draw 'movement zone'
  69.         SetColor 50,0,0
  70.         DrawRect(centerX - movementZone, centerY - movementZone, movementZone * 2, movementZone * 2)
  72.         ' draw actual mouse location
  73.         SetColor 255, 0, 0
  74.         DrawLine(prevMouseX - 4, prevMouseY, prevMouseX + 4, prevMouseY)
  75.         DrawLine(prevMouseX, prevMouseY - 4, prevMouseX, prevMouseY + 4)
  77.         ' draw "controller" locations
  78.         SetColor 0,255,0
  79.         DrawLine(controllerX - 4, controllerY - 4,controllerX + 4, controllerY + 4)
  80.         DrawLine(controllerX + 4, controllerY - 4, controllerX - 4, controllerY + 4)
  82.         SetColor 0,255,255
  83.         DrawLine(slowControllerX - 4, slowControllerY - 4, slowControllerX + 4, slowControllerY + 4)
  84.         DrawLine(slowControllerX + 4, slowControllerY - 4, slowControllerX - 4, slowControllerY + 4)
  87.         SetColor 255, 255, 255
  88.         DrawText "Click to reset, [ESC] to exit.  Green x: full speed controlled object.",0,0
  89.         DrawText "Blue x: 1/2 speed controlled object.  Red +: actual mouse position.",0, 12
  90.         DrawText "Red square: mouse movement area.",0,24
  91.         DrawText "MouseXSpeed="+MouseXSpeed,0,36
  92.         DrawText "MouseYSpeed="+MouseYSpeed,0,48
  93.         Flip
  94. Wend

Comments : none...


SimplePortal 2.3.6 © 2008-2014, SimplePortal