用JavaScript做超级玛丽小游戏

一、前言

前几天用JS实现扫雷和贪吃蛇(通过HTML的DOM节点实现基本界面,界面背景简单,交互简单)。比较复杂的是植物大战僵尸,不同的关卡设置单独的函数。所以还比较难。

超级玛丽通过canvas实现背景,交互很复杂,功能很多,JS代码完全是有汇编语言反编译成C语言,然后把C语言转换成JS实现的。完全使用原生JS实现超级玛丽或者魂斗罗还没有实现。思路:类似植物大战僵尸,设置英雄和不同的敌人是不同的类,不同的关卡设置不同的函数实现。难点:界面交互很多,游戏角色动画较多。理论上 CodeCombat 可以实现类似的效果,那么红白机游戏也可以实现。膜拜反编译汇编语言的大神。

话不多说,先上效果图

d8b5c72121fe4bcbb1d2083f14aa8f66.png

 f92555eaf1974371a510f363b662e0da.png

 二、代码实现


HTML结构代码🎹


<!doctype html>

<html>

 

  <head>

    <meta charset="utf-8" />

    <title>超级玛丽</title>

    <link rel="stylesheet" type="text/css" href="./css/index.css">

  </head>

 

  <body>

    <h1>Javascript反编译超级玛丽</h1>

 

    <!-- 使用canvas实现游戏背景 -->

    <canvas id="canvas" width="256" height="240"></canvas>

    

    <!-- 用户操作 -->

    <div>

      Key: ASDW / IO / Enter

      <input id="chkSwapAB" type="checkbox">

      <label for="chkSwapAB">Swap AB</label>

    </div>

    <div>

      Zoom:

      <button id="btnSize1X">1x</button>

      <button id="btnSize2X">2x</button>

      <button id="btnSizeScreen" disabled>Full Screen</button>

      <input id="chkPxStyle" type="checkbox" disabled>

      <label for="chkPxStyle">Pixelated (webkit only)</label>

    </div>

    <div>

      Gear:

      <input id="rangeSpeed" type="range" value="0">

      <span id="labelSpeed">1</span>x

    </div>

    <div>

      <input id="chkMute" type="checkbox">

      <label for="chkMute" disabled>Mute</label>

      <button id="btnBenchmark">Benchmark</button>

    </div>

    <div>

    </div>

 

    <!-- 性能分析(开发使用,实际游戏可以注释这部分源码) -->

    <p>performance statistic:</p>

    <table>

      <tr>

        <td></td>

        <td>Now (ms)</td>

        <td>Avg (ms)</td>

      </tr>

      <tr>

        <td>FPS</td>

        <td id="itemFPSNow"></td>

        <td id="itemFPSAvg"></td>

      </tr>

      <tr>

        <td>Game Logic</td>

        <td id="itemGameNow"></td>

        <td id="itemGameAvg"></td>

      </tr>

      <tr>

        <td>APU</td>

        <td id="itemAPUNow"></td>

        <td id="itemAPUAvg"></td>

      </tr>

      <tr>

        <td>PPU</td>

        <td id="itemPPUNow"></td>

        <td id="itemPPUAvg"></td>

      </tr>

      <tr>

        <td>Render</td>

        <td id="itemRenderNow"></td>

        <td id="itemRenderAvg"></td>

      </tr>

    </table>

    <script src="./js/smb-min.js"></script>

    <script src="./js/ui.js"></script>

  </body>

 

</html>


CSS部分设置界面基本背景 👾


 

body {

  font-family: monospace;

  background-color: #eee;

}

 

#canvas:-webkit-full-screen {

  height: 100vmin !important;

  width: 106.67vmin !important;

}

 

#canvas {

  margin-bottom: 20px;

}

 

#rangeSpeed {

  width: 400px;

}

 

table {

  border-collapse: collapse;

}

 

td {

  border: solid #999 1px;

  padding: 2px 20px;

}


下面是 UI 部分 JS🎽

现在通过键盘的ASDW等键操作游戏,如果设置手柄,那么改一下键盘事件的鼠标事件操作(毕竟键盘操作不如手柄操作好用)。


// Super Mario Bros. JS Version

// recompiled by EtherDream

// translate by MichaelAn

 

// util 工具函数

function now() {

  // performance 接口可以获取到当前页面中与性能相关的信息(这里返回一个时间点)

  return performance.now();

}

 

function toFix(v) {

  // | 位运算符

  return (v * 100 | 0) / 100;

}

 

// 内存复制32位?

