上一节课我们展示了一个最简单的人物在屏幕内移动的例子,但人物移动时并没有配合的动画,这次我们来个高级版本的。在GB内我们管所有移动的物体都叫做精灵(Sprite)。组成精灵的瓦块(Tile)是一个8x8的像素组合。每个瓦块是一堆0或1位的组合,如果手工画这个肯定会疯掉。我们可以使用Gameboy Tile Designer(以下简称gbtd)来进行瓦块的绘画。

GBTD的使用

gbtd是个免费的绿色程序下载回来即可使用,macOS的用户可以通过wine来进行使用。运行GBTD后首先我们点击View选择Tile size,8x16。这里要做下解释GB的标准瓦块大小为8x8像素,但精灵特殊可以支持8x16的像素。GB的性能一个屏幕最多支持256个独立瓦块,GBC支持512个。大多游戏内一个主角都会使用掉16x16的像素来进行展示,也就是要同时控制4个瓦块,同屏幕精灵多了性能就会下降,所以我们要尽量使用8x16的精灵。 gbtd

这个截图是一个我已经做好的Mario的瓦块文件,前两个坐标的是mario静止时的状态,后几个下标是mario跑起来时的瓦块。我在附录里会把这个文件放出来,大家可以直接使用。接下来我们要导出gbdk可以用的.c文件。选择File->Export to->文件类型选择gbdk,filename我们使用mario.c,Label是我们数组变量的名字,我们也写上Mario。From会导出的瓦块下标,我们写入从0到9导出10个瓦块。 gbtd

导出后会生成一个mario.c的文件,我们打开看一下这就是我们后续要用到的瓦块。

unsigned char mario[] =
{
  0x07,0x07,0x0F,0x0F,0x01,0x0E,0x0B,0x14,
  0x09,0x16,0x07,0x18,0x07,0x00,0x02,0x0F,
  0x02,0x1F,0x03,0x3F,0x37,0x0D,0x3F,0x07,
  0x3F,0x0F,0x0E,0x0E,0x00,0x1C,0x00,0x3C,
  0xC0,0xC0,0xF8,0xF8,0xA0,0x40,0xB8,0x40,
  0xDC,0x20,0x80,0x78,0xF0,0x00,0x00,0xC0,
  0x40,0xF8,0xC0,0xFC,0xEC,0xB0,0xFC,0xE0,
  0xFC,0xF0,0x70,0x70,0x00,0x38,0x00,0x3C,
  0x07,0x07,0x0F,0x1F,0x00,0x3F,0x6D,0x12,
  0x6C,0x13,0x37,0x08,0x1C,0x1F,0x3E,0x31,
  0x2E,0x31,0x3E,0x39,0x1F,0x1F,0x11,0x1F,
  0x00,0x0F,0x0C,0x5F,0x02,0x7E,0x00,0x3C,
  0xC0,0xC0,0xF0,0xF0,0xA0,0x40,0xF8,0x00,
  0xCC,0x30,0xE0,0x18,0x70,0xC0,0xC0,0xF8,
  0x00,0xF8,0x00,0xF8,0x00,0xF0,0xE0,0xE0,
  0xE0,0xE0,0x40,0xC0,0x00,0x00,0x00,0x00,
  0x07,0x07,0x0F,0x0F,0x01,0x0E,0x0B,0x14,
  0x09,0x16,0x07,0x18,0x07,0x00,0x02,0x07,
  0x01,0x1F,0x03,0x1F,0x01,0x1F,0x13,0x1C,
  0x0B,0x0C,0x07,0x07,0x00,0x03,0x00,0x03,
  0xC0,0xC0,0xF8,0xF8,0xA0,0x40,0xB8,0x40,
  0xDC,0x20,0x80,0x78,0xF0,0x00,0x00,0xC0,
  0x80,0xE0,0xE0,0x60,0xF0,0xF0,0xF0,0x70,
  0xE0,0xE0,0x00,0xE0,0x00,0xF0,0x00,0x80,
  0x03,0x03,0x07,0x07,0x00,0x07,0x05,0x0A,
  0x04,0x0B,0x03,0x0C,0x03,0x00,0x00,0x07,
  0x08,0x07,0x1C,0x07,0x07,0x1F,0x0F,0x1F,
  0x0E,0x3E,0x00,0x21,0x00,0x01,0x00,0x00,
  0xE0,0xE0,0xFC,0xFC,0xD0,0x20,0xDC,0x20,
  0xEE,0x10,0xC0,0x3C,0xF8,0x00,0x48,0xE0,
  0x1C,0xE0,0x18,0xE0,0xF0,0xF0,0xF0,0xF0,
  0xE0,0xE0,0x00,0xC0,0x00,0xE0,0x00,0x00,
  0x07,0x07,0x0F,0x0F,0x01,0x0E,0x0B,0x14,
  0x09,0x16,0x07,0x18,0x07,0x00,0x03,0x3F,
  0xC3,0x3F,0xE2,0x0E,0xCF,0x0F,0x1F,0x1F,
  0x3F,0x3F,0x1C,0x7C,0x00,0x70,0x00,0x38,
  0xC0,0xC0,0xF8,0xF8,0xA0,0x40,0xB8,0x40,
  0xDC,0x20,0x80,0x78,0xF0,0x00,0x00,0xC0,
  0x8E,0xF0,0xE6,0xF8,0xE0,0xE4,0xF0,0xFC,
  0xF0,0xFC,0x70,0x7C,0x00,0x00,0x00,0x00
};

