我的《超级马里奥》我做主:会漂移掉头还带刹车音效,C++从零打造,网友:小心任天堂找你...

点击上方“视学算法”,选择加"星标"或“置顶

重磅干货,第一时间送达4e8d890054d75e081f63a80728030f62.png

丰色 发自 凹非寺
量子位 报道 | 公众号 QbitAI

你见过这样的级马里奥吗?

跑着跑着突然停下来个帅气掉头,还自带刹车音效

“踩”扁“板栗仔”(goomba)时直接“变酷”(得到一副墨镜):

e2d6f5885766eba9f93e52294969346f.gif

这,就是一位油管博主用C++和SFML自己从头制作的红白机版超级马里奥。

C++不用介绍,SFML想必有很多人也熟悉,就是一个用来简化写小游戏或者多媒体应用程序的API,包括系统,窗口,图形,音频和网络五大模块。

52cc4e2cddec9ebd5965150ba6379465.gif

920ccaf5dcc22005180bb19e008ab037.gif

除了常规的功能和操作,你可以加入任何自己喜欢的元素。

由于画面看起来实在太逼真,有人甚至提醒博主:小心“版权狂魔”任天堂来找你哦

f25f509f581627d0b0e8b7b600f42a27.png

心动么?

你也可以自己做一个~

话不多说,来看教程。

手把手教你用C++打造超级马里奥

一共分为4大块。

1、基本控制

设置游戏窗口大小为256x240。

我们先自己绘制一个留胡子的小伙子——马里奥。

35a5a49fcb9d65fe369f381a5c523f2f.png

通过函数将它载入程序。

Mario::Mario() :

    x(0.5f * SCREEN_WIDTH),
    y(0.5f * SCREEN_HEIGHT)
{
    texture.loadFromFile("Resources/Images/Mario.png");

    sprite.setTexture(texture);
}

void Mario::draw(sf: :RenderWindow& i_window)
{
    sprite.setPosition(round(x), round(y));

    i_window.draw(sprite);
}

得到这样的界面:

5f777748b81c92a912d786498dfb1c49.png

然后处理地图,由于地图的宽度不同,将它存储为数组向量。

typedef std::vector<std::array<Cel1, SCREEN_HEIGHT / CELL_SIZE>> Map;
sf::Texture map_texture;
map_texture.1oadFromFile("Resources/Images/Map.png");

Map map(SCREEN_WIDTH/CELL_SIZE);

Mario mario;