function memcpy32(dstBuf, dstPos, srcBuf, srcPos, len) {

  var src = srcBuf.subarray(srcPos >> 2, (srcPos + len) >> 2);

  dstBuf.set(src, dstPos >> 2);

}

 

function char(str) {

  return str.charCodeAt(0);

}

 

// 创建二进制缓存区(

var gameBuf = new ArrayBuffer(1024 * 1024 * 16);

 

// 根据原始二进制缓存区创建不同数据类型分的数组

// Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。

// Uint32Array 创建32位无符号整形数组

// Float32Array 创建32位浮点型数组 

var HEAPU8 = new Uint8Array(gameBuf);

var HEAPU32 = new Uint32Array(gameBuf);

var HEAPF32 = new Float32Array(gameBuf);

 

// fill static data 填充静态数据

asm_smb_init_mem(gameBuf);

 

// init asm.js module 开始游戏(汇编反编译实现)

var gameMod = asm_smb_mod(self, {}, gameBuf);

gameMod.init();

 

// 下面是游戏的参数

var fpsAll = 0;

var gameTimeAll = 0;

var apuTimeAll = 0;

var ppuTimeAll = 0;

var renderTimeAll = 0;

var lastTime = 0;

 

var nFPS = 0;

var nSec = -1;

var nGameFrame = 0;

var nAudioFrame = 0;

var nSpeed = 1;

var nRemain = 0;

 

// image 图像尺寸

var PIC_W = 256;

var PIC_H = 240;

var PIC_LEN = PIC_W * PIC_H * 4;

var picPtr = gameMod.getImageBuf();

 

// 获取界面中的canvas,根据图片的尺寸设置canvas的尺寸

var imageCtx = canvas.getContext('2d');

var imgData = imageCtx.createImageData(PIC_W, PIC_H);

var pixelData = imgData.data;

if (pixelData.buffer) {

  // H5

  var imgDstU32 = new Uint32Array(pixelData.buffer);

} else {

  // IE: fill alpha data

  for (var i = 0; i < PIC_LEN; i += 4) {

    pixelData[i + 3] = 0xff;

  }

}

 

 

// audio 设置声音打开

var isMute = true;

var audioCtx;

 

(function() {

  var _AudioContext = window.AudioContext || window.webkitAudioContext;

  if (!_AudioContext) {

    return;

  }

  audioCtx = new _AudioContext();

 

  var scriptNode = audioCtx.createScriptProcessor(2048, 1, 1);

  var SAMPLES_PER_FRAME = 44100 / 60;

  var AUDIO_SIZE = 2048 * 4;

  var AUDIO_BUF_SIZE = 4096 * 4;

  var audioBufPtr = gameMod.getAudioBuf();

  var lastPos = 0;

 

  scriptNode.onaudioprocess = function(e) {

    var outBuff = e.outputBuffer;

    var dstBuf = outBuff.getChannelData(0);

 

    var currPos = nAudioFrame * SAMPLES_PER_FRAME * 4;

    var newBytes = currPos - lastPos;

// if (newBytes < 0) debugger;

 

    if (newBytes === 0) {

      return;

    }

 

    var size = newBytes;

    if (size > AUDIO_SIZE) {

      if (size >= AUDIO_SIZE * 2) {

        console.log('audio drop frame');

        lastPos += (size - AUDIO_SIZE);

      }

      size = AUDIO_SIZE;

    }

 

    var pos = lastPos % AUDIO_BUF_SIZE;

    var remain = AUDIO_BUF_SIZE - pos;

    if (remain > size) {

      memcpy32(dstBuf, 0, HEAPF32, audioBufPtr + pos, size);

    } else {

      memcpy32(dstBuf, 0, HEAPF32, audioBufPtr + pos, remain);

      memcpy32(dstBuf, remain, HEAPF32, audioBufPtr, size - remain);

    }

    lastPos += size;

  };

 

  scriptNode.connect(audioCtx.destination);

 

  chkMute.disabled = false;

})();

 

 

function audioSetMute(enabled) {

  if (!audioCtx) {

    return;

  }

  if (enabled) {

    audioCtx.suspend();

  } else {

    audioCtx.resume();

  }

  isMute = enabled;

}

 

// 处理渲染性能分析

