ゲームパッドの入力と変換

<iframe width="560" height="400" src="https://www.youtube.com/embed/TO6KsmQIGL0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

```html:Gamepad.html
<!DOCTYPE html>
<html lang="ja"><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>


var Minecraft = {
	keys:{

		X:'LeftButton',	Y:'RightButton',
		A:'Space',		B:'E',
		
		LY_NEGATIVE:'W',
		LX_NEGATIVE:'A',
		LY_POSITIVE:'S',
		LX_POSITIVE:'D',

		UP:'F5',
		DOWN:'Q',
		LEFT:'F',

		SELECT:'F11',
		START:'Escape',
		
		LB:e=>{return Minecraft.hotbar(e,-1)},	
		RB:e=>{return Minecraft.hotbar(e,1)},	
		
		LT:'LControlKey',	RT:'LShiftKey',
		LS:'F3',			RS:'F11',
	
	},
	mousemove:'RightStick',
	
	hotbarId:0,
	hotbar(e,d){
		if(e.type=='down'){
			Minecraft.hotbarId += 9+d
			Minecraft.hotbarId %= 9
		}
		e.type = 'key'+e.type
		e.key = 'D'+(Minecraft.hotbarId+1)
		return e
	}
}


$(e=>{
	var n = new Controller.Native()
	var v = new Controller.View()
	var s = new Controller.Status().on(e=>{
		var event = new Controller.Event(e,Minecraft)
		n.dispatch(event)
		if(event.type) v.log(JSON.stringify(event).replace(/"/g,''))
	})
	setInterval(e=>{
		s.loop()
		v.fps().sticks(s.gamepadAxes)
	},1000/60)
})


var Controller = {}

Controller.Native = class{
	constructor(){
		if(chrome.webview){
			this.bridge = chrome.webview.hostObjects.bridge
			this.host = chrome.webview.hostObjects
		}else{
			this.bridge = {dispatch:console.log}
			this.host = {bridge:this.bridge}
		}
	}
	dispatch(e){
		if(e.type){
			var s = JSON.stringify(e)
			this.host.bridge.dispatch(s)
		}
	}
}

Object.assign((Controller.Data = class{
	constructor(){
		this.count	= 0
		this.connection	= {}
		this.listener	= console.log
		this.dummy	= {
			buttons:this.buttons.map(k=>{return {pressed:false}}),
			axes:this.axes.map(k=>{return 0})
		}
	}
	button(a,b,i){
		if(a.pressed==b.pressed) return null
		switch(b.pressed){
			case(true):	this.listener({type:'down',	key:this.buttons[i]});break
			default:	this.listener({type:'up',	key:this.buttons[i]})
		}
	}
	stick(a,b,i){
		if(a==b) return null
		var p = this.axes[i]+'_POSITIVE'
		var n = this.axes[i]+'_NEGATIVE'
		switch(true){
			case(a>0):	this.listener({type:'up',	key:p});break
			case(a<0):	this.listener({type:'up',	key:n});break
		}
		switch(true){
			case(b>0):	this.listener({type:'down',	key:p});break
			case(b<0):	this.listener({type:'down',	key:n});break
		}
	}
	move(axes,s){
		if(axes.filter(v=>Math.abs(v)>0.15).length==0) return null
		this.listener({type:'move',axes:axes,stick:s})
	}
}).prototype,{
	buttons:'A,B,X,Y,LB,RB,LT,RT,SELECT,START,LS,RS,UP,DOWN,LEFT,RIGHT,B16'.split(','),
	axes:'LX,LY,RX,RY'.split(',')
})

Controller.Status = class extends Controller.Data {
	constructor(){
		super()
		window.addEventListener("gamepadconnected",e=>{
			console.log(e)
			this.count++
			this.status(e.gamepad)
		});
		window.addEventListener("gamepaddisconnected",e=>{
			console.log(e)
			this.connection[e.gamepad.index] = null
			this.count--
		});
		this.gamepadAxes = []
	}
	on(f){
		this.listener = f
		return this
	}
	loop(){
		if(this.count==0) return
		this.item = Array.prototype.slice.call(navigator.getGamepads())
		this.item.forEach(g=>this.status(g))
		return this
	}
	status(gamepad){
		if(gamepad == null) return
		var g = this.connection[gamepad.index]||this.dummy
		gamepad.buttons.slice(0,16).forEach((v,i)=>this.button(g.buttons[i],v,i))
		gamepad.axes.forEach((v,i)=>this.stick(Math.round(g.axes[i]),Math.round(v),i))
		this.move(gamepad.axes.slice(0,2),'LeftStick')
		this.move(gamepad.axes.slice(2,4),'RightStick')
		this.connection[gamepad.index] = gamepad
		this.gamepadAxes = gamepad.axes
	}
}

Object.assign((Controller.Event = class{
	constructor(e,data){
		var v = data.keys[e.key]||null
		if(e.type=='move'){
			if(e.stick==data.mousemove) this.mouseMove(e)
		}else if(v){
			if(typeof v==='string'){
				var k = data.keys[e.key]
				var i = this.MOUSE_BUTTONS.indexOf(k)
				if(i==-1){
					this.type	= 'key'+e.type
					this.key	= k
				}else{
					this.type	= 'mouse'+e.type
					this.button	= i
				}
			}else{
				Object.assign(this,v(e))
			}
		}
	}
	mouseMove(e){
		this.type = 'mousemove'
		this.x = this.mouseSpeed(e.axes[0])
		this.y = this.mouseSpeed(e.axes[1])
	}
	mouseSpeed(v){
		var a = Math.abs(v)
		if(a==1) return v*18
		return v*6
	}
}).prototype,{
	type:null,
	MOUSE_BUTTONS:'LeftButton,MiddleButton,RightButton'.split(',')
})

Controller.View = class{
	constructor(){
		this.time = performance.now()
		this.$view=$('<div id="View">').append(
			this.$log=$('<div id="Log">'),
			this.$axe=$('<div id="Axis">').append(
				$('<div class="range">').append(this.$ls=$('<div class="stick">')),
				$('<div class="range">').append(this.$rs=$('<div class="stick">'))
			),
			this.$fps=$('<div id="Fps">')
		)
		$('body').append(this.$view)
	}
	log(v){
		this.$log.append($('<div>').append(v))
		this.$log.children().slice(0,-100).remove()
		window.scroll(0,$(document).height())
		return this
	}
	sticks(a){
		if(a==null) return
		if(a.length<4) return
		this.$ls.css({left:a[0]*30,top:a[1]*30})
		this.$rs.css({left:a[2]*30,top:a[3]*30})
		return this
	}
	fps(){
		var t = performance.now()
		this.$fps.empty().append(Math.round(1000/(t-this.time)),'FPS')
		this.time = t
		return this
	}
}


</script>
<style>

*{
	box-sizing:border-box;
	margin:0;padding:0;
}
body{
	font-size:12px;
	line-height:20px;
}

#Log div{
	border-bottom:1px solid rgba(0,0,0,0.04);
	padding:0 10px;
	white-space:nowrap;
	text-overflow:ellipsis;
	overflow:hidden;
}

#Axis{
	position:fixed;
	right:0;top:0;
	width:280px;height:160px;
}
#Axis .range{
	position:absolute;
	top:30px;
	width:100px;
	height:100px;
	border-radius:100px;
	background-color:rgba(0,0,0,0.1);
}
#Axis .range:nth-child(1){left:30px;}
#Axis .range:nth-child(2){right:30px;}
#Axis .stick{
	position:absolute;
	transform: translate(25px,25px);
	width:50px;
	height:50px;
	border-radius:30px;
	border:1px solid rgba(0,0,0,0.1);
	background-color: white;
}

#Fps{
	position: fixed;
	right:0;top:0;
	padding:10px;
}

</style>
</head></html>


```