<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Tilt Racing Game</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
font-family: sans-serif;
touch-action: none;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
#score {
color: #fff;
font-size: 24px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
#start-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
z-index: 10;
}
#start-btn {
padding: 15px 30px;
font-size: 20px;
font-weight: bold;
background: #ff4757;
color: #fff;
border: none;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
}
</style>
<!-- Three.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<div id="ui">
<div id="score">SCORE: 0</div>
</div>
<div id="start-overlay">
<h1 style="margin-bottom: 10px;">傾きレースゲーム</h1>
<p style="margin-bottom: 30px; text-align: center; line-height: 1.5;">
スマホを左右に傾けてハンドル操作!<br>障害物を避けて走り抜けよう。
</p>
<button id="start-btn">スタート / センサー許可</button>
</div>
<script>
let scene, camera, renderer;
let player, track;
let obstacles = [];
let score = 0;
let isPlaying = false;
let tiltX = 0;
const ROAD_WIDTH = 12;
const SPEED = 0.6;
function init() {
// 1. シーン・カメラ・レンダラー設定
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0xa0a0a0, 0.015);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 3, 7);
camera.lookAt(0, 1, 0);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xa0a0a0);
document.body.appendChild(renderer.domElement);
// 2. 照明
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(10, 20, 10);
scene.add(dirLight);
// 3. 道路(トラック)
const trackGeo = new THREE.PlaneGeometry(ROAD_WIDTH, 1000);
const trackMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
track = new THREE.Mesh(trackGeo, trackMat);
track.rotation.x = -Math.PI / 2;
track.position.z = -450;
scene.add(track);
// 白線(サイドライン)
const lineGeo = new THREE.PlaneGeometry(0.3, 1000);
const lineMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
const leftLine = new THREE.Mesh(lineGeo, lineMat);
leftLine.position.set(-ROAD_WIDTH/2 + 0.3, 0.01, 0);
track.add(leftLine);
const rightLine = new THREE.Mesh(lineGeo, lineMat);
rightLine.position.set(ROAD_WIDTH/2 - 0.3, 0.01, 0);
track.add(rightLine);
// 4. プレイヤー(自機)
const playerGeo = new THREE.BoxGeometry(1.2, 0.6, 2.2);
const playerMat = new THREE.MeshStandardMaterial({ color: 0xff4757 });
player = new THREE.Mesh(playerGeo, playerMat);
player.position.set(0, 0.3, 0);
scene.add(player);
// ウィンドウサイズ変更対応
window.addEventListener('resize', onWindowResize);
// スタートボタンイベント
document.getElementById('start-btn').addEventListener('click', startGame);
}
// 傾きセンサーの権限要求&イベント登録
function requestOrientationPermission() {
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
// iOS 13+ 用の許可要求
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
} else {
alert('センサーの使用許可が得られませんでした。');
}
})
.catch(console.error);
} else {
// Android / 古いiOS用
window.addEventListener('deviceorientation', handleOrientation);
}
}
// 傾きデータの取得
function handleOrientation(event) {
// gamma: 左右の傾き(-90 ~ 90)
let g = event.gamma;
if (g !== null) {
// 感度調整(-30度〜30度を -1.0 〜 1.0 に正規化)
tiltX = Math.max(-1, Math.min(1, g / 30));
}
}
function startGame() {
requestOrientationPermission();
document.getElementById('start-overlay').style.display = 'none';
score = 0;
obstacles.forEach(obs => scene.remove(obs));
obstacles = [];
player.position.x = 0;
isPlaying = true;
animate();
}
function spawnObstacle() {
if (Math.random() < 0.05) { // 生成確率
const obsGeo = new THREE.BoxGeometry(1.5, 1.2, 1.5);
const obsMat = new THREE.MeshStandardMaterial({ color: 0x2ed573 });
const obstacle = new THREE.Mesh(obsGeo, obsMat);
// 道路内のランダムな位置に配置
const x = (Math.random() - 0.5) * (ROAD_WIDTH - 2);
obstacle.position.set(x, 0.6, -80);
scene.add(obstacle);
obstacles.push(obstacle);
}
}
function animate() {
if (!isPlaying) return;
requestAnimationFrame(animate);
// 1. 自機の移動(傾きに基づく)
player.position.x += tiltX * 0.25;
// 移動制限(コースアウト防止)
const limit = (ROAD_WIDTH / 2) - 0.8;
if (player.position.x > limit) player.position.x = limit;
if (player.position.x < -limit) player.position.x = -limit;
// 自機を少し傾ける演出
player.rotation.z = -tiltX * 0.2;
// 2. 障害物の生成と移動
spawnObstacle();
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.position.z += SPEED * 1.5;
// 当たり判定 (簡易AABB)
const dx = Math.abs(player.position.x - obs.position.x);
const dz = Math.abs(player.position.z - obs.position.z);
if (dx < 1.2 && dz < 1.5) {
gameOver();
return;
}
// 画面外(カメラの後ろ)に出たら削除
if (obs.position.z > 5) {
scene.remove(obs);
obstacles.splice(i, 1);
score += 100;
document.getElementById('score').innerText = `SCORE: ${score}`;
}
}
renderer.render(scene, camera);
}
function gameOver() {
isPlaying = false;
alert(`GAME OVER!\nスコア: ${score}`);
document.getElementById('start-overlay').style.display = 'flex';
document.getElementById('start-btn').innerText = 'リトライ';
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.onload = init;
</script>
</body>
</html>
Auto Amazon Links: プロダクトが見つかりません。


コメント