用.NET Core和Angular编写的多人服务器端GameBoy模拟器

One of the great joys of sharing and discovering code online is when you stumble upon something so truly epic, so amazing, that you have to dig in. Head over to https://github.com/axle-h/Retro.Net and ask yourself why this GitHub project has only 20 stars?

在线共享和发现代码的最大乐趣之一是,当您偶然发现如此真正史诗般,如此惊人的东西以至于必须深入研究时。请转至https://github.com/axle-h/Retro.Net和问自己为什么这个GitHub项目只有20星?

Alex Haslehurst has created some retro hardware libraries in open source .NET Core with an Angular Front End!

Alex Haslehurst已使用Angular前端在开源.NET Core中创建了一些复古硬件库!

Translation?

翻译?

A multiplayer server-side Game Boy emulator. Epic.

多人服务器端Game Boy模拟器。 史诗。

You can run it in minutes with

您可以在几分钟内运行它

docker run -p 2500:2500 alexhaslehurst/server-side-gameboy

Then just browse to http://localhost:2500 and play Tetris on the original GameBoy!

然后只需浏览到http:// localhost:2500并在原始GameBoy上玩俄罗斯方块!

I love this for a number of reasons.

我喜欢这个有很多原因。

First, I love his perspective:

首先,我喜欢他的观点:

Please check out my GameBoy emulator written in .NET Core; Retro.Net. Yes, a GameBoy emulator written in .NET Core. Why? Why not. I plan to do a few write-ups about my experience with this project. Firstly: why it was a bad idea.

请检查我用.NET Core编写的GameBoy模拟器; Retro.Net 是的,.NET Core编写的GameBoy模拟器。 为什么? 为什么不。 我计划写一些关于我在这个项目中的经历的文章。 首先:为什么这是一个坏主意。

  1. Emulation on .NET

    .NET上的仿真

  2. Emulating the GameBoy CPU on .NET

    在.NET上模拟GameBoy CPU

The biggest issue one has trying to emulate a CPU with a platform like .NET is the lack of reliable high-precision timing. However, he manages a nice from-scratch emulation of the Z80 processor, modeling low level things like registers in very high level C#. I love that public class GameBoyFlagsRegister is a thing. ;) I did similar things when I ported a 15 year old "Tiny CPU" to .NET Core/C#.

试图用.NET这样的平台来模拟CPU的最大问题是缺乏可靠的高精度计时。 但是,他管理着Z80处理器的良好的从头开始仿真,在非常高级的C#中对诸如寄存器之类的低级事物进行了建模。 我喜欢公共类GameBoyFlagsRegister是一回事。 ;)将15岁的“微型CPU”移植到.NET Core / C#时,我做过类似的事情。

Be sure to check out Alex's extremely detailed explanation on how he modeled the Z80 microprocessor.

一定要检查一下Alex对Z80微处理器建模的非常详尽的解释

Luckily the GameBoy CPU, a Sharp LR35902, is derived from the popular and very well documented Zilog Z80 - A microprocessor that is unbelievably still in production today, over 40 years after it’s introduction.

幸运的是,夏普LR35902是GameBoy CPU,它是从广受欢迎且有据可查的Zilog Z80衍生而来的。ZilogZ80是一款微处理器,在推出40多年后,至今仍在生产中。

The Z80 is an 8-bit microprocessor, meaning that each operation is natively performed on a single byte. The instruction set does have some 16-bit operations but these are just executed as multiple cycles of 8-bit logic. The Z80 has a 16-bit wide address bus, which logically represents a 64K memory map. Data is transferred to the CPU over an 8-bit wide data bus but this is irrelevant to simulating the system at state machine level. The Z80 and the Intel 8080 that it derives from have 256 I/O ports for accessing external peripherals but the GameBoy CPU has none - favouring memory mapped I/O instead