导入精灵到我们的程序中

我们打开上节课的main.c文件,把这次的mario.c文件导入进来,并修改暂时的瓦块看一下效果。

#include <gb/gb.h>
#include <stdio.h>
//导入新文件进来
#include "mario.c"


void main()
{
    //使用mario数组来展示数据,并且修改导入的下标大小,按8x8计算,我们需要导入20个瓦块进来。
    set_sprite_data(0, 20, mario);
    set_sprite_tile(0, 0);
    move_sprite(0, 20, 20);
    SHOW_SPRITES;
    while (1)
    {
        if(joypad()==J_RIGHT)
        {
            scroll_sprite(0, 2, 0);
        }
        if(joypad()==J_LEFT)
        {
            scroll_sprite(0, -2, 0);
        }
        delay(50);
    }
    
}

保存文件make运行。可以看到结果在游戏界面内只显示了马里奥的左上角,其他并没有显示,但在调试程序中可以看到其它的瓦块也已经加载进来了。 bgb-mario

这是因为我们还没有告诉gbdk我们的精灵要按8x16来显示,以及我们要组成我们的马里奥还要同时显示两组瓦块,我们再次修改程序试一试。

#include <gb/gb.h>
#include <stdio.h>
#include "mario.c"


void main()
{
    //设置精灵为8x16
    SPRITES_8x16;
    set_sprite_data(0, 20, mario);
    set_sprite_tile(0, 0);
    move_sprite(0, 20, 20);
    //设置精灵1,从下标2开始
    set_sprite_tile(1, 2);
    //移动右半部分到正确的位置
    move_sprite(1,20+8, 20);
    SHOW_SPRITES;
    while (1)
    {
        if(joypad()==J_RIGHT)
        {
            //每次移动要两个精灵同时移动,否则马里奥会分体。这也是刚说的为什么如果使用大像素人物尽量使用8x16的精灵
            scroll_sprite(0, 2, 0);
            scroll_sprite(1, 2, 0);
        }
        if(joypad()==J_LEFT)
        {
            scroll_sprite(0, -2, 0);
            scroll_sprite(1, -2, 0);
        }
        delay(50);
    }
    
}

make我们来实验下效果。

ezgif-2-a33da2875bbf.gif

目前我们的马里奥还是在屏幕上硬飘,没有跑起来怎么办?

马里奥跑起来吧

让马里奥跑起来其实要做的就是不停的切动画,边看代码边看注释。

#include <gb/gb.h>
#include <stdio.h>
#include "mario.c"
//设置一个全局变量循环动画
UINT8 run_index = 0;

void main()
{
    SPRITES_8x16;
    set_sprite_data(0, 20, mario);
    set_sprite_tile(0, 0);
    move_sprite(0, 20, 20);
    set_sprite_tile(1, 2);
    move_sprite(1,20+8, 20);
    SHOW_SPRITES;
    while (1)
    {
        if(joypad()==J_RIGHT)
        {
            //每次动态修改要展示的瓦块下标
            set_sprite_tile(0, (run_index+4)*2);
            set_sprite_tile(1, (run_index+4)*2+2);

            scroll_sprite(0, 2, 0);
            scroll_sprite(1, 2, 0);
            //当循环到达上限时重置到0
            if(run_index==4)
            {
                run_index = 0;
            }
            else
            {
                run_index+=2;
            }
            
        }
        else if(joypad()==J_LEFT)
        {
            set_sprite_tile(0, (run_index+4)*2);
            set_sprite_tile(1, (run_index+4)*2+2);

            scroll_sprite(0, -2, 0);
            scroll_sprite(1, -2, 0);

            if(run_index==4)
            {
                run_index = 0;
            }
            else
            {
                run_index+=2;
            }
        }
        else 
        {
            //当人物静止时恢复静止状体
            set_sprite_tile(0, 0);
            set_sprite_tile(1, 2);
        }
        delay(80);
    }
    
}

make运行,这次我们烧录到卡带用真机试试效果。

可以看到我们的马里奥奔跑起来了,但是向左跑的时候人物并没有转向。最笨的方法是编辑瓦块的时候再做3针向左的动画出来,但这样会大量占用我们宝贵的存储空间。怎么办?

下一课我们会讲解精灵进阶,如何实现两个方向的跑动,又不需要重复画瓦块。并会产生一个方便使用的Role类,来操作复杂的精灵。同时我们还会讲解GBC上的上色方案“调色板”。

附注