跑着跑着突然停下来个帅气掉头,“踩”扁“板栗仔”(goomba)时直接“变酷”(得到一副墨镜):
这,就是一位油管博主用 C++ 和 SFML 自己从头制作的红白机版超级马里奥。
C++ 不用介绍,SFML 想必有很多人也熟悉,就是一个用来简化写小游戏或者多媒体应用程序的 API,包括系统,窗口,图形,音频和网络五大模块。
除了常规的功能和操作,你可以加入任何自己喜欢的元素。
由于画面看起来实在太逼真,有人甚至提醒博主:小心“版权狂魔”任天堂来找你哦!
心动么?
你也可以自己做一个~
话不多说,来看教程。
手把手教你用 C++ 打造超级马里奥
一共分为 4 大块。
1、基本控制
设置游戏窗口大小为 256x240。
我们先自己绘制一个留胡子的小伙子 —— 马里奥。
通过函数将它载入程序。
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); }
得到这样的界面:
然后处理地图,由于地图的宽度不同,将它存储为数组向量。
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; { }
现在画面是这样的:
接着开始集中打造马里奥。
先让他能动起来,前进后退:
并且获得重力:
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; }
有了,但得让马里奥落到地上。
那就获取一下马里奥的坐标,用下面这些公式检查与之相交的所有单元格:
成功:
但是不能让马里奥跑出地图:
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 种可能,然后使用位运算检查方向。
成功:
接下来,看看它能不能跳过这个墙。
显然不行……
搞起来,其中,为了使马里奥的跳跃高度和我们按住键盘的时长为正比,需要创建一个跳跃计时器变量。
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); } }
再来挑战一下:
完美。
最后,给它添加加速度和摩擦力,也就是我们在文章一开头看到的那种刹车特效。
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、地图
将地图存为图片之前,需分为两部分,上部分存为砖块,下部分存为实体。
使用一个新函数将图像转为 map。
Map convert_sketch(const sf::Image& i_map_sketch, Mario& i_mario)
修改 drawback 函数获得砖块像素颜色,绘制砖块。再画点云朵,基础地图就好了。
接下来就是挨个绘制剩余元素了。
if (sf::Color(109,255,85)==pixel)//Flagpole { sprite_x=12; if (sf::Color(109,255,85) == pixel_up) { sprite_y=1 } }
成果如下:
什么?缺个城堡?作者表示:累了,随便吧……
接下来,使用下面这个公式,让界面跟着马里奥前进后退。
short view_x = std::clamp<int>(mario.get_x()+0.5f *(CELL_SIZE - SCREEN_WIDTH),θ,CELL_SIZE*n)
地图搞定,上板栗仔!
3、板栗仔
板栗仔的行动和马里奥相似,代码可以基本复制。不同的是一旦它们碰到东西就会改变方向。
如何让板栗仔出现?
当马里奥靠近它们时,更新地图。
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); } }
然后在这部分加上板栗仔和马里奥的的死亡函数,包括两个条件,一是当马里奥跳到板栗仔头上,板栗仔挂;二是当马里奥碰到板栗仔后,马里奥挂。
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 后,终于没问题。
到了最后一部分了。
4、优化
这部分主要就是做做代码优化,根据自己喜好改变一些原作风格什么的。
比如重新绘制一个马里奥,并分成三种状态:暂停、行走、跳跃以及 die。
还有玩家突然切换前进方向时的俏皮动作:
写一个切换状态函数进行控制。
void Animation::update() { animation_iterator++; while (animation_iterator >= animation_speed) { animation_iterator -= animation_speed; current_frame = (1 + current_frame)% total_frames; } }
终于,全部搞定!!
怎么样?还挺成功吧?
过程其实也不乏挑战,有网友就表示:我以为很简单,直到我看到了代码。
而现在你是不是也对背后的作者产生了一丝好奇?
下面就来认识一下。
作者介绍
这位博主叫 Kofybrek,今年 6 月刚刚成为一名 YouTuber,目前已有 1000 粉丝。
他用 C++ 做了很多小游戏:包括扫雷、俄罗斯方块、吃豆人等等。
也搞机器学习,比如教 AI 玩 Flappy Bird。
从他的座右铭“I do programming for fun”,可以看出小哥是很喜欢用编程做一些好玩的东西了,可以期待他更多的作品。
最后,如果你想试试亲手打造这样一个马里奥,可以戳下面的链接。
代码:
https://github.com/Kofybrek/Super-Mario-Bros