Hey all,
Just spent a little while today adding gamepad support to the HTML5 target. It is currently on a pull request into official, but if you want to test it out or is not accepted into official then here it is below.
Example: http://www.skn3.com/junk/gamepad/MonkeyGame.html
In targets/html5/modules/native/html5game.js, make changes to the top of the file.
You can see there are 2 blocks of code to add inbetween // --- start gamepad api by skn3 --------- and // --- end gamepad api by skn3 ---------
var webglGraphicsSeq=1;
function BBHtml5Game( canvas ){
BBGame.call( this );
BBHtml5Game._game=this;
this._canvas=canvas;
this._loading=0;
this._timerSeq=0;
this._gl=null;
if( CFG_OPENGL_GLES20_ENABLED=="1" ){
//can't get these to fire!
canvas.addEventListener( "webglcontextlost",function( event ){
event.preventDefault();
// print( "WebGL context lost!" );
},false );
canvas.addEventListener( "webglcontextrestored",function( event ){
++webglGraphicsSeq;
// print( "WebGL context restored!" );
},false );
var attrs={ alpha:false };
this._gl=this._canvas.getContext( "webgl",attrs );
if( !this._gl ) this._gl=this._canvas.getContext( "experimental-webgl",attrs );
if( !this._gl ) this.Die( "Can't create WebGL" );
gl=this._gl;
}
// --- start gamepad api by skn3 ---------
this._gamepads = null;
this._gamepadLookup = [-1,-1,-1,-1];//support 4 gamepads
var that = this;
window.addEventListener("gamepadconnected", function(e) {
that.connectGamepad(e.gamepad);
});
window.addEventListener("gamepaddisconnected", function(e) {
that.disconnectGamepad(e.gamepad);
});
//need to process already connected gamepads (before page was loaded)
var gamepads = this.getGamepads();
if (gamepads && gamepads.length > 0) {
for(var index=0;index < gamepads.length;index++) {
this.connectGamepad(gamepads[index]);
}
}
// --- end gamepad api by skn3 ---------
}
BBHtml5Game.prototype=extend_class( BBGame );
BBHtml5Game.Html5Game=function(){
return BBHtml5Game._game;
}
// --- start gamepad api by skn3 ---------
BBHtml5Game.prototype.getGamepads = function() {
return navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
}
BBHtml5Game.prototype.connectGamepad = function(gamepad) {
if (!gamepad) {
return false;
}
//check if this is a standard gamepad
if (gamepad.mapping == "standard") {
//yup so lets add it to an array of valid gamepads
//find empty controller slot
var slot = -1;
for(var index = 0;index < this._gamepadLookup.length;index++) {
if (this._gamepadLookup[index] == -1) {
slot = index;
break;
}
}
//can we add this?
if (slot != -1) {
this._gamepadLookup[slot] = gamepad.index;
//console.log("gamepad at html5 index "+gamepad.index+" mapped to monkey gamepad unit "+slot);
}
} else {
console.log('Monkey has ignored gamepad at raw port #'+gamepad.index+' with unrecognised mapping scheme \''+gamepad.mapping+'\'.');
}
}
BBHtml5Game.prototype.disconnectGamepad = function(gamepad) {
if (!gamepad) {
return false;
}
//scan all gamepads for matching index
for(var index = 0;index < this._gamepadLookup.length;index++) {
if (this._gamepadLookup[index] == gamepad.index) {
//remove this gamepad
this._gamepadLookup[index] = -1
break;
}
}
}
BBHtml5Game.prototype.PollJoystick=function(port, joyx, joyy, joyz, buttons){
//is this the first gamepad being polled
if (port == 0) {
//yes it is so we use the web api to get all gamepad info
//we can then use this in subsequent calls to PollJoystick
this._gamepads = this.getGamepads();
}
//dont bother processing if nothing to process
if (!this._gamepads) {
return false;
}
//so use the monkey port to find the correct raw data
var index = this._gamepadLookup[port];
if (index == -1) {
return false;
}
var gamepad = this._gamepads[index];
if (!gamepad) {
return false;
}
//so now process gamepad axis/buttons according to the standard mappings
//https://w3c.github.io/gamepad/#remapping
//left stick axis
joyx[0] = gamepad.axes[0];
joyy[0] = -gamepad.axes[1];
//right stick axis
joyx[1] = gamepad.axes[2];
joyy[1] = -gamepad.axes[3];
//triggers
//emulate same functionality of GLFW
joyz[0] = gamepad.buttons[6].value;
joyz[1] = gamepad.buttons[7].value;
//clear button states
for(var index = 0;index <32;index++) {
buttons[index] = false;
}
//map html5 "standard" mapping to monkeys joy codes
/*
Const JOY_A=0
Const JOY_B=1
Const JOY_X=2
Const JOY_Y=3
Const JOY_LB=4
Const JOY_RB=5
Const JOY_BACK=6
Const JOY_START=7
Const JOY_LEFT=8
Const JOY_UP=9
Const JOY_RIGHT=10
Const JOY_DOWN=11
Const JOY_LSB=12
Const JOY_RSB=13
Const JOY_MENU=14
*/
buttons[0] = gamepad.buttons[0] && gamepad.buttons[0].pressed;
buttons[1] = gamepad.buttons[1] && gamepad.buttons[1].pressed;
buttons[2] = gamepad.buttons[2] && gamepad.buttons[2].pressed;
buttons[3] = gamepad.buttons[3] && gamepad.buttons[3].pressed;
buttons[4] = gamepad.buttons[4] && gamepad.buttons[4].pressed;
buttons[5] = gamepad.buttons[5] && gamepad.buttons[5].pressed;
buttons[6] = gamepad.buttons[8] && gamepad.buttons[8].pressed;
buttons[7] = gamepad.buttons[9] && gamepad.buttons[9].pressed;
buttons[8] = gamepad.buttons[14] && gamepad.buttons[14].pressed;
buttons[9] = gamepad.buttons[12] && gamepad.buttons[12].pressed;
buttons[10] = gamepad.buttons[15] && gamepad.buttons[15].pressed;
buttons[11] = gamepad.buttons[13] && gamepad.buttons[13].pressed;
buttons[12] = gamepad.buttons[10] && gamepad.buttons[10].pressed;
buttons[13] = gamepad.buttons[11] && gamepad.buttons[11].pressed;
buttons[14] = gamepad.buttons[16] && gamepad.buttons[16].pressed;
//success
return true
}
// --- end gamepad api by skn3 ---------
The code for the example uses mojo2 and is here:
Import mojo2
Function Main()
New Game()
End
'app
Class Game Extends App
Field canvas:Canvas
Field stickLeft:Float[2]
Field stickRight:Float[2]
Field triggerLeft:float
Field triggerRight:float
Field buttons:Bool[JOY_MENU + 1]
Method OnCreate()
SetUpdateRate(60)
canvas = New Canvas
End
Method OnUpdate()
'update all states
stickLeft[0] = JoyX(0)
stickLeft[1] = JoyY(0)
stickRight[0] = JoyX(1)
stickRight[1] = JoyY(1)
'have to do this (Max) otherwise GLFW seems to combine both triggers incorrectly!
triggerLeft = Max(0.0, JoyZ(0))
triggerRight = Max(0.0, JoyZ(1))
For Local index:= JOY_A To JOY_MENU
buttons[index] = JoyHit(index) Or JoyDown(index)
Next
End
Method OnRender()
canvas.Clear(0, 0, 0)
Local padCenterX:= DeviceWidth() / 2.0
Local padCenterY:= DeviceHeight() / 2.0
'left stick
DrawAnalog(canvas, padCenterX - 125, padCenterY - 50, 40, stickLeft[0], -stickLeft[1], buttons[JOY_LSB])
'right stick
DrawAnalog(canvas, padCenterX + 60, padCenterY + 50, 40, stickRight[0], -stickRight[1], buttons[JOY_RSB])
'dpad
DrawDPad(canvas, padCenterX - 100, padCenterY + 10, 80, buttons[JOY_UP], buttons[JOY_DOWN], buttons[JOY_LEFT], buttons[JOY_RIGHT])
'left trigger
DrawAxis(canvas, padCenterX - 165, padCenterY - 150, 80, 20, triggerLeft)
'right trigger
DrawAxis(canvas, padCenterX + 85, padCenterY - 150, 80, 20, triggerRight)
'left shoulder
DrawButton(canvas, padCenterX - 165, padCenterY - 120, 80, 20, buttons[JOY_LB])
'right shoulder
DrawButton(canvas, padCenterX + 85, padCenterY - 120, 80, 20, buttons[JOY_RB])
'buttons
Local buttonSize:= 30.0
Local buttonCenterX:= padCenterX + 125
Local buttonCenterY:= padCenterY - 50
'a
DrawButton(canvas, buttonCenterX, buttonCenterY + 40 - (buttonSize / 2.0), buttonSize / 2.0, buttons[JOY_A])
'b
DrawButton(canvas, buttonCenterX + 40 - (buttonSize / 2.0), buttonCenterY, buttonSize / 2.0, buttons[JOY_B])
'x
DrawButton(canvas, buttonCenterX - 40 + (buttonSize / 2.0), buttonCenterY, buttonSize / 2.0, buttons[JOY_X])
'y
DrawButton(canvas, buttonCenterX, buttonCenterY - 40 + (buttonSize / 2.0), buttonSize / 2.0, buttons[JOY_Y])
'start
DrawButton(canvas, padCenterX + buttonSize, buttonCenterY - (buttonSize / 4.0), buttonSize, buttonSize / 2.0, buttons[JOY_START])
'back
DrawButton(canvas, padCenterX - buttonSize - buttonSize, buttonCenterY - (buttonSize / 4.0), buttonSize, buttonSize / 2.0, buttons[JOY_BACK])
'menu
DrawButton(canvas, padCenterX, buttonCenterY, buttonSize * 0.75, buttons[JOY_MENU])
canvas.Flush()
End
End
'functions
Function DrawAxis:Void(canvas:Canvas, x:Float, y:float, width:Float, height:float, axis:float)
canvas.SetColor(0.5, 0.5, 0.5)
canvas.DrawRect(x, y, width, height)
Local size:= Min(16.0, Max(2.0, width * 0.1))
Local offset:= (width / 2.0) + (axis * ( (width - size) / 2.0)) - (size / 2.0)
If offset >= 0 And offset < width
canvas.SetColor(0.3, 0.3, 0.3)
canvas.DrawRect(x + offset, y, size, height)
EndIf
End
Function DrawDPad:Void(canvas:Canvas, x:Float, y:Float, size:Float, up:Bool, down:Bool, left:Bool, right:Bool)
Local buttonSize:= size / 3.0
Local buttonPadding:= Min(6.0, Max(2.0, size * 0.04))
canvas.SetColor(0.5, 0.5, 0.5)
canvas.DrawRect(x, y + buttonSize, size, buttonSize)
canvas.DrawRect(x + buttonSize, y, buttonSize, size)
canvas.SetColor(0.3, 0.3, 0.3)
canvas.DrawRect(x + buttonSize, y + buttonSize, buttonSize, buttonSize)
'up
If up
canvas.SetColor(0.0, 1.0, 0.0)
Else
canvas.SetColor(0.3, 0.3, 0.3)
EndIf
canvas.DrawRect(x + buttonSize + buttonPadding, y + buttonPadding, buttonSize - buttonPadding - buttonPadding, buttonSize - buttonPadding)
'down
If down
canvas.SetColor(0.0, 1.0, 0.0)
Else
canvas.SetColor(0.3, 0.3, 0.3)
EndIf
canvas.DrawRect(x + buttonSize + buttonPadding, y + size - buttonSize, buttonSize - buttonPadding - buttonPadding, buttonSize - buttonPadding)
'left
If left
canvas.SetColor(0.0, 1.0, 0.0)
Else
canvas.SetColor(0.3, 0.3, 0.3)
EndIf
canvas.DrawRect(x + buttonPadding, y + buttonSize + buttonPadding, buttonSize - buttonPadding, buttonSize - buttonPadding - buttonPadding)
'right
If right
canvas.SetColor(0.0, 1.0, 0.0)
Else
canvas.SetColor(0.3, 0.3, 0.3)
EndIf
canvas.DrawRect(x + size - buttonSize, y + buttonSize + buttonPadding, buttonSize - buttonPadding, buttonSize - buttonPadding - buttonPadding)
End
Function DrawAnalog:Void(canvas:Canvas, x:Float, y:Float, radius:Float, axisX:Float, axisY:Float, pressed:Bool)
Local padding:= Min(8.0, Max(1.0, radius * 0.1))
canvas.SetColor(0.5, 0.5, 0.5)
canvas.DrawCircle(x, y, radius)
If pressed
canvas.SetColor(0, 1.0, 0)
canvas.DrawCircle(x, y, radius - padding)
EndIf
canvas.SetColor(0.3, 0.3, 0.3)
Local length:= Sqrt( (axisX * axisX) + (axisY * axisY))
Local size:= Min(19.0, Max(7.0, radius * 0.1))
Local cursorX:Float
Local cursorY:Float
If length > 0
Local ratio:Float
If Abs(axisX) > Abs(axisY)
ratio = Abs(axisX / 1.0) / length
Else
ratio = Abs(axisY / 1.0) / length
EndIf
cursorX = axisX * radius * ratio
cursorY = axisY * radius * ratio
EndIf
DrawCross(canvas, x + cursorX - (size / 2.0), y + cursorY - (size / 2.0), size, size)
End
Function DrawCross:Void(canvas:Canvas, x:Float, y:Float, width:Float, height:Float, diagonal:Bool = True)
' --- draw a cross ---
If diagonal
canvas.DrawLine(x, y, x + width, y + height)
canvas.DrawLine(x + width, y, x, y + height)
Else
Local halfWidth:= width / 2.0
Local halfHeight:= height / 2.0
canvas.DrawLine(x + halfWidth, y, x + halfHeight, y + height)
canvas.DrawLine(x, y + halfHeight, x + width, y + halfHeight)
EndIf
End
Function DrawButton:Void(canvas:Canvas, x:Float, y:Float, radius:Float, pressed:Bool)
Local padding:= Min(8.0, Max(1.0, radius * 0.1))
canvas.SetColor(0.5, 0.5, 0.5)
canvas.DrawCircle(x, y, radius)
If pressed
canvas.SetColor(0, 1.0, 0)
canvas.DrawCircle(x, y, radius - padding)
EndIf
End
Function DrawButton:Void(canvas:Canvas, x:Float, y:Float, width:Float, height:Float, pressed:Bool)
Local padding:= Min(8.0, Max(1.0, width * 0.05))
canvas.SetColor(0.5, 0.5, 0.5)
canvas.DrawRect(x, y, width, height)
If pressed
canvas.SetColor(0, 1.0, 0)
canvas.DrawRect(x + padding, y + padding, width - padding - padding, height - padding - padding)
EndIf
End
|