Skip to content

Commit

Permalink
Virtual Joystick #1
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Chirls committed Nov 27, 2020
1 parent 48cadba commit bee27e1
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 1 deletion.
22 changes: 21 additions & 1 deletion examples/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import AxisComposite from '../src/controls/AxisComposite';
import Action from '../src/Action';
import PressInteraction from '../src/interactions/PressInteraction';
import ReleaseInteraction from '../src/interactions/ReleaseInteraction';
import VirtualJoystick from '../src/devices/virtualjoystick';
import DOMRenderer from '../src/devices/virtualjoystick/DOMRenderer';

const gamepad = new Gamepad();
const leftStick = gamepad.getControl('leftStick');
Expand All @@ -20,17 +22,29 @@ const kbdWASD = new DPadComposite({
right: kbd.getControl('D')
});

const pointer = new Pointer();
const pointer = new Pointer({
touch: false
});

const rotateArrowKeys = new AxisComposite({
negative: kbd.getControl('arrowleft'),
positive: kbd.getControl('arrowright')
});

const leftTouchJoystick = new VirtualJoystick({
filter: evt => evt.pageX < Math.max(200, window.innerWidth * 0.4)
}).getControl();


const rightTouchJoystick = new VirtualJoystick({
filter: evt => evt.pageX > window.innerWidth - Math.max(200, window.innerWidth * 0.4)
}).getControl();

const moveAction = new Action({
bindings: [
leftStick,
kbdWASD,
leftTouchJoystick,
{
control: pointer.getControl('delta'),
processors: [
Expand All @@ -46,6 +60,7 @@ const rotateAction = new Action({
bindings: [
rightStickHoriz,
rotateArrowKeys,
rightTouchJoystick.find('x'),
pointer.getControl('wheel')
]
});
Expand Down Expand Up @@ -100,4 +115,9 @@ function update(t = performance.now()) {
requestAnimationFrame(update);
}

/* eslint-disable no-new */
new DOMRenderer(leftTouchJoystick);
new DOMRenderer(rightTouchJoystick);
/* eslint-enable no-new */

update();
73 changes: 73 additions & 0 deletions src/devices/virtualjoystick/DOMRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export default function DOMRenderer(control, options = {}) {
const { device } = control;
const {
innerRadius = Math.max(24, device.radius / 4),
parentElement = document.body
} = options;

const outer = document.createElement('div');
Object.assign(outer.style, {
position: 'fixed',
pointerEvents: 'none',
borderRadius: 999e20 + 'px',

transition: 'opacity 0.1s',

//temp
backgroundColor: 'rgba(128, 128, 128, 0.5)'
});

const inner = document.createElement('div');
Object.assign(inner.style, {
position: 'fixed',
pointerEvents: 'none',
borderRadius: 999e20 + 'px',

transition: 'opacity 0.1s',

//temp
backgroundColor: 'rgba(128, 128, 128, 0.5)'
});
outer.appendChild(inner);

let rafId = 0;

function render() {
const [x, y] = control.read();

if (control.mode === 'static' || x || y) {
const outerSize = device.radius * 2;
const ox = device.x - device.radius;
const oy = device.y - device.radius;

outer.style.left = ox + 'px';
outer.style.top = oy + 'px';
outer.style.width = outer.style.height = outerSize + 'px';
outer.style.opacity = 1;

const innerSize = innerRadius * 2;

inner.style.left = device.x + x * device.radius - innerRadius + 'px';
inner.style.top = device.y - y * device.radius - innerRadius + 'px';
inner.style.width = inner.style.height = innerSize + 'px';
inner.style.opacity = 1;
} else {
outer.style.opacity = 0;
inner.style.opacity = 0;
}

rafId = requestAnimationFrame(render);
}

rafId = requestAnimationFrame(render);

this.destroy = () => {
cancelAnimationFrame(rafId);

if (outer.parentNode) {
outer.parentNode.removeChild(outer);
}
};

parentElement.appendChild(outer);
}
166 changes: 166 additions & 0 deletions src/devices/virtualjoystick/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import StickInputControl from '../../controls/StickInputControl';

export default function VirtualJoystick({
element = document.body,
radius = 60,
x = 0,
y = 0,
mode = 'dynamic',
lockX = false,
lockY = false,
touch = true,
pen = true,
mouse = false,
filter = null,
touchActionStyle = true
} = {}) {

let startEvent = null;
let lastEvent = null;
let startX = 0;
let startY = 0;
let deltaX = 0;
let deltaY = 0;

const isStatic = mode === 'static';
const supported = mouse || pen || touch && navigator.maxTouchPoints > 0;

const allowedPointerTypes = {
pen,
mouse,
touch
};

/*
For now, each device only handles one pointer at a time
*/
function pointerDown(evt) {
if (!startEvent &&
allowedPointerTypes[evt.pointerType] && (!filter || filter(evt))) {

if (isStatic) {
const dx = evt.offsetX - x;
const dy = evt.offsetY - y;
if (Math.hypot(dx, dy) > radius) {
return;
}

startX = x;
startY = y;
} else {
startX = evt.offsetX;
startY = evt.offsetY;
}

startEvent = evt;
lastEvent = evt;
}
}

function pointerMove(evt) {
if (startEvent && startEvent.pointerId === evt.pointerId) {
lastEvent = evt;

// calculate and set x/y
const dx = lockX ? 0 : evt.offsetX - startX;
const dy = lockY ? 0 : evt.offsetY - startY;
const length = Math.hypot(dx, dy);

const divisor = Math.max(length, radius);
deltaX = dx / divisor;
deltaY = -dy / divisor;
}
}

function pointerUp(evt) {
if (startEvent && startEvent.pointerId === evt.pointerId) {
lastEvent = evt;
startEvent = null;
}
}

const styleElement = touchActionStyle && element.style ? element : document.body;
if (touchActionStyle) {
styleElement.style.touchAction = 'none';
}

this.getControl = (/*name,*/ options = {}) => {
// if (typeof options === 'string') {
// throw new Error('VirtualJoystick accepts options object');
// }
const read = () => {
return startEvent ?
[deltaX, deltaY] :
StickInputControl.defaultValue;
};

return new StickInputControl(read, Object.assign(
{},
options,
{ device: this }
));
};

this.destroy = () => {
if (touchActionStyle) {
styleElement.style.touchAction = '';
}

element.removeEventListener('pointerdown', pointerDown);
element.removeEventListener('pointermove', pointerMove);
element.removeEventListener('pointerup', pointerUp);
element.removeEventListener('pointercancel', pointerUp);
};

Object.defineProperties(this, {
pointerType: {
get: () => lastEvent && lastEvent.pointerType || ''
},

connected: {
enumerable: false,
configurable: false,
writable: false,
value: supported
},

timestamp: {
get: () => lastEvent && lastEvent.timeStamp || 0
},

element: {
enumerable: false,
configurable: false,
writable: false,
value: element
},

x: {
get: () => isStatic ? x : startX,
set: val => {
x = val;
}
},

y: {
get: () => isStatic ? y : startY,
set: val => {
y = val;
}
},

radius: {
get: () => radius,
set: val => {
radius = val;
}
}
});

if (supported) {
element.addEventListener('pointerdown', pointerDown);
element.addEventListener('pointermove', pointerMove);
element.addEventListener('pointerup', pointerUp);
element.addEventListener('pointercancel', pointerUp);
}
}

0 comments on commit bee27e1

Please sign in to comment.