設(shè)置
  • 日夜間
    隨系統(tǒng)
    淺色
    深色
  • 主題色

C++ 從零打造《超級馬里奧》:會漂移掉頭,還帶剎車音效

量子位 2021/11/19 14:40:47 責(zé)編:汪淼

你見過這樣的超級馬里奧嗎?

跑著跑著突然停下來個帥氣掉頭,“踩”扁“板栗仔”(goomba)時直接“變酷”(得到一副墨鏡):

這,就是一位油管博主用 C++ 和 SFML 自己從頭制作的紅白機版超級馬里奧。

C++ 不用介紹,SFML 想必有很多人也熟悉,就是一個用來簡化寫小游戲或者多媒體應(yīng)用程序的 API,包括系統(tǒng),窗口,圖形,音頻和網(wǎng)絡(luò)五大模塊。

除了常規(guī)的功能和操作,你可以加入任何自己喜歡的元素。

由于畫面看起來實在太逼真,有人甚至提醒博主:小心“版權(quán)狂魔”任天堂來找你哦!

心動么?

你也可以自己做一個~

話不多說,來看教程。

手把手教你用 C++ 打造超級馬里奧

一共分為 4 大塊。

1、基本控制

設(shè)置游戲窗口大小為 256x240。

我們先自己繪制一個留胡子的小伙子 —— 馬里奧。

通過函數(shù)將它載入程序。

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);
}

得到這樣的界面:

然后處理地圖,由于地圖的寬度不同,將它存儲為數(shù)組向量。

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;
    {
}

現(xiàn)在畫面是這樣的:

接著開始集中打造馬里奧。

先讓他能動起來,前進后退:

并且獲得重力:

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;
}

有了,但得讓馬里奧落到地上。

那就獲取一下馬里奧的坐標(biāo),用下面這些公式檢查與之相交的所有單元格:

成功:

但是不能讓馬里奧跑出地圖:

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));
    }
}

接下來添加碰撞。

用二進制表示馬里奧碰到的單元格,用一個地圖碰撞函數(shù)檢查并返回 0000-1111 這 15 種可能,然后使用位運算檢查方向。

成功:

接下來,看看它能不能跳過這個墻。

顯然不行……

搞起來,其中,為了使馬里奧的跳躍高度和我們按住鍵盤的時長為正比,需要創(chuàng)建一個跳躍計時器變量。

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);
    }
}

再來挑戰(zhàn)一下:

完美。

最后,給它添加加速度和摩擦力,也就是我們在文章一開頭看到的那種剎車特效。

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、地圖

將地圖存為圖片之前,需分為兩部分,上部分存為磚塊,下部分存為實體。

使用一個新函數(shù)將圖像轉(zhuǎn)為 map。

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

修改 drawback 函數(shù)獲得磚塊像素顏色,繪制磚塊。再畫點云朵,基礎(chǔ)地圖就好了。

接下來就是挨個繪制剩余元素了。

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、板栗仔

板栗仔的行動和馬里奧相似,代碼可以基本復(fù)制。不同的是一旦它們碰到東西就會改變方向。

如何讓板栗仔出現(xiàn)?

當(dāng)馬里奧靠近它們時,更新地圖。

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);
    }
}

然后在這部分加上板栗仔和馬里奧的的死亡函數(shù),包括兩個條件,一是當(dāng)馬里奧跳到板栗仔頭上,板栗仔掛;二是當(dāng)馬里奧碰到板栗仔后,馬里奧掛。

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);

經(jīng)歷過 n 個 bug 后,終于沒問題。

到了最后一部分了。

4、優(yōu)化

這部分主要就是做做代碼優(yōu)化,根據(jù)自己喜好改變一些原作風(fēng)格什么的。

比如重新繪制一個馬里奧,并分成三種狀態(tài):暫停、行走、跳躍以及 die。

還有玩家突然切換前進方向時的俏皮動作:

寫一個切換狀態(tài)函數(shù)進行控制。

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

終于,全部搞定?。?/p>

怎么樣?還挺成功吧?

過程其實也不乏挑戰(zhàn),有網(wǎng)友就表示:我以為很簡單,直到我看到了代碼。

而現(xiàn)在你是不是也對背后的作者產(chǎn)生了一絲好奇?

下面就來認(rèn)識一下。

作者介紹

這位博主叫 Kofybrek,今年 6 月剛剛成為一名 YouTuber,目前已有 1000 粉絲。

他用 C++ 做了很多小游戲:包括掃雷、俄羅斯方塊、吃豆人等等。

也搞機器學(xué)習(xí),比如教 AI 玩 Flappy Bird。

從他的座右銘“I do programming for fun”,可以看出小哥是很喜歡用編程做一些好玩的東西了,可以期待他更多的作品。

最后,如果你想試試親手打造這樣一個馬里奧,可以戳下面的鏈接。

代碼:

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

廣告聲明:文內(nèi)含有的對外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。

相關(guān)文章

關(guān)鍵詞:馬里奧,C++

軟媒旗下網(wǎng)站: IT之家 最會買 - 返利返現(xiàn)優(yōu)惠券 iPhone之家 Win7之家 Win10之家 Win11之家

軟媒旗下軟件: 軟媒手機APP應(yīng)用 魔方 最會買 要知