Snek/src/js/input.js

292 lines
6.6 KiB
JavaScript

const config=require('config');
let currentInputs={};
let handlers=[];
let hud;
const toAngleMagnitude=(x, y) => {
return {
angle: ((Math.atan2(x, y)+2*Math.PI)%(2*Math.PI))/Math.PI,
magnitude: Math.hypot(x, y)
};
};
const handleAngleMagnitude=(x, y, threshold=0, fn=null) => {
const {angle, magnitude}=toAngleMagnitude(x, y);
if(magnitude>threshold) {
let inputs=currentInputs;
if(angle>.25 && angle <.75) inputs.right=true;
else if(angle>.75 && angle<1.25) inputs.up=true;
else if(angle>1.25 && angle<1.75) inputs.left=true;
else inputs.down=true;
if(fn) fn(angle, magnitude);
}
};
const removeChild=(parent, child) => {
if(child.parentNode==parent) parent.removeChild(child);
};
const handleCrosspad=(() => {
const ns='http://www.w3.org/2000/svg';
const cross=document.createElementNS(ns, 'svg');
cross.classList.add('crosspadOverlay');
cross.setAttribute('width', 1000);
cross.setAttribute('height', 1000);
let dr=document.createElementNS(ns, 'line');
dr.setAttribute('x1', 0);
dr.setAttribute('y1', 0);
dr.setAttribute('x2', 1000);
dr.setAttribute('y2', 1000);
dr.setAttribute('stroke', 'black');
cross.appendChild(dr);
let dl=document.createElementNS(ns, 'line');
dl.setAttribute('x1', 1000);
dl.setAttribute('y1', 0);
dl.setAttribute('x2', 0);
dl.setAttribute('y2', 1000);
dl.setAttribute('stroke', 'black');
cross.appendChild(dl);
let useOverlay=false;
let enabled=false;
const displayOverlay=() => {
if(useOverlay && enabled) hud.appendChild(cross);
else removeChild(hud, cross);
};
config.watchB('input.touchscreen.crosspad.overlay', (k, v) => {
useOverlay=v;
displayOverlay();
});
const fn=e =>
handleAngleMagnitude(
e.touches[0].clientX-window.innerWidth/2,
e.touches[0].clientY-window.innerHeight/2,
0,
null
);
const init=() => {
useOverlay=config.getB('input.touchscreen.crosspad.overlay');
enabled=true;
displayOverlay();
};
const fini=() => {
enabled=false;
displayOverlay();
};
return {
touchstart: fn,
touchmove: fn,
init, fini
};
})();
const handleKeyboard={
keydown: e => {
let inputs=currentInputs;
if(e.key=='ArrowUp') inputs.up=true;
else if(e.key=='ArrowDown') inputs.down=true;
else if(e.key=='ArrowLeft') inputs.left=true;
else if(e.key=='ArrowRight') inputs.right=true;
}
};
const handleJoystick=(() => {
let cvs=document.createElement('canvas');
cvs.classList.add('joystickOverlay');
let ctx=cvs.getContext('2d');
let enabled=false;
let useOverlay=false;
let firstTouch=false;
let center={
x: 0,
y: 0
};
let deadzone;
const displayOverlay=() => {
if(!enabled || !useOverlay || !firstTouch) return removeChild(hud, cvs);
cvs.width=cvs.height=4*deadzone+120;
hud.appendChild(cvs);
ctx.clearRect(0, 0, cvs.width, cvs.width);
ctx.strokeStyle='black';
ctx.lineWidth=1;
ctx.beginPath();
const rad=2*deadzone+50;
ctx.moveTo(rad*Math.cos(Math.PI/4)+cvs.width/2, rad*Math.sin(Math.PI/4)+cvs.width/2);
ctx.lineTo(rad*Math.cos(Math.PI/4+Math.PI)+cvs.width/2, rad*Math.sin(Math.PI/4+Math.PI)+cvs.width/2);
ctx.moveTo(rad*Math.cos(-Math.PI/4)+cvs.width/2, rad*Math.sin(-Math.PI/4)+cvs.width/2);
ctx.lineTo(rad*Math.cos(-Math.PI/4+Math.PI)+cvs.width/2, rad*Math.sin(-Math.PI/4+Math.PI)+cvs.width/2);
ctx.stroke();
ctx.strokeStyle='gray';
ctx.lineWidth=2;
ctx.beginPath();
ctx.ellipse(cvs.width/2, cvs.width/2, deadzone, deadzone, 0, 0, Math.PI*2);
ctx.stroke();
ctx.lineWidth=3;
ctx.beginPath();
ctx.ellipse(cvs.width/2, cvs.width/2, deadzone*2+50, deadzone*2+50, 0, 0, Math.PI*2);
ctx.stroke();
cvs.style.left=center.x+'px';
cvs.style.top=center.y+'px';
};
const init=() => {
enabled=true;
deadzone=config.getN('input.touchscreen.joystick.deadzone');
useOverlay=config.getB('input.touchscreen.joystick.overlay');
displayOverlay();
};
const fini=() => {
enabled=false;
displayOverlay();
};
config.watchN('input.touchscreen.joystick.deadzone', (k, v) => {
deadzone=v;
displayOverlay();
});
config.watchB('input.touchscreen.joystick.overlay', (k, v) => {
useOverlay=v;
displayOverlay();
});
return {
init, fini,
touchstart: e => {
center.x=e.touches[0].clientX;
center.y=e.touches[0].clientY;
firstTouch=true;
displayOverlay();
},
touchmove: e =>
handleAngleMagnitude(
e.touches[0].clientX-center.x,
e.touches[0].clientY-center.y,
deadzone,
null
)
}
})();
const handleSwipe=(() => {
let center={
x: 0,
y: 0
};
let deadzone;
let resetCenter=e => {
center.x=e.touches[0].clientX;
center.y=e.touches[0].clientY;
};
const init=() => {
deadzone=config.getN('input.touchscreen.swipe.deadzone');
};
config.watchN('input.touchscreen.swipe.deadzone', (k, v) => {
deadzone=v;
});
return {
init,
touchstart: resetCenter,
touchmove: e =>
handleAngleMagnitude(
e.touches[0].clientX-center.x,
e.touches[0].clientY-center.y,
deadzone,
() => resetCenter(e)
)
}
})();
const handleGamepads=(() => {
let deadzone;
const init=() => {
deadzone=config.getN('input.touchscreen.swipe.deadzone');
};
config.watchN('input.touchscreen.swipe.deadzone', (k, v) => {
deadzone=v;
});
return {
init,
frame: () => {
const gp=navigator.getGamepads()[0];
let inputs=currentInputs;
if(!gp || !gp.axes) return;
handleAngleMagnitude(
gp.axes[0],
gp.axes[1],
deadzone,
null
);
}
};
})();
const handleEvent=(type, evt) => {
for(let handler of handlers) {
let fn=handler[type];
if(fn) fn(evt);
}
};
const enableHandler=handler => {
if(!handlers.includes(handler)) {
handlers.push(handler);
if(handler.init) handler.init();
}
};
const disableHandler=handler => {
let idx=handlers.indexOf(handler);
if(idx!=-1) {
handlers.splice(idx, 1);
if(handler.fini) handler.fini();
}
};
const linkHandler=(handler, key) => {
if(config.getB(key)) enableHandler(handler);
config.watchB(key, (k, v) => {
if(v) enableHandler(handler);
else disableHandler(handler);
});
};
const init=({hud: hudElem}) => {
hud=hudElem;
linkHandler(handleCrosspad, 'input.touchscreen.crosspad.enabled');
linkHandler(handleJoystick, 'input.touchscreen.joystick.enabled');
linkHandler(handleSwipe, 'input.touchscreen.swipe.enabled');
linkHandler(handleGamepads, 'input.gamepad.enabled');
linkHandler(handleKeyboard, 'input.keyboard.enabled');
};
const clear=() =>
Object
.keys(currentInputs)
.forEach(key => delete currentInputs[key]);
for(let type of ['keydown', 'touchstart', 'touchmove']) {
window.addEventListener(type, handleEvent.bind(null, type));
}
return module.exports={
inputs: currentInputs,
clear,
framefn: handleEvent.bind(null, 'frame'),
init
};