for(unsigned short a = θ; a < map.size(); a++)
{
    for (unsigned short b = map[a].size() - 2;b< map[a].size(); b++)
    {
        map[a][b] = Cell: :Wa1l;
    {
}

089e176146c8f561870084848f14cd5b.png

现在画面是这样的:

12d062f70934674350ea7e9408442a5b.png

接着开始集中打造马里奥。

先让他能动起来,前进后退:

e795b33d0a76b950a4b2c73fd60039a8.gif

并且获得重力:

void Mario::update()
{
    if (1 == sf::Keyboard: :isKeyPressed(sf: :Keyboard: :Left))
    { 
        x-=MARIO_SPEED;
    }
    else if (1 == sf::Keyboard::isKeyPressed(sf: :Keyboard: :Right))
    {
        x+= MARIO_SPEED;
    }

    vertical_speed += GRAVITY;
    y += vertical_speed;
}

68f4dad36ab11f8c859089d298310ddd.gif

有了,但得让马里奥落到地上。

那就获取一下马里奥的坐标,用下面这些公式检查与之相交的所有单元格:

3a96443e1994ddd66cf69a5f41115286.png

成功:

49fb9b1f61eed15734bec58ef072e85f.gif

但是不能让马里奥跑出地图:

void Mario::update(const Map& i_map)
{
    if (1 == sf::Keyboard::isKeyPressed(sf: :Keyboard: :Left))
    { 
        x = std::max<float>(x - MARIO_SPEED,θ);
    }
    else if (1 == sf::Keyboard: :isKeyPressed(sf: :Keyboard: :Right))
    {
        x=std::min<float>(MARIO_SPEED + x,CELL_SIZE *(i_map.size() - 1));
    }
}

接下来添加碰撞。

用二进制表示马里奥碰到的单元格,用一个地图碰撞函数检查并返回0000-1111这15种可能,然后使用位运算检查方向。

1ad31dea9468ece2e30fe66b9757a2c7.png

6eafb2d7790ad86e830e617685a38952.gif

成功:

e60896ae2c44169e4192061c0f5c9970.gif

接下来,看看它能不能跳过这个墙。

c46c66c67629c34600c0d8e3b3c8a3c6.gif

显然不行……

搞起来,其中,为了使马里奥的跳跃高度和我们按住键盘的时长为正比,需要创建一个跳跃计时器变量。

if (1 == sf: :Keyboard: :isKeyPressed(sf: :Keyboard: :Up))
{
    if (θ == vertical_speed && θ < map_collision(x, 1 + y, Cell::Wa1l, i_map))
    { 
      vertical_speed = MARIO_JUMP_SPEED;
      jump_timer = MARIO_JUMP_TIMER;
    }
    else if (θ < jump_timer)
    {
        vertical_speed = MARIO_JUMP_SPEED;
        jump_timer--;
    }
    else
    { 
        vertical_speed = std::min<float>(GRAVITY + vertical_speed, MAX_VERTICAL_SPEED);
    }
}

再来挑战一下:

66f1df5ffbc80ee90ed02b3cb383ec72.gif

完美。

最后,给它添加加速度和摩擦力,也就是我们在文章一开头看到的那种刹车特效。

if (1 == sf::Keyboard: :isKeyPressed(sf: :Keyboard: :Left))
{
    horizontal_speed=std::max(horizontal_speed-MARIO_ACCELERATION,-MARIO_WALK_SPEED);
}
else if (1 == sf: :Keyboard::isKeyPressed(sf::Keyboard::Right))
{ 
    horizontal_speed =std::min(MARIO_ACCELERATION +horizontal_speed,MARIO_WALK_SPEED);
}
else if (θ < horizontal_speed)
{ 
    horizontal_speed-=MARIO_ACCELERATION;
}
else if (θ> horizontal_speed)
{ 
    horizontal_speed+=MARIO_ACCELERATION;
}

至此,基本控制就完成了,进入地图绘制部分。

2、地图

将地图存为图片之前,需分为两部分,上部分存为砖块,下部分存为实体。

185d9e8156f7045a6ffa42f17ec259b1.png

使用一个新函数将图像转为map。

Map convert_sketch(const sf::Image& i_map_sketch, Mario& i_mario)

修改drawback函数获得砖块像素颜色,绘制砖块。再画点云朵,基础地图就好了。

73893d802502a764b0a25b03edc3c6db.png

接下来就是挨个绘制剩余元素了。

if (sf::Color(109,255,85)==pixel)//Flagpole
{
    sprite_x=12;
    if (sf::Color(109,255,85) == pixel_up)
    {
        sprite_y=1
    }
}

成果如下:

dae07ab3af261235d5ad0995b305dc76.gif21335b0808decdd49deedccc99dfe2d7.gif

什么?缺个城堡?作者表示:累了,随便吧……

接下来,使用下面这个公式,让界面跟着马里奥前进后退。

short view_x = std::clamp<int>(mario.get_x()+0.5f *(CELL_SIZE - SCREEN_WIDTH),θ,CELL_SIZE*n)

5d49ae78e3b8edad737049e801d03e12.gif

地图搞定,上板栗仔!

3、板栗仔

板栗仔的行动和马里奥相似,代码可以基本复制。不同的是一旦它们碰到东西就会改变方向。

如何让板栗仔出现?

57ecb157065cfce7be62fa8a0e20df74.png

当马里奥靠近它们时,更新地图。

void Goomba::draw(unsigned 1_view_x, sf::RenderWindow& i_window)
{
    if (-CELL_SIZE < round(y) && round(x) > static_cast<int>(i_view_x) - CELL_SIZE && round(x)
    {
        sprite.setTexture(texture);
        sprite.setPosition(round(x),round(y));
        i_window.draw(sprite);
    }
}

6c179ea5606e7b5779968582b5e89529.png

然后在这部分加上板栗仔和马里奥的的死亡函数,包括两个条件,一是当马里奥跳到板栗仔头上,板栗仔挂;二是当马里奥碰到板栗仔后,马里奥挂。

if(0 ==death_timer)
{
    vertical_speed =std::min(GRAVITY + vertical_speed, MAX_VERTICAL_SPEED);
    y+= vertical_speed;
}
else if (1 == death_timer)
{
    vertical_speed = MARIO_JUMP_SPEED;
}
death_timer = std::max(0, death_timer - 1);

经历过n个bug后,终于没问题。

185f9751e3313817ce472c289e427d10.gif

到了最后一部分了。

4、优化

这部分主要就是做做代码优化,根据自己喜好改变一些原作风格什么的。

比如重新绘制一个马里奥,并分成三种状态:暂停、行走、跳跃以及die。

d2b0dc0a8d956268df1f7edd3f607279.png

还有玩家突然切换前进方向时的俏皮动作:

ede116fa52352eb8aba8d44a507e3c43.png

写一个切换状态函数进行控制。

void Animation::update()
{
      animation_iterator++;
      while (animation_iterator >= animation_speed)
      {
            animation_iterator -= animation_speed;
            current_frame = (1 + current_frame)% total_frames;
      }
}

终于,全部搞定!!

97e311d655d8cb2c13edeb75a9c5be72.gif

怎么样?还挺成功吧?

过程其实也不乏挑战,有网友就表示:我以为很简单,直到我看到了代码。

de0099a0175eac066eb6644a5b77c5bc.png

而现在你是不是也对背后的作者产生了一丝好奇?

下面就来认识一下。

作者介绍

这位博主叫Kofybrek,今年6月刚刚成为一名YouTuber,目前已有1000粉丝。

ff28aa7e54ca44754a4ea9a4288f7d90.png

他用C++做了很多小游戏:包括扫雷、俄罗斯方块、吃豆人等等。

也搞机器学习,比如教AI玩Flappy Bird。

ea4f06ca5f71ec158e82c46ab18d5c7a.png

从他的座右铭“I do programming for fun”,可以看出小哥是很喜欢用编程做一些好玩的东西了,可以期待他更多的作品。

最后,如果你想试试亲手打造这样一个马里奥,可以戳下面的链接。

代码:
https://github.com/Kofybrek/Super-Mario-Bros

教程视频:

https://www.youtube.com/watch?v=Kfc2W9EOHRk

—  —

本文系网易新闻•网易号特色内容激励计划签约账号【量子位】原创内容,未经账号授权,禁止随意转载。

ee4abcc9208d8ec60b31bf13c97639bb.png

outside_default.png

点个在看 paper不断!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值