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

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

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

你見(jiàn)過(guò)這樣的超級(jí)馬里奧嗎?

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

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

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

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

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

心動(dòng)么?

你也可以自己做一個(gè)~

話不多說(shuō),來(lái)看教程。

手把手教你用 C++ 打造超級(jí)馬里奧

一共分為 4 大塊。

1、基本控制

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

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

通過(guò)函數(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);
}

得到這樣的界面:

然后處理地圖,由于地圖的寬度不同,將它存儲(chǔ)為數(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)在畫面是這樣的:

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

先讓他能動(dòng)起來(lái),前進(jì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));
    }
}

接下來(lái)添加碰撞。

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

成功:

接下來(lái),看看它能不能跳過(guò)這個(gè)墻。

顯然不行……

搞起來(lái),其中,為了使馬里奧的跳躍高度和我們按住鍵盤的時(shí)長(zhǎng)為正比,需要?jiǎng)?chuàng)建一個(gè)跳躍計(jì)時(shí)器變量。

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

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

完美。

最后,給它添加加速度和摩擦力,也就是我們?cè)谖恼乱婚_頭看到的那種剎車特效。

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

至此,基本控制就完成了,進(jìn)入地圖繪制部分。

2、地圖

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

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

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

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

接下來(lái)就是挨個(gè)繪制剩余元素了。

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

成果如下:

什么?缺個(gè)城堡?作者表示:累了,隨便吧……

接下來(lái),使用下面這個(gè)公式,讓界面跟著馬里奧前進(jìn)后退。

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

地圖搞定,上板栗仔!

3、板栗仔

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

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

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

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ù),包括兩個(gè)條件,一是當(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)歷過(guò) n 個(gè) bug 后,終于沒(méi)問(wèn)題。

到了最后一部分了。

4、優(yōu)化

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

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

還有玩家突然切換前進(jìn)方向時(shí)的俏皮動(dòng)作:

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

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

終于,全部搞定??!

怎么樣?還挺成功吧?

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

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

下面就來(lái)認(rèn)識(shí)一下。

作者介紹

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

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

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

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

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

代碼:

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

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

相關(guān)文章

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

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

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