Z80是一个8位微处理器,这意味着每个操作本机都在单个字节上执行。 该指令集确实有一些16位运算,但是这些指令只是作为多个8位逻辑周期执行的。 Z80具有16位宽的地址总线,该总线在逻辑上表示64K内存映射。 数据通过8位宽的数据总线传输到CPU,但这与在状态机级别模拟系统无关。 Z80和衍生自的Z80和Intel 8080具有256个I / O端口,用于访问外部外围设备,但GameBoy CPU没有-而是支持内存映射的I / O。

He didn't just create an emulator - there's lots of those - but uniquely he runs it on the server-side while allowing shared controls in a browser. "In between each unique frame, all connected clients can vote on what the next control input should be. The server will choose the one with the most votes… most of the time." Massively multi-player online GameBoy! Then he streams out the next frame! "GPU rendering is completed on the server once per unique frame, compressed with LZ4 and streamed out to all connected clients over websockets."

他不仅创建了一个仿真器-其中有很多-而且他独特地在服务器端运行它,同时允许浏览器中的共享控件。 “在每个唯一的帧之间,所有连接的客户端都可以对下一个控制输入应该进行投票。服务器将在大多数时候选择投票最多的那个。” 大型多人在线GameBoy! 然后他流出下一帧! “ GPU渲染在服务器上每个唯一的帧完成一次,用LZ4压缩并通过websocket流传输到所有连接的客户端。”

This is a great learning repository because:

这是一个很棒的学习库,因为:

  • it has complex business logic on the server-side but the front end uses Angular and web-sockets and open web technologies.

    它在服务器端具有复杂的业务逻辑,但是前端使用Angular和Web套接字以及开放Web技术。
  • It's also nice that he has a complete multi-stage Dockerfile that is itself a great example of how to build both .NET Core and Angular apps in Docker.

    他有一个完整的多阶段Dockerfile,这本身就是如何在Docker中同时构建.NET Core和Angular应用的一个很好的例子,这也很好。
  • Extensive (thousands) of Unit Tests with the Shouldly Assertion Framework and Moq Mocking Framework.

    带有“应有断言”框架和“ Moq模拟”框架的大量(数千个)单元测试。

  • Great example usages of Reactive Programming

    React式编程的绝佳示例用法
  • Unit Testing on both server AND client, using Karma Unit Testing for Angular

    使用Karma Angular单元测试在服务器和客户端上进行单元测试

Here's a few favorite elegant code snippets in this huge repository.

这是这个庞大的存储库中一些最喜欢的优雅代码片段。

The Reactive Button Presses:

React按钮按下:

_joyPadSubscription = _joyPadSubject
    .Buffer(FrameLength)
    .Where(x => x.Any())
    .Subscribe(presses =>
                {
                    var (button, name) = presses
                        .Where(x => !string.IsNullOrEmpty(x.name))
                        .GroupBy(x => x.button)
                        .OrderByDescending(grp => grp.Count())
                        .Select(grp => (button: grp.Key, name: grp.Select(x => x.name).First()))
                        .FirstOrDefault();
                    joyPad.PressOne(button);
                    Publish(name, $"Pressed {button}");

                    Thread.Sleep(ButtonPressLength);
                    joyPad.ReleaseAll();
                });

The GPU Renderer:

GPU渲染器:

private void Paint()
{
    var renderSettings = new RenderSettings(_gpuRegisters);

    var backgroundTileMap = _tileRam.ReadBytes(renderSettings.BackgroundTileMapAddress, 0x400);
    var tileSet = _tileRam.ReadBytes(renderSettings.TileSetAddress, 0x1000);
    var windowTileMap = renderSettings.WindowEnabled ? _tileRam.ReadBytes(renderSettings.WindowTileMapAddress, 0x400) : new byte[0];

    byte[] spriteOam, spriteTileSet;
    if (renderSettings.SpritesEnabled) {
        // If the background tiles are read from the sprite pattern table then we can reuse the bytes.
        spriteTileSet = renderSettings.SpriteAndBackgroundTileSetShared ? tileSet : _tileRam.ReadBytes(0x0, 0x1000);
        spriteOam = _spriteRam.ReadBytes(0x0, 0xa0);
    }
    else {
        spriteOam = spriteTileSet = new byte[0];
    }

    var renderState = new RenderState(renderSettings, tileSet, backgroundTileMap, windowTileMap, spriteOam, spriteTileSet);

    var renderStateChange = renderState.GetRenderStateChange(_lastRenderState);
    if (renderStateChange == RenderStateChange.None) {
        // No need to render the same frame twice.
        _frameSkip = 0;
        _framesRendered++;
        return;
    }

    _lastRenderState = renderState;
    _tileMapPointer = _tileMapPointer == null ? new TileMapPointer(renderState) : _tileMapPointer.Reset(renderState, renderStateChange);
    var bitmapPalette = _gpuRegisters.LcdMonochromePaletteRegister.Pallette;
    for (var y = 0; y < LcdHeight; y++) {
        for (var x = 0; x < LcdWidth; x++) {
            _lcdBuffer.SetPixel(x, y, (byte) bitmapPalette[_tileMapPointer.Pixel]);

            if (x + 1 < LcdWidth) {
                _tileMapPointer.NextColumn();
            }
        }

        if (y + 1 < LcdHeight){
            _tileMapPointer.NextRow();
        }
    }
    
    _renderer.Paint(_lcdBuffer);
    _frameSkip = 0;
    _framesRendered++;
}

The GameBoy Frames are composed on the server side then compressed and sent to the client over WebSockets. He's got backgrounds and sprites working, and there's still work to be done.

GameBoy框架在服务器端组成,然后压缩并通过WebSockets发送给客户端。 他有背景和精灵在工作,还有很多工作要做。

The Raw LCD is an HTML5 canvas:

Raw LCD是HTML5画布:

<canvas #rawLcd [width]="lcdWidth" [height]="lcdHeight" class="d-none"></canvas>
<canvas #lcd
        [style.max-width]="maxWidth + 'px'"
        [style.max-height]="maxHeight + 'px'"
        [style.min-width]="minWidth + 'px'"
        [style.min-height]="minHeight + 'px'"
        class="lcd"></canvas>

I love this whole project because it has everything. TypeScript, 2D JavaScript Canvas, retro-gaming, and so much more!

我喜欢整个项目,因为它拥有一切。 TypeScript,2D JavaScript Canvas,复古游戏等等!

const raw: HTMLCanvasElement = this.rawLcdCanvas.nativeElement;
const rawContext: CanvasRenderingContext2D = raw.getContext("2d");
const img = rawContext.createImageData(this.lcdWidth, this.lcdHeight);

for (let y = 0; y < this.lcdHeight; y++) {
  for (let x = 0; x < this.lcdWidth; x++) {
    const index = y * this.lcdWidth + x;
    const imgIndex = index * 4;
    const colourIndex = this.service.frame[index];
    if (colourIndex < 0 || colourIndex >= colours.length) {
      throw new Error("Unknown colour: " + colourIndex);
    }

    const colour = colours[colourIndex];

    img.data[imgIndex] = colour.red;
    img.data[imgIndex + 1] = colour.green;
    img.data[imgIndex + 2] = colour.blue;
    img.data[imgIndex + 3] = 255;
  }
}
rawContext.putImageData(img, 0, 0);

context.drawImage(raw, lcdX, lcdY, lcdW, lcdH);

I would encourage you to go STAR and CLONE https://github.com/axle-h/Retro.Net and give it a run with Docker! You can then use Visual Studio Code and .NET Core to compile and run it locally. He's looking for help with GameBoy sound and a Debugger.

我鼓励您去STAR和CLONE https://github.com/axle-h/Retro.Net并与Docker一起运行! 然后,您可以使用Visual Studio Code和.NET Core在本地进行编译和运行。 他正在寻找GameBoy声音和调试器的帮助。

翻译自: https://www.hanselman.com/blog/a-multiplayer-serverside-gameboy-emulator-written-in-net-core-and-angular

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值