Update index.html
Browse files- index.html +365 -18
index.html
CHANGED
@@ -1,19 +1,366 @@
|
|
1 |
<!doctype html>
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<!doctype html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8" />
|
5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
6 |
+
<title>Sling Humans — Prototype</title>
|
7 |
+
<style>
|
8 |
+
html,body{height:100%;margin:0;background:linear-gradient(#bfe9ff,#eaf7ff);font-family:Inter,system-ui,Segoe UI,Arial}
|
9 |
+
#game {width:100%;height:100vh;overflow:hidden}
|
10 |
+
.ui {position:absolute;left:14px;top:14px;background:rgba(255,255,255,0.9);padding:10px;border-radius:10px;box-shadow:0 6px 18px rgba(13,38,76,0.08);z-index:10}
|
11 |
+
.ui button{background:#1976d2;color:white;border:none;padding:8px 12px;border-radius:8px;cursor:pointer}
|
12 |
+
.ui .small{font-size:13px;color:#0b2540;margin-top:6px}
|
13 |
+
.credits{position:absolute;right:14px;top:14px;background:rgba(255,255,255,0.9);padding:8px 10px;border-radius:8px;font-size:13px;z-index:10}
|
14 |
+
</style>
|
15 |
+
</head>
|
16 |
+
<body>
|
17 |
+
<div id="game"></div>
|
18 |
+
<div class="ui">
|
19 |
+
<div style="font-weight:700">Sling Humans — Prototype</div>
|
20 |
+
<div style="margin-top:8px;display:flex;gap:8px">
|
21 |
+
<button id="restart">Restart</button>
|
22 |
+
<button id="next">Next Level</button>
|
23 |
+
<button id="powerup">Mega Launch</button>
|
24 |
+
</div>
|
25 |
+
<div class="small">Score: <span id="score">0</span> • Ammo: <span id="ammo">5</span> • Stars: <span id="stars">0</span></div>
|
26 |
+
<div style="margin-top:8px;font-size:13px;color:#444">Human: <select id="hType">
|
27 |
+
<option value="regular">Regular</option>
|
28 |
+
<option value="heavy">Heavyweight</option>
|
29 |
+
<option value="jumper">Jumper</option>
|
30 |
+
<option value="boomer">Boomer</option>
|
31 |
+
</select></div>
|
32 |
+
</div>
|
33 |
+
<div class="credits">Cartoon style — non graphic</div>
|
34 |
+
|
35 |
+
<!-- Phaser 3 CDN -->
|
36 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
|
37 |
+
<script>
|
38 |
+
/*
|
39 |
+
Sling Humans — Prototype
|
40 |
+
- Single-file Phaser 3 game
|
41 |
+
- Uses emoji + basic shapes
|
42 |
+
- Extendable: add assets, audio, more levels
|
43 |
+
*/
|
44 |
+
|
45 |
+
const WIDTH = Math.min(window.innerWidth, 1200);
|
46 |
+
const HEIGHT = Math.min(window.innerHeight, 800);
|
47 |
+
|
48 |
+
const config = {
|
49 |
+
type: Phaser.AUTO,
|
50 |
+
parent: 'game',
|
51 |
+
width: WIDTH,
|
52 |
+
height: HEIGHT,
|
53 |
+
backgroundColor: 0xe8f7ff,
|
54 |
+
physics: { default: 'matter', matter: { gravity: { y: 1.0 }, debug: false } },
|
55 |
+
scene: { preload, create, update }
|
56 |
+
};
|
57 |
+
|
58 |
+
const game = new Phaser.Game(config);
|
59 |
+
|
60 |
+
let slingPos = { x: 160, y: HEIGHT - 180 };
|
61 |
+
let currentHuman = null, isDragging=false;
|
62 |
+
let dottedLines = [];
|
63 |
+
let score = 0, ammo = 5, level = 0, stars = 0;
|
64 |
+
let blocksGroup = [], targetsGroup = [];
|
65 |
+
let megaLaunchActive = false;
|
66 |
+
|
67 |
+
function preload() {
|
68 |
+
// no external assets; can load sprites/sounds here later
|
69 |
+
}
|
70 |
+
|
71 |
+
function create() {
|
72 |
+
const scene = this;
|
73 |
+
|
74 |
+
// ground
|
75 |
+
const ground = scene.matter.add.rectangle(WIDTH/2, HEIGHT-24, WIDTH, 48, { isStatic: true, label: 'ground' });
|
76 |
+
|
77 |
+
// catapult base visuals
|
78 |
+
scene.add.circle(slingPos.x, slingPos.y, 20, 0x6d4c41).setDepth(2);
|
79 |
+
scene.add.rectangle(slingPos.x+28, slingPos.y+12, 10, 40, 0x4e342e).setOrigin(0.5).setDepth(2);
|
80 |
+
|
81 |
+
// UI hooks
|
82 |
+
document.getElementById('restart').onclick = () => resetLevel(scene);
|
83 |
+
document.getElementById('next').onclick = () => nextLevel(scene);
|
84 |
+
document.getElementById('powerup').onclick = () => { megaLaunchActive = true; document.getElementById('powerup').innerText='Mega! (On)'; setTimeout(()=>{megaLaunchActive=false; document.getElementById('powerup').innerText='Mega Launch';}, 8000); };
|
85 |
+
|
86 |
+
// level build
|
87 |
+
buildLevel(scene, level);
|
88 |
+
|
89 |
+
// spawn human
|
90 |
+
spawnHuman(scene);
|
91 |
+
|
92 |
+
// input handlers
|
93 |
+
scene.input.on('pointerdown', (pointer) => {
|
94 |
+
if (!currentHuman) return;
|
95 |
+
const bodies = Phaser.Physics.Matter.Matter.Query.point([currentHuman.body], pointer);
|
96 |
+
if (bodies.length) {
|
97 |
+
isDragging = true;
|
98 |
+
scene.matter.body.setStatic(currentHuman.body, true); // hold it while dragging
|
99 |
+
}
|
100 |
+
});
|
101 |
+
|
102 |
+
scene.input.on('pointermove', (pointer) => {
|
103 |
+
if (!isDragging || !currentHuman) return;
|
104 |
+
// clamp drag radius
|
105 |
+
const maxDrag = 250;
|
106 |
+
const dx = pointer.x - slingPos.x;
|
107 |
+
const dy = pointer.y - slingPos.y;
|
108 |
+
const dist = Math.min(Math.hypot(dx,dy), maxDrag);
|
109 |
+
const angle = Math.atan2(dy,dx);
|
110 |
+
const px = slingPos.x + Math.cos(angle)*dist;
|
111 |
+
const py = slingPos.y + Math.sin(angle)*dist;
|
112 |
+
currentHuman.setPosition(px, py);
|
113 |
+
drawTrajectory(scene, currentHuman.x, currentHuman.y, slingPos.x - px, slingPos.y - py);
|
114 |
+
});
|
115 |
+
|
116 |
+
scene.input.on('pointerup', (pointer) => {
|
117 |
+
if (!isDragging || !currentHuman) return;
|
118 |
+
isDragging = false;
|
119 |
+
clearTrajectory();
|
120 |
+
// compute launch velocity
|
121 |
+
const dx = slingPos.x - currentHuman.x;
|
122 |
+
const dy = slingPos.y - currentHuman.y;
|
123 |
+
let power = Math.sqrt(dx*dx + dy*dy);
|
124 |
+
const angle = Math.atan2(dy, dx);
|
125 |
+
let velocityFactor = 0.08 * (megaLaunchActive ? 2.2 : 1.0); // mega launch stronger
|
126 |
+
const vx = Math.cos(angle) * power * velocityFactor;
|
127 |
+
const vy = Math.sin(angle) * power * velocityFactor;
|
128 |
+
scene.matter.body.setStatic(currentHuman.body, false);
|
129 |
+
scene.matter.body.setVelocity(currentHuman.body, { x: vx, y: vy });
|
130 |
+
// small spin
|
131 |
+
scene.matter.body.setAngularVelocity(currentHuman.body, (Math.random()*6-3));
|
132 |
+
// track with camera
|
133 |
+
scene.cameras.main.startFollow(currentHuman, true, 0.06, 0.06);
|
134 |
+
// reduce ammo and update UI
|
135 |
+
ammo = Math.max(0, ammo-1);
|
136 |
+
updateUi();
|
137 |
+
// after a delay spawn next if ammo left
|
138 |
+
scene.time.delayedCall(800, ()=>{ if (ammo>0) spawnHuman(scene); });
|
139 |
+
});
|
140 |
+
|
141 |
+
// collision detection with blocks/targets
|
142 |
+
scene.matter.world.on('collisionstart', (event) => {
|
143 |
+
event.pairs.forEach(pair => {
|
144 |
+
const { bodyA, bodyB } = pair;
|
145 |
+
[bodyA, bodyB].forEach(b => {
|
146 |
+
if (!b.gameObject) return;
|
147 |
+
const go = b.gameObject;
|
148 |
+
// if boomer explosion type and collision strong, explode
|
149 |
+
if (go.humanType === 'boomer') {
|
150 |
+
const speed = pair.collision.penetration && Math.hypot(pair.collision.penetration.x, pair.collision.penetration.y) || 0;
|
151 |
+
if (speed > 1.2 && !go.exploded) {
|
152 |
+
go.exploded = true;
|
153 |
+
explodeAt(scene, go.x, go.y);
|
154 |
+
scene.matter.world.remove(go.body);
|
155 |
+
go.destroy();
|
156 |
+
}
|
157 |
+
}
|
158 |
+
});
|
159 |
+
// scoring: check if block knocked far or high force
|
160 |
+
const bodies = [bodyA.gameObject, bodyB.gameObject];
|
161 |
+
bodies.forEach(obj => {
|
162 |
+
if (obj && obj.isBlock && !obj.scored) {
|
163 |
+
// if block speed large or rotated a lot -> scored
|
164 |
+
const speed = obj.body.speed || 0;
|
165 |
+
const angle = Math.abs(obj.rotation || 0);
|
166 |
+
if (speed > 3 || angle > 0.6) {
|
167 |
+
obj.scored = true;
|
168 |
+
score += (obj.blockType==='glass' ? 30 : obj.blockType==='wood' ? 15 : 8);
|
169 |
+
updateUi();
|
170 |
+
// small tint animation
|
171 |
+
scene.tweens.add({ targets: obj, alpha: 0.3, duration: 200, yoyo: true });
|
172 |
+
}
|
173 |
+
}
|
174 |
+
});
|
175 |
+
});
|
176 |
+
});
|
177 |
+
|
178 |
+
// camera bounds
|
179 |
+
scene.cameras.main.setBounds(0,0, WIDTH*1.5, HEIGHT);
|
180 |
+
scene.cameras.main.setZoom(1.0);
|
181 |
+
}
|
182 |
+
|
183 |
+
function update() {
|
184 |
+
// nothing heavy here; could check win/lose
|
185 |
+
const scene = this;
|
186 |
+
// check win: all targets destroyed
|
187 |
+
if (targetsGroup.every(t=>t.destroyed)) {
|
188 |
+
// level clear
|
189 |
+
if (!this._cleared) {
|
190 |
+
this._cleared = true;
|
191 |
+
scene.cameras.main.fadeOut(600, 255,255,255);
|
192 |
+
scene.time.delayedCall(700, ()=> {
|
193 |
+
// award stars
|
194 |
+
const blocksLeft = blocksGroup.filter(b=>!b.scored).length;
|
195 |
+
stars = Math.min(3, Math.max(1, Math.floor(score/100)+1));
|
196 |
+
document.getElementById('stars').innerText = stars;
|
197 |
+
alert('Level Cleared! Score: '+score+' Stars: '+stars);
|
198 |
+
nextLevel(scene);
|
199 |
+
});
|
200 |
+
}
|
201 |
+
}
|
202 |
+
// if out of ammo and no current human, show message
|
203 |
+
if (ammo<=0 && !currentHuman) {
|
204 |
+
// optional: show a small hint
|
205 |
+
}
|
206 |
+
}
|
207 |
+
|
208 |
+
/* ---------- Helpers ---------- */
|
209 |
+
|
210 |
+
function spawnHuman(scene) {
|
211 |
+
const type = document.getElementById('hType').value;
|
212 |
+
const emoji = '🧑';
|
213 |
+
const container = scene.add.container(slingPos.x, slingPos.y);
|
214 |
+
const circle = scene.add.circle(0,0,22, 0xffcc80).setStrokeStyle(2,0xdb8b3b);
|
215 |
+
const txt = scene.add.text(-10,-16, emoji, { fontSize: '28px' });
|
216 |
+
container.add([circle, txt]);
|
217 |
+
scene.matter.add.gameObject(container, { shape: { type: 'circle', radius: 22 }, label: 'human' });
|
218 |
+
container.setDepth(3);
|
219 |
+
container.isHuman = true;
|
220 |
+
container.humanType = type;
|
221 |
+
// set physics params by type
|
222 |
+
const body = container.body;
|
223 |
+
body.friction = 0.8;
|
224 |
+
body.restitution = (type==='jumper' ? 0.8 : 0.2);
|
225 |
+
body.density = (type==='heavy' ? 0.009 : type==='boomer' ? 0.002 : 0.004);
|
226 |
+
// special behaviours
|
227 |
+
if (type==='jumper') {
|
228 |
+
container.onGroundBounce = true;
|
229 |
+
}
|
230 |
+
if (type==='boomer') {
|
231 |
+
// boomer will explode on high impact (handled in collision)
|
232 |
+
}
|
233 |
+
// mark human as current
|
234 |
+
currentHuman = container;
|
235 |
+
// small bobbing animation
|
236 |
+
scene.tweens.add({ targets: container, y: container.y-6, duration: 600, yoyo:true, repeat:-1 });
|
237 |
+
// ensure it collides with world and blocks
|
238 |
+
scene.matter.world.setCollisionCategory(0);
|
239 |
+
// collisions with blocks
|
240 |
+
blocksGroup.forEach(b => scene.matter.add.collider(container, b));
|
241 |
+
// collisions with targets
|
242 |
+
targetsGroup.forEach(t => scene.matter.add.collider(container, t));
|
243 |
+
// update UI
|
244 |
+
updateUi();
|
245 |
+
}
|
246 |
+
|
247 |
+
function buildLevel(scene, lvl) {
|
248 |
+
// clear old
|
249 |
+
blocksGroup.forEach(b=>{ try{ b.destroy(); }catch(e){} });
|
250 |
+
targetsGroup.forEach(t=>{ try{ t.destroy(); }catch(e){} });
|
251 |
+
blocksGroup = []; targetsGroup = [];
|
252 |
+
|
253 |
+
// create a simple structure of blocks depending on level
|
254 |
+
const baseX = WIDTH - 300;
|
255 |
+
const baseY = HEIGHT - 120;
|
256 |
+
|
257 |
+
const pattern = 2 + (lvl % 3); // complexity
|
258 |
+
for (let s=0; s<pattern; s++) {
|
259 |
+
const cols = 2 + s;
|
260 |
+
const rows = 2 + Math.floor(s/1);
|
261 |
+
const startX = baseX + s*70;
|
262 |
+
for (let i=0;i<cols;i++){
|
263 |
+
for (let j=0;j<rows;j++){
|
264 |
+
const x = startX + i*48;
|
265 |
+
const y = baseY - j*42;
|
266 |
+
const type = (Math.random()<0.15 ? 'metal' : Math.random()<0.4 ? 'glass' : 'wood');
|
267 |
+
const color = type==='metal' ? 0x90a4ae : type==='glass' ? 0x81d4fa : 0xffcc80;
|
268 |
+
const rect = scene.add.rectangle(x,y,44,38,color).setStrokeStyle(2,0x333333);
|
269 |
+
scene.matter.add.gameObject(rect, { shape: { type:'rectangle', width:44, height:38 }});
|
270 |
+
rect.isBlock = true; rect.blockType = type; rect.scored = false;
|
271 |
+
blocksGroup.push(rect);
|
272 |
+
}
|
273 |
+
}
|
274 |
+
}
|
275 |
+
|
276 |
+
// add a couple of "targets" (robots)
|
277 |
+
for (let k=0;k<Math.min(3, 1+lvl); k++) {
|
278 |
+
const tx = baseX + 40 + k*70;
|
279 |
+
const ty = baseY - (2+k%2)*42 - 20;
|
280 |
+
const container = scene.add.container(tx, ty);
|
281 |
+
const body = scene.add.circle(0,0,18,0xa5d6a7).setStrokeStyle(2,0x2e7d32);
|
282 |
+
const smile = scene.add.text(-9,-12, '🤖', {fontSize:'26px'});
|
283 |
+
container.add([body, smile]);
|
284 |
+
scene.matter.add.gameObject(container, { shape: { type: 'circle', radius: 18 }});
|
285 |
+
container.isTarget = true; container.destroyed = false;
|
286 |
+
targetsGroup.push(container);
|
287 |
+
// collision behavior: if high impact -> destroyed
|
288 |
+
scene.matter.world.on('collisionactive', (ev) => {
|
289 |
+
ev.pairs.forEach(pair => {
|
290 |
+
if ((pair.bodyA === container.body || pair.bodyB === container.body)) {
|
291 |
+
// check relative speed
|
292 |
+
const speed = pair.collision.penetration && Math.hypot(pair.collision.penetration.x, pair.collision.penetration.y) || 0;
|
293 |
+
if (speed > 1.1 && !container.destroyed) {
|
294 |
+
container.destroyed = true;
|
295 |
+
// small explosion
|
296 |
+
explodeAt(scene, container.x, container.y);
|
297 |
+
try { scene.matter.world.remove(container.body); } catch(e){}
|
298 |
+
container.destroy();
|
299 |
+
score += 80;
|
300 |
+
updateUi();
|
301 |
+
}
|
302 |
+
}
|
303 |
+
});
|
304 |
+
});
|
305 |
+
}
|
306 |
+
|
307 |
+
// camera reset
|
308 |
+
scene.cameras.main.setScroll(0,0);
|
309 |
+
scene.cameras.main.stopFollow();
|
310 |
+
}
|
311 |
+
|
312 |
+
function drawTrajectory(scene, x0, y0, vx, vy) {
|
313 |
+
// clear old
|
314 |
+
clearTrajectory();
|
315 |
+
// simulate points using simple physics approx (not matter sim)
|
316 |
+
const g = 1.0 * 60; // pixel/s^2 scale approx
|
317 |
+
const steps = 25;
|
318 |
+
const dt = 1/12;
|
319 |
+
for (let i=0;i<steps;i++){
|
320 |
+
const t = i*dt;
|
321 |
+
const px = x0 + vx * 60 * t; // 60 to scale to pixels
|
322 |
+
const py = y0 + vy * 60 * t + 0.5 * g * t * t;
|
323 |
+
const dot = scene.add.circle(px, py, 3, 0x0d47a1).setAlpha(0.6).setDepth(1);
|
324 |
+
dottedLines.push(dot);
|
325 |
+
}
|
326 |
+
}
|
327 |
+
|
328 |
+
function clearTrajectory() {
|
329 |
+
dottedLines.forEach(d=>d.destroy());
|
330 |
+
dottedLines = [];
|
331 |
+
}
|
332 |
+
|
333 |
+
function explodeAt(scene, x, y) {
|
334 |
+
// particle-ish effect using small circles
|
335 |
+
for (let i=0;i<18;i++){
|
336 |
+
const p = scene.add.circle(x, y, 4, 0xff8a80);
|
337 |
+
scene.tweens.add({ targets: p, x: x + Phaser.Math.Between(-120,120), y: y + Phaser.Math.Between(-120,120), alpha: 0, duration: 500 + Math.random()*400, onComplete: ()=>p.destroy() });
|
338 |
+
}
|
339 |
+
}
|
340 |
+
|
341 |
+
function updateUi() {
|
342 |
+
document.getElementById('score').innerText = score;
|
343 |
+
document.getElementById('ammo').innerText = ammo;
|
344 |
+
document.getElementById('stars').innerText = stars;
|
345 |
+
}
|
346 |
+
|
347 |
+
function resetLevel(scene) {
|
348 |
+
score = 0; ammo = 5; stars = 0;
|
349 |
+
currentHuman = null; isDragging=false; megaLaunchActive=false;
|
350 |
+
updateUi();
|
351 |
+
// remove all matter children and rebuild scene - easiest: reload page
|
352 |
+
window.location.reload();
|
353 |
+
}
|
354 |
+
|
355 |
+
function nextLevel(scene) {
|
356 |
+
level += 1; ammo = 5 + Math.min(level, 3);
|
357 |
+
score = score + 50; // small bonus
|
358 |
+
currentHuman = null;
|
359 |
+
buildLevel(scene, level);
|
360 |
+
spawnHuman(scene);
|
361 |
+
updateUi();
|
362 |
+
}
|
363 |
+
|
364 |
+
</script>
|
365 |
+
</body>
|
366 |
+
</html>
|