- ?個(gè)人主頁:北·海
- ???CSDN新晉作者
- ???歡迎 ??點(diǎn)贊?評論?收藏
- ?收錄專欄:C/C++
- ??希望作者的文章能對你有所幫助,有不足的地方請?jiān)谠u論區(qū)留言指正,大家一起學(xué)習(xí)交流!??
天天酷跑,一款童年游戲,主要是進(jìn)行跳躍操作,和躲避障礙物,該結(jié)主要實(shí)現(xiàn)背景圖的連續(xù)播放,跳躍,與障礙物創(chuàng)建?
一.游戲的展示效果
?二.本節(jié)開發(fā)日志
? ?1.創(chuàng)建項(xiàng)目
? ??2.導(dǎo)入素材
?? ?3.創(chuàng)建游戲界面
? ? ? ? 實(shí)際的開發(fā)流程
? ? ? ? ?對于初學(xué)者,最好的開發(fā)方式,從用戶界面入手?? ? ?選擇圖形庫或者其他引擎
?? ? ?天天酷跑,是基于easyx圖形庫的
?? ? ?1)創(chuàng)建游戲窗口
?? ? ?2)實(shí)現(xiàn)游戲背景
?? ? ? ?a.3重背景不同的速度同時(shí)移動
?? ??? ?b.循環(huán)滾動背景的實(shí)現(xiàn)
?? ? ?3)實(shí)現(xiàn)游戲背景
?? ? ? ?a.加載背景資源
?? ??? ?b.渲染 (背景知識 : 坐標(biāo))
?? ??? ?遇到問題 : 背景圖片的png格式圖片出現(xiàn)黑色 使用putimagePNG2接口? ? ? ? 4)創(chuàng)建人物
? ? ? ? 5)創(chuàng)建障礙物小烏龜
三.素材
在主頁有放置
四.游戲?qū)崿F(xiàn)
? ? 1)創(chuàng)建C++項(xiàng)目將素材導(dǎo)入項(xiàng)目
#include <iostream>
using namespace std;
int main(){
return 0;
}
?2)游戲初始化
對于新手而言,建議拿到游戲的時(shí)候,從游戲的背景圖進(jìn)行入手,由于在游戲邊玩邊從磁盤加載資源,這樣會影響游戲的體驗(yàn),這也就是為什么王者榮耀進(jìn)去時(shí)候要加載的原因,都是在開始游戲之前先把資源加載到項(xiàng)目里面,我們就可以在游戲開始之前進(jìn)行創(chuàng)建一個(gè)用于初始化的函數(shù),用于專門加載這些資源
需要用到的頭文件 : graphics.h,這個(gè)需要安裝easyx圖形庫,可在該官網(wǎng)進(jìn)行下載安裝
需要用到的函數(shù)? ? :? ?loadimage用于將資源從磁盤加載進(jìn)來
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?putimagePNG2,自定義的接口用于解決png圖片出現(xiàn)黑邊,和將加載的圖片渲染到屏幕上,在主頁文章有介紹該接口
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? initgraph : 用于創(chuàng)建游戲窗口
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?sprintf函數(shù),可以對字符串進(jìn)行格式化,改每個(gè)圖片的路徑
IMAGE: 用于存放圖片資源
??
?1.實(shí)現(xiàn)游戲窗口的創(chuàng)建與游戲背景的加載
#include <iostream>
#include <graphics.h>
//窗口的大小,一般在開發(fā)中由項(xiàng)目經(jīng)理規(guī)定,定義為宏不僅可以增強(qiáng)代碼的可讀性還可以增強(qiáng)代碼的健壯性
#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
using namespace std;
IMAGE imgBg[3];
void init(){
//創(chuàng)建游戲窗口
initgraph(WIN_WIDTH, WIN_HEIGHT);
//加載游戲背景
char name[64];
for (int i = 0; i < 3; i++) {
//"res/bg001.png res/bg002.png
sprintf_s(name, "res/bg%03d.png", i + 1);
loadimage(&imgBgs[i], name);
}
}
int main(){
init();//用于初始化
return 0;
}
?2.渲染游戲背景圖,可以創(chuàng)建一個(gè)專門渲染圖片的函數(shù)updataBg()
這邊不太會搞視頻展示圖,所以就簡單描述一下,背景圖往左連綿不斷的走,有三層,草坪,山,天空,既然能連綿不斷,這個(gè)實(shí)現(xiàn)的方法拿圖來解釋
由于由三層,每層的y坐標(biāo)都是不同的,y坐標(biāo)可以測量出來,由于背景需要滾動,原理是移動當(dāng)前背景圖,使其向左移動,當(dāng)圖片的最右邊達(dá)到游戲窗口的最左邊時(shí)候,重新復(fù)原圖片的位置,使圖片的最左邊到達(dá)窗口的最左邊,進(jìn)行循環(huán),循環(huán)的時(shí)候,三層圖片以不同的速度進(jìn)行移動,所以創(chuàng)建一個(gè)數(shù)組bgSpeed,用于存放三個(gè)圖片的速度,bgX數(shù)組用于存放當(dāng)前的x值,由于游戲是個(gè)死循環(huán),所以除了初始化,其余函數(shù)套在while(1)循環(huán)里
int bgSpeed[3] = {1,2,4};
int bgX[3];//背景圖片的x坐標(biāo)
//渲染游戲背景
void updateBg() {
putimagePNG2(bgX[0], 0, WIN_WIDTH,&imgBgs[0]);
putimagePNG2(bgX[1], 119,WIN_WIDTH, &imgBgs[1]);
putimagePNG2 (bgX[2], 330, WIN_WIDTH,&imgBgs[2]);
}
void init(){
//for循環(huán)里,添加bgX[i] = 0 ;給變量進(jìn)行初始化
}
int main(){
'''
init();
while(1){
updateBg();
}
...
}
?3.由于上面渲染的渲染背景圖還是靜態(tài)的,沒有移動,因?yàn)閎gx并沒有改變,此時(shí)需要一個(gè)專門改變游戲數(shù)據(jù)的函數(shù),fly函數(shù)吧
void fly(){
for(int i = 0 ;i<3;i++){
bgX[i] -=bgSpeed[i];//使每層圖片減去他對應(yīng)的速度
//做一個(gè)判斷,當(dāng)達(dá)到最左邊時(shí)候,復(fù)位圖片的位置
if(bgX[i] <=-WIN_WIDTH){
bgX[i] = 0;
}
}
}
4.此時(shí)的背景圖已經(jīng)開始滾動了,但是還有個(gè)小bug,因?yàn)檫@個(gè)渲染時(shí)用循環(huán)putimage上去的,就相當(dāng)于將圖片打印到上面,這也會出現(xiàn)的情況是每層圖片之間會一閃一閃的,解決這個(gè)問題的方法,是先將他們輸出到緩存區(qū),一并打印出來,用到的函數(shù)如下:
BeginBatchDraw()
EndBatchDraw();
?此時(shí)就解決了背景的第一個(gè)問題
背景圖實(shí)現(xiàn),程序的進(jìn)度:
#include <iostream> #include<graphics.h> #include "tools.h" #define WIN_WIDTH 1012 #define WIN_HEIGHT 396 using namespace std; /* 1.先做背景 */ IMAGE imgBgs[3]; int bgSpeed[3] = { 1,2,4 }; int bgX[3]; void init() { //創(chuàng)建游戲窗口 initgraph(WIN_WIDTH, WIN_HEIGHT); //加載背景圖 char name[64]; for (int i = 0; i < 3; i++) { //"res/bg001.png res/bg002.png sprintf_s(name, "res/bg%03d.png", i + 1); loadimage(&imgBgs[i], name); bgX[i] = 0; } } void updataBg() { putimagePNG2(bgX[0], 0,WIN_WIDTH, &imgBgs[0]); putimagePNG2(bgX[1], 119,WIN_WIDTH, &imgBgs[1]); putimagePNG2(bgX[2], 330,WIN_WIDTH, &imgBgs[2]); } void fly() { //背景圖 for (int i = 0; i < 3; i++) { bgX[i] -= bgSpeed[i]; if (bgX[i] < -WIN_WIDTH) { bgX[i] = 0; } } } int main() { init(); while (1) { BeginBatchDraw(); updataBg(); EndBatchDraw(); fly(); } system("pause"); return 0; }
3)創(chuàng)建玩家
1.由于圖片在動,只需要將人物在原地就行,將人物放在最中間,由于要實(shí)現(xiàn)人物的運(yùn)動,這個(gè)的原理是將人物的多組圖片進(jìn)行輪播,人物的x,y坐標(biāo)可以通過數(shù)學(xué)進(jìn)行算出來,此時(shí)我們先在初始化函數(shù)里加載圖片,在初始化函數(shù)里計(jì)算出人物的x,y坐標(biāo),由于人物的輪播,可以傳創(chuàng)建一個(gè)幀序列
//人物的背景圖
IMAGE imgHero[12];
int heroX;
int heroY;
int heroIndex;//幀序號
void init(){
...
//加載人物的圖片,仍然老套路,用到sprintf函數(shù)
for (int i = 0; i < 12; i++) {
sprintf(name, "res/hero%d.png", i + 1);
loadimage(&imgHero[i], name);
}
//人物初始化
heroX = WIN_WIDTH * 0.5 - imgHero[0].getwidth() * 0.5;
heroY = 345 - imgHero[0].getheight();
heroIndex = 0;
}
由于幀序列要改變才能實(shí)現(xiàn)輪播,所以需要在數(shù)據(jù)層的fly函數(shù),進(jìn)行修改幀序列
void fly(){
....
//修改幀序列,
/*heroIndex++;//如果使用這種方法的話,當(dāng)進(jìn)行越界,我們需要的是從1-12進(jìn)行循環(huán),所以我們用取余數(shù)進(jìn)行實(shí)現(xiàn)
*/
heroIndex = (heroIndex+1)%12;
}
數(shù)據(jù)也可也可以改變了,我們需要將人物的圖片渲染在窗口中,在main函數(shù)中只需要一條代碼
int main(){
...
//實(shí)現(xiàn)人物的奔跑
putimagePNG2(heroX, heroY, WIN_WIDTH , &imgHero[heroIndex]);
...
}
效果圖
這樣人物就可以原地跑起來了,解決了第二個(gè)問題
4)實(shí)現(xiàn)玩家的跳躍
1.人物的跳躍是當(dāng)玩家按下按鍵后,人物的y坐標(biāo)開始上升,當(dāng)達(dá)到某個(gè)高度后,然后下落
先來實(shí)現(xiàn)用戶點(diǎn)擊函數(shù)keyEvent(),由于只需要改變y坐標(biāo),底層的數(shù)據(jù)變化實(shí)在fly函數(shù)里面,我們只需要獲取到用戶的點(diǎn)擊之后,給個(gè)可以改變y值得信號即可
用到的_kbhit()函數(shù)和_getch()函數(shù)都必須包含頭文件conio.h
問題 : 都是從鍵盤獲取一個(gè)字符為什么不用scanf()而用_getch()
答:如果使用scanf函數(shù),?當(dāng)輸入數(shù)據(jù),必須按回車,才能將數(shù)據(jù)輸入進(jìn)去,此時(shí)不按回車的話,程序會等待用戶按回車鍵,此時(shí)的程序就卡在這里等待了,而用_hbhit()函數(shù),不用等待,直接按下就可以輸入進(jìn)去
void keyEvent(){
//先接收用戶得鼠標(biāo)消息
char ch = 0 ;
if(_kbhit()){
ch = _getch();
if(ch ===' '){
jump();
}
}
}
實(shí)現(xiàn)初始化順便打開可跳躍得按鈕?
//全局變量
bool heroJump;//跳躍狀態(tài)
int jumpHeightMax;//跳躍得最大高度
int heroJumpOff;//跳躍得偏移量
void init(){
...
//對跳躍得數(shù)據(jù)進(jìn)行初始化
//跳躍初始化
heroJump = false;
jumpHeightMax = 345 - imgHero[0].getheight() - 120;
heroJumpOff = -4;
}
void jump(){
heroJump = true;
}
在fly函數(shù)里改變底層數(shù)據(jù)
void fly() {
...
//實(shí)現(xiàn)跳躍
if (heroJump) {
if (heroY < jumpHeightMax) {
heroJumpOff = 4;//+ (-4)等于向上走,+4等于向下走
}
heroY += heroJumpOff;
if (heroY > 345 - imgHero[0].getheight()) {
//達(dá)到地面
heroJump = false;
heroJumpOff = -4;
}
}
}
?此時(shí)的英雄就可以進(jìn)行跳躍了
這樣人物就可以跳躍起來了,就能跳躍障礙物了,解決了第三個(gè)問題,此時(shí)還面臨一個(gè)小bug,就是在當(dāng)跳躍的途中,這個(gè)人物還處于一個(gè)跑步的狀態(tài),影響了用戶體驗(yàn),解決這個(gè)問題,我們就可以在改變底層數(shù)據(jù)里改部分代碼,如果處于起跳狀態(tài)則不更新幀序列,否則不跳的時(shí)候更新幀序列,以下是改變之后的渲染代碼:
//人物跳躍
if (heroJump) {
if (heroY < jumpHeightMax) {
heroJumpOff = 4;
}
heroY += heroJumpOff;
if (heroY > 345 - imgHero[0].getheight()) {
heroJump = false;
heroJumpOff = -4;
}
}
else {
//heroIndex++ 這種方式會越界
heroIndex = (heroIndex + 1) % 12;
}
這樣就解決了這個(gè)小bug,以上實(shí)現(xiàn)了背景圖的輪播,人物的動態(tài)化,和人物的起跳,主要學(xué)到了,將渲染層,數(shù)據(jù)層分開,其他小的功能進(jìn)行分支化,將main函數(shù)寫的簡介,實(shí)現(xiàn)功能模塊化,接下來就可以實(shí)現(xiàn)障礙物了
5)障礙物小烏龜?shù)膶?shí)現(xiàn)
1.障礙物有許多種,先實(shí)現(xiàn)一種,其他的在做優(yōu)化,依舊是老套路,初始化里面加載資源,渲染層進(jìn)行打印圖片,fly里面改變數(shù)據(jù)
2.先將小烏龜設(shè)置為靜態(tài)的,優(yōu)化時(shí)候也可加個(gè)幀序列實(shí)現(xiàn)動態(tài)的
定義小烏龜?shù)淖兞?/p>
//全局狀態(tài)
//小烏龜
IMAGE imgTortoise;
//小烏龜存在開關(guān)
bool torToiseExist;
int torToiseX;
int torToiseY;
?將小烏龜進(jìn)行初始化
//初始化小烏龜
void init(){
...
//小烏龜初始化
loadimage(&imgTortoise, "res/t1.png");
torToiseY = 345 + 5 - imgTortoise.getheight();
torToiseExist = false;
}
從底層數(shù)據(jù)方面對小烏龜進(jìn)行創(chuàng)建與使其移動
編程技巧:在創(chuàng)建小烏龜時(shí)候,如果直接寫
void fly(){
...
//創(chuàng)建小烏龜
static int frameCount = 0;
static int enemyFre = 100;
frameCount++;
if (frameCount > enemyFre) {
frameCount = 0;
enemyFre = 100+ rand() % 50;
//創(chuàng)建障礙物
if (!torToiseExist) {
torToiseExist = true;
torToiseX = WIN_WIDTH;
}
}
//使小烏龜?shù)倪\(yùn)動,運(yùn)動應(yīng)該于草坪的移動速度保持一致,才能顯得靜止在草坪上
if (torToiseExist) {
torToiseX -= bgSpeed[2];
if (torToiseX < -imgTortoise.getwidth()) {
torToiseExist = false;
}
}
}
將小烏龜渲染出來
void updateEnemy() {
//渲染小烏龜
if (torToiseExist) {
putimagePNG2(torToiseX, torToiseY,WIN_WIDTH, &imgTortoise);
}
}
?將其函數(shù)在main函數(shù)中調(diào)用,和渲染人物一起渲染出來
?此時(shí)代碼進(jìn)度為:
#include <iostream>
#include<graphics.h>
#include <conio.h>
#include "tools.h"
#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
using namespace std;
/*
1.先做背景
2.創(chuàng)建玩家
3.實(shí)現(xiàn)玩家跳躍
*/
//背景
IMAGE imgBgs[3];
int bgSpeed[3] = { 1,2,4 };
int bgX[3];
//人物
IMAGE imgHero[12];
int heroX;
int heroY;
int heroIndex;//幀序列
//跳躍
bool heroJump;//跳躍狀態(tài)
int jumpHeightMax;//跳躍的最大高度
int heroJumpOff;
//小烏龜
IMAGE imgTortoise;
//小烏龜存在開關(guān)
bool torToiseExist;
int torToiseX;
int torToiseY;
void init() {
//創(chuàng)建游戲窗口
initgraph(WIN_WIDTH, WIN_HEIGHT);
//加載背景圖
char name[64];
for (int i = 0; i < 3; i++) {
//"res/bg001.png res/bg002.png
sprintf_s(name, "res/bg%03d.png", i + 1);
loadimage(&imgBgs[i], name);
bgX[i] = 0;
}
//加載人物背景圖
for (int i = 0; i < 12; i++) {
sprintf(name, "res/hero%d.png", i + 1);
loadimage(&imgHero[i], name);
}
//人物的位置放在窗口的最中間,由于人物需要運(yùn)動,用多組圖片進(jìn)行輪播,需要定義一個(gè)幀序列
heroX = WIN_WIDTH * 0.5 - imgHero[0].getwidth() * 0.5;
heroY = 345 - imgHero[0].getheight();
heroIndex = 0;
//跳躍
heroJump = false;
jumpHeightMax = 345 - imgHero[0].getheight() - 120;
heroJumpOff = -4;
//小烏龜初始化
loadimage(&imgTortoise, "res/t1.png");
torToiseY = 345 + 5 - imgTortoise.getheight();
torToiseExist = false;
}
void updataBg() {
putimagePNG2(bgX[0], 0, WIN_WIDTH, &imgBgs[0]);
putimagePNG2(bgX[1], 119, WIN_WIDTH, &imgBgs[1]);
putimagePNG2(bgX[2], 330, WIN_WIDTH, &imgBgs[2]);
}
void updateEnemy() {
//渲染小烏龜
if (torToiseExist) {
putimagePNG2(torToiseX, torToiseY,WIN_WIDTH, &imgTortoise);
}
}
void fly() {
//背景圖
for (int i = 0; i < 3; i++) {
bgX[i] -= bgSpeed[i];
if (bgX[i] < -WIN_WIDTH) {
bgX[i] = 0;
}
}
//實(shí)現(xiàn)跳躍
if (heroJump) {
if (heroY < jumpHeightMax) {
heroJumpOff = 4;//+ (-4)等于向上走,+4等于向下走
}
heroY += heroJumpOff;
if (heroY > 345 - imgHero[0].getheight()) {
//達(dá)到地面
heroJump = false;
heroJumpOff = -4;
}
}
else {
//改變?nèi)宋飵蛄? heroIndex = (heroIndex + 1) % 12;
}
//創(chuàng)建小烏龜
static int frameCount = 0;
static int enemyFre = 100;
frameCount++;
if (frameCount > enemyFre) {
frameCount = 0;
enemyFre = 100+ rand() % 50;
//創(chuàng)建障礙物
if (!torToiseExist) {
torToiseExist = true;
torToiseX = WIN_WIDTH;
}
}
//使小烏龜?shù)倪\(yùn)動,運(yùn)動應(yīng)該于草坪的移動速度保持一致,才能顯得靜止在草坪上
if (torToiseExist) {
torToiseX -= bgSpeed[2];
if (torToiseX < -imgTortoise.getwidth()) {
torToiseExist = false;
}
}
}
void jump() {
//跳躍只需要改變y值即可,在底層數(shù)據(jù)管理函數(shù)實(shí)現(xiàn),此時(shí)只需要給出可以改數(shù)據(jù)的信號即可
heroJump = true;
}
void keyEvent() {
//獲取玩家鍵盤事件
char ch = 0;
if (_kbhit()) {
ch = _getch();
if (ch == ' ') {
jump();
}
}
}
int main() {
init();
while (1) {
keyEvent();
BeginBatchDraw();
//渲染背景
updataBg();
//渲染人物
putimagePNG2(heroX, heroY, &imgHero[heroIndex]);
//渲染障礙物
updateEnemy();
EndBatchDraw();
fly();
Sleep(30);
}
system("pause");
return 0;
}
6)對代碼進(jìn)行優(yōu)化
1.在main函數(shù)的最后用了一條函數(shù)sleep(30),會將程序休眠30毫秒,如果在此期間有用戶的鍵盤輸入,會降低游戲的體驗(yàn)性
2.在創(chuàng)建小烏龜時(shí)候,由于只演示了一種障礙物,而添加障礙物時(shí)候,應(yīng)該建立一個(gè)專門創(chuàng)建障礙物的函數(shù)
先解決第一點(diǎn)
在tools.cpp里有g(shù)etDalay,用于計(jì)算上一次調(diào)用到現(xiàn)在的間隔時(shí)間,在全局狀態(tài)定義一個(gè)計(jì)時(shí)器timer,在定義一個(gè)刷新界面的變量update,如果update為真,則開始刷新界面,如果間隔大于30毫秒,則將刷新界面變量至于true狀態(tài),可以代替30ms的休眠間隔,增強(qiáng)體驗(yàn)感
int main() { init(); while (true) { keyEvent(); timer += getDelay(); if (timer > 30) { timer = 0; update = true; } if (update) { update = false; BeginBatchDraw(); updataBg(); //實(shí)現(xiàn)人物的奔跑 putimagePNG2(heroX, heroY, &imgHero[heroIndex]); //渲染障礙物 updateEnemy(); EndBatchDraw(); fly(); } } system("pause"); return 0; }
解決第二點(diǎn),將創(chuàng)建小烏龜障礙物先封裝為一個(gè)函數(shù),在下一期進(jìn)行改善為可隨機(jī)創(chuàng)建多種障礙物
void creatObstacle() { //小烏龜初始化 if (!torToiseExist) { torToiseExist = true; torToiseX = WIN_WIDTH; } }
在原來fly函數(shù)里面,用creatObstacle()代替上面函數(shù)體內(nèi)的三條語句即可
?五.本節(jié)總結(jié)
總結(jié) : 這結(jié)實(shí)現(xiàn)了背景輪播圖的實(shí)現(xiàn),人物的創(chuàng)建,障礙物的創(chuàng)建,動態(tài)的原理就是改變幀序列,開發(fā)技巧是將渲染層,數(shù)據(jù)層分開,其他功能封裝為函數(shù),圍繞這兩個(gè)函數(shù)展開,將main函數(shù)變的簡單明了,講述了開發(fā)技巧,計(jì)時(shí)器的使用,改善休眠間隔的方法文章來源:http://www.zghlxwxcb.cn/news/detail-681125.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-681125.html
到了這里,關(guān)于[C/C++]天天酷跑游戲超詳細(xì)教程-上篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!