Merge: gamnit: virtual gamepad
authorJean Privat <jean@pryen.org>
Tue, 11 Apr 2017 12:30:27 +0000 (08:30 -0400)
committerJean Privat <jean@pryen.org>
Tue, 11 Apr 2017 12:30:27 +0000 (08:30 -0400)
Intro an easy to use virtual gamepad for quick ports of desktop games to mobiles devices with a touchscreen only. The gamepad is composed of up to 2 d-pads and up to 6 action buttons. Each button and direction on the pad is mapped to customizable keyboard keys. As such, it is ideal to simulate keyboard inputs on mobile devices.

The `virtual_gamepad` module uses the `app_files` annotation which automatically includes the required PNG files in Android's APK files when the module is imported.

The virtual gamepad is derived from the one in Asteronits, which it replaced. It is now used in Asteronits and Action Nitro, and it will be added to Devil's Avocados as soon as this is merged. The gamepad comes with a selection of button allowing for some customization, however custom buttons are also supported.

Usage example from Devil's Avocados:
~~~
var gamepad = new VirtualGamepad
gamepad.add_dpad
gamepad.add_button("q", gamepad_spritesheet.turn_left)
gamepad.add_button("e", gamepad_spritesheet.turn_right)
gamepad.add_button("space", gamepad_spritesheet.star)
gamepad.visible = true
app.gamepad = gamepad
~~~

And the result, on Android:

![screenshot_20170404-125407](https://cloud.githubusercontent.com/assets/208057/24668912/641d091a-1936-11e7-81ff-eaf026540db6.png)

This version should manage quite well the edge cases of touchscreen controls, which explains the complexity of `virtual_gamepad.nit`. However it is still limited, future work could add:
- Virtual analog joystick, it would be easier to code than the d-pad.
- Abstraction for gamepads (and analog joysticks). This would require unification with the current Android physical gamepad support and implementing desktop physical gamepad support with SDL2.

Pull-Request: #2405
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>
Reviewed-by: Jean Privat <jean@pryen.org>

1  2 
contrib/asteronits/src/asteronits.nit
lib/gamnit/flat.nit

@@@ -110,12 -110,6 +110,12 @@@ redef class Ap
                                return true
                        else if event.name == "escape" then
                                exit 0
 +                      else if event.name == "." and event.is_down then
 +                              dynamic_resolution_ratio *= 2.0
 +                              print dynamic_resolution_ratio
 +                      else if event.name == "," and event.is_down then
 +                              dynamic_resolution_ratio /= 2.0
 +                              print dynamic_resolution_ratio
                        end
                end
  
@@@ -230,15 -224,15 +230,15 @@@ redef class KeyEven
        # How does this event affect the ship thrust?
        fun thrust: Float
        do
-               if is_arrow_up or name == "w" then return 1.0
+               if name == "up" or name == "w" then return 1.0
                return 0.0
        end
  
        # How does this event affect the ship thrust?
        fun rotation: Float
        do
-               if is_arrow_right or name == "d" then return -1.0
-               if is_arrow_left or name == "a" then return 1.0
+               if name == "right" or name == "d" then return -1.0
+               if name == "left" or name == "a" then return 1.0
                return 0.0
        end
  end
diff --combined lib/gamnit/flat.nit
@@@ -41,9 -41,9 +41,9 @@@ import performance_analysi
  
  import gamnit
  import gamnit::cameras
 +import gamnit::dynamic_resolution
  import gamnit::limit_fps
 -
 -import android_two_fingers_motion is conditional(android)
 +import gamnit::camera_control
  
  # Draw a `texture` at `center`
  #
@@@ -217,20 -217,20 +217,20 @@@ redef class Ap
  
        # Camera for world `sprites` and `depth::actors` with perspective
        #
 -      # By default, the camera is configured to respect the resolution
 -      # of the screen in world coordinates at `z == 0.0`.
 +      # By default, the camera is configured to a height of 1080 units
 +      # of world coordinates at `z == 0.0`.
        var world_camera: EulerCamera is lazy do
                var camera = new EulerCamera(app.display.as(not null))
  
 -              # Aim for pixel resolution at level 0
 -              camera.reset_height
 -              camera.near = 100.0
 +              # Aim for full HD pixel resolution at level 0
 +              camera.reset_height 1080.0
 +              camera.near = 10.0
  
                return camera
        end
  
        # Camera for `ui_sprites` using an orthogonal view
 -      var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
 +      var ui_camera = new UICamera(app.display.as(not null)) is lazy
  
        # World sprites to draw as seen by `world_camera`
        var sprites: Set[Sprite] = new SpriteSet
        # Draw the whole screen, all `glDraw...` calls should be executed here
        protected fun frame_core_draw(display: GamnitDisplay)
        do
 -              perf_clock_main.lapse
 +              frame_core_dynamic_resolution_before display
  
 +              perf_clock_main.lapse
                frame_core_world_sprites display
                perfs["gamnit flat world_sprites"].add perf_clock_main.lapse
  
                frame_core_ui_sprites display
                perfs["gamnit flat ui_sprites"].add perf_clock_main.lapse
 +
 +              frame_core_dynamic_resolution_after display
        end
  
        private fun frame_core_sprites(display: GamnitDisplay, sprite_set: SpriteSet, camera: Camera)
@@@ -676,6 -673,10 +676,10 @@@ private class SpriteSe
  
        redef fun clear
        do
+               for sprite in self do
+                       sprite.context = null
+                       sprite.sprite_set = null
+               end
                super
                for c in contexts_items do c.destroy
                contexts_map.clear