| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> |
| <title>GBA.js - Joystick Edition</title> |
| |
| <link rel="stylesheet" href="resources/main.css"> |
| <script src="js/util.js"></script> |
| <script src="js/core.js"></script> |
| <script src="js/arm.js"></script> |
| <script src="js/thumb.js"></script> |
| <script src="js/mmu.js"></script> |
| <script src="js/io.js"></script> |
| <script src="js/audio.js"></script> |
| <script src="js/video.js"></script> |
| <script src="js/video/proxy.js"></script> |
| <script src="js/video/software.js"></script> |
| <script src="js/irq.js"></script> |
| <script src="js/keypad.js"></script> |
| <script src="js/sio.js"></script> |
| <script src="js/savedata.js"></script> |
| <script src="js/gpio.js"></script> |
| <script src="js/gba.js"></script> |
|
|
| <style> |
| body, html { |
| background: #000; color: #fff; margin: 0; padding: 0; |
| width: 100%; height: 100%; overflow: hidden; font-family: sans-serif; |
| display: flex; flex-direction: column; |
| } |
| |
| |
| #screen-container { |
| flex: 1; display: flex; align-items: center; justify-content: center; |
| background: #111; position: relative; |
| } |
| canvas#screen { max-width: 100%; max-height: 100%; image-rendering: pixelated; } |
| |
| |
| #touch-controller { |
| height: 45vh; background: #1a1a1a; border-top: 3px solid #333; |
| display: grid; grid-template-columns: 1fr 1fr; |
| position: relative; |
| } |
| |
| |
| .joystick-zone { display: flex; align-items: center; justify-content: center; position: relative; } |
| |
| #joystick-base { |
| width: 140px; height: 140px; background: rgba(255,255,255,0.1); |
| border-radius: 50%; border: 2px solid #444; position: relative; |
| touch-action: none; |
| } |
| |
| #joystick-handle { |
| width: 60px; height: 60px; background: #555; |
| border: 2px solid #888; border-radius: 50%; |
| position: absolute; top: 50%; left: 50%; |
| transform: translate(-50%, -50%); |
| box-shadow: 0 4px 10px rgba(0,0,0,0.5); |
| pointer-events: none; |
| } |
| |
| |
| .action-zone { display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 20px; } |
| .ab-buttons { display: flex; gap: 25px; transform: rotate(-15deg); } |
| |
| .btn-circle { |
| width: 75px; height: 75px; border-radius: 50%; |
| display: flex; align-items: center; justify-content: center; |
| font-weight: bold; font-size: 24px; color: #fff; |
| box-shadow: 0 6px 0 #000; |
| } |
| .btn-a { background: #ff1744; } |
| .btn-b { background: #ffea00; color: #000; } |
| .btn-circle:active { transform: translateY(4px); box-shadow: 0 2px 0 #000; opacity: 0.9; } |
| |
| .meta-row { display: flex; gap: 20px; margin-top: 30px; } |
| .btn-pill { background: #444; padding: 8px 20px; border-radius: 20px; font-size: 11px; font-weight: bold; } |
| |
| #load-btn { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 15px 30px; background: #00eaff; color: #000; border: none; font-weight: bold; border-radius: 8px; z-index: 5; } |
| .hidden { display: none; } |
| </style> |
| </head> |
| <body> |
|
|
| <div id="screen-container"> |
| <canvas id="screen" width="480" height="320"></canvas> |
| <button id="load-btn" onclick="document.getElementById('file-input').click()">CHOOSE GBA GAME</button> |
| <input id="file-input" type="file" accept=".gba" class="hidden" onchange="loadGame(this.files[0])"> |
| </div> |
|
|
| [attachment_0](attachment) |
|
|
| <div id="touch-controller"> |
| <div class="joystick-zone"> |
| <div id="joystick-base"> |
| <div id="joystick-handle"></div> |
| </div> |
| </div> |
|
|
| <div class="action-zone"> |
| <div class="ab-buttons"> |
| <div class="btn-circle btn-b" data-key="5">B</div> |
| <div class="btn-circle btn-a" data-key="4">A</div> |
| </div> |
| <div class="meta-row"> |
| <div class="btn-pill" data-key="10">SELECT</div> |
| <div class="btn-pill" data-key="11">START</div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| var gba; |
| try { |
| gba = new GameBoyAdvance(); |
| gba.keypad.eatInput = true; |
| gba.setCanvas(document.getElementById('screen')); |
| } catch (e) { console.error(e); } |
| |
| function loadGame(file) { |
| var reader = new FileReader(); |
| reader.onload = (e) => { |
| gba.loadRomFromFile(e.target.result, (res) => { |
| if(res) { gba.runMain(); document.getElementById('load-btn').classList.add('hidden'); } |
| }); |
| }; |
| reader.readAsArrayBuffer(file); |
| } |
| |
| |
| const base = document.getElementById('joystick-base'); |
| const handle = document.getElementById('joystick-handle'); |
| const baseRect = base.getBoundingClientRect(); |
| const centerX = baseRect.width / 2; |
| const centerY = baseRect.height / 2; |
| const maxLimit = 50; |
| |
| let activeKeys = { 0:false, 1:false, 2:false, 3:false }; |
| |
| base.addEventListener('touchstart', handleJoystick, {passive: false}); |
| base.addEventListener('touchmove', handleJoystick, {passive: false}); |
| base.addEventListener('touchend', () => { |
| handle.style.left = '50%'; |
| handle.style.top = '50%'; |
| resetDirections(); |
| }, {passive: false}); |
| |
| function handleJoystick(e) { |
| e.preventDefault(); |
| const touch = e.touches[0]; |
| const rect = base.getBoundingClientRect(); |
| let x = touch.clientX - rect.left - centerX; |
| let y = touch.clientY - rect.top - centerY; |
| |
| const distance = Math.sqrt(x*x + y*y); |
| if (distance > maxLimit) { |
| x = x * (maxLimit / distance); |
| y = y * (maxLimit / distance); |
| } |
| |
| handle.style.left = (centerX + x) + 'px'; |
| handle.style.top = (centerY + y) + 'px'; |
| |
| |
| const threshold = 15; |
| updateKey(2, x < -threshold); |
| updateKey(3, x > threshold); |
| updateKey(0, y < -threshold); |
| updateKey(1, y > threshold); |
| } |
| |
| function updateKey(keyId, isPressed) { |
| if (isPressed && !activeKeys[keyId]) { |
| gba.keypad.keydown(keyId); |
| activeKeys[keyId] = true; |
| } else if (!isPressed && activeKeys[keyId]) { |
| gba.keypad.keyup(keyId); |
| activeKeys[keyId] = false; |
| } |
| } |
| |
| function resetDirections() { |
| for(let i=0; i<4; i++) updateKey(i, false); |
| } |
| |
| |
| document.querySelectorAll('[data-key]').forEach(btn => { |
| const k = parseInt(btn.getAttribute('data-key')); |
| btn.addEventListener('touchstart', (e) => { e.preventDefault(); gba.keypad.keydown(k); if(navigator.vibrate) navigator.vibrate(15); }); |
| btn.addEventListener('touchend', (e) => { e.preventDefault(); gba.keypad.keyup(k); }); |
| }); |
| </script> |
| </body> |
| </html> |
|
|