function render() {

  requestAnimationFrame(render);

 

  var t1 = now();

 

  nRemain += nSpeed;

  while (nRemain > 0) {

    gameMod.updateGameFrame();

    nRemain--;

  }

 

  var t2 = now();

  if (!isMute) {

    gameMod.updateAudioFrame();

    nAudioFrame++;

  }

 

  var t3 = now();

  gameMod.updateImageBuf();

 

  var t4 = now();

  if (imgDstU32) {

    // H5

    memcpy32(imgDstU32, 0, HEAPU32, picPtr, PIC_LEN);

  } else {

    // IE

    for (var i = 0; i < PIC_LEN; i += 4) {

      pixelData[i ] = HEAPU8[picPtr + i ];

      pixelData[i + 1] = HEAPU8[picPtr + i + 1];

      pixelData[i + 2] = HEAPU8[picPtr + i + 2];

      // alpha alway 255

    }

  }

  imageCtx.putImageData(imgData, 0, 0);

 

  var t5 = now();

 

  var gameTime = t2 - t1;

  var apuTime = t3 - t2;

  var ppuTime = t4 - t3;

  var renderTime = t5 - t4;

 

  gameTimeAll += gameTime;

  apuTimeAll += apuTime;

  ppuTimeAll += ppuTime;

  renderTimeAll += renderTime;

 

  nGameFrame++;

  nFPS++;

 

  if (t5 - lastTime >= 1000) {

    // skip first frame

    nSec++;

    if (nSec > 0) {

      fpsAll += nFPS;

      itemFPSNow.textContent = nFPS;

      itemFPSAvg.textContent = toFix(fpsAll / nSec);

    }

 

    itemGameNow.textContent = toFix(gameTime);

    itemAPUNow.textContent = toFix(apuTime);

    itemPPUNow.textContent = toFix(ppuTime);

    itemRenderNow.textContent = toFix(renderTime);

 

    itemGameAvg.textContent = toFix(gameTimeAll / nGameFrame);

    itemAPUAvg.textContent = toFix(apuTimeAll / nGameFrame);

    itemPPUAvg.textContent = toFix(ppuTimeAll / nGameFrame);

    itemRenderAvg.textContent = toFix(renderTimeAll / nGameFrame);    

 

    nFPS = 0;

    lastTime = t5;

  }

}

 

var JOY_A = 0;

var JOY_B = 1;

var JOY_SELECT = 2;

var JOY_START = 3;

var JOY_UP = 4;

var JOY_DOWN = 5;

var JOY_LEFT = 6;

var JOY_RIGHT = 7;

var PAD_MAP = {};

 

// gamepad 设置游戏的快捷键

(function() {

  PAD_MAP[char('W')] = JOY_UP;

  PAD_MAP[char('D')] = JOY_RIGHT;

  PAD_MAP[char('S')] = JOY_DOWN;

  PAD_MAP[char('A')] = JOY_LEFT;

  PAD_MAP[char(' ')] = JOY_SELECT;

  PAD_MAP[char('\r')] = JOY_START;

 

  // 监听键盘输入(如果使用手柄,获取手柄的事件并绑定函数)

  document.addEventListener('keydown', function(e) {

    var key = PAD_MAP[e.keyCode];

    if (key !== undefined) {

      gameMod.setKeyState(key, 1);

    }

  });

  document.addEventListener('keyup', function(e) {

    var key = PAD_MAP[e.keyCode];

    if (key !== undefined) {

      gameMod.setKeyState(key, 0);

    }

  });

})();

 

// config 配置

(function() {

  var cvsSty = canvas.style;

 

  chkSwapAB.onclick = function() {

    if (this.checked) {

      PAD_MAP[char('O')] = JOY_A;

      PAD_MAP[char('I')] = JOY_B;

    } else {

      PAD_MAP[char('I')] = JOY_A;

      PAD_MAP[char('O')] = JOY_B;

    }

  };

  chkSwapAB.onclick();

 

  function setCanvasScale(rate) {

    cvsSty.width = (PIC_W * rate) + 'px';

    cvsSty.height = (PIC_H * rate) + 'px';

  }

 

  btnSize1X.onclick = function() {

    setCanvasScale(1);

  };

  btnSize2X.onclick = function() {

    setCanvasScale(2);

  };

  btnSize2X.onclick();

 

  var rFS = canvas.requestFullScreen ||

    canvas.webkitRequestFullscreen ||

    canvas.mozRequestFullScreen;

 

  if (rFS) {

    btnSizeScreen.disabled = false;

    btnSizeScreen.onclick = function() {

      rFS.call(canvas);

    };

  }

 

  // 处理浏览器兼容性

  if (/WebKit/.test(navigator.userAgent)) {

    chkPxStyle.disabled = false;

    chkPxStyle.checked = true;

    chkPxStyle.onclick = function() {

      cvsSty.imageRendering = this.checked ? 'pixelated' : '';

    };

    chkPxStyle.onclick();

  }

 

  rangeSpeed.onchange = function() {

    nSpeed = toFix(Math.pow(2, this.value / 10));

    labelSpeed.textContent = nSpeed;

  };

  rangeSpeed.value = 0;

 

  chkMute.onchange = function() {

    audioSetMute(this.checked);

  };

  chkMute.onchange();

 

 

  btnBenchmark.onclick = function() {

    audioSetMute(true);

 

    var N = 1000; // 1000 per round

    var round = 0;

    var time = 0;

 

    for (;;) {

      var t = now();

      for (var i = 0; i < N; i++) {

        gameMod.updateGameFrame();

      }

      t = now() - t;

 

      round++;

      time += t;

      if (time > 1000) {

        break;

      }

    }

 

    var frames = round * N;

    var sec = time / 1000;

    alert('Benchmark Result: ' + Math.round(frames / sec) + 'FPS');

 

    audioSetMute(false);

  };

})();

 

// Page Visibility

(function() {

  var key, evt;

  if ('hidden' in document) {

    key = 'hidden';

    evt = 'visibilitychange';

  } else if ('msHidden' in document) {

    key = 'msHidden';

    evt = 'msvisibilitychange';

  } else if ('webkitHidden' in document) {

    key = 'webkitHidden';

    evt = 'webkitvisibilitychange';

  }

  if (!key) {

    return;

  }

  document.addEventListener(evt, function() {

    var isHidden = document[key];

    audioSetMute(isHidden);

  });

})();

 

function main() {

  render();

}

 

main();

下面是汇编语言反编译的JS部分。代码太多,CSDN 不让上传:>> 所以到github上面下载吧:

https://github.com/Michael18811380328/game/

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们开始制作一个简单的JS超级玛丽小游戏吧! 首先,我们需要一个画布和一个可以控制的角色。我们可以使用HTML5 Canvas来创建游戏画面,然后使用JavaScript控制角色的移动。 HTML代码: ``` <canvas id="gameCanvas" width="800" height="600"></canvas> ``` 接下来,我们需要一个JavaScript文件来控制游戏逻辑。我们首先定义一个Game类,它包含游戏的各种元素和方法。我们还需要定义一个角色类,它包含角色的属性和方法。 JS代码: ``` // 定义角色类 class Character { constructor(x, y, width, height, speed) { this.x = x; this.y = y; this.width = width; this.height = height; this.speed = speed; } moveLeft() { this.x -= this.speed; } moveRight() { this.x += this.speed; } moveUp() { this.y -= this.speed; } moveDown() { this.y += this.speed; } } // 定义游戏类 class Game { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext("2d"); this.character = new Character(100, 100, 50, 50, 5); this.keysPressed = {}; this.setupEventListeners(); } // 监听键盘事件 setupEventListeners() { document.addEventListener("keydown", (e) => { this.keysPressed[e.key] = true; }); document.addEventListener("keyup", (e) => { delete this.keysPressed[e.key]; }); } // 更新游戏状态 update() { if (this.keysPressed["ArrowLeft"]) { this.character.moveLeft(); } if (this.keysPressed["ArrowRight"]) { this.character.moveRight(); } if (this.keysPressed["ArrowUp"]) { this.character.moveUp(); } if (this.keysPressed["ArrowDown"]) { this.character.moveDown(); } } // 渲染画面 render() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.fillStyle = "blue"; this.ctx.fillRect( this.character.x, this.character.y, this.character.width, this.character.height ); } // 开始游戏循环 start() { setInterval(() => { this.update(); this.render(); }, 1000 / 60); } } // 创建游戏实例并开始游戏 const game = new Game("gameCanvas"); game.start(); ``` 在这个代码中,我们首先定义了一个Character类和一个Game类。Character类包含角色的属性和移动方法,Game类包含游戏的各种元素和方法,包括角色、键盘事件监听、更新游戏状态、渲染画面和开始游戏循环。 在Game类的构造函数中,我们创建了一个角色实例,并设置了键盘事件监听。在update()方法中,我们根据按下的键盘键来移动角色。在render()方法中,我们清除画布并绘制角色。在start()方法中,我们使用setInterval()来循环更新游戏状态和渲染画面。 现在我们可以在网页上看到一个蓝色的方块,我们可以使用方向键来控制它的移动了。接下来,我们可以添加更多的元素和功能来完善这个小游戏,比如障碍物、积分、生命值、敌人等等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值