WEBVTT 00:00:14.493 --> 00:00:17.814 下一个演讲 我个人十分期待 00:00:17.829 --> 00:00:23.189 我也知道你们很多人为此在这里等候 00:00:24.259 --> 00:00:26.209 我也非常激动 00:00:26.862 --> 00:00:31.593 我自己有台GameBoy 我也很荣幸能拥有一台 00:00:31.593 --> 00:00:36.481 我一直放着 有时也拿出来玩玩 00:00:36.491 --> 00:00:39.041 当然一般情况下还是玩模拟器多 00:00:39.770 --> 00:00:43.861 而且我只在模拟器上玩我拥有实体卡带的游戏 00:00:45.817 --> 00:00:52.533 本来就应该是这样的 对吧 00:00:54.049 --> 00:00:54.457 嗯是的 00:00:55.770 --> 00:00:57.254 非常期待 00:00:57.684 --> 00:01:01.518 顺便 我个人在Gameboy平台上最喜欢的游戏 00:01:02.257 --> 00:01:07.038 第一个 是Megaman 2 00:01:07.660 --> 00:01:14.095 随后是Wario Land, Mystic Quest 如果你不同意的话 00:01:14.095 --> 00:01:16.565 那说明你还不够了解我 00:01:18.676 --> 00:01:24.636 好了讲讲演讲吧 00:01:24.936 --> 00:01:26.556 非常期待 00:01:27.307 --> 00:01:31.785 主讲是Michael Steil 00:01:32.638 --> 00:01:36.430 他在25C3给过一个非常棒的演讲 00:01:36.670 --> 00:01:40.515 “The Ultimate Commodore 64 Talk” 现在 00:01:40.711 --> 00:01:43.704 为大家带来的是 “The Ultimate Gameboy Talk” 00:01:43.876 --> 00:01:44.929 很期待 00:01:45.153 --> 00:01:48.980 Michael Steil 白天他在工作研究操作系统相关技术 00:01:49.166 --> 00:01:51.726 晚上回到家就研究这些古董系统 00:01:51.916 --> 00:01:55.150 在之前 它是个游戏机破解工作者 00:01:55.150 --> 00:01:57.748 请为他热烈的鼓掌 00:01:57.870 --> 00:01:59.740 把讲台交给Michel 00:02:07.640 --> 00:02:09.190 大家好 00:02:09.997 --> 00:02:11.422 那 我的名字是Michael Steil 00:02:11.537 --> 00:02:13.484 这次讲座是“The Ultimate Gameboy Talk” (一场讲座,关于Gameboy的一切) 00:02:21.978 --> 00:02:23.806 这场讲座的主要想法就是 00:02:23.927 --> 00:02:28.176 在60分钟内 尽可能多得谈谈Gameboy的各种硬件细节 00:02:28.365 --> 00:02:31.531 60分钟,关于Gameboy的一切 00:02:31.742 --> 00:02:36.059 我准备了大约200张幻灯片 800张不同的表 00:02:36.110 --> 00:02:38.693 所以信息密度可能比一般稍微高了一点 00:02:38.992 --> 00:02:41.046 所以赶紧开始吧 00:02:41.151 --> 00:02:42.760 这次的GameBoy演讲 00:02:42.914 --> 00:02:45.766 算是一个大系列之一 00:02:45.914 --> 00:02:47.808 我在几年前讲了Commodore 64 00:02:48.012 --> 00:02:50.859 后来有人就接上讲了Atari 2600 00:02:51.132 --> 00:02:53.801 讲了Galaksija 还有Amiga 500 00:02:53.930 --> 00:02:55.090 所以现在又轮到我了 00:02:56.848 --> 00:02:58.563 我选择了GameBoy 00:02:58.705 --> 00:03:00.609 为什么GameBoy那么有趣 00:03:00.781 --> 00:03:03.517 因为他们销售了很多很多的GameBoy 00:03:03.710 --> 00:03:06.451 GameBoy和GBC加起来就有1亿1800万台 00:03:06.610 --> 00:03:11.454 如果再考虑上兼容GameBoy的GBA机型 不包括GBM 00:03:11.523 --> 00:03:13.906 加起来这几乎是2亿台机器 00:03:14.108 --> 00:03:17.837 大约有1600款游戏 00:03:19.307 --> 00:03:23.619 从1989年开始生产 8-Bit型号一直生产到2003年 00:03:23.738 --> 00:03:26.689 一样,如果考虑兼容的GBA型号 00:03:26.837 --> 00:03:31.256 8-Bit兼容机一直生产到2009年 00:03:31.334 --> 00:03:32.992 也就是20年 相当厉害 00:03:34.361 --> 00:03:36.343 我们再来看看当时的竞争对手 00:03:36.475 --> 00:03:38.348 就在当时 就在GameBoy发布后 00:03:38.614 --> 00:03:42.669 Atari发布了Lynx SEGA发布了Game Gear NEC发布了Turbo Express 00:03:42.776 --> 00:03:45.674 这些竞品有一个共同点 它们都有彩屏 00:03:45.782 --> 00:03:47.365 很棒的彩屏 00:03:47.442 --> 00:03:51.794 但是还有一个共同点 就是渣续航 大约3-5小时 00:03:51.941 --> 00:03:54.275 然而GameBoy可以做到15-30小时 00:03:54.467 --> 00:03:57.290 然而妥协就是,只有一个这样的屏幕 00:03:57.391 --> 00:04:00.440 而且一旦开始卷动就很难看清楚东西 00:04:01.669 --> 00:04:04.851 当然,并不是所有的GameBoy都是这个情况 00:04:06.199 --> 00:04:09.407 最初的GameBoy 也是生产最长时间的型号 00:04:09.550 --> 00:04:14.092 最初代号是DMG 代表的是Dot-Matrix Game(点阵游戏) 00:04:14.392 --> 00:04:20.509 1996年他们发布了GBP 带有效果好得多的屏幕 体积也小了很多 代号MGB 00:04:20.639 --> 00:04:23.645 GBL只在日本发售 带有屏幕背光 00:04:23.753 --> 00:04:29.464 最后是GBC 2倍的CPU速度 2倍的内存 2倍的显存 以及支持彩色 00:04:31.491 --> 00:04:36.407 接着是GBA系列 架构完全不同 基于ARM CPU 00:04:36.407 --> 00:04:40.800 但是仍然和GB、GBC游戏100%兼容 00:04:40.947 --> 00:04:45.036 至于GBA SP,存在两种型号 00:04:45.193 --> 00:04:48.531 如果你要买的话,记得买AGS101 00:04:48.611 --> 00:04:51.007 也就是带有背光而不是前光的型号 00:04:52.077 --> 00:04:55.165 而且可以看见任天堂总是有一点超前 00:04:55.165 --> 00:04:59.418 不单单是做了翻盖设计 而且还需要一个转接头才能接一般的耳机 00:05:06.069 --> 00:05:09.403 如果你想在电视上玩GameBoy 00:05:09.549 --> 00:05:13.736 有两种选择 要么是Super Nintendo插上Super Game Boy 00:05:13.837 --> 00:05:19.996 有两个版本 SGB2是日本专有型号 有专门的计时系统 不会比正常的快3% 00:05:20.120 --> 00:05:23.947 以及Game Boy Player 搭配NGC使用 00:05:24.007 --> 00:05:27.141 这三个都是内置了完整的GameBoy硬件 00:05:27.261 --> 00:05:31.126 基本就是一个GameBoy 只是把图形输出到主机而不是自己的屏幕 00:05:32.435 --> 00:05:34.132 所以GameBoy具体的外观 00:05:34.271 --> 00:05:38.775 一个2.6英寸的屏幕 按键 单声道喇叭 耳机是立体的 00:05:38.988 --> 00:05:42.266 有一个联机口 通过串行总线连接两台GameBoy 00:05:42.381 --> 00:05:43.749 对比度和音量调节旋钮 00:05:43.927 --> 00:05:46.904 背面 这里是插游戏卡带的地方 00:05:47.056 --> 00:05:48.791 这里则是安装电池的地方 00:05:49.233 --> 00:05:51.321 一般的游戏卡带就长这个样子 00:05:51.462 --> 00:05:53.148 一般的游戏卡带里面一般也就是一片ROM芯片 00:05:53.148 --> 00:05:54.482 没有什么特殊的 (具体后面会讲) 00:05:55.589 --> 00:05:57.115 参数 00:05:57.275 --> 00:05:58.895 GameBoy的参数是怎么样的 00:05:58.968 --> 00:06:02.263 拿来和一些其它可能根本不具备可比性的机型比一比吧 00:06:02.348 --> 00:06:04.678 CPU是1MHz的8Bit处理器 00:06:04.678 --> 00:06:10.319 有人可能会讲这里错了 应该是个4MHz的CPU 但是我一会会解释为什么这里称之为1MHz 00:06:11.738 --> 00:06:15.160 8KB的内存 对于这类的游戏机其实算是充裕了 00:06:15.305 --> 00:06:17.395 显存(VRAM)也是8KB 稍微有点紧张 00:06:17.578 --> 00:06:24.413 分辨率160*144 非常糟糕 但是对于这个屏幕尺寸 其实效果也还可以 00:06:24.622 --> 00:06:29.251 同屏最大4种颜色 就是4级灰度 00:06:29.417 --> 00:06:32.181 每行最多10个精灵 00:06:32.374 --> 00:06:35.605 所以如果和这里其它的一些系统比较的话 00:06:35.805 --> 00:06:43.414 很明显 GameBoy比Atari2600要先进得多 但是却又比不上SNES 00:06:43.563 --> 00:06:48.339 更接近于一般的NES(美版红白机)或者是Commodore 64 00:06:48.528 --> 00:06:53.671 有趣的是 NES和C64是在80年代早期发布的 00:06:53.869 --> 00:06:55.312 GameBoy是89年 00:06:55.498 --> 00:07:01.010 而且如我前面所说 兼容机一直生产到了2009年 00:07:01.159 --> 00:07:02.937 这也就非常有趣了 00:07:03.070 --> 00:07:08.969 它是8-Bit机 但是是最晚一批大量使用的8-Bit游戏机 00:07:10.765 --> 00:07:12.021 看看内部 00:07:12.164 --> 00:07:14.946 右边的板没什么花头 00:07:15.054 --> 00:07:19.075 从正面看 LCD就是连接在这块板上 以及扬声器和按键 00:07:20.790 --> 00:07:23.458 背后这块板就有趣多了 00:07:23.637 --> 00:07:27.913 可以看见3块芯片 这个是DMG 也就是最初的型号 00:07:28.077 --> 00:07:29.870 2片一样的RAM芯片 00:07:29.870 --> 00:07:32.549 一片工作内存(WRAM) 一片视频内存(VRAM) 00:07:32.748 --> 00:07:35.962 中间这个叫DMG-CPU 00:07:36.117 --> 00:07:38.742 然而其实更应该被称为SoC(System on a Chip) 00:07:38.958 --> 00:07:44.824 正常这类的系统都应该有很多芯片 00:07:44.824 --> 00:07:48.330 但是这里全部都集成进了一个芯片 也就这个DMG芯片 00:07:48.473 --> 00:07:51.777 来对比一下其它的GameBoy机型 00:07:51.777 --> 00:07:54.235 比如SGB 这些芯片看起来很接近 00:07:54.235 --> 00:07:57.066 其实基本上就是一样的 00:07:58.276 --> 00:08:05.201 GBP 稍微优化了一下 只带有一片RAM芯片 00:08:05.284 --> 00:08:11.057 GBL看起来基本没有区别 只是MGB系列的另外一个小型号 00:08:11.057 --> 00:08:12.454 带有背光而已 00:08:12.624 --> 00:08:15.792 接着是SGB2 基于GBP设计 00:08:17.107 --> 00:08:19.481 这是GBC内部的样子 00:08:19.647 --> 00:08:21.326 都有一个巨大的集成片 00:08:22.467 --> 00:08:24.512 这个呢 这个比较特殊 00:08:24.667 --> 00:08:26.840 可能从标识上不容易认 00:08:27.022 --> 00:08:28.796 这个是GB Boy 00:08:29.022 --> 00:08:31.889 总是有公司山寨GameBoy 00:08:32.058 --> 00:08:35.390 这个型号 是一个相当完美的克隆 00:08:35.505 --> 00:08:41.141 基本上就是 完全抄了一个原版的芯片 00:08:42.420 --> 00:08:44.953 来自中国的山寨GameBoy 00:08:45.065 --> 00:08:48.295 而且至今都可以在eBay上花三四十刀买到 00:08:49.084 --> 00:08:53.137 然而很遗憾晶振快了30% 游戏都玩不了了 00:08:56.186 --> 00:08:58.406 回来讲DMG的板子 00:08:58.406 --> 00:09:00.835 DMG CPU还是相当有趣的 00:09:00.864 --> 00:09:02.394 他接管整个系统的一切 00:09:02.695 --> 00:09:04.632 DMG CPU包括了什么 00:09:04.818 --> 00:09:09.705 CPU 中断控制器 定时器 内存 启动ROM 00:09:10.063 --> 00:09:16.113 还有所有的外设以及IO控制器 摇杆 用于联机的串行控制器 00:09:16.113 --> 00:09:17.788 声音控制器 还有 图形控制器 00:09:17.788 --> 00:09:19.263 也就是PPU (Pixel Processing Unit) 00:09:19.949 --> 00:09:21.430 来讲讲CPU吧 00:09:21.674 --> 00:09:22.862 历史角度来讲 00:09:23.088 --> 00:09:24.760 最初的GameBoy,1989年 00:09:24.961 --> 00:09:29.926 发布时间是在NES的和SNES的之间 00:09:31.228 --> 00:09:33.038 NES搭载的是6502处理器 00:09:33.245 --> 00:09:36.408 SNES搭载的是65816处理器 00:09:36.408 --> 00:09:38.391 也就是相同CPU的16位版本 00:09:39.144 --> 00:09:42.823 所以显然GameBoy搭载了SHARP LR35902 (强行显然2333) 00:09:44.134 --> 00:09:47.588 SHARP LR35902是什么 00:09:48.167 --> 00:09:49.347 这个和6502没啥关系 00:09:49.569 --> 00:09:53.758 更接近于i8080和Z80 00:09:53.758 --> 00:09:54.398 但是也不是其中任何一种 00:09:55.249 --> 00:09:56.961 两种cpu都相当有趣 00:09:57.166 --> 00:09:59.975 比如8080用于Altair 00:10:00.208 --> 00:10:03.414 也就是Bill Gates最初写程序并销售的平台 00:10:03.634 --> 00:10:09.994 Z80则是用于所有没有采用6502的8bit家用机 00:10:10.177 --> 00:10:11.482 相当成功的架构 00:10:12.692 --> 00:10:16.441 所以如果你想象 这是i8080的功能 00:10:16.630 --> 00:10:18.833 这是Z80的功能 00:10:18.833 --> 00:10:21.479 也就是一个超集 而且完全向下兼容 00:10:24.006 --> 00:10:28.013 然后这个 是GameBoy CPU 00:10:30.114 --> 00:10:33.886 核心架构是相同的 也就8080的那些 00:10:34.095 --> 00:10:39.298 比如寄存器组 指令编码 这些都是一样的 00:10:39.941 --> 00:10:41.499 当然也有一些不支持的功能 00:10:42.182 --> 00:10:44.635 但是又支持一部分Z80的功能 00:10:44.834 --> 00:10:47.214 然而大部分的Z80功能都不支持 00:10:47.432 --> 00:10:49.396 接着又在这之上加了一点自己的功能 00:10:50.465 --> 00:10:51.664 来看一遍吧 00:10:51.933 --> 00:10:55.576 先来讲讲8080的架构 00:10:55.827 --> 00:10:57.535 它拥有这些寄存器 00:10:57.535 --> 00:11:02.670 一个累加器 可以用于进行算数运算 其它寄存器不行 00:11:02.808 --> 00:11:04.054 一个状态寄存器 00:11:04.100 --> 00:11:08.525 在GB内 只有两个有用的标志位 零 和 进位 00:11:08.725 --> 00:11:11.006 剩下两个是用于十进制调整的 00:11:11.193 --> 00:11:14.568 然后是BCDEHL 6个8位寄存器 00:11:14.803 --> 00:11:19.041 但是你可以把B和C合起来 变成BC 00:11:19.237 --> 00:11:21.008 DE HL同理 00:11:21.148 --> 00:11:25.385 所以你实际上拥有可以作为指针的16位寄存器 00:11:25.930 --> 00:11:30.130 所以加起来一共有4个16位计算器 00:11:30.806 --> 00:11:34.531 7个8位寄存器 加上一个特殊的指针寄存器 00:11:34.531 --> 00:11:40.921 指向hl寄存器指向的内存地址的数据 00:11:41.187 --> 00:11:45.208 可以放到任何可以操作寄存器的指令中使用 00:11:47.542 --> 00:11:49.071 然后来看指令 00:11:49.337 --> 00:11:55.482 读取/存储指令 同一个指令可以直接或者间接寻址 00:11:55.706 --> 00:11:59.675 堆栈是16位的 只能操作16位寄存器 00:11:59.938 --> 00:12:02.543 这些是算数和逻辑指令 00:12:02.723 --> 00:12:04.809 如之前所说 这些指令只能配合累加器使用 00:12:04.809 --> 00:12:07.621 inc和dec(自加自减)指令是例外 可以配合任何寄存器使用 00:12:07.786 --> 00:12:10.889 而且也可以配合16位寄存器使用 00:12:11.124 --> 00:12:17.685 移位指令 还有控制指令 跳转 调用 返回 条件 间接操作 等等 00:12:17.846 --> 00:12:23.789 还有一些杂类的指令 清除设置进位标志 空指令 停机 停用启用中断 00:12:24.399 --> 00:12:26.726 中断模型 00:12:27.634 --> 00:12:31.992 大部分当时的机器都有一个中断向量来控制中断 00:12:32.123 --> 00:12:34.914 然而这个上面 并不是向量 也不止一个 00:12:35.113 --> 00:12:37.168 他并不是跳转到一个中断向量 00:12:37.349 --> 00:12:40.299 而是跳转到内存中开始部分一个固定的地址 00:12:40.470 --> 00:12:45.322 不同的中断对应不同的地址 比如0x40 0x48这种 00:12:46.159 --> 00:12:48.603 还有一些软件中断 00:12:48.782 --> 00:12:51.435 你可以使用特殊指令跳转到这些地方 00:12:51.645 --> 00:12:54.737 RST 0是一个特殊指令 因为就相当于RESET 00:12:55.038 --> 00:12:58.708 当你启动8080 CPU 他就从0地址开始执行 00:13:00.358 --> 00:13:03.877 来谈谈一些不被支持的8080功能 00:13:04.043 --> 00:13:07.799 这个是GB的标志位 这个是8080的标志位 00:13:07.952 --> 00:13:14.597 它有两个额外的标志 一个是符号位 还算有点用 一个奇偶校验 就不太有用 00:13:14.732 --> 00:13:16.494 所以这些指令都是不支持的 00:13:16.638 --> 00:13:20.781 还有一些其它指令 因为种种原因 他们决定不包括这些指令 00:13:20.910 --> 00:13:26.798 端口IO 你可能在8086上听说过 有独立的端口空间 00:13:26.798 --> 00:13:29.265 在8080上也是支持的 但是GameBoy上不支持 00:13:29.265 --> 00:13:31.486 因为GB使用了MMIO(内存映射IO) 00:13:32.870 --> 00:13:41.212 Z80支持大量额外的移位和位移位 设置位 清楚位指令 00:13:41.226 --> 00:13:42.219 这些都是支持的 00:13:42.420 --> 00:13:47.203 相对跳转 也就是更优化的跳转指令 00:13:47.428 --> 00:13:49.143 从中断返回 00:13:49.143 --> 00:13:50.327 这些都是支持的 00:13:50.529 --> 00:13:54.005 而不支持的则是一些Z80有趣的功能 00:13:54.242 --> 00:13:59.254 比如备份寄存器组 额外的寄存器 还有很多很多功能 00:13:59.254 --> 00:14:03.809 还有适合复制内存的自加循环指令 00:14:03.981 --> 00:14:07.688 不过他们也有一些自己的指令来代替这些 00:14:07.967 --> 00:14:12.800 比如它具有访问预减后加指令 00:14:12.800 --> 00:14:19.584 比如你要访问hl指针 可以使用预减指令或者后加指令 00:14:21.009 --> 00:14:22.689 还有一个叫做 第0页 (Zero Page) 的概念 00:14:22.852 --> 00:14:25.874 这个叫法有点晕 因为并不是真正的第0页 00:14:26.171 --> 00:14:27.792 而是在内存的顶部 00:14:27.948 --> 00:14:31.602 如果你了解6502就知道 这个是从6502借来的概念 00:14:31.914 --> 00:14:40.228 含义只是对于在内存最上端的数据可以优化编码 00:14:40.458 --> 00:14:45.755 比如原先这样载入需要3个字节 执行需要4个时钟机器周期 00:14:45.755 --> 00:14:49.291 现在可以写成这样 也就是2个字节 执行需要3个机器周期 00:14:49.889 --> 00:14:53.149 显然得在最高的位置放一些有用的东西 00:14:53.149 --> 00:14:54.646 特别是对计时敏感的东西 00:14:54.884 --> 00:14:56.497 还有一些额外的堆栈指令 00:14:56.713 --> 00:15:00.088 存储SP的指令 一个用于交换一个字节高低4位的指令 00:15:00.088 --> 00:15:04.912 这样8个指令 以及一个额外的电源指令 00:15:05.866 --> 00:15:07.529 操作码表看起来是这样的 00:15:07.748 --> 00:15:10.069 表明了颜色所以看起来更加直观一点 00:15:12.955 --> 00:15:15.963 有几个操作码是无效的 或者说没有被用到 00:15:15.963 --> 00:15:18.135 如果执行的话都会造成死机 应该算是个好设计 00:15:19.384 --> 00:15:22.392 这个比较特殊 操作码CB 00:15:22.604 --> 00:15:25.525 是个操作码前缀 以此为前缀又有256个操作码 00:15:25.744 --> 00:15:34.854 这也就是从Z80借来的滚动、移位指令 以及自己新增加的指令的空间 00:15:35.265 --> 00:15:38.481 再来看另外一个指令 只是作为一个例子 00:15:38.733 --> 00:15:42.863 这是从一个固定地址载入到累加器 00:15:42.863 --> 00:15:47.388 表示需要3个字节 在4MHz下需要16个时钟 00:15:47.388 --> 00:15:52.785 虽然存在争议 就是应该是1MHz还是4MHz (实际4MHz 但是把4个周期当1个看) 00:15:52.941 --> 00:15:56.409 因为所有的时钟周期数都是可以被4整除的 00:15:57.168 --> 00:16:01.438 因为整个系统都是和内存挂钩的 00:16:01.498 --> 00:16:06.272 CPU只能以RAM能够提供数据的速度来计算 00:16:07.218 --> 00:16:11.620 所以你可以说这是一个1MHz的CPU 00:16:11.820 --> 00:16:14.238 然后这个指令需要4个周期 00:16:14.416 --> 00:16:15.708 这个数字就小得多 00:16:15.881 --> 00:16:18.102 而且这个数字也才可以和类似的系统进行比较 00:16:18.304 --> 00:16:24.939 比如说和同样拥有1MHz内存速度的6502机器比较 00:16:24.939 --> 00:16:28.070 所以说 理论上 CPU确实频率是4MHz 00:16:28.332 --> 00:16:30.063 RAM速度是1MHz 00:16:30.270 --> 00:16:34.074 PPU以4MHz的速度绘图 00:16:34.294 --> 00:16:36.718 连接到速度为2MHz的VRAM 00:16:36.928 --> 00:16:38.582 所以这里就稍微有点复杂 00:16:38.791 --> 00:16:40.720 但是大部分情况下 00:16:40.888 --> 00:16:45.677 大部分的数据可以以1MHz为基准表示 00:16:45.909 --> 00:16:49.639 但是要精确的话 并不是1MHz 00:16:49.858 --> 00:16:54.044 而是1MiHz 1024*1024Hz 00:16:54.267 --> 00:16:59.172 所以他们没有使用十进制 而是用了二进制 00:16:59.337 --> 00:17:00.411 很有趣 00:17:00.620 --> 00:17:05.308 所以以后我提到周期 我说的是1MHz的机器周期 00:17:07.729 --> 00:17:13.749 所以这是一个拥有16位地址空间的8-Bit CPU 00:17:13.749 --> 00:17:19.092 因为拥有16Bit的指针 所以一共也就是64KB的可见地址 00:17:19.302 --> 00:17:24.875 32KB的是ROM 直接来自卡带 映射到卡带 00:17:25.103 --> 00:17:28.991 然后在上方有一个Boot ROM 一会提 00:17:29.195 --> 00:17:36.088 VRAM也映射在这里 可选的外部RAM 以及内部RAM 00:17:36.364 --> 00:17:40.180 没有使用的部分 要么没有东西 要么就是镜像已有的 00:17:40.521 --> 00:17:41.690 把最高地址的这些放大 00:17:41.690 --> 00:17:47.946 可以看到另外一页的OAM内存 就像是一种特殊用途的VRAM 00:17:48.164 --> 00:17:50.579 独立于VRAM 一会会具体讲 00:17:50.822 --> 00:17:55.076 最后一页 也就是第0页 包括了IO部分 00:17:55.308 --> 00:17:57.617 所有的寄存器 声音的视频的 00:17:57.875 --> 00:18:05.215 还有127字节的HRAM 独立于其它的RAM 00:18:05.454 --> 00:18:07.890 所以这代表游戏只能有32KB 00:18:08.105 --> 00:18:10.661 有些游戏确实只需要32KB 00:18:10.826 --> 00:18:13.556 比如说俄罗斯方块 就刚好挤进32KB 里面就一片芯片 00:18:13.837 --> 00:18:16.302 生产也很方便 00:18:16.506 --> 00:18:18.102 然后其它的卡带 00:18:18.412 --> 00:18:22.985 容量没有理论上限 但是实际上最大是到2MB 00:18:23.246 --> 00:18:25.708 这个是128KB 00:18:25.929 --> 00:18:29.737 实现的办法是 增加一个MBC (内存分页控制器) 00:18:29.963 --> 00:18:32.601 一个特殊的可以切换分页的芯片 00:18:32.823 --> 00:18:35.338 这个在各种系统上都是很常见的做法 00:18:35.563 --> 00:18:40.907 这些控制器可以很不一样 但是实际上 大部分的工作方式是这样的 00:18:41.134 --> 00:18:45.931 最低16KB空间 永远映射到Bank0 00:18:46.050 --> 00:18:51.204 而16KB-32KB这部分可以映射到Bank1 Bank2等等 00:18:52.961 --> 00:18:57.109 切换分页的方法就是往一个地址空间内写入一些特定数值 00:18:57.365 --> 00:19:01.319 这些就会被MBC读取到 然后切换分页 00:19:01.572 --> 00:19:03.613 这点对于外部RAM也是一样的 00:19:03.836 --> 00:19:08.919 游戏卡带如果含有外置内存 比如用于保存游戏 00:19:09.166 --> 00:19:11.841 比如装有备份电池的内存 这也是保存游戏的唯一办法 00:19:12.075 --> 00:19:15.581 可以把外部内存映射在这里 一样的模型 00:19:15.850 --> 00:19:18.063 所以这个BootROM是什么 00:19:18.331 --> 00:19:22.440 CPU从内存地址0开始执行 00:19:22.649 --> 00:19:26.820 BootROM的作用就是显示这些东西 然后响一声 00:19:27.292 --> 00:19:29.252 BootROM是内置在GameBoy里面的 00:19:29.508 --> 00:19:32.900 花了一段时间这些代码才被提取出来 还是挺麻烦的 00:19:33.070 --> 00:19:34.250 (不是我完成的) 00:19:35.040 --> 00:19:37.271 这个就是完整的BootROM 00:19:37.271 --> 00:19:38.321 首先初始化RAM 00:19:38.321 --> 00:19:39.692 初始化声音 00:19:39.692 --> 00:19:42.429 设置和解码显示在屏幕上的logo 00:19:42.649 --> 00:19:44.762 卷动logo 播放声音 00:19:44.988 --> 00:19:48.647 然后就是有趣的部分 它对比logo 00:19:48.848 --> 00:19:51.912 游戏卡带内需要有一份logo副本 00:19:52.114 --> 00:19:55.531 如果两者不一样 就不会继续开机 00:19:55.743 --> 00:20:00.327 这样任天堂就能控制什么游戏是给这个平台发行的 00:20:00.531 --> 00:20:02.175 游戏必须要内置这个logo 00:20:02.288 --> 00:20:08.145 如果未经允许加入 那不单单是违反版权保护 而且还是违反商标保护 00:20:09.584 --> 00:20:13.142 最后是计算校验和 算是再一次确认 00:20:13.142 --> 00:20:15.550 否则你得拆出来吹吹卡带 吹吹机子 重新插一遍 00:20:15.550 --> 00:20:19.249 接着它关闭BootROM 继续执行 00:20:19.369 --> 00:20:26.868 这个logo 实际上是从卡带里面读取并且显示出来的 00:20:27.094 --> 00:20:30.025 如果不插卡带开机会看见这个 00:20:30.741 --> 00:20:34.276 但是这并不代表游戏开发商可以往里面装入任何logo 00:20:34.276 --> 00:20:36.110 因为GameBoy会对比这个logo 00:20:36.136 --> 00:20:38.084 如果logo错误就不会继续启动了 00:20:39.328 --> 00:20:41.859 因为没有清理代码 00:20:42.029 --> 00:20:44.112 没有重启系统或者其它事情 00:20:44.112 --> 00:20:47.155 所以有些游戏就直接开始调戏这个logo 00:20:47.393 --> 00:20:48.323 直接在此之上继续 00:20:48.553 --> 00:20:50.776 demo也显然很喜欢调戏这个 00:20:50.987 --> 00:20:54.442 任天堂logo已经在屏幕上了 就对它做点啥吧(阴险 00:20:59.343 --> 00:21:00.033 很不错 00:21:01.771 --> 00:21:06.171 BootROM一直运行 直到最后一条指令关闭BootROM 00:21:06.383 --> 00:21:11.717 这是即使是最低地址显示的也是游戏卡带的内容了 00:21:11.926 --> 00:21:13.542 它就继续从这里执行 00:21:13.739 --> 00:21:16.507 运行来自游戏中的下一条指令 00:21:16.660 --> 00:21:18.548 通常这里是一个跳转指令 00:21:18.755 --> 00:21:22.131 因为这其实是所谓的ROM头 按照要求就应该是这样的 00:21:23.489 --> 00:21:27.571 头部必须内置任天堂Logo 头部校验和 这是很重要的 00:21:27.571 --> 00:21:29.576 剩下的元数据就不重要了 00:21:29.576 --> 00:21:32.158 对于当时的游戏开发者来说什么很重要 00:21:32.158 --> 00:21:34.452 但是没有软件会真正检查这些内容 00:21:34.452 --> 00:21:38.040 在这之后 就可以放真正的游戏数据了 00:21:39.259 --> 00:21:42.939 还没讲到过的东西就是IO部分和HRAM了 00:21:42.939 --> 00:21:51.788 也就是在最高页 所谓的第0页 可以比较高效得操作 00:21:51.973 --> 00:21:55.682 最高的127字节是额外的内存 00:21:55.864 --> 00:22:00.100 剩下的 就是这些不同的设备 00:22:00.293 --> 00:22:02.735 这些是所有系统内的寄存器 00:22:02.907 --> 00:22:06.572 中断控制器 声音控制器 摇杆输入 串口传输 定时器 还有PPU 00:22:06.743 --> 00:22:09.543 这些就是要讲的所有东西 00:22:09.915 --> 00:22:11.318 摇杆输入 00:22:11.530 --> 00:22:15.356 非常简单 这是所有GameBoy拥有的输入 00:22:15.513 --> 00:22:16.992 4个按钮 4个方向 00:22:17.203 --> 00:22:21.424 于是你会想 弄8个GPIO 这样就能实现了 00:22:21.424 --> 00:22:23.674 但是也可以用6个GPIO 00:22:24.226 --> 00:22:26.390 2列4行 00:22:26.533 --> 00:22:32.675 你选择想要测试的行 然后知道这一行按下了什么按键 00:22:32.725 --> 00:22:34.620 这样就能用6个而不是8个 00:22:35.141 --> 00:22:36.719 按键就是这些 很简单 00:22:37.448 --> 00:22:38.837 串行数据传输 00:22:39.134 --> 00:22:42.286 你可以用一条联机线把两台GameBoy连在一起 00:22:42.459 --> 00:22:49.700 联机线其实就是 1条单向数据线 1条另外一个方向的数据线 1条时钟线 00:22:49.922 --> 00:22:54.410 两台GameBoy得确定谁发送时钟 谁接收时钟 00:22:54.626 --> 00:22:56.593 这些就是控制这些东西的位 00:22:56.761 --> 00:22:59.443 一个设置输出时钟 永远是8kHz 00:22:59.655 --> 00:23:04.342 接收时钟范围很宽 可以从0一直到0.5MHz都能收到 00:23:04.342 --> 00:23:09.831 一旦传输开始 就会一位位移出字节 而且永远是全双工的 00:23:11.676 --> 00:23:12.732 定时器 00:23:12.983 --> 00:23:15.694 任何系统都得有个时钟 这个系统只有一个时钟 00:23:17.349 --> 00:23:21.083 TMA寄存器用来存放计数初值 00:23:21.311 --> 00:23:24.697 然后可以选择四种速度之一 00:23:24.891 --> 00:23:27.744 接着打开定时器 向上计时 00:23:27.966 --> 00:23:35.026 直到溢出 就会载入初值 然后可选的产生一个中断 00:23:35.239 --> 00:23:36.993 讲到中断 00:23:37.163 --> 00:23:40.125 中断控制器支持5种不同的中断 00:23:40.289 --> 00:23:45.213 V-Blank(场消隐) LCD状态 也就是和PPU相关 一会讲 00:23:45.419 --> 00:23:47.328 已经讲过的定时器 可以产生中断 00:23:47.328 --> 00:23:50.020 串口 可以在接受到数据的时候产生中断 00:23:50.020 --> 00:23:52.003 摇杆 可以在有按键按下的时候产生中断 00:23:52.457 --> 00:23:54.479 这个是中断启用寄存器 00:23:54.659 --> 00:23:56.299 一个中断标志寄存器 00:23:56.539 --> 00:23:59.100 可以看见有哪些中断还没有被处理 00:23:59.293 --> 00:24:02.672 这些是中断发生时会跳转的地址 00:24:02.875 --> 00:24:05.406 你不需要手动确定发生的是哪个中断 00:24:05.576 --> 00:24:07.138 因为直接跳转的就是不同的地址 00:24:07.314 --> 00:24:09.946 所以 声音控制器 00:24:10.255 --> 00:24:14.512 声音控制器的寄存器数量是最多的 00:24:14.675 --> 00:24:19.523 一共有四个声部 每个声部有自己的一组独立的寄存器 00:24:19.700 --> 00:24:21.505 这样看更为合理 00:24:21.697 --> 00:24:24.468 4个声部 每个声部5个寄存器 00:24:26.451 --> 00:24:29.890 每个寄存器有特定的含义 00:24:31.608 --> 00:24:33.370 但是含义只是大致的 00:24:33.370 --> 00:24:35.917 因为四个声道并不完全一样 00:24:36.125 --> 00:24:38.573 有两个很接近的 产生方波 00:24:38.798 --> 00:24:42.321 剩下两个一个是PCM采样 一个是杂波 00:24:42.646 --> 00:24:46.497 看位定义的话 有一定相似性 但是还是不一样 00:24:46.608 --> 00:24:49.569 具体通道有具体的定义 00:24:50.250 --> 00:24:53.107 先看看共通的一些参数 00:24:53.291 --> 00:24:56.382 每个声部都有一个开关位 也就是控制这个通道的开关 00:24:56.611 --> 00:24:59.147 你可以在某个时候关掉这个通道 00:24:59.147 --> 00:25:02.520 但是还有一个长度开关 以及长度设置寄存器 00:25:02.734 --> 00:25:05.681 所以比如你可以这么设置 在0.25秒后自动关闭 00:25:06.801 --> 00:25:09.794 然后先来看PCM采样声部的寄存器 也是最简单的一个 00:25:11.291 --> 00:25:14.790 这个声部的作用就是 可以播放任何的波形 00:25:14.972 --> 00:25:18.656 它有额外的16字节寄存器 00:25:18.806 --> 00:25:21.488 里面有32个位置 每个4位 00:25:21.685 --> 00:25:28.475 你可以在里面放任何你想要的波形 00:25:28.688 --> 00:25:29.780 这里是几个例子 00:25:29.946 --> 00:25:31.577 比如你可以产生一个锯齿波 00:25:36.060 --> 00:25:37.197 算是相当基本的波形 00:25:37.428 --> 00:25:38.972 也可以有正弦波 00:25:44.014 --> 00:25:46.107 或者是任何自定义的东西 00:25:50.880 --> 00:25:52.134 相当灵活 00:25:52.797 --> 00:25:54.335 然后有一个频率控制 00:25:54.541 --> 00:25:58.818 也就是控制采样的播放速度 00:25:59.076 --> 00:26:00.550 2位的音量 00:26:00.793 --> 00:26:03.721 音量可以在100% 50% 25%和静音之间控制 00:26:05.196 --> 00:26:10.074 另外两个 是很相似的两个方波声部 00:26:10.267 --> 00:26:13.160 这些位都是一样的,功能也一样 00:26:13.415 --> 00:26:18.077 你不能指定波形 波形是固定的 只能是方波 00:26:18.087 --> 00:26:19.590 就是只能是高或者低 00:26:20.533 --> 00:26:22.260 但是高低比例(占空比)可以不同 00:26:22.260 --> 00:26:25.780 这两个位就是控制占空比的 00:26:27.930 --> 00:26:30.668 可以设置12.5%高 剩下的低 00:26:33.387 --> 00:26:34.235 25% 00:26:36.604 --> 00:26:37.349 50% 00:26:37.349 --> 00:26:40.065 75%听起来应该和25%一样,只是反相了 00:26:43.317 --> 00:26:46.507 这个是两个声部都支持的功能 00:26:46.758 --> 00:26:49.493 另外有一个功能叫音量扫频 (Volume Sweep,翻译不准确,暂时想不到更好的) 00:26:49.507 --> 00:26:52.299 就是音量可以增高或者降低 00:26:52.461 --> 00:26:56.264 降低就是模拟乐器的情况 00:26:58.633 --> 00:27:01.104 或者也能增高 效果比较有趣 00:27:04.527 --> 00:27:10.458 然后 只有第一个声部有扫频功能 00:27:10.663 --> 00:27:12.419 可以升高 00:27:15.245 --> 00:27:16.193 也可以降低 00:27:18.689 --> 00:27:21.157 所以可以注意到主要是用于音效的 00:27:21.331 --> 00:27:22.617 更多的音效例子 00:27:25.440 --> 00:27:26.928 这就是可以实现的效果 00:27:27.616 --> 00:27:30.428 然后还有第四个杂波通道 00:27:30.608 --> 00:27:35.580 就是一个只能产生伪随机数的移位寄存器 00:27:36.029 --> 00:27:41.021 根据你设置的15bit模式或者7bit模式 00:27:41.237 --> 00:27:45.394 他能产生两种不同的波形 00:27:48.194 --> 00:27:49.348 这是15bit模式 00:27:51.571 --> 00:27:52.693 这是7bit模式 00:27:54.135 --> 00:27:55.584 这些是所有的寄存器 00:27:56.061 --> 00:28:00.043 4个声部的寄存器 3个通用寄存器 00:28:00.393 --> 00:28:03.556 这个可以设置左声道和右声道的音量 00:28:03.734 --> 00:28:07.525 有趣的是卡带可以有自己的音频控制器 00:28:07.737 --> 00:28:10.853 可以产生接到这个上面的音频信号 00:28:11.023 --> 00:28:12.207 但是没有游戏做过这种事情 00:28:12.814 --> 00:28:20.852 然后还有一个寄存器 可以设置一个通道是接到左声道还是右声道 或者都接、都不接 00:28:21.043 --> 00:28:23.064 然后有一个电源开关 00:28:23.195 --> 00:28:26.561 关掉音频部分可以减少约30%的电力消耗 00:28:28.576 --> 00:28:33.482 然后 GameBoy的声音系统不单单被用于游戏 00:28:33.763 --> 00:28:38.540 人们现在还在GB上用Little Sound DJ一类的软件编曲 00:28:38.739 --> 00:28:40.539 我展示一个简单的例子 00:29:17.130 --> 00:29:18.143 音频就到此为止 00:29:18.706 --> 00:29:21.188 我们来谈谈Pixel Processing Unit (像素处理单元/PPU) 00:29:21.607 --> 00:29:25.451 PPU就是产生图形的部分 拥有12个寄存器 00:29:25.588 --> 00:29:26.510 并不是很多 00:29:27.343 --> 00:29:28.954 首先来看看规格 00:29:29.294 --> 00:29:32.432 在之前讲过,160*144像素,不是很多 00:29:32.629 --> 00:29:36.219 4级灰度 其实更接近于4级不同的绿色 00:29:36.400 --> 00:29:39.077 当然后续的GameBoy要好得多 00:29:39.272 --> 00:29:41.464 所有屏幕上的东西都是以8*8像素的方格为单位的 00:29:41.657 --> 00:29:44.445 屏幕上有一定数量的方格 还有一些精灵 00:29:44.609 --> 00:29:46.138 这些东西都得装进8KB的显存 00:29:46.340 --> 00:29:49.401 具体解释下这个8*8像素方格 00:29:49.640 --> 00:29:51.441 我们看一下俄罗斯方块的游戏画面 00:29:51.639 --> 00:29:54.354 所有东西都是基于这些方块的 00:29:54.564 --> 00:30:00.709 塞尔达也是一样 画上网格线就能看见重复的图案 而且都是和网格线对齐的 00:30:00.897 --> 00:30:06.241 超级马里奥大陆也是很明显 因为没有很多不同的方格 00:30:06.513 --> 00:30:09.399 甚至是像Donkey Kong Land这样的游戏 00:30:09.639 --> 00:30:12.185 在画上网格后还是能看出来的 00:30:12.439 --> 00:30:14.157 可以看见重复对齐的图案 00:30:14.360 --> 00:30:16.587 尽管他们很好地隐藏了这一点 00:30:17.841 --> 00:30:21.024 有些游戏干脆就利用这个特性来设计 00:30:21.024 --> 00:30:23.806 在Turrican里面干脆就用方块做了个动画 00:30:24.536 --> 00:30:26.737 我们来看看方格是什么样的 00:30:27.072 --> 00:30:30.312 一个方格尺寸是8*8 可以有4种颜色 00:30:32.150 --> 00:30:35.199 4种颜色的编码就是00 01 10和11 00:30:36.035 --> 00:30:40.242 把这个叠加到像素上看看编码是如何完成的 00:30:40.496 --> 00:30:42.901 看第一行 直接当成一个二进制数读 00:30:43.066 --> 00:30:46.921 于是就是 02 FF 一行像素需要2个字节 00:30:47.140 --> 00:30:50.707 整个下来一个方块就是16字节 00:30:51.981 --> 00:30:56.458 你可能注意到了这个颜色的排序比较奇怪 00:30:56.683 --> 00:30:58.978 这是因为调色板是可以自定的 00:30:59.249 --> 00:31:00.869 我可以选择任何想要的调色板 00:31:00.869 --> 00:31:04.210 系统内对于这些背景方块有个2位到2位的映射 00:31:05.517 --> 00:31:09.854 原生的表示方法是 00表示白 11表示黑 00:31:10.235 --> 00:31:12.978 我可以选择任意的这种的调色板 00:31:13.153 --> 00:31:16.175 如果有需要 我也可以选择一样的颜色 00:31:17.754 --> 00:31:19.615 系统内一共支持256个方块 00:31:19.856 --> 00:31:22.036 看这个设置 你认出是什么游戏了吗 00:31:22.240 --> 00:31:24.041 这是俄罗斯方块 00:31:24.410 --> 00:31:26.585 如果你不认识底部那些跳舞的人 00:31:26.766 --> 00:31:28.438 说明你没有通关过俄罗斯方块 00:31:31.437 --> 00:31:32.441 这是塞尔达 00:31:33.742 --> 00:31:37.260 这是超级马里奥大陆 只使用了128个方块 00:31:39.032 --> 00:31:40.859 有人认识这个吗? 00:31:42.954 --> 00:31:44.987 这个只是一个拼图 00:31:46.167 --> 00:31:47.121 是网球 00:31:50.327 --> 00:31:55.315 20*18个方块填满了整个屏幕 00:31:55.509 --> 00:31:56.829 但是其实事情不完全是这样 00:31:57.016 --> 00:32:01.617 在视频内存中 有32*32方块 00:32:01.797 --> 00:32:03.018 这才是完整的背景映射 00:32:03.222 --> 00:32:05.640 屏幕上能看见的只是一个区域(Viewport) 00:32:07.246 --> 00:32:12.144 256*256个像素还是相当方便的数字 00:32:12.670 --> 00:32:16.683 卷动很方便,只要移动这个区域就可以了 00:32:17.603 --> 00:32:23.023 使用这个模拟器就能很清楚得看到背景中发生了什么事情 00:32:23.256 --> 00:32:24.941 以及可视区域是如何移动的 00:32:25.115 --> 00:32:28.806 其实就像是一个摄像机,在32*32的映射中移动 00:32:29.793 --> 00:32:33.591 但是这种方法只适用于最大地图只有32*32的游戏 00:32:33.727 --> 00:32:35.767 但是那种需要无限卷动的游戏怎么办 00:32:35.965 --> 00:32:37.614 比如超级马里奥大陆 00:32:37.832 --> 00:32:40.371 最多可以有这么多的列 00:32:40.535 --> 00:32:42.398 可以把区域移动到这里 00:32:42.533 --> 00:32:45.073 但是到最后怎么办 00:32:45.269 --> 00:32:47.510 它会直接卷到左边 00:32:47.762 --> 00:32:52.106 如果我们能在显示出来之前重绘这些画面 00:32:52.281 --> 00:32:53.275 就可以拥有无限的世界大小 00:32:54.038 --> 00:32:56.435 在模拟器里就看得很清楚了 00:32:56.638 --> 00:33:01.521 在屏幕显示不到的地方会不断刷新新的地图 00:33:01.708 --> 00:33:03.559 对于二维也是一个道理 00:33:03.559 --> 00:33:05.059 这是Donkey Kong Land 00:33:05.059 --> 00:33:06.521 这个看起来很奇怪 00:33:06.521 --> 00:33:11.234 只在需要显示的地方绘制新的地图 00:33:14.066 --> 00:33:16.645 这是我们已经谈论过的一个图层 00:33:18.674 --> 00:33:19.701 背景图层 00:33:19.843 --> 00:33:23.084 除了背景外 还能放一层图像 00:33:23.253 --> 00:33:24.286 窗口 00:33:24.458 --> 00:33:25.286 可以直接覆盖 00:33:25.286 --> 00:33:26.706 也可以放在任意地方 00:33:26.706 --> 00:33:28.392 有个XY坐标 00:33:28.392 --> 00:33:31.142 他会从起始坐标开始向右下方绘制 00:33:31.363 --> 00:33:33.109 然而没有透明支持 00:33:33.287 --> 00:33:36.006 通常的用法就是放到右边 00:33:36.193 --> 00:33:37.698 或者放到下面 00:33:37.835 --> 00:33:40.611 因为这个是不受卷动影响的 00:33:41.943 --> 00:33:46.690 显然这个功能对于屏幕下方的分数显示来说是很必要的 00:33:46.732 --> 00:33:49.097 对于游戏来说非常简单好用的功能 00:33:49.898 --> 00:33:51.806 当然也能放在右边 00:33:52.033 --> 00:33:55.572 这些是GBC游戏 但是不影响 在GB上也能玩 00:33:57.305 --> 00:34:00.371 然后在背景和窗口层之上还有一个图层 00:34:00.371 --> 00:34:01.421 就是精灵层 00:34:01.619 --> 00:34:06.075 精灵就是放在屏幕上的不用受限于8*8方格的物体 00:34:06.075 --> 00:34:07.550 可以自由摆放 00:34:07.739 --> 00:34:09.801 现在屏幕上有3个精灵 00:34:09.909 --> 00:34:11.533 任天堂把它们叫做 对象,OBJ 00:34:12.171 --> 00:34:14.789 但是我还是继续叫他们精灵 因为就是精灵 00:34:15.025 --> 00:34:16.843 就直接拿这个蘑菇举例吧 00:34:17.033 --> 00:34:20.170 每个精灵都有自己的属性 00:34:20.344 --> 00:34:23.609 系统内有个 OAM 也就是对象属性映射表 00:34:23.853 --> 00:34:25.100 这个就是一个OAM记录 00:34:25.308 --> 00:34:26.695 含有以下值 00:34:26.872 --> 00:34:28.119 第一个是X位置 00:34:28.294 --> 00:34:30.854 把它放到屏幕的左边 00:34:31.030 --> 00:34:34.435 你可能会以为应该横向坐标为0 00:34:34.674 --> 00:34:35.683 但是不是 是8 00:34:35.861 --> 00:34:36.561 为什么呢 00:34:36.672 --> 00:34:37.694 因为如果你把它放在4 00:34:37.845 --> 00:34:38.579 他就在这 00:34:38.579 --> 00:34:39.484 如果设置成0 00:34:39.484 --> 00:34:41.192 那就看不见了 因为宽度是8 00:34:41.280 --> 00:34:42.441 需要有一种方法能让它进入屏幕 00:34:42.956 --> 00:34:45.756 对于屏幕顶端的情况也是一样的 00:34:45.931 --> 00:34:49.451 但是你看现在的Y坐标是16 00:34:49.451 --> 00:34:58.386 因为一个精灵最高可以有16像素高 00:34:58.605 --> 00:35:01.685 把它放到一个自然的位置 00:35:01.921 --> 00:35:05.932 下一个问题就是应该长什么样 00:35:06.159 --> 00:35:08.393 首先看见是一个8*8的方格 00:35:08.393 --> 00:35:09.358 和之前的方格是一样的编码 00:35:09.358 --> 00:35:11.262 只是这个还带有透明色 00:35:11.262 --> 00:35:13.636 所以00 表示透明 00:35:13.834 --> 00:35:16.706 因为是一样的编码 所以也是一样的方格 00:35:16.870 --> 00:35:20.666 而且系统内也有256个方格位可供使用 00:35:20.853 --> 00:35:23.173 你看现在是方块编号0x90 00:35:24.323 --> 00:35:26.614 剩下一个是 X翻转 选项 00:35:27.094 --> 00:35:32.310 所以你没有必要为 向左走 和 向右走 的蘑菇单独存两个方块 00:35:32.497 --> 00:35:35.417 水平翻转一下 就有向右走的了 00:35:35.547 --> 00:35:37.649 垂直翻转一下 就有死蘑菇了 00:35:37.820 --> 00:35:41.739 同时垂直和水平翻转 就有向右走的死蘑菇啦~ 00:35:42.734 --> 00:35:44.879 调回来 00:35:45.077 --> 00:35:47.009 下一个是调色板 00:35:47.169 --> 00:35:52.196 因为其中一种的颜色表示的是透明 00:35:52.398 --> 00:35:55.289 所以只剩下3种颜色可以选择 00:35:55.492 --> 00:36:00.633 他们不想强制固定这三种颜色 00:36:00.801 --> 00:36:05.221 所以你可以在四种颜色里面挑3种想要的 00:36:05.379 --> 00:36:07.863 而且不同的精灵不需要使用同样的3种颜色 00:36:08.059 --> 00:36:10.439 因为系统内可以有两个调色板 00:36:10.664 --> 00:36:14.757 所以才有这个位 可以选择是调色板0还是调色板1 00:36:14.879 --> 00:36:18.455 调色板1和0分别效果就是这样 00:36:19.642 --> 00:36:21.032 剩下一个是优先级 00:36:21.347 --> 00:36:25.030 就是精灵碰到背景时如何绘图 00:36:26.000 --> 00:36:28.124 如果优先级是1就有趣了 00:36:28.281 --> 00:36:30.311 它会画在这些白色像素之上 00:36:30.441 --> 00:36:34.345 但是在所有不是白色的像素后面 00:36:34.407 --> 00:36:36.106 当然白色并不是一个什么特殊颜色 00:36:36.196 --> 00:36:41.700 只是指背景调色板里的0号颜色 00:36:41.815 --> 00:36:46.412 所以改调色板 就能让蘑菇显示在任意颜色之上 00:36:46.556 --> 00:36:50.189 优先级0的话就会绘制在任何东西之上 00:36:50.189 --> 00:36:51.874 当然不包括透明像素 00:36:53.394 --> 00:36:55.337 然后就是精灵之间的优先级问题 00:36:55.498 --> 00:36:57.055 这个没有办法自己设置 00:36:57.055 --> 00:36:57.930 这个是固定的 00:36:57.985 --> 00:37:01.273 现在蘑菇在正方形之上 00:37:01.406 --> 00:37:04.687 因为它的横向坐标比正方形小 00:37:04.830 --> 00:37:06.765 一旦达到一样的坐标 00:37:06.795 --> 00:37:09.267 精灵编号小的显示在上 00:37:09.267 --> 00:37:14.032 因为精灵在内存中也是一个数组 存前面的画上面 00:37:14.032 --> 00:37:20.774 然而一旦正方形的X坐标小于蘑菇的X坐标 就会画在上面 00:37:20.904 --> 00:37:24.494 一部分游戏中穿透其它精灵的时候 就能看到这种现象 00:37:25.814 --> 00:37:30.017 系统支持最多40个精灵 屏幕上最多也是能放40个 00:37:30.439 --> 00:37:31.660 但是还有一个限制 00:37:32.008 --> 00:37:33.784 每一行最多只能有10个 00:37:33.909 --> 00:37:35.356 比如这里有11个 00:37:35.513 --> 00:37:37.375 10个是按照一行像素计算的 00:37:37.463 --> 00:37:39.059 所以这些像素就不会被显示出来 00:37:39.933 --> 00:37:42.366 或者是下面两行 00:37:42.403 --> 00:37:45.313 这个不是讲从左到右的第十个 00:37:45.488 --> 00:37:50.728 而是按照精灵的编号来决定的 00:37:51.815 --> 00:37:54.252 所以这是一个完整的OAM记录 00:37:54.431 --> 00:37:55.909 一共4个字节 00:37:56.060 --> 00:37:58.082 这是内存中的一个OAM记录 00:37:58.238 --> 00:38:01.483 FE00就是存储这些记录的起始地址 00:38:01.625 --> 00:38:03.785 一共有40个记录 里面能存40个精灵 00:38:03.785 --> 00:38:05.397 整个这个叫做OAM内存 00:38:05.397 --> 00:38:08.709 就是一个存在这个位置的专用内存 00:38:08.709 --> 00:38:10.420 并不是显存的一部分 00:38:11.655 --> 00:38:13.432 还有一件事情 00:38:13.648 --> 00:38:15.385 就是即使是小马里奥 00:38:15.541 --> 00:38:17.914 也超过了8*8 00:38:18.082 --> 00:38:20.568 你可以用4个精灵来实现 00:38:20.727 --> 00:38:21.829 这也是游戏里的做法 00:38:21.956 --> 00:38:23.110 但是还有一个模式 00:38:23.110 --> 00:38:25.479 可以拥有16像素高的精灵 00:38:25.479 --> 00:38:27.004 但是这个设定是全局的 00:38:27.004 --> 00:38:30.373 整个游戏都得使用16像素高的精灵 00:38:31.043 --> 00:38:33.085 所以已经见过3层了 00:38:33.228 --> 00:38:37.660 还有一件事情是 屏幕是可以被彻底关闭的 00:38:37.777 --> 00:38:39.549 这样就有第五种颜色了(强行) 00:38:39.697 --> 00:38:42.394 稍微比白色位再淡一点 00:38:42.545 --> 00:38:46.075 没啥用 因为得把屏幕关掉 00:38:46.184 --> 00:38:49.162 一旦打开屏幕但是不画任何东西 00:38:49.346 --> 00:38:50.544 就是白色 00:38:50.544 --> 00:38:53.456 然后打开背景层 比如说要显示个淡灰色 00:38:53.456 --> 00:38:55.686 然后就完全代替了这个颜色 00:38:55.686 --> 00:38:56.925 再画个窗口 00:38:57.158 --> 00:38:58.536 注意没有透明 00:38:58.661 --> 00:39:01.369 还能在上面画精灵 00:39:01.498 --> 00:39:05.097 但是注意精灵和窗口的颜色就无法区分了 00:39:05.248 --> 00:39:07.071 所以说窗口上没有裁剪一类的事情 00:39:08.556 --> 00:39:11.894 所以内存映射是怎么样的 00:39:12.060 --> 00:39:13.767 4KB的精灵方块 00:39:13.767 --> 00:39:16.157 4KB的背景方块 00:39:16.330 --> 00:39:19.115 1KB的背景映射表 00:39:19.215 --> 00:39:20.735 32*32 00:39:20.845 --> 00:39:22.955 1KB的窗口映射表 00:39:22.989 --> 00:39:24.380 这不是最高效的表示方法 00:39:24.380 --> 00:39:27.278 但是他们这么做了 因为方便 00:39:27.419 --> 00:39:30.229 但是我们只有8KB的显存 00:39:30.354 --> 00:39:34.234 放个精灵方块和背景方块 就用完了 00:39:34.658 --> 00:39:36.143 换种方法 00:39:36.143 --> 00:39:39.746 先放上精灵方块 然后在最后放上背景和窗口映射 00:39:39.746 --> 00:39:41.383 背景方块怎么办 00:39:41.383 --> 00:39:42.560 就让它们重合 00:39:43.561 --> 00:39:46.575 有不同的配置 3个位 00:39:46.615 --> 00:39:48.374 可以让它们完全重合 00:39:48.374 --> 00:39:49.480 也可以只重合一部分 00:39:49.645 --> 00:39:52.402 这些也可以交换或者重合 00:39:53.031 --> 00:39:54.567 重合表示什么意思 00:39:54.607 --> 00:40:00.790 这个情况就是精灵方块和背景方块完全重合 00:40:00.790 --> 00:40:03.352 可以共享完全相同的方块 00:40:03.352 --> 00:40:05.026 但是也可以这么放 00:40:05.026 --> 00:40:11.543 前128个就是精灵专用 后128个就是背景专用 00:40:11.543 --> 00:40:14.574 中间128个共享 00:40:14.574 --> 00:40:16.468 比如这个超级马里奥大陆 00:40:16.662 --> 00:40:20.113 前2/3都用于精灵了 00:40:20.337 --> 00:40:22.021 剩下的1/3被用于背景 00:40:24.471 --> 00:40:26.669 下一个部分是横向计时 00:40:30.253 --> 00:40:32.866 在基于CRT的机器当中 00:40:33.006 --> 00:40:38.269 这就是CRT显示图像的慢动作效果 00:40:38.839 --> 00:40:41.674 从头到底 从左到右 00:40:43.048 --> 00:40:44.503 对于GameBoy也是一样的 00:40:44.694 --> 00:40:47.222 它以60Hz的速度不停地扫描 00:40:47.395 --> 00:40:48.883 从上到下 一行一行 00:40:48.883 --> 00:40:49.668 从左到右 00:40:49.668 --> 00:40:52.471 这不是因为使用了一些旧的器件 00:40:52.663 --> 00:40:57.010 他们完全重新实现了这一部分的内容 00:40:57.169 --> 00:40:59.820 但是液晶本身就需要以60Hz的速度刷新 00:40:59.994 --> 00:41:01.131 所以就这样了 00:41:01.358 --> 00:41:05.937 如果要实现一些特效的话是很重要的 00:41:06.148 --> 00:41:06.942 比如这个游戏中 00:41:07.108 --> 00:41:09.754 屏幕不同的区域行为是不一样的 00:41:09.920 --> 00:41:14.110 比如我们先只看这个滚动的城市图 00:41:14.253 --> 00:41:18.538 直接全屏滚动肯定是不成问题的 00:41:18.656 --> 00:41:20.456 但是我们只想让它在屏幕上的一部分滚动 00:41:20.625 --> 00:41:22.923 就可以通过这些额外的寄存器完成 00:41:23.061 --> 00:41:28.466 如果你之前接触过8位机的编程 比如C64什么的 那么应该会理解 00:41:28.659 --> 00:41:31.262 你可以知道当前正在画哪一行 00:41:31.442 --> 00:41:33.987 或者马上会画哪一行 00:41:33.987 --> 00:41:37.382 除了一直等着 还可以设定一个中断 00:41:37.597 --> 00:41:39.736 一旦到达某一行就会执行特定代码 00:41:39.909 --> 00:41:43.746 我们先把SCX寄存器设定到0 00:41:43.897 --> 00:41:45.815 然后在第8行触发 00:41:45.984 --> 00:41:48.288 所以它就会以卷动为0的参数显示出这些 00:41:48.471 --> 00:41:56.823 然后现在我把横向滚动 设置成比如23 然后下一帧设置成24 这样就能滚动了 00:41:57.534 --> 00:42:02.573 把对比寄存器设置到42 就会一直继续绘图到42行 00:42:02.738 --> 00:42:09.815 设置成其它的 再修改LYC 画完这个路面 一会具体讲路面的事情 00:42:11.240 --> 00:42:14.239 最后再设置回0 因为仪表盘不需要卷动 00:42:14.423 --> 00:42:16.693 下一帧也是同样这么处理 00:42:16.819 --> 00:42:22.673 这个例子中就不需要修改X卷动寄存器了 00:42:22.918 --> 00:42:26.585 这个例子中 右上角的马里奥其实是一个窗口 00:42:27.621 --> 00:42:33.450 我们在之前讲过 窗口会从左到右从上到下绘图 00:42:33.450 --> 00:42:35.581 本身并不能这样只停在右上角 00:42:35.581 --> 00:42:36.510 但是可以做到 00:42:37.473 --> 00:42:39.750 在第0行触发中断 启用窗口 00:42:39.950 --> 00:42:42.457 在第40行的时候再触发中断 关闭窗口 00:42:42.658 --> 00:42:48.656 于是在40行的时候 PPU的情况就是:“窗口?啥子窗口?没听过这个说法” 00:42:48.843 --> 00:42:50.915 于是就不会继续画 00:42:51.092 --> 00:42:53.470 这个方法在不同的游戏中都有应用 00:42:53.660 --> 00:42:57.853 一些是关于窗口,一些则是和卷动有关 00:42:59.274 --> 00:43:02.454 但是如果你不是在一个特定行触发 00:43:02.618 --> 00:43:05.168 而是每一行都触发的话 就能实现一些比如这样的效果 00:43:05.368 --> 00:43:06.854 左边是屏幕上的效果 00:43:06.854 --> 00:43:11.069 右边是实际上存储在显存中的数据 00:43:12.055 --> 00:43:13.972 如果你在每一行都改变卷动寄存器 00:43:14.502 --> 00:43:16.893 比如按照这个曲线调整 00:43:16.893 --> 00:43:19.140 那么就能实现对图像的变形 00:43:19.140 --> 00:43:20.672 然后每一帧都改变这个曲线 00:43:20.895 --> 00:43:28.810 程序要做的就是 在每一行都向SCX写入一个值 00:43:29.080 --> 00:43:32.830 当然如果图片每行都显示那就每行都得修改 00:43:32.996 --> 00:43:37.560 而这个赛车游戏的效果其实差不多也是一样的道理 00:43:37.709 --> 00:43:39.294 这是视频内存中的情况 00:43:39.505 --> 00:43:40.740 只是一条直的路 00:43:40.933 --> 00:43:44.652 但是在绘图的时候被扭曲 00:43:44.859 --> 00:43:46.159 放大这块 00:43:46.159 --> 00:43:48.457 这是原始数据 这是效果 00:43:48.597 --> 00:43:50.923 忽略这里的精灵 00:43:51.075 --> 00:43:53.829 这是为了扭曲需要的曲线 00:43:53.985 --> 00:43:56.999 这是需要的偏移 也就是SCX 00:43:56.999 --> 00:44:00.903 如果你每行都调整SCX 就能扭曲成这样 00:44:00.986 --> 00:44:02.522 另外一件事情就是 00:44:02.648 --> 00:44:08.304 路中间的线变成了虚线 路外部也有一些横线的图案 00:44:08.445 --> 00:44:11.675 这是通过每几行就调整调色板来实现的 00:44:14.318 --> 00:44:19.141 如果看这个游戏的话 甚至可以实现弹跳的效果 00:44:19.282 --> 00:44:24.039 这是通过在每行同时调整纵向和横向卷动寄存器实现的 00:44:24.206 --> 00:44:28.269 所以就可以重复一行的内容或者跳过某些行 00:44:28.465 --> 00:44:30.808 只要计算好 就可以实现这种效果 00:44:30.996 --> 00:44:36.018 只要在行中修改垂直卷动寄存器 00:44:36.195 --> 00:44:38.518 就能实现二维的扭曲效果 00:44:38.742 --> 00:44:43.518 但是我们还得再仔细讲一讲 横向计时 00:44:46.706 --> 00:44:49.011 在一行绘制的时候 发生了什么 00:44:49.975 --> 00:44:53.125 这是PPU的像素传输模式 00:44:53.296 --> 00:44:55.172 通常需要43个时钟的时间 00:44:55.401 --> 00:44:57.822 一共144行 00:44:58.037 --> 00:45:00.579 但是你不能直接认为 00:45:00.788 --> 00:45:04.958 在第一行结束后 它会立即绘制下一行的第一个像素 00:45:05.099 --> 00:45:06.584 现实不是这样的 00:45:06.759 --> 00:45:10.907 因为在每一行的开始还有一个OAM搜索阶段 需要20时钟 00:45:11.117 --> 00:45:12.278 我一会讲 00:45:12.424 --> 00:45:16.370 每行的结尾还有一个51时钟的行消隐 00:45:16.642 --> 00:45:19.532 在行消隐阶段,PPU就闲着,什么事情都不做。 00:45:19.720 --> 00:45:23.640 还有一个场消隐阶段 就是在帧之间的空闲 00:45:23.834 --> 00:45:25.404 来计算一下 00:45:25.602 --> 00:45:28.984 一行有114时钟 频率是1MHz 00:45:29.021 --> 00:45:32.619 154行 所以需要这么多个时钟周期完成1帧 00:45:32.619 --> 00:45:35.163 如果以用基准频率除以这个 00:45:35.344 --> 00:45:37.763 得到的就是59.7Hz的刷新率 00:45:41.486 --> 00:45:45.174 PPU当前所在的状态是可以直接读出的 00:45:45.334 --> 00:45:49.351 CPU可以知道 而且也可以基于这个设定中断 00:45:49.351 --> 00:45:51.842 但是CPU为什么需要知道呢 00:45:52.492 --> 00:45:55.114 我们来看看不同模式下都发生了什么 00:45:55.313 --> 00:45:58.607 首先 每行开始20周期的OAM搜索是做什么的 00:45:58.778 --> 00:46:04.111 每一行 PPU都需要确定这一行中哪些精灵是可见的 00:46:04.111 --> 00:46:06.319 所以系统内一共有40个精灵 00:46:06.531 --> 00:46:12.422 然后它得找出 这一行中可见的精灵 00:46:12.549 --> 00:46:16.319 然后放进一个最多可以保存10个精灵的 可见精灵数组 00:46:16.541 --> 00:46:21.146 逻辑是 x坐标不能为0 因为那样就不可见了 00:46:21.345 --> 00:46:27.219 然后当前所绘制的行必须要在精灵的第一行和最后一行之间 00:46:27.404 --> 00:46:29.315 符合要求就加入可见精灵数组 00:46:29.516 --> 00:46:32.012 然后这需要20周期的时间 00:46:32.266 --> 00:46:35.531 顺便在原版的GameBoy中这里有个有趣的bug 00:46:35.724 --> 00:46:43.990 如果你在OAM搜索期间 进行了任何数字在FE00到FEFF之间的16位运算 00:46:44.179 --> 00:46:45.511 也就是到OAM内存的指针 00:46:45.654 --> 00:46:47.113 即使你没有访问内存 00:46:47.942 --> 00:46:52.023 他也会破坏这一块的内存 00:46:56.036 --> 00:46:58.587 还有什么需要注意的 00:46:59.894 --> 00:47:01.665 CPU连接到内存 00:47:01.907 --> 00:47:04.234 PPU连接到显存 00:47:04.443 --> 00:47:07.573 OAM内存是特殊的 PPU也直接连接到OAM内存 00:47:07.727 --> 00:47:12.387 CPU也可以连接到显存 这样就能写入到显存 00:47:12.387 --> 00:47:13.918 但是并不是这样完成的 00:47:14.106 --> 00:47:15.958 那样需要双倍速的显存 00:47:16.127 --> 00:47:20.127 你看C64是那样做的 但是在GameBoy上就必须经过PPU 00:47:20.544 --> 00:47:23.308 PPU内部有个开关 PPU可以决定CPU能不能访问 00:47:23.308 --> 00:47:24.843 如果PPU不让CPU访问 00:47:24.843 --> 00:47:27.597 那么CPU进行写入 什么都不会发生 00:47:27.597 --> 00:47:29.163 CPU进行读取 读到的全部是FF 00:47:29.270 --> 00:47:31.001 至少不会发生什么严重的问题 00:47:31.001 --> 00:47:32.286 但是也没有什么意义 00:47:32.286 --> 00:47:36.791 所以说CPU必须确保PPU在合适的模式下 这样才能顺利访问 00:47:38.338 --> 00:47:41.602 在像素传输阶段 不能访问显存 00:47:41.832 --> 00:47:48.169 但是在OAM搜索 行消隐和场消隐阶段都是可以访问的 00:47:48.346 --> 00:47:50.409 如果你想要访问OAM内存 00:47:50.635 --> 00:47:54.281 OAM搜索或者像素传输都不可以 00:47:54.392 --> 00:47:57.096 像素传输时PPU要绘图 需要精灵数据 00:47:57.279 --> 00:47:59.655 只能在这些时候才能访问 00:47:59.818 --> 00:48:02.782 所以在屏幕绘图时就得十分小心 00:48:03.822 --> 00:48:06.877 所以这些时间对于CPU来说都是不好的 00:48:07.079 --> 00:48:08.777 不应该做任何重要的事情 00:48:08.967 --> 00:48:12.010 比如说CPU想要把新的一行移动进背景映射 00:48:12.010 --> 00:48:13.572 最好是在场消隐阶段完成 00:48:13.754 --> 00:48:16.016 有最多的不被打断的时间 00:48:16.511 --> 00:48:21.348 而所有的游戏逻辑和AI可以在屏幕绘图的时候完成 00:48:21.487 --> 00:48:26.737 但是这个时候不能立即写入新的精灵位置 00:48:26.784 --> 00:48:28.176 因为OAM也是不能访问的 00:48:28.830 --> 00:48:30.789 所以游戏一般的做法都是 00:48:30.988 --> 00:48:36.306 把新的精灵信息全部写入一个OAM内存的副本 00:48:36.376 --> 00:48:39.781 然后在场消隐阶段复制进OAM 00:48:40.041 --> 00:48:44.042 就是从任何这里其中一个 存放OAM副本的地方 00:48:44.042 --> 00:48:47.290 复制进OAM 这个图不是按比例画的 00:48:48.733 --> 00:48:50.854 而且这个过程不需要自己完成 00:48:50.854 --> 00:48:52.572 有一个DMA功能 00:48:52.708 --> 00:48:55.534 写入想要复制的块 00:48:55.737 --> 00:48:58.629 然后等待160个时钟 00:48:58.824 --> 00:49:02.550 在复制的过程中 CPU还是在运行的 00:49:02.756 --> 00:49:06.988 但是不能访问任何的源地址空间 00:49:07.158 --> 00:49:08.200 所以只能等着 00:49:08.390 --> 00:49:11.261 但是因为这个代码也得有地方存放 00:49:11.431 --> 00:49:14.107 所以可能的地方只有是HRAM 00:49:14.318 --> 00:49:15.542 也算是个不错的用途 00:49:17.235 --> 00:49:18.951 然后是像素流水线 00:49:19.120 --> 00:49:21.939 我们继续深入来研究像素流水线 00:49:21.939 --> 00:49:28.662 这个还算是非常新的研究 之前是没有公开的 00:49:28.915 --> 00:49:35.030 像素FIFO(先入先出缓存)是GameBoy绘图的核心概念 00:49:35.575 --> 00:49:37.210 我们直接跳到中间过程 00:49:37.210 --> 00:49:38.645 屏幕上已经有一些像素了 00:49:38.645 --> 00:49:42.296 比如这5个像素已经被送给LCD了 00:49:42.806 --> 00:49:45.481 然后像素FIFO 里面有一些像素 00:49:45.635 --> 00:49:49.959 然后每一个4MHz的步骤 它都会移出一个像素 00:49:50.153 --> 00:49:51.456 送给LCD 00:49:51.696 --> 00:49:53.794 移出一个像素 送给LCD 00:49:54.054 --> 00:49:55.862 然后再移出下一个给LCD 00:49:55.862 --> 00:49:59.277 你可能注意到那个绿色的指示灯变成红色了 00:49:59.691 --> 00:50:04.152 因为像素FIFO必须要有至少8个像素才能移出像素 00:50:04.364 --> 00:50:05.785 为什么 一会会讲到 00:50:05.931 --> 00:50:09.392 现在需要产生新的数据来填充FIFO了 00:50:09.581 --> 00:50:12.684 Fetch(获取)单元就是做这个的 00:50:12.882 --> 00:50:14.555 它会读取背景方块单元 00:50:14.729 --> 00:50:18.645 9802是目前在读取的映射位置 00:50:18.842 --> 00:50:22.056 它从背景映射中读取方块的ID 需要1个周期 00:50:22.219 --> 00:50:28.033 然后从方块数据内存 读取第一部分和第二部分的数据 00:50:28.226 --> 00:50:31.615 因为每一行的方块是16位 00:50:31.800 --> 00:50:34.773 这样它就能产生8个新的像素 00:50:34.945 --> 00:50:38.781 然后就重新开始 进入下一个位置 00:50:38.949 --> 00:50:42.876 就可以把这8个像素移入FIFO的上半部分 00:50:43.058 --> 00:50:45.666 这样就可以继续移出新的像素 00:50:45.848 --> 00:50:47.540 然而在执行过程中 00:50:47.765 --> 00:50:51.435 并不是说等待有空了 然后开始Fetch填充 移出再这样 00:50:51.435 --> 00:50:54.001 并不是交替进行的 而是同时进行的 00:50:55.522 --> 00:50:58.042 FIFO是2倍速的 00:50:58.224 --> 00:51:02.102 所以Fetch进行1个步骤 FIFO移出两个像素 00:51:02.259 --> 00:51:06.617 移出 移出 读取第一字节的数据 00:51:06.802 --> 00:51:10.699 移出 移出 读取第二字节的数据 00:51:10.853 --> 00:51:12.410 然后到这个时候 00:51:13.386 --> 00:51:16.310 现在还不能把数据存入FIFO 00:51:16.431 --> 00:51:17.839 因为FIFO还放不下 00:51:17.939 --> 00:51:19.920 所以Fetch先关闭 00:51:20.042 --> 00:51:21.884 等待2个周期 00:51:23.093 --> 00:51:24.803 也就是空闲一段时间 00:51:24.953 --> 00:51:26.521 然后放入数据 00:51:26.640 --> 00:51:28.911 所以如果你看内存访问的模式 00:51:29.059 --> 00:51:33.291 就能看见3次读取 1次空闲 3次读取 1次空闲这样 00:51:35.279 --> 00:51:38.942 所以 FIFO就是4MHz时钟下 每个周期移出一个像素 00:51:39.080 --> 00:51:40.975 除非含有超过8个像素 否则暂停 00:51:40.975 --> 00:51:42.501 Fetch单元 运行频率是2MHz 00:51:42.501 --> 00:51:44.538 需要3个周期来获取8个像素 00:51:44.832 --> 00:51:48.331 然后在第四周期停住 直到FIFO有空位 00:51:49.050 --> 00:51:51.159 横向移动的实现方法非常简单 00:51:51.308 --> 00:51:52.859 比如说我们移动3个像素 00:51:53.001 --> 00:51:54.778 所以所有东西都向左移动了3个像素 00:51:54.931 --> 00:51:57.398 最先的3个像素就直接无视掉了 00:51:57.531 --> 00:52:00.239 然后下一个像素送往LCD 00:52:00.378 --> 00:52:01.568 而在行的最后 00:52:01.568 --> 00:52:02.817 事情变得有趣了 00:52:02.939 --> 00:52:05.807 因为我们想在X=160的时候触发 00:52:05.902 --> 00:52:09.063 FIFO可能还包含一些根本不会被画出来的像素 00:52:09.149 --> 00:52:14.225 然后Fetch可能也已经在获取下8个像素的内容了 也不要了 00:52:14.346 --> 00:52:16.885 然后就全部停止 00:52:16.885 --> 00:52:19.217 就是干了太多活了 直接进入行消隐 00:52:19.217 --> 00:52:25.427 这也是一行需要43周期 而不是40周期的原因 00:52:26.603 --> 00:52:27.784 如果我们开启了窗口 00:52:27.889 --> 00:52:30.687 比如说窗口坐标是26 00:52:30.815 --> 00:52:34.448 我们现在就在26 然后FIFO已经缓存了一些像素了 00:52:34.558 --> 00:52:36.870 它会完全清空FIFO 00:52:37.037 --> 00:52:38.161 然后FIFO会被停止 00:52:38.161 --> 00:52:39.714 因为这些像素已经不需要了 00:52:41.443 --> 00:52:44.928 然后Fetch会被转到窗口映射 00:52:45.086 --> 00:52:46.964 然后Fetch被重新开始 00:52:46.964 --> 00:52:49.614 然后还是一样 读ID 读两个字节 00:52:49.614 --> 00:52:51.440 就得到了窗口的数据 00:52:51.596 --> 00:52:52.990 然后就可以放入像素FIFO 00:52:53.102 --> 00:52:56.555 只要这些开始被移出了 窗口就绘制出来了 00:52:56.665 --> 00:52:59.561 而精灵则是有10个比较器 00:52:59.720 --> 00:53:01.908 也是根据X位置触发 00:53:02.047 --> 00:53:05.116 比如说在这里X=26有一个精灵 00:53:05.258 --> 00:53:09.861 一样 像素FIFO里面有很多像素 然后Fetch执行到一半 00:53:09.990 --> 00:53:12.534 首先我们暂时暂停像素FIFO 00:53:12.717 --> 00:53:14.328 就不能继续移出新的像素 00:53:15.185 --> 00:53:18.392 然后暂时调整Fetch让它产生精灵数据 00:53:18.475 --> 00:53:20.610 重新开始Fetch 00:53:20.743 --> 00:53:22.145 然后就得到了精灵数据 00:53:22.324 --> 00:53:24.551 然而这次不是放到最后 00:53:24.668 --> 00:53:26.832 而是和前8个像素叠加 00:53:26.993 --> 00:53:28.798 混合到已有的像素上 00:53:28.962 --> 00:53:32.176 这也就解释了为什么FIFO中必须要有至少8个像素 00:53:32.344 --> 00:53:35.052 因为精灵是用这种方法来混合的 00:53:36.668 --> 00:53:41.373 然后和其它的系统有一个区别 00:53:41.558 --> 00:53:43.917 在窗口开始前 都以恒定速度移出像素 00:53:44.096 --> 00:53:46.093 当窗口开始的时候 00:53:46.203 --> 00:53:51.491 FIFO被清空 很长一段时间都不能输出数据 00:53:51.605 --> 00:53:54.326 直到FIFO内有了窗口数据 00:53:54.420 --> 00:53:57.477 也就是等到恢复了 00:53:57.608 --> 00:53:59.887 也就是需要至少43个周期 00:54:00.064 --> 00:54:03.535 如果有精灵就需要更多 00:54:03.695 --> 00:54:06.485 在基于液晶的系统上这么做是完全可行的 00:54:06.485 --> 00:54:07.798 你可以暂停发送像素 00:54:07.932 --> 00:54:09.656 而在基于CRT的系统上 不行 00:54:09.915 --> 00:54:11.524 比如说在C64上 00:54:11.681 --> 00:54:14.184 一行必须是正好的40时钟 00:54:14.263 --> 00:54:18.659 因为任何晚出来的像素看起来都会被右移 00:54:20.833 --> 00:54:23.468 所以这个图并不是完全准确 00:54:23.539 --> 00:54:26.540 像素传输应该是至少43时钟 00:54:26.663 --> 00:54:30.451 行消隐则是剩下的时间 00:54:30.690 --> 00:54:34.554 然后实际过程中 取决于屏幕上的内容 更接近于这样 00:54:37.485 --> 00:54:41.076 但是之前这个并不是像素FIFO真正的工作方式 00:54:41.076 --> 00:54:43.305 它并不存储像素颜色 00:54:43.305 --> 00:54:49.008 它存储的是原始的位组合 00:54:49.170 --> 00:54:50.153 以及来源 00:54:50.339 --> 00:54:53.341 比如说这里是9个背景像素 00:54:53.519 --> 00:54:55.023 Fetch也是一样的 00:54:55.146 --> 00:54:56.797 它并不产生像素颜色 00:54:56.972 --> 00:55:00.012 它产生的是位组合 00:55:00.210 --> 00:55:04.945 加上来源或者精灵调色板 00:55:05.096 --> 00:55:07.394 我们来把这个混合一下 00:55:07.594 --> 00:55:10.489 精灵的00表示在背景之上绘图 00:55:10.699 --> 00:55:12.415 来过一遍 00:55:12.607 --> 00:55:16.748 这里是精灵调色板1 数据00 表明是透明的 背景获胜 00:55:16.821 --> 00:55:20.358 这个情况下精灵获胜 因为应该画在背景之上 00:55:20.456 --> 00:55:22.696 大部分像素都是这个情况 00:55:22.848 --> 00:55:30.493 而最后这个像素 是精灵调色板1 数据透明 所以背景获胜 00:55:30.671 --> 00:55:33.986 再来和同一坐标下另外一个精灵混合一下 00:55:36.850 --> 00:55:42.185 这里是精灵调色板0 在背景之上 精灵获胜 00:55:42.338 --> 00:55:45.590 然后这个情况下 新的精灵不会覆盖旧的 00:55:45.754 --> 00:55:50.255 所以旧的获胜 然后就一直这样 00:55:50.389 --> 00:55:52.712 这也就是精灵会画在右边精灵的上面 00:55:52.712 --> 00:55:54.713 但是不会画在已有精灵之上 00:55:54.860 --> 00:55:57.934 高ID精灵输给低ID精灵的原因 00:55:58.101 --> 00:56:00.427 最后这里新的精灵又获胜了 00:56:00.589 --> 00:56:03.671 然后应用上调色板 00:56:03.768 --> 00:56:06.079 只有在最后像素被移出的时候才进行 00:56:06.195 --> 00:56:09.138 这里对应调色板 移出一个像素到液晶 00:56:09.291 --> 00:56:14.015 查表 转换 LCD 00:56:14.492 --> 00:56:16.728 另外一个 黑色 00:56:17.928 --> 00:56:22.053 这也是在彩色机型上的做法 00:56:22.209 --> 00:56:24.278 从SGB开始 00:56:24.448 --> 00:56:27.231 现有的游戏不能处理颜色 但是可以上色 00:56:27.371 --> 00:56:32.578 然后就是 已有的三种调色板现在变成了RGB调色板 00:56:32.693 --> 00:56:34.343 其它所有的都是一致的 00:56:35.844 --> 00:56:38.726 但是一旦移出像素 00:56:38.881 --> 00:56:41.625 它就对应RGB颜色 00:56:41.838 --> 00:56:43.266 把这个粉色的像素显示在屏幕上 00:56:43.517 --> 00:56:46.194 S1 11是这里 00:56:46.367 --> 00:56:48.645 黑色像素 00:56:48.788 --> 00:56:53.689 再来一个例子 S1 01 精灵调色板1 显示在屏幕上 00:56:54.674 --> 00:56:56.704 所有技术性内容到此为止 00:57:08.829 --> 00:57:09.582 还有5分钟的时间 00:57:12.031 --> 00:57:13.216 来讲讲开发 00:57:13.539 --> 00:57:16.200 如果你对GameBoy开发感兴趣 00:57:16.340 --> 00:57:17.871 有一些很不错的开发工具 00:57:18.021 --> 00:57:21.791 Rednex Game Boy 开发系统是一套命令行工具 00:57:21.961 --> 00:57:24.623 可以配合自己的编辑器和Makefile使用 00:57:24.801 --> 00:57:27.053 如果想要Debug 00:57:27.208 --> 00:57:30.109 BGB模拟器 就很不错 00:57:30.109 --> 00:57:35.449 为Windows设计 但是配合Wine在OSX和Linux上也没问题 00:57:35.720 --> 00:57:38.525 内置调试器 断点啊 单步啊 一类的 00:57:38.525 --> 00:57:43.272 然后还有一个显存查看器 可以查看所有发生的事情 00:57:43.365 --> 00:57:47.284 很适合在上面运行Demo然后观察发生了什么 00:57:48.287 --> 00:57:50.790 如果你想要在真机上运行 00:57:50.790 --> 00:57:52.902 也有像Everdrive一类的设备 00:57:53.032 --> 00:57:54.495 可以插SD卡 00:57:56.183 --> 00:57:58.690 因为还有4分钟时间 00:57:58.836 --> 00:58:02.864 我们来谈谈我最喜欢的GameBoy外设 00:58:02.864 --> 00:58:04.171 Game Boy Camera 00:58:04.338 --> 00:58:05.895 但是不是技术层面 00:58:06.009 --> 00:58:07.930 只是吹一波这个设备 00:58:08.099 --> 00:58:09.305 这个就是Game Boy Camera 00:58:09.473 --> 00:58:11.714 你把它插入GameBoy 00:58:11.837 --> 00:58:13.607 能拍照片 00:58:13.695 --> 00:58:15.150 然后可以用Game Boy Printer打印出来 00:58:15.296 --> 00:58:17.140 用的热敏纸 00:58:17.271 --> 00:58:18.833 如果还买得到的话 00:58:20.228 --> 00:58:23.912 然后可以拍出像这样的照片 00:58:24.082 --> 00:58:25.958 放大一点 00:58:30.602 --> 00:58:31.990 非常棒的照片 00:58:33.064 --> 00:58:37.603 每张照片 都是用这个14K像素的CCD拍出来的 00:58:38.662 --> 00:58:42.142 位深2位 (译者注:硬件为3位,抖动为2位以匹配GameBoy屏幕) 00:58:52.581 --> 00:58:55.969 所以下次去旅游的时候 记得带上GameBoy 00:59:09.490 --> 00:59:15.961 然后还有Game Boy Camera 加上一台带并口的PC 还有已经买不到的联机线 00:59:16.094 --> 00:59:24.312 所以感谢这些人 研究GameBoy 以及帮助我准备这场讲座 00:59:24.826 --> 00:59:27.711 这些人也是以各种途径帮助了我 00:59:29.596 --> 00:59:30.540 所以 00:59:40.085 --> 00:59:43.508 在这个系列中 这已经是第五场讲座 00:59:43.671 --> 00:59:44.798 下一个是什么 00:59:45.065 --> 00:59:48.296 明年应该有个讲座吧 00:59:48.494 --> 00:59:51.206 我建议两个讲座 00:59:51.286 --> 00:59:54.301 我就先给34C3几个提名 00:59:55.255 --> 00:59:58.912 Dominik Wagner来讲Acorn Archimedes 01:00:03.862 --> 01:00:16.084 然后Jannis Harder来讲SNES 01:00:16.384 --> 01:00:18.810 这是你们自己的选择 可以做这些讲座 01:00:18.973 --> 01:00:21.520 或者也可以在自己头上倒冰水 01:00:25.766 --> 01:00:27.144 感谢参加 明年见 01:00:28.571 --> 01:01:07.000 subtitles created by c3subtitles.de in the year 2017. Join, and help us!