minigame 1 add joystick

This commit is contained in:
2026-05-06 07:12:19 +00:00
parent f2705ffd62
commit cc1da7a114
2 changed files with 199 additions and 5 deletions
+128 -4
View File
@@ -1962,6 +1962,10 @@
let quizQuestionMissionPhase = null;
let quizQuestionMissionCountdownTimer = null;
let quizQuestionMissionDeferredPhase = null;
/** mng8a80o live: virtual joystick (+x right, +y down) normalized ~[-1,1] */
let quizQuestionMissionJoyVecX = 0;
let quizQuestionMissionJoyVecY = 0;
let quizQuestionMissionJoyPointerId = null;
/** Stack Tower (mnn93hpi) — flow เดียวกับ crown / quiz mission */
let stackTowerMissionPhase = null;
let stackTowerMissionCountdownTimer = null;
@@ -15395,6 +15399,60 @@
}
}
function quizQuestionMissionJoystickResetVisual() {
quizQuestionMissionJoyVecX = 0;
quizQuestionMissionJoyVecY = 0;
const knob = document.getElementById('quiz-q-mission-joystick-knob');
if (knob) knob.style.transform = 'translate(-50%, -50%)';
}
function quizQuestionMissionJoystickUpdateFromClientXY(clientX, clientY) {
const base = document.getElementById('quiz-q-mission-joystick-base');
const knob = document.getElementById('quiz-q-mission-joystick-knob');
if (!base || !knob) return;
const r = base.getBoundingClientRect();
const cx = r.left + r.width * 0.5;
const cy = r.top + r.height * 0.5;
let dx = clientX - cx;
let dy = clientY - cy;
const half = Math.min(r.width, r.height) * 0.5;
const maxKn = Math.max(18, half * 0.52);
const len = Math.hypot(dx, dy);
if (len > maxKn && len > 1e-6) {
dx = (dx / len) * maxKn;
dy = (dy / len) * maxKn;
}
const nlen = Math.hypot(dx, dy);
const dead = maxKn * 0.12;
if (nlen < dead) {
quizQuestionMissionJoyVecX = 0;
quizQuestionMissionJoyVecY = 0;
knob.style.transform = 'translate(-50%, -50%)';
return;
}
quizQuestionMissionJoyVecX = dx / maxKn;
quizQuestionMissionJoyVecY = dy / maxKn;
knob.style.transform = 'translate(calc(-50% + ' + dx + 'px), calc(-50% + ' + dy + 'px))';
}
function syncQuizQuestionMissionJoystickPlay() {
const root = document.getElementById('quiz-question-mission-joystick');
if (!root) return;
const on = !!(isQuizQuestionMissionHudActivePlay() && !isChatFocused());
root.classList.toggle('is-hidden', !on);
root.setAttribute('aria-hidden', on ? 'false' : 'true');
if (!on) {
const base = document.getElementById('quiz-q-mission-joystick-base');
if (base && quizQuestionMissionJoyPointerId != null) {
try {
base.releasePointerCapture(quizQuestionMissionJoyPointerId);
} catch (_e) { /* ignore */ }
}
quizQuestionMissionJoyPointerId = null;
quizQuestionMissionJoystickResetVisual();
}
}
function syncPlayCyberHud() {
const root = document.getElementById('play-cyber-hud');
const stackEl = document.getElementById('play-canvas-stack');
@@ -16754,6 +16812,7 @@
}
drawQuizTfScorePopupsLayer(ctx, worldToScreen, zDraw, timeMs);
syncPlayCyberHud();
syncQuizQuestionMissionJoystickPlay();
syncQuizCarryGrabButton();
syncGauntletCrownJumpButton();
syncStackTowerDropButtonPlay();
@@ -17068,10 +17127,24 @@
}
}
if (!usePath) {
if (keys['ArrowUp'] || keys['KeyW']) { accY = -1; me.direction = 'up'; }
if (keys['ArrowDown'] || keys['KeyS']) { accY = 1; me.direction = 'down'; }
if (keys['ArrowLeft'] || keys['KeyA']) { accX = -1; me.direction = 'left'; }
if (keys['ArrowRight'] || keys['KeyD']) { accX = 1; me.direction = 'right'; }
let kx = 0;
let ky = 0;
if (keys['ArrowLeft'] || keys['KeyA']) kx -= 1;
if (keys['ArrowRight'] || keys['KeyD']) kx += 1;
if (keys['ArrowUp'] || keys['KeyW']) ky -= 1;
if (keys['ArrowDown'] || keys['KeyS']) ky += 1;
let jx = 0;
let jy = 0;
if (isQuizQuestionMissionHudActivePlay()) {
jx = quizQuestionMissionJoyVecX;
jy = quizQuestionMissionJoyVecY;
}
accX = kx + jx;
accY = ky + jy;
if (accX !== 0 || accY !== 0) {
if (Math.abs(accY) > Math.abs(accX)) me.direction = accY > 0 ? 'down' : 'up';
else if (accX !== 0) me.direction = accX > 0 ? 'right' : 'left';
}
}
const preWalkX = me.x, preWalkY = me.y;
if (accX !== 0 || accY !== 0) {
@@ -17177,6 +17250,57 @@
});
})();
(function wireQuizQuestionMissionJoystick() {
const base = document.getElementById('quiz-q-mission-joystick-base');
const bg = document.getElementById('quiz-q-mission-joystick-bg');
const knobImg = document.getElementById('quiz-q-mission-joystick-knob-img');
if (!base) return;
if (bg) {
bg.src = questionMissionAssetUrl('btn-joystick-1.png');
bg.onerror = function () {
this.onerror = null;
this.removeAttribute('src');
};
}
if (knobImg) {
knobImg.src = questionMissionAssetUrl('btn-joystick-2.png');
knobImg.onerror = function () {
this.onerror = null;
this.removeAttribute('src');
};
}
function endJoy(e) {
if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return;
try {
base.releasePointerCapture(e.pointerId);
} catch (_err) { /* ignore */ }
quizQuestionMissionJoyPointerId = null;
quizQuestionMissionJoystickResetVisual();
}
base.addEventListener('pointerdown', (e) => {
if (!isQuizQuestionMissionHudActivePlay() || isChatFocused()) return;
if (e.button != null && e.button !== 0) return;
e.preventDefault();
try {
base.setPointerCapture(e.pointerId);
} catch (_err) { /* ignore */ }
quizQuestionMissionJoyPointerId = e.pointerId;
quizQuestionMissionJoystickUpdateFromClientXY(e.clientX, e.clientY);
});
base.addEventListener('pointermove', (e) => {
if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return;
e.preventDefault();
quizQuestionMissionJoystickUpdateFromClientXY(e.clientX, e.clientY);
});
base.addEventListener('pointerup', endJoy);
base.addEventListener('pointercancel', endJoy);
base.addEventListener('lostpointercapture', (e) => {
if (quizQuestionMissionJoyPointerId == null || e.pointerId !== quizQuestionMissionJoyPointerId) return;
quizQuestionMissionJoyPointerId = null;
quizQuestionMissionJoystickResetVisual();
});
})();
(function wireGauntletCrownHowtoPrimary() {
const btn = document.getElementById('btn-gch-ready');
if (!btn) return;
+71 -1
View File
@@ -1673,6 +1673,67 @@
overflow: visible;
min-height: unset;
}
/* mng8a80o — virtual joystick (Roblox-style) มุมซ้ายล่าง */
.quiz-q-mission-joystick {
position: fixed;
z-index: 56;
left: max(10px, env(safe-area-inset-left, 0px));
bottom: max(12px, env(safe-area-inset-bottom, 0px));
width: min(132px, 28vw);
height: min(132px, 28vw);
touch-action: none;
user-select: none;
-webkit-user-select: none;
pointer-events: auto;
box-sizing: border-box;
}
html.play-preview-editor-embed .quiz-q-mission-joystick {
position: absolute;
bottom: max(12px, env(safe-area-inset-bottom, 0px));
}
.quiz-q-mission-joystick.is-hidden {
display: none !important;
}
.quiz-q-mission-joystick-base {
position: relative;
width: 100%;
height: 100%;
cursor: grab;
}
.quiz-q-mission-joystick-base:active {
cursor: grabbing;
}
.quiz-q-mission-joystick-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none;
image-rendering: pixelated;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45));
}
.quiz-q-mission-joystick-knob {
position: absolute;
left: 50%;
top: 50%;
width: 42%;
height: 42%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
transition: transform 0.04s linear;
}
.quiz-q-mission-joystick-knob-img {
width: 100%;
height: 100%;
object-fit: contain;
image-rendering: pixelated;
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.35));
}
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-score-list {
box-sizing: border-box;
width: 100%;
@@ -2938,6 +2999,15 @@
<div class="play-cyber-corruption-text">RAM ERROR · CORRUPTING · DELETE INITIATED · 01001101 01110011 01100101 01100011</div>
</div>
</div>
<!-- ภารกิจคำถาม mng8a80o ช่วง live: virtual joystick (QUESTION/btn-joystick-*.png) -->
<div id="quiz-question-mission-joystick" class="quiz-q-mission-joystick is-hidden" aria-hidden="true">
<div class="quiz-q-mission-joystick-base" id="quiz-q-mission-joystick-base">
<img id="quiz-q-mission-joystick-bg" class="quiz-q-mission-joystick-bg" src="" alt="" decoding="async" />
<div class="quiz-q-mission-joystick-knob" id="quiz-q-mission-joystick-knob">
<img id="quiz-q-mission-joystick-knob-img" class="quiz-q-mission-joystick-knob-img" src="" alt="" decoding="async" />
</div>
</div>
</div>
</div>
<div id="play-quiz-scoreboard" class="is-hidden" aria-live="polite">
<div class="play-quiz-scoreboard-title">คะแนน</div>
@@ -3075,7 +3145,7 @@
</div>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0306"></script>
<script src="js/play.js?v=0.0332"></script>
<script src="js/play.js?v=0.0333"></script>
<div class="version-tag">v —</div>
</body>
</html>