你見(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之家所有文章均包含本聲明。