🎮ArcadeLab

Kota Merdeka - Mirip GTA SA

by CrystalLegend30
222 lines8.9 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Kota Merdeka - Mirip GTA SA</title>
    <style>
        body { margin: 0; overflow: hidden; font-family: Arial; }
        #info {
            position: absolute; top: 10px; left: 10px;
            color: white; background: rgba(0,0,0,0.6);
            padding: 10px; border-radius: 5px; z-index: 100;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/js/controls/PointerLockControls.js"></script>
</head>
<body>
    <div id="info">
        🎮 Kontrol:<br>
        WASD = Gerak | Mouse = Lihat Sekitar<br>
        Spasi = Lompat/Gas | Shift = Lari<br>
        E = Masuk/Keluar Mobil | Klik layar untuk mulai
    </div>

    <script>
        let scene, camera, renderer, controls;
        let player, car, isInCar = false;
        let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
        let canJump = true;
        const playerSpeed = 0.12;
        const runSpeed = 0.22;
        const carSpeed = 0.15;
        const carTurnSpeed = 0.03;

        // Inisialisasi dasar
        function init() {
            // Scene
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x87CEEB);
            scene.fog = new THREE.Fog(0x87CEEB, 30, 150);

            // Kamera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 200);
            camera.position.set(0, 5, -10);

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            document.body.appendChild(renderer.domElement);

            // Kontrol kamera
            controls = new THREE.PointerLockControls(camera, document.body);
            document.addEventListener('click', () => controls.lock());
            controls.addEventListener('lock', () => document.getElementById('info').style.display = 'none');
            controls.addEventListener('unlock', () => document.getElementById('info').style.display = 'block');

            // Pencahayaan
            const light = new THREE.DirectionalLight(0xffffff, 1);
            light.position.set(20, 50, 20);
            light.castShadow = true;
            scene.add(light);
            scene.add(new THREE.AmbientLight(0xffffff, 0.5));

            // Tanah
            const groundGeo = new THREE.PlaneGeometry(200, 200);
            const groundMat = new THREE.MeshStandardMaterial({ color: 0x3a7d44, roughness: 0.8 });
            const ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Bangunan kota
            createBuildings();

            // Karakter pemain
            createPlayer();

            // Kendaraan
            createCar();

            // Deteksi tombol
            document.addEventListener('keydown', onKeyDown);
            document.addEventListener('keyup', onKeyUp);

            window.addEventListener('resize', onWindowResize);
            animate();
        }

        // Buat bangunan sederhana
        function createBuildings() {
            const colors = [0x8b4513, 0x2c3e50, 0x95a5a6, 0xe74c3c];
            for (let x = -80; x <= 80; x += 20) {
                for (let z = -80; z <= 80; z += 20) {
                    if (Math.abs(x) < 10 && Math.abs(z) < 10) continue;
                    const height = Math.random() * 8 + 4;
                    const buildingGeo = new THREE.BoxGeometry(8, height, 8);
                    const buildingMat = new THREE.MeshStandardMaterial({ 
                        color: colors[Math.floor(Math.random() * colors.length)] 
                    });
                    const building = new THREE.Mesh(buildingGeo, buildingMat);
                    building.position.set(x, height/2, z);
                    building.castShadow = true;
                    building.receiveShadow = true;
                    scene.add(building);
                }
            }
        }

        // Buat karakter
        function createPlayer() {
            const playerGeo = new THREE.CapsuleGeometry(0.5, 1, 4, 8);
            const playerMat = new THREE.MeshStandardMaterial({ color: 0x2196F3 });
            player = new THREE.Mesh(playerGeo, playerMat);
            player.position.set(0, 1, 0);
            player.castShadow = true;
            scene.add(player);
        }

        // Buat mobil
        function createCar() {
            const bodyGeo = new THREE.BoxGeometry(2, 1, 4);
            const bodyMat = new THREE.MeshStandardMaterial({ color: 0xe63946 });
            const body = new THREE.Mesh(bodyGeo, bodyMat);
            body.position.y = 0.6;

            const wheelGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 16);
            const wheelMat = new THREE.MeshStandardMaterial({ color: 0x222222 });
            const wheel1 = new THREE.Mesh(wheelGeo, wheelMat);
            wheel1.rotation.z = Math.PI / 2;
            wheel1.position.set(-1, 0.3, 1.5);
            const wheel2 = wheel1.clone(); wheel2.position.x = 1;
            const wheel3 = wheel1.clone(); wheel3.position.z = -1.5;
            const wheel4 = wheel2.clone(); wheel4.position.z = -1.5;

            car = new THREE.Group();
            car.add(body, wheel1, wheel2, wheel3, wheel4);
            car.position.set(8, 0, 0);
            car.castShadow = true;
            scene.add(car);
        }

        // Input tombol
        function onKeyDown(e) {
            switch(e.code) {
                case 'KeyW': moveForward = true; break;
                case 'KeyS': moveBackward = true; break;
                case 'KeyA': moveLeft = true; break;
                case 'KeyD': moveRight = true; break;
                case 'Space': if(!isInCar && canJump) { player.position.y += 1.2; canJump = false; setTimeout(() => canJump = true, 800); } break;
                case 'ShiftLeft': case 'ShiftRight': player.isRunning = true; break;
                case 'KeyE': enterExitCar(); break;
            }
        }

        function onKeyUp(e) {
            switch(e.code) {
                case 'KeyW': moveForward = false; break;
                case 'KeyS': moveBackward = false; break;
                case 'KeyA': moveLeft = false; break;
                case 'KeyD': moveRight = false; break;
                case 'ShiftLeft': case 'ShiftRight': player.isRunning = false; break;
            }
        }

        // Masuk/keluar mobil
        function enterExitCar() {
            const jarak = player.position.distanceTo(car.position);
            if(!isInCar && jarak < 3) {
                isInCar = true;
                player.visible = false;
                camera.position.set(0, 2, -5);
                camera.parent = car;
            } else if(isInCar) {
                isInCar = false;
                player.visible = true;
                player.position.copy(car.position).add(new THREE.Vector3(-3, 0, 0));
                camera.parent = null;
                camera.position.set(player.position.x, player.position.y + 2, player.position.z - 5);
            }
        }

        // Gerakan
        function updateMovement() {
            const speed = player.isRunning ? runSpeed : playerSpeed;
            const direction = new THREE.Vector3();
            camera.getWorldDirection(direction);
            direction.y = 0;
            direction.normalize();

            if(!isInCar) {
                if(moveForward) player.position.addScaledVector(direction, speed);
                if(moveBackward) player.position.addScaledVector(direction, -speed * 0.7);
                if(moveLeft) player.position.addScaledVector(direction.cross(new THREE.Vector3(0,1,0)), -speed * 0.7);
                if(moveRight) player.position.addScaledVector(direction.cross(new THREE.Vector3(0,1,0)), speed * 0.7);
                if(player.position.y > 1) player.position.y -= 0.05;
                camera.position.set(player.position.x, player.position.y + 2, player.position.z - 5);
            } else {
                if(moveForward) car.translateZ(-carSpeed);
                if(moveBackward) car.translateZ(carSpeed * 0.6);
                if(moveLeft) car.rotation.y += carTurnSpeed;
                if(moveRight) car.rotation.y -= carTurnSpeed;
            }
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            updateMovement();
            renderer.render(scene, camera);
        }

        init();
    </script>
</body>
</html>

Game Source: Kota Merdeka - Mirip GTA SA

Creator: CrystalLegend30

Libraries: three

Complexity: complex (222 lines, 8.9 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: kota-merdeka-mirip-gta-sa-crystallegend30" to link back to the original. Then publish at arcadelab.ai/publish.