| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <title>致敬超级马里奥 · 小游戏</title>
- <style>
- html,body{margin:0;padding:0;background:#1a1a2e;font-family:"Microsoft YaHei",system-ui,sans-serif;}
- #wrap{max-width:1000px;margin:0 auto;padding:16px;}
- h1{color:#fff;text-align:center;font-size:22px;margin:8px 0 12px;letter-spacing:2px;}
- h1 span{color:#e52521;}
- #hud{display:flex;justify-content:space-between;align-items:center;background:#000;color:#fff;
- font-family:Consolas,monospace;font-size:15px;padding:8px 16px;border-radius:8px 8px 0 0;
- border:3px solid #333;border-bottom:none;}
- #hud b{color:#ffd800;}
- #stage{position:relative;}
- canvas{display:block;width:100%;background:#5c94fc;border:3px solid #333;border-radius:0 0 8px 8px;image-rendering:pixelated;}
- #overlay{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center;
- background:rgba(0,0,0,.55);color:#fff;text-align:center;border-radius:0 0 8px 8px;}
- #overlay h2{font-size:34px;margin:0 0 10px;color:#ffd800;text-shadow:3px 3px 0 #e52521;}
- #overlay p{font-size:15px;line-height:1.9;margin:4px 0;}
- #overlay button{margin-top:16px;padding:10px 34px;font-size:17px;font-weight:700;color:#fff;background:#e52521;
- border:none;border-radius:8px;cursor:pointer;box-shadow:0 4px 0 #8c1410;}
- #overlay button:active{transform:translateY(3px);box-shadow:0 1px 0 #8c1410;}
- .tips{color:#9aa4c9;text-align:center;font-size:13px;margin-top:10px;line-height:1.8;}
- kbd{background:#333;color:#ffd800;border-radius:4px;padding:1px 7px;font-family:Consolas,monospace;}
- </style>
- </head>
- <body>
- <div id="wrap">
- <h1>🍄 致敬<span>超级马里奥</span> · MINI GAME</h1>
- <div id="hud">
- <div>SCORE <b id="hud-score">000000</b></div>
- <div>🪙 ×<b id="hud-coins">00</b></div>
- <div>WORLD <b>1-1</b></div>
- <div>TIME <b id="hud-time">300</b></div>
- <div>❤️ ×<b id="hud-lives">3</b></div>
- </div>
- <div id="stage">
- <canvas id="game" width="960" height="480"></canvas>
- <div id="overlay">
- <h2 id="ov-title">致敬超级马里奥</h2>
- <p id="ov-text">一个用 Canvas 手写的小小致敬作品<br>吃金币、顶问号砖、踩扁栗子怪、冲向旗杆!</p>
- <button id="ov-btn" onclick="startGame()">开始游戏</button>
- </div>
- </div>
- <div class="tips">
- <kbd>←</kbd><kbd>→</kbd> 或 <kbd>A</kbd><kbd>D</kbd> 移动 | <kbd>空格</kbd> / <kbd>↑</kbd> / <kbd>W</kbd> 跳跃 |
- 从上方踩敌人可消灭 | 掉坑 / 碰敌人会损失生命 | 冲到旗杆即通关
- </div>
- </div>
- <script>
- 'use strict';
- /* ================= 基础常量 ================= */
- var TILE=32, W=170, H=15, CW=960, CH=480;
- var cv=document.getElementById('game'), ctx=cv.getContext('2d');
- var SOLID={1:1,2:1,3:1,4:1,5:1,6:1,7:1};
- var keys={};
- addEventListener('keydown',function(e){
- if(['ArrowLeft','ArrowRight','ArrowUp','Space'].indexOf(e.code)>=0) e.preventDefault();
- keys[e.code]=true;
- if(state==='ready'&&e.code==='Enter') startGame();
- });
- addEventListener('keyup',function(e){ keys[e.code]=false; });
- /* ================= 音效(WebAudio 小蜂鸣) ================= */
- var AC=null;
- function beep(freq,dur,type,slide){
- try{
- if(!AC) AC=new (window.AudioContext||window.webkitAudioContext)();
- var o=AC.createOscillator(), g=AC.createGain();
- o.type=type||'square'; o.frequency.setValueAtTime(freq,AC.currentTime);
- if(slide) o.frequency.exponentialRampToValueAtTime(slide,AC.currentTime+dur);
- g.gain.setValueAtTime(.12,AC.currentTime);
- g.gain.exponentialRampToValueAtTime(.001,AC.currentTime+dur);
- o.connect(g); g.connect(AC.destination); o.start(); o.stop(AC.currentTime+dur);
- }catch(e){}
- }
- function sJump(){ beep(320,.18,'square',660); }
- function sCoin(){ beep(988,.08,'square'); setTimeout(function(){beep(1319,.22,'square');},70); }
- function sStomp(){ beep(300,.15,'sawtooth',80); }
- function sBump(){ beep(140,.1,'square',90); }
- function sDie(){ [494,466,440,392,330,262].forEach(function(f,i){ setTimeout(function(){beep(f,.14,'triangle');},i*110); }); }
- function sWin(){ [523,659,784,1047,784,1047].forEach(function(f,i){ setTimeout(function(){beep(f,.15,'square');},i*120); }); }
- /* ================= 关卡数据 ================= */
- var grid, coins, enemies, popups, flagX;
- function buildLevel(){
- grid=[]; for(var y=0;y<H;y++){ grid.push(new Array(W).fill(0)); }
- function ground(a,b){ for(var x=a;x<=b;x++){ grid[13][x]=1; grid[14][x]=1; } }
- ground(0,69); ground(73,119); ground(123,169);
- function pipe(x,h){ for(var i=0;i<h;i++){ grid[12-i][x]=5; grid[12-i][x+1]=5; } }
- pipe(18,2); pipe(36,3); pipe(54,4);
- function set(x,y,v){ grid[y][x]=v; }
- set(12,9,3);
- set(15,9,2); set(16,9,3); set(17,9,2); set(18,9,3); set(19,9,2); // 注意:18,9 在管道上方,可站可顶
- set(17,5,3);
- set(40,9,2); set(41,9,2); set(42,9,3); set(43,9,2); set(44,9,2);
- set(41,6,2); set(42,6,3); set(43,6,2);
- set(62,9,3); set(64,9,3); set(66,9,3);
- set(80,9,2); set(81,9,2); set(82,9,3); set(83,9,2); set(84,9,2);
- set(95,8,2); set(96,8,2); set(97,8,2); set(98,8,2);
- set(108,9,3); set(110,9,3);
- // 终点前阶梯
- for(var i=0;i<4;i++){ for(var j=0;j<=i;j++){ grid[12-j][140+i]=6; } }
- for(var i2=0;i2<4;i2++){ for(var j2=0;j2<3-i2;j2++){ grid[12-j2][145+i2]=6; } }
- set(152,12,6);
- flagX=152;
- // 可踩木箱(辅助跳上高台/高砖)
- function crate(x,y){ grid[y][x]=7; }
- crate(8,12); // 起点热身箱
- crate(14,12); // 辅助跳上 15-19 砖排
- crate(22,12); crate(23,12); // 管道后垫脚箱
- crate(39,12); crate(39,11); crate(39,10); crate(39,9); // 高塔箱:跳上 41-43 高台
- crate(52,12); crate(52,11); crate(52,10); // 三层箱:爬上高水管
- crate(60,12); // 辅助顶 62/64/66 问号
- crate(69,12); // 坑前垫脚箱
- crate(93,12); crate(93,11); // 双层箱:跳上 95-98 砖排
- crate(106,12); // 辅助顶 108/110 问号
- crate(119,12); // 坑前垫脚箱
- crate(138,12); // 阶梯前垫脚箱
- // 金币(空中)
- coins=[
- {x:24,y:8},{x:25,y:8},{x:26,y:8},
- {x:41,y:4},{x:42,y:4},{x:43,y:4},
- {x:47,y:10},{x:48,y:10},{x:49,y:10},
- {x:70,y:9},{x:71,y:8},{x:72,y:9},
- {x:95,y:6},{x:96,y:6},{x:97,y:6},{x:98,y:6},
- {x:112,y:10},{x:113,y:10},{x:114,y:10},
- {x:126,y:10},{x:127,y:10},{x:128,y:10}
- ].map(function(c){ c.taken=false; return c; });
- // 敌人
- enemies=[22,28,41,47,60,66,76,82,90,100,110,126,132].map(function(x){
- return {x:x*TILE+4,y:11*TILE,w:24,h:26,vx:-1,vy:0,dead:false,deadT:0,anim:0};
- });
- popups=[];
- }
- /* ================= 状态 ================= */
- var state='ready'; // ready | play | dead | win | over
- var score=0, coinCount=0, lives=3, timeLeft=300, camX=0, stateT=0;
- var player={x:64,y:11*TILE,w:22,h:28,vx:0,vy:0,onGround:false,face:1,anim:0};
- function resetPlayer(){ player.x=64; player.y=11*TILE; player.vx=0; player.vy=0; player.face=1; camX=0; }
- function startGame(){
- score=0; coinCount=0; lives=3; timeLeft=300;
- buildLevel(); resetPlayer();
- state='play';
- document.getElementById('overlay').style.display='none';
- }
- function showOverlay(title,text,btn){
- var ov=document.getElementById('overlay');
- ov.style.display='flex';
- document.getElementById('ov-title').textContent=title;
- document.getElementById('ov-text').innerHTML=text;
- document.getElementById('ov-btn').textContent=btn;
- }
- /* ================= 碰撞工具 ================= */
- function tileAt(px,py){
- var tx=Math.floor(px/TILE), ty=Math.floor(py/TILE);
- if(tx<0||tx>=W) return 1;
- if(ty<0||ty>=H) return 0;
- return grid[ty][tx];
- }
- function solidPx(px,py){ return !!SOLID[tileAt(px,py)]; }
- function hitBlock(tx,ty){
- var v=grid[ty][tx];
- if(v===3){
- grid[ty][tx]=4;
- coinCount++; score+=200;
- popups.push({x:tx*TILE+TILE/2,y:ty*TILE,vy:-6,t:0,kind:'coin'});
- sCoin();
- }else if(v===2){
- score+=10; sBump();
- }
- }
- /* ================= 更新逻辑 ================= */
- function update(dt){
- if(state==='dead'||state==='win'||state==='over'){
- stateT-=dt;
- if(state==='dead'&&stateT<=0){
- if(lives<=0){ state='over'; sDie(); showOverlay('GAME OVER','最终得分 <b style="color:#ffd800">'+score+'</b><br>再接再厉,致敬永不放弃的马里奥精神!','重新开始'); }
- else { buildLevel(); resetPlayer(); state='play'; }
- }
- if(state==='win'&&stateT<=0){ showOverlay('🚩 通关!','恭喜冲上旗杆!最终得分 <b style="color:#ffd800">'+score+'</b><br>金币 ×'+coinCount+' | 剩余生命 ×'+lives,'再玩一次'); state='over'; }
- return;
- }
- timeLeft-=dt;
- if(timeLeft<=0){ die(); return; }
- var p=player;
- // 输入
- var acc=.4, maxV=4.2;
- if(keys.ArrowLeft||keys.KeyA){ p.vx=Math.max(p.vx-acc,-maxV); p.face=-1; }
- else if(keys.ArrowRight||keys.KeyD){ p.vx=Math.min(p.vx+acc,maxV); p.face=1; }
- else { p.vx*= .82; if(Math.abs(p.vx)<.1) p.vx=0; }
- if((keys.Space||keys.ArrowUp||keys.KeyW)&&p.onGround){ p.vy=-11.5; p.onGround=false; sJump(); }
- p.vy=Math.min(p.vy+.55,14);
- // 水平
- p.x+=p.vx;
- if(p.x<0) p.x=0;
- if(p.vx>0){
- if(solidPx(p.x+p.w,p.y+3)||solidPx(p.x+p.w,p.y+p.h-3)){
- p.x=Math.floor((p.x+p.w)/TILE)*TILE-p.w-.01; p.vx=0;
- }
- }else if(p.vx<0){
- if(solidPx(p.x,p.y+3)||solidPx(p.x,p.y+p.h-3)){
- p.x=(Math.floor(p.x/TILE)+1)*TILE+.01; p.vx=0;
- }
- }
- // 垂直
- p.y+=p.vy; p.onGround=false;
- if(p.vy>0){
- if(solidPx(p.x+2,p.y+p.h)||solidPx(p.x+p.w-2,p.y+p.h)){
- p.y=Math.floor((p.y+p.h)/TILE)*TILE-p.h-.01; p.vy=0; p.onGround=true;
- }
- }else if(p.vy<0){
- if(solidPx(p.x+2,p.y)||solidPx(p.x+p.w-2,p.y)){
- var ty=Math.floor(p.y/TILE);
- var tx=Math.floor((p.x+p.w/2)/TILE);
- p.y=(ty+1)*TILE+.01; p.vy=0;
- hitBlock(tx,ty);
- }
- }
- if(Math.abs(p.vx)>.2) p.anim+=Math.abs(p.vx)*.06;
- // 掉坑
- if(p.y>H*TILE+60){ die(); return; }
- // 金币
- coins.forEach(function(c){
- if(c.taken) return;
- var cx=c.x*TILE+TILE/2, cy=c.y*TILE+TILE/2;
- if(Math.abs(p.x+p.w/2-cx)<22 && Math.abs(p.y+p.h/2-cy)<24){
- c.taken=true; coinCount++; score+=100; sCoin();
- popups.push({x:cx,y:cy-8,vy:-4,t:0,kind:'spark'});
- }
- });
- // 敌人
- enemies.forEach(function(e){
- if(e.dead){ e.deadT+=dt; return; }
- e.anim+=.1;
- e.vy=Math.min(e.vy+.5,12);
- e.x+=e.vx;
- if(solidPx(e.x,e.y+e.h/2)||solidPx(e.x+e.w,e.y+e.h/2)){ e.vx*=-1; e.x+=e.vx*2; }
- e.y+=e.vy;
- if(solidPx(e.x+2,e.y+e.h)||solidPx(e.x+e.w-2,e.y+e.h)){
- e.y=Math.floor((e.y+e.h)/TILE)*TILE-e.h-.01; e.vy=0;
- }
- if(e.y>H*TILE+60){ e.dead=true; return; }
- // 与玩家碰撞
- if(p.x<e.x+e.w&&p.x+p.w>e.x&&p.y<e.y+e.h&&p.y+p.h>e.y){
- if(p.vy>0 && (p.y+p.h-e.y)<14){
- e.dead=true; score+=300; p.vy=-8; sStomp();
- popups.push({x:e.x+e.w/2,y:e.y,vy:-3,t:0,kind:'score'});
- }else{ die(); }
- }
- });
- // 旗杆
- if(p.x+p.w>=flagX*TILE+10){
- state='win'; stateT=1.6; score+=1000+Math.max(0,Math.floor(timeLeft))*5; sWin();
- }
- // 弹出动画
- popups.forEach(function(o){ o.t+=dt; o.y+=o.vy; o.vy+=.25; });
- popups=popups.filter(function(o){ return o.t<.7; });
- // 相机
- camX=Math.max(0,Math.min(p.x-320,W*TILE-CW));
- // HUD
- document.getElementById('hud-score').textContent=('000000'+score).slice(-6);
- document.getElementById('hud-coins').textContent=('00'+coinCount).slice(-2);
- document.getElementById('hud-time').textContent=Math.max(0,Math.ceil(timeLeft));
- document.getElementById('hud-lives').textContent=lives;
- }
- function die(){
- lives--; state='dead'; stateT=1.2; sDie();
- }
- /* ================= 绘制 ================= */
- function drawCloud(x,y,s){
- ctx.fillStyle='rgba(255,255,255,.9)';
- ctx.beginPath();
- ctx.arc(x,y,14*s,0,7); ctx.arc(x+18*s,y-8*s,16*s,0,7); ctx.arc(x+38*s,y,14*s,0,7);
- ctx.fill();
- }
- function draw(){
- // 天空
- var g=ctx.createLinearGradient(0,0,0,CH);
- g.addColorStop(0,'#5c94fc'); g.addColorStop(1,'#8ec9ff');
- ctx.fillStyle=g; ctx.fillRect(0,0,CW,CH);
- // 云(视差)
- for(var i=0;i<14;i++){
- var cx=((i*467)%(W*TILE))-camX*.5;
- if(cx>-120&&cx<CW+60) drawCloud(cx,60+(i*53)%120,1+(i%3)*.3);
- }
- // 远山
- ctx.fillStyle='#3fa65b';
- for(var h2=0;h2<10;h2++){
- var hx=h2*640-camX*.7;
- if(hx>-300&&hx<CW+100){
- ctx.beginPath(); ctx.moveTo(hx,CH-64); ctx.quadraticCurveTo(hx+140,CH-260,hx+280,CH-64); ctx.fill();
- }
- }
- ctx.save(); ctx.translate(-Math.floor(camX),0);
- // 砖块
- for(var ty=0;ty<H;ty++){
- for(var tx=Math.floor(camX/TILE)-1;tx<Math.floor((camX+CW)/TILE)+2;tx++){
- if(tx<0||tx>=W) continue;
- var v=grid[ty][tx]; if(!v) continue;
- var x=tx*TILE,y=ty*TILE;
- if(v===1){ // 地面
- ctx.fillStyle='#c8722a'; ctx.fillRect(x,y,TILE,TILE);
- ctx.fillStyle='#8a4a12'; ctx.fillRect(x,y+TILE-4,TILE,4); ctx.fillRect(x,y,2,TILE);
- ctx.fillStyle='#e8975a'; ctx.fillRect(x+3,y+3,TILE-6,3);
- }else if(v===2){ // 砖
- ctx.fillStyle='#b5502a'; ctx.fillRect(x,y,TILE,TILE);
- ctx.strokeStyle='#7a2f14'; ctx.lineWidth=2;
- ctx.strokeRect(x+1,y+1,TILE-2,TILE-2);
- ctx.beginPath(); ctx.moveTo(x,y+16); ctx.lineTo(x+TILE,y+16);
- ctx.moveTo(x+16,y); ctx.lineTo(x+16,y+16); ctx.moveTo(x+8,y+16); ctx.lineTo(x+8,y+TILE);
- ctx.moveTo(x+24,y+16); ctx.lineTo(x+24,y+TILE); ctx.stroke();
- }else if(v===3){ // 问号
- ctx.fillStyle='#ffb800'; ctx.fillRect(x,y,TILE,TILE);
- ctx.strokeStyle='#8a5a00'; ctx.lineWidth=2; ctx.strokeRect(x+1,y+1,TILE-2,TILE-2);
- ctx.fillStyle='#fff'; ctx.font='bold 18px Consolas'; ctx.textAlign='center';
- ctx.fillText('?',x+TILE/2,y+22);
- }else if(v===4){ // 已顶
- ctx.fillStyle='#9a6a30'; ctx.fillRect(x,y,TILE,TILE);
- ctx.strokeStyle='#5f3d16'; ctx.lineWidth=2; ctx.strokeRect(x+1,y+1,TILE-2,TILE-2);
- }else if(v===5){ // 管道
- var top = ty===0 || grid[ty-1][tx]!==5;
- ctx.fillStyle='#2ea043'; ctx.fillRect(x,y,TILE,TILE);
- ctx.fillStyle='#1b6e2c'; ctx.fillRect(x+TILE-5,y,5,TILE);
- ctx.fillStyle='#7ee294'; ctx.fillRect(x+3,y,5,TILE);
- if(top){ ctx.fillStyle='#2ea043'; ctx.fillRect(x-2,y,TILE+4,10);
- ctx.strokeStyle='#1b6e2c'; ctx.strokeRect(x-2,y,TILE+4,10); }
- }else if(v===6){ // 硬块
- ctx.fillStyle='#b8b8c8'; ctx.fillRect(x,y,TILE,TILE);
- ctx.strokeStyle='#6a6a7a'; ctx.lineWidth=2; ctx.strokeRect(x+1,y+1,TILE-2,TILE-2);
- ctx.fillStyle='#e8e8f2'; ctx.fillRect(x+3,y+3,TILE-6,3);
- }else if(v===7){ // 可踩木箱
- ctx.fillStyle='#d8a24a'; ctx.fillRect(x,y,TILE,TILE);
- ctx.strokeStyle='#8a5a20'; ctx.lineWidth=3; ctx.strokeRect(x+2,y+2,TILE-4,TILE-4);
- ctx.lineWidth=2;
- ctx.beginPath();
- ctx.moveTo(x+3,y+3); ctx.lineTo(x+TILE-3,y+TILE-3);
- ctx.moveTo(x+TILE-3,y+3); ctx.lineTo(x+3,y+TILE-3);
- ctx.stroke();
- ctx.fillStyle='#f2c987'; ctx.fillRect(x+4,y+4,TILE-8,3);
- }
- }
- }
- // 旗杆
- var fx=flagX*TILE+TILE/2;
- ctx.fillStyle='#8a8a9a'; ctx.fillRect(fx-2,3*TILE,4,9*TILE);
- ctx.fillStyle='#ffd800'; ctx.beginPath(); ctx.arc(fx,3*TILE,6,0,7); ctx.fill();
- ctx.fillStyle='#2ea043'; ctx.beginPath();
- ctx.moveTo(fx-2,3*TILE+4); ctx.lineTo(fx-30,3*TILE+16); ctx.lineTo(fx-2,3*TILE+28); ctx.fill();
- // 金币
- var t=Date.now()/200;
- coins.forEach(function(c){
- if(c.taken) return;
- var cx=c.x*TILE+TILE/2, cy=c.y*TILE+TILE/2;
- var w=Math.abs(Math.sin(t+c.x))*10+3;
- ctx.fillStyle='#ffd800'; ctx.strokeStyle='#b8860b'; ctx.lineWidth=2;
- ctx.beginPath(); ctx.ellipse(cx,cy,w,12,0,0,7); ctx.fill(); ctx.stroke();
- });
- // 敌人(栗子怪风格)
- enemies.forEach(function(e){
- if(e.dead&&e.deadT>.5) return;
- ctx.save();
- if(e.dead) ctx.globalAlpha=Math.max(0,1-e.deadT*2);
- var x=e.x,y=e.y;
- ctx.fillStyle='#8a4a12';
- ctx.beginPath(); ctx.ellipse(x+e.w/2,y+10,e.w/2,10,0,Math.PI,0); ctx.fill();
- ctx.fillRect(x,y+10,e.w,8);
- ctx.fillStyle='#3d2008';
- var step=Math.sin(e.anim*3)>0?2:0;
- ctx.fillRect(x+2,y+18,8,8-step); ctx.fillRect(x+e.w-10,y+18,8,8-(2-step));
- ctx.fillStyle='#fff';
- ctx.fillRect(x+5,y+8,5,6); ctx.fillRect(x+e.w-10,y+8,5,6);
- ctx.fillStyle='#000';
- ctx.fillRect(x+7,y+11,2,3); ctx.fillRect(x+e.w-8,y+11,2,3);
- ctx.restore();
- });
- // 玩家(红帽蓝背带裤小人)
- var p=player;
- ctx.save();
- ctx.translate(p.x+p.w/2,p.y);
- ctx.scale(p.face,1);
- ctx.translate(-p.w/2,0);
- var leg=Math.sin(p.anim*6)>0&&Math.abs(p.vx)>.2?2:0;
- ctx.fillStyle='#e52521'; ctx.fillRect(2,0,18,7); ctx.fillRect(14,2,10,4); // 帽子
- ctx.fillStyle='#ffd9b3'; ctx.fillRect(4,7,16,8); // 脸
- ctx.fillStyle='#000'; ctx.fillRect(14,9,3,4); // 眼
- ctx.fillStyle='#5a3218'; ctx.fillRect(4,13,4,3); // 胡子
- ctx.fillStyle='#2a56c6'; ctx.fillRect(3,15,18,9); // 背带裤
- ctx.fillStyle='#e52521'; ctx.fillRect(0,15,4,7); ctx.fillRect(20,15,4,7); // 手臂
- ctx.fillStyle='#5a3218'; ctx.fillRect(3,24,7,4-leg); ctx.fillRect(13,24,7,4-(2-leg)); // 鞋
- ctx.restore();
- // 弹出物
- popups.forEach(function(o){
- if(o.kind==='coin'){
- ctx.fillStyle='#ffd800'; ctx.strokeStyle='#b8860b';
- ctx.beginPath(); ctx.ellipse(o.x,o.y,7,10,0,0,7); ctx.fill(); ctx.stroke();
- }else if(o.kind==='score'){
- ctx.fillStyle='#fff'; ctx.font='bold 13px Consolas'; ctx.textAlign='center';
- ctx.fillText('+300',o.x,o.y);
- }else{
- ctx.fillStyle='rgba(255,255,255,'+(1-o.t/.7)+')';
- ctx.beginPath(); ctx.arc(o.x,o.y,6+o.t*20,0,7); ctx.fill();
- }
- });
- ctx.restore();
- }
- /* ================= 主循环 ================= */
- var last=performance.now();
- function loop(now){
- var dt=Math.min((now-last)/1000,.05); last=now;
- update(dt);
- draw();
- requestAnimationFrame(loop);
- }
- buildLevel();
- requestAnimationFrame(loop);
- </script>
- </body>
- </html>
|