feat: add mobile virtual touch controls
- document.js: add is_touch detection and virtual button CSS - input.js: create virtual D-pad + action buttons + touch drag camera - main.js: skip Pointer Lock on touch devices
This commit is contained in:
+22
-1
@@ -1,4 +1,7 @@
|
||||
|
||||
// Touch detection
|
||||
is_touch = 'ontouchstart' in window;
|
||||
|
||||
document.body.innerHTML +=
|
||||
'<style>'+
|
||||
'*{font-family:sans-serif;}'+
|
||||
@@ -10,6 +13,24 @@ document.body.innerHTML +=
|
||||
'#a,#h{position:absolute;bottom:3%;left:20%;right:0;font-size:3.2vw;}'+
|
||||
'#h{left:-20%;}'+
|
||||
'h1{font-size:16vw;margin:0;}'+
|
||||
(is_touch ?
|
||||
'#ctrl{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10}'+
|
||||
'#ctrl-left{position:absolute;bottom:18%;left:2%;width:28%;height:40%;pointer-events:auto}'+
|
||||
'#ctrl-right{position:absolute;top:0;right:0;width:70%;height:100%;pointer-events:auto}'+
|
||||
'#ctrl-actions{position:absolute;bottom:3%;right:2%;display:flex;gap:8px;pointer-events:auto}'+
|
||||
'.ctrl-btn{background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.25);'+
|
||||
'border-radius:12px;color:rgba(255,255,255,0.5);font-size:18px;'+
|
||||
'display:flex;align-items:center;justify-content:center;'+
|
||||
'user-select:none;-webkit-user-select:none;touch-action:none}'+
|
||||
'.ctrl-btn:active{background:rgba(255,255,255,0.35)}'+
|
||||
'#btn-up{position:absolute;top:0;left:50%;transform:translateX(-50%);width:48px;height:48px}'+
|
||||
'#btn-down{position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:48px;height:48px}'+
|
||||
'#btn-left{position:absolute;left:0;top:50%;transform:translateY(-50%);width:48px;height:48px}'+
|
||||
'#btn-right{position:absolute;right:0;top:50%;transform:translateY(-50%);width:48px;height:48px}'+
|
||||
'.ctrl-shoot{width:64px;height:64px;border-radius:50%;font-size:24px}'+
|
||||
'.ctrl-prev,.ctrl-next{width:40px;height:40px;font-size:14px}'+
|
||||
'.ctrl-jump{width:52px;height:52px;border-radius:50%;font-size:20px}'
|
||||
: '')+
|
||||
'</style>'+
|
||||
'<div id="g">'+
|
||||
'<canvas id=c width=320 height=180></canvas>'+
|
||||
@@ -21,4 +42,4 @@ document.body.innerHTML +=
|
||||
'<p><input id="f" type="button" value="FULLSCREEN"></p>'+
|
||||
'<p>'+
|
||||
'code: <a href="https://phoboslab.org">phoboslab.org</a> / music: <a href="http://no-fate.net">no-fate.net</a>'+
|
||||
'</p>';
|
||||
'</p>';
|
||||
|
||||
@@ -81,3 +81,72 @@ c.onmouseup = (ev) => {
|
||||
keys[key_action + ev.button] = 0;
|
||||
};
|
||||
|
||||
// ─── Mobile virtual controls ───
|
||||
|
||||
if (is_touch) {
|
||||
let ctrl = document.createElement('div');
|
||||
ctrl.id = 'ctrl';
|
||||
ctrl.innerHTML =
|
||||
'<div id="ctrl-left">'+
|
||||
'<button class="ctrl-btn" id="btn-up">▲</button>'+
|
||||
'<button class="ctrl-btn" id="btn-left">◀</button>'+
|
||||
'<button class="ctrl-btn" id="btn-right">▶</button>'+
|
||||
'<button class="ctrl-btn" id="btn-down">▼</button>'+
|
||||
'</div>'+
|
||||
'<div id="ctrl-right"></div>'+
|
||||
'<div id="ctrl-actions">'+
|
||||
'<button class="ctrl-btn ctrl-shoot" id="btn-shoot">⚡</button>'+
|
||||
'<button class="ctrl-btn ctrl-prev" id="btn-prev">◀◀</button>'+
|
||||
'<button class="ctrl-btn ctrl-next" id="btn-next">▶▶</button>'+
|
||||
'<button class="ctrl-btn ctrl-jump" id="btn-jump">▲</button>'+
|
||||
'</div>';
|
||||
document.getElementById('g').appendChild(ctrl);
|
||||
|
||||
// Prevent touches from reaching the canvas
|
||||
ctrl.addEventListener('touchstart', (e) => e.preventDefault(), {passive: false});
|
||||
ctrl.addEventListener('touchmove', (e) => e.preventDefault(), {passive: false});
|
||||
|
||||
// Bind button press/release
|
||||
let bindBtn = (id, keyIndex) => {
|
||||
let el = document.getElementById(id);
|
||||
el.addEventListener('touchstart', () => { keys[keyIndex] = 1; });
|
||||
el.addEventListener('touchend', () => { keys[keyIndex] = 0; });
|
||||
el.addEventListener('touchcancel', () => { keys[keyIndex] = 0; });
|
||||
};
|
||||
bindBtn('btn-up', 1);
|
||||
bindBtn('btn-down', 3);
|
||||
bindBtn('btn-left', 2);
|
||||
bindBtn('btn-right', 4);
|
||||
bindBtn('btn-shoot', 7);
|
||||
bindBtn('btn-jump', 9);
|
||||
bindBtn('btn-prev', 5);
|
||||
bindBtn('btn-next', 6);
|
||||
|
||||
// Right zone touch drag → camera
|
||||
let rightZone = document.getElementById('ctrl-right'),
|
||||
touch_id = null,
|
||||
last_tx = 0,
|
||||
last_ty = 0;
|
||||
rightZone.addEventListener('touchstart', (e) => {
|
||||
let t = e.changedTouches[0];
|
||||
touch_id = t.identifier;
|
||||
last_tx = t.clientX;
|
||||
last_ty = t.clientY;
|
||||
});
|
||||
rightZone.addEventListener('touchmove', (e) => {
|
||||
for (let t of e.changedTouches) {
|
||||
if (t.identifier === touch_id) {
|
||||
mouse_x += t.clientX - last_tx;
|
||||
mouse_y += t.clientY - last_ty;
|
||||
last_tx = t.clientX;
|
||||
last_ty = t.clientY;
|
||||
}
|
||||
}
|
||||
});
|
||||
rightZone.addEventListener('touchend', (e) => {
|
||||
for (let t of e.changedTouches) {
|
||||
if (t.identifier === touch_id) touch_id = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -124,8 +124,10 @@ game_load = async () => {
|
||||
|
||||
f.onclick = () => g.requestFullscreen();
|
||||
g.onclick = () => {
|
||||
g.onclick = () => c.requestPointerLock();
|
||||
g.onclick();
|
||||
if (!is_touch) {
|
||||
g.onclick = () => c.requestPointerLock();
|
||||
g.onclick();
|
||||
}
|
||||
|
||||
audio_init();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user