Touch.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. -- Mobile framework for Android/iOS
  2. -- Gamepad from Ninja Snow War
  3. -- Gyroscope (activated by default)
  4. -- Touches patterns:
  5. -- - 1 finger touch = pick object through raycast
  6. -- - 1 or 2 fingers drag = rotate camera
  7. -- - 3 fingers touch = switch between first/third person view
  8. -- - 4 fingers touch = switch shadows on/off
  9. -- - 2 fingers sliding in opposite direction (up/down) = zoom in/out
  10. -- 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...
  11. -- Setup:
  12. -- - On init, call this script using 'require "LuaScripts/Utilities/Touch" end' then 'InitTouchInput()' on mobile platforms
  13. -- -> to detect platform, use 'if GetPlatform() == "Android" or GetPlatform() == "iOS"')
  14. -- - Subscribe to touch events (Begin, Move, End) using 'SubscribeToTouchEvents()'
  15. -- - Call the update function 'updateTouches()' from HandleUpdate or equivalent update handler function
  16. TOUCH_SENSITIVITY = 5.0
  17. GYROSCOPE_THRESHOLD = 0.1
  18. CAMERA_MIN_DIST = 1.0
  19. CAMERA_INITIAL_DIST = 5.0
  20. CAMERA_MAX_DIST = 20.0
  21. local moveTouchID = -1
  22. local rotateTouchID = -1
  23. local fireTouchID = -1
  24. local moveButton
  25. local fireButton
  26. local touchButtonSize = 96
  27. local touchButtonBorder = 12
  28. local zoom = false
  29. local newFirstPerson = nil
  30. local shadowMode = true
  31. firstPerson = false
  32. touchEnabled = false
  33. cameraDistance = CAMERA_INITIAL_DIST
  34. cameraNode = nil
  35. -- Create Gamepad Buttons
  36. function InitTouchInput()
  37. moveButton = ui.root:CreateChild("BorderImage")
  38. moveButton.texture = cache:GetResource("Texture2D", "Textures/TouchInput.png")
  39. moveButton.imageRect = IntRect(0, 0, 96, 96) -- Crop right side of the texture
  40. moveButton:SetAlignment(HA_LEFT, VA_BOTTOM)
  41. moveButton:SetPosition(touchButtonBorder, -touchButtonBorder)
  42. moveButton:SetSize(touchButtonSize, touchButtonSize)
  43. moveButton.opacity = 0.25
  44. fireButton = ui.root:CreateChild("BorderImage")
  45. fireButton.texture = cache:GetResource("Texture2D", "Textures/TouchInput.png")
  46. fireButton.imageRect = IntRect(96, 0, 192, 96) -- Crop left side of the texture
  47. fireButton:SetAlignment(HA_RIGHT, VA_BOTTOM)
  48. fireButton:SetPosition(-touchButtonBorder, -touchButtonBorder)
  49. fireButton:SetSize(touchButtonSize, touchButtonSize)
  50. fireButton.opacity = 0.25
  51. touchEnabled = true
  52. end
  53. function SubscribeToTouchEvents()
  54. SubscribeToEvent("TouchBegin", "HandleTouchBegin")
  55. SubscribeToEvent("TouchEnd", "HandleTouchEnd")
  56. end
  57. function updateTouches(controls) -- Called from HandleUpdate
  58. local controls = characterNode:GetScriptObject().controls
  59. local camera = cameraNode:GetComponent("Camera")
  60. zoom = false -- reset bool
  61. -- Touch Inputs
  62. if touchEnabled then
  63. -- Zoom in/out
  64. if input.numTouches == 2 then
  65. local touch1 = input:GetTouch(0)
  66. local touch2 = input:GetTouch(1)
  67. -- Check for zoom pattern (touches moving in opposite directions)
  68. if (touch1.delta.y > 0 and touch2.delta.y < 0) or (touch1.delta.y < 0 and touch2.delta.y > 0) then zoom = true else zoom = false end
  69. -- Check for zoom direction (in/out)
  70. if zoom then
  71. if Abs(touch1.position.y - touch2.position.y) > Abs(touch1.lastPosition.y - touch2.lastPosition.y) then sens = -1 else sens = 1 end
  72. cameraDistance = cameraDistance + Abs(touch1.delta.y - touch2.delta.y) * sens * TOUCH_SENSITIVITY / 50
  73. cameraDistance = Clamp(cameraDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST) -- Restrict zoom range to [1;20]
  74. end
  75. end
  76. -- Switch 1st/3rd person mode
  77. if input.numTouches == 3 then newFirstPerson = not firstPerson end
  78. -- Switch draw debug
  79. if input.numTouches == 4 then shadowMode = not renderer.drawShadows end
  80. -- Rotate and Move
  81. if not zoom then
  82. for i = 0, input.numTouches-1 do -- numtouches=[0;4]
  83. local touch = input:GetTouch(i) -- TouchState
  84. if touch.touchID == rotateTouchID then
  85. controls.yaw = controls.yaw + TOUCH_SENSITIVITY * camera.fov / graphics.height * touch.delta.x
  86. controls.pitch = controls.pitch + TOUCH_SENSITIVITY * camera.fov / graphics.height * touch.delta.y
  87. controls.pitch = Clamp(controls.pitch, -80, 80) -- Limit pitch
  88. end
  89. if touch.touchID == moveTouchID then
  90. local relX = touch.position.x - moveButton.screenPosition.x - touchButtonSize / 2
  91. local relY = touch.position.y - moveButton.screenPosition.y - touchButtonSize / 2
  92. if relY < 0 and Abs(relX * 3 / 2) < Abs(relY) then controls:Set(CTRL_FORWARD, true) end
  93. if relY > 0 and Abs(relX * 3 / 2) < Abs(relY) then controls:Set(CTRL_BACK, true) end
  94. if relX < 0 and Abs(relY * 3 / 2) < Abs(relX) then controls:Set(CTRL_LEFT, true) end
  95. if relX > 0 and Abs(relY * 3 / 2) < Abs(relX) then controls:Set(CTRL_RIGHT, true) end
  96. end
  97. end
  98. end
  99. if fireTouchID >= 0 then controls:Set(CTRL_JUMP, true) end
  100. end
  101. -- Gyroscope (emulated by SDL through a virtual joystick)
  102. if input.numJoysticks > 0 then -- numJoysticks = 1 on iOS & Android
  103. local joystick = input:GetJoystick(0) -- JoystickState
  104. if joystick.numAxes >= 2 then
  105. if joystick:GetAxisPosition(0) < -GYROSCOPE_THRESHOLD then controls:Set(CTRL_LEFT, true) end
  106. if joystick:GetAxisPosition(0) > GYROSCOPE_THRESHOLD then controls:Set(CTRL_RIGHT, true) end
  107. if joystick:GetAxisPosition(1) < -GYROSCOPE_THRESHOLD then controls:Set(CTRL_FORWARD, true) end
  108. if joystick:GetAxisPosition(1) > GYROSCOPE_THRESHOLD then controls:Set(CTRL_BACK, true) end
  109. end
  110. end
  111. end
  112. function HandleTouchBegin(eventType, eventData)
  113. local touchID = eventData:GetInt("TouchID") -- Get #touches or dragging value
  114. local pos = IntVector2(eventData:GetInt("X"), eventData:GetInt("Y")) -- Get touch coordinates
  115. local element = ui:GetElementAt(pos, false) -- Get gamepad UIElement touched (if any)
  116. -- Check for gamepad button touched. If none, rotate
  117. if element == moveButton then moveTouchID = touchID
  118. elseif element == fireButton then fireTouchID = touchID
  119. else rotateTouchID = touchID end
  120. -- Raycast of RigidBodies (for example to acquire a target)
  121. local camera = cameraNode:GetComponent("Camera")
  122. local cameraRay = camera:GetScreenRay(eventData:GetInt("X") / graphics.width, eventData:GetInt("Y") / graphics.height)
  123. local result = scene_:GetComponent("PhysicsWorld"):RaycastSingle(cameraRay, camera.farClip, 2) -- NB: here we restrict targets to layer 2
  124. if result.body ~= nil then print("Physics raycast hit " .. result.body:GetNode().name) end
  125. -- Raycast of drawable components (for targets with or without physics)
  126. local result2 = scene_:GetComponent("Octree"):RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip, DRAWABLE_GEOMETRY)
  127. if result2.drawable ~= nil then print("Drawable raycast hit " .. result2.drawable:GetNode().name) end
  128. end
  129. function HandleTouchEnd(eventType, eventData)
  130. local touchID = eventData:GetInt("TouchID")
  131. if touchID == moveTouchID then moveTouchID = -1 end
  132. if touchID == rotateTouchID then rotateTouchID = -1 end
  133. if touchID == fireTouchID then fireTouchID = -1 end
  134. -- On-release Update
  135. firstPerson = newFirstPerson
  136. renderer.drawShadows = shadowMode
  137. end