24
2024
03
01:03:13

在线FC模拟器

简介

在这里插入图片描述
小时候第一次接触到FC游戏,还是在一个小伙伴的家里,打开后插上卡,然后电视画面就变了,对我来说这看起来就像魔法一样,那么神奇,那么欢乐,一直陪伴了我整个童年。后来PC游戏,手机游戏的出现,画面越来越好玩法越来越多,但是始终没有童年的那个味道了。

jsnes

一次偶然的发现,我看到了jsnes(一款基于js的nes模拟),回忆起了童年,那时的回忆似乎可以在移动互联网时代得倒新生。

并且实际上存在许多基于jsnes的网站比如:http://fc.liflag.cn/
在这里插入图片描述
但是实际体验下来不是很好,另外需要手动导入游戏,无法做到想玩的时刻都能玩。

于是乎经过几周的奋战,终于做了一个在线的nes网站。将jsnes整合了下,核心代码如下。

import jsnes from '../open_source/jsnes'

var SCREEN_WIDTH = 256;
var SCREEN_HEIGHT = 240;
var FRAMEBUFFER_SIZE = SCREEN_WIDTH * SCREEN_HEIGHT;

var Simulator = function() {
  this.nes = null
  this.buffer = null
  this.framebuffer_u32 = null
  this.framebuffer_u8 = null
  this.audio = null
  this.romData = null
  this.saveData = null
  this.needUpdateFrame = false
  this.stopFrame = false
}

Simulator.prototype = {
  load: function(romData, saveData) {  
    this.stop()

    this.romData = romData
    this.saveData = saveData

    this.buffer = new ArrayBuffer(FRAMEBUFFER_SIZE * 4);
    this.framebuffer_u8 = new Uint8ClampedArray(this.buffer);
    this.framebuffer_u32 = new Uint32Array(this.buffer);
    var that = this
    var nes = new jsnes.NES({
      onFrame: function(framebuffer_24){
        for(var i = 0; i < FRAMEBUFFER_SIZE; i++) {
          that.framebuffer_u32[i] = 0xFF000000 | framebuffer_24[i];
        }

        that.needUpdateFrame = true
      },
      onAudioSample: function(l, r){
        that.audio(l, r)
      },
      sampleRate: 44100,
      preferredFrameRate: 60
    });

    this.nes = nes
    if (null != saveData) {
        if (null == saveData.romData) {
            saveData.romData = romData
        }

        this.nes.fromJSON(saveData)
    } else {
        this.nes.loadROM(romData)
    }
  },
  
  stop: function() {
    if (null != this.nes) {
      this.nes.reset()
    }

    this.nes = null
    this.audio = null
    this.romData = null
    this.saveData = null
    this.buffer = null
    this.framebuffer_u32 = null
    this.framebuffer_u8 = null
    this.needUpdateFrame = false
    this.stopFrame = false
  },
  
  start: function(onAudio) {
    this.audio = onAudio

    // do frame first
    this.frame()
  },

  reset: function(romData, saveData) {
    var audio = this.audio
    this.load(romData, saveData)
    this.start(audio)
  },
  
  buttonDown: function(player, key) {
    this.nes.buttonDown(player, jsnes.Controller[key]);
  },
  
  buttonUp: function(player, key) {
    this.nes.buttonUp(player, jsnes.Controller[key]);
  },

  currentContext() {
      if (!this.start || null == this.nes) {
          return null
      }

      return this.nes.toJSON()
  },

  frame() {
    if (!this.start || null == this.nes) {
      return
    }

    if (this.stopFrame) {
      return
    }

    this.nes.frame()
  },

  frameBuffer() {
    var needUpade = this.needUpdateFrame
    this.needUpdateFrame = false
    return {
      render: needUpade,
      buffer: needUpade ? this.framebuffer_u8 : null
    }
  },

  pause() {
    this.stopFrame = true
  },
  
  resume() {
    this.stopFrame = false
  }

}

export {
  Simulator
}

在这里插入图片描述

但实际上jsnes这个项目并不是很完整,可以看出内部逻辑可能也是参考了别的项目,代码非常丑陋(但是能用),甚至突然出现未定义的变量(根本不知道这变量哪来的,用来干嘛的)。以及仅仅支持了有限的格式(mapper类型,不同的游戏可能是不同的格式)。花了很大的精力增加了mapper,但是新增的mapper也不敢保证100%正确(能力有限)。

jsnes自带支持的mapper
Mappers[0]
Mappers[1]
Mappers[2]
Mappers[3]
Mappers[4]
Mappers[5]
Mappers[7]
Mappers[11]
Mappers[34]
Mappers[38]
Mappers[66]
Mappers[94]
Mappers[140]
Mappers[180]

新增的mapper(参考nesdev wiki)
Mappers[9]
Mappers[10]
Mappers[15]
Mappers[18]
Mappers[21]
Mappers[22]
Mappers[23]
Mappers[32]
Mappers[33]
Mappers[48]
Mappers[71]
Mappers[72]
Mappers[75]
Mappers[78]
Mappers[79]
Mappers[87]
Mappers[105]
Mappers[182]

node

在这里插入图片描述

当完成基本的游戏后,又一次小伙伴告知需要联机功能???于是连夜写了一个node服务,将游戏在node服务器中执行,然后每一帧都下发到浏览器上。
但是问题是显而易见的,一开始发现数据量很大(分辨率是512 * 480),每次都下发完整的rawdata带宽扛不住,后来用zip压缩后大概是1000-5000的长度,可以接受。然而所有的游戏都用我的服务器流量,钱包扛不住了。
所以使用node服务端执行游戏的方案实际上是行不通的。

webrtc

在这里插入图片描述
webrct是基于p2p的网络,通过coturn创建了stun和turn,并在同一台机器上起了node作为房间管理和rtc信令交换。
所有的运行在P1中,P2则被动接受游戏canvas生成的视频流,和audioContext产生的音频流。
目前存在的最大问题则是画面延迟,由于视频存在编解码的延迟,没有很好的方案解决。如果直接传递游戏帧的rawdata,则带宽扛不住(如果zip后再传,则p1直接卡死,性能太差)。

另外如果音视频流进行合并后会增加100ms的延迟,所以目前音视频流是单独处理用单独的组件播放的。

测试下来在30桢的录制下,P2和P1之间的延迟还是比较小的,大致50ms左右(2桢)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以前往这里进行进入组队大厅体验(针对PC简单适配了下,PC可以通过键盘操作游戏)。




推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

本文链接:https://hqyman.cn/post/5555.html 非本站原创文章欢迎转载,原创文章需保留本站地址!

分享到:
打赏





休息一下~~


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

请先 登录 再评论,若不是会员请先 注册

您的IP地址是: