魔塔小游戏
思路构建
刚学习了qt,所以想写一点小时候玩的游戏,魔塔游戏简单,正合适新手练习。在我们写项目之前,就需要先构思如何写,就相当于写作文,一样的意思,现有准备工作,才可以有条不紊的写代码,而且写出来方便查看。
1、魔塔是一个2D游戏,只有一个地图,任务在其中行走,所以我们可以选择使用数组来表示地图,使用QLabel 来显示图片,这样地图就做好了。
2、定义英雄类,其中有英雄的人物信息。
3、定义怪物类,其中有怪物的信息,不同的怪物设置一个属性来区分。
4、实现按键事件,控制人物行走,人物行走,其实就是展示人物的label进行移动
5、分析人物遇见不同情况而不同的处理
6、写人物战斗算法,计算经验,血量等等属性
7、人物移动,收取物品,击杀怪物,实时显示
代码实现
定义地图
魔塔共50层,每一层是一个平面,就相当于我们的X,Y坐标系。所以可以定义一个三维数组,来存储不同楼层的排布。
// 定义一个三维数组
// 这个是自定义地图,定义了一个专门的类来存储地图,而这个是原始地图,并不会被修改,
// 游戏正常进行的地图由其他类成员来操作,这样防止重新开始游戏后初始化地图被修改。
int Map::map[50][13][13] =
{
{
//关卡1
{0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,16,1,9,8,9,1,1,1,1,1,1,0},
{0,0,0,0,0,0,0,0,0,0,0,1,0},
{0,6,1,1,3,1,0,13,5,1,0,1,0},
{0,1,11,1,0,1,0,14,6,1,0,1,0},
{0,0,3,0,0,1,0,0,0,3,0,1,0},
{0,5,1,1,0,1,3,10,18,10,0,1,0},
{0,1,11,1,0,1,0,0,0,0,0,1,0},
{0,0,3,0,0,1,1,1,1,1,1,1,0},
{0,1,1,1,0,0,3,0,0,0,3,0,0},
{0,6,1,5,0,5,1,1,0,1,10,1,0},
{0,6,17,5,0,1,15,1,0,9,7,9,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0}
},
{ //关卡2
{0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,5,14,0,5,7,5,0,1,0,1,6,0},
{0,1,6,0,7,5,7,0,1,3,10,1,0},
{0,18,1,0,5,4,5,0,1,0,0,0,0},
{0,3,0,0,0,1,0,0,1,0,1,13,0},
{0,1,1,10,1,1,1,9,1,1,1,1,0},
{0,3,0,0,1,1,1,0,1,0,0,0,0},
{0,11,1,0,0,1,0,0,1,0,1,6,0},
{0,1,5,0,1,1,1,0,1,3,18,5,0},
{0,6,13,0,1,1,1,0,1,0,0,0,0},
{0,0,0,0,0,1,0,0,8,0,1,1,0},
{0,19,15,1,1,1,1,0,1,0,1,16,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0}
}
};
//在这里我将素材图片和数组中的值一一对应,这样方便显示地图。
0-墙
1-地板
2-蓝门
3-黄门
4-蓝钥匙
5-黄钥匙
6-红血瓶
7-蓝血瓶
8-大史雷姆
9-小史雷姆
10-小蝙蝠
11-骷髅战士
13-红宝石
14-蓝宝石
15-英雄
16-上楼
17-法杖
18-大法师
19-下楼
// 这里我是完全按照老板游戏设置的地图,所以我只写了两层,如果大家有时间,可以继续往下写,我这里只是一个初版,大家可以继续完善。
定义好地图数组之后,我们则需要按照数组值用图片将地图绘制出来。我自己初步定义一个图片值为 60 *60 ,游戏地图是长13个,宽13个。还有右边需要显示英雄信息, 60 * (13 * 60)。因此可以初步设置游戏界面大小是 14 * 60 , 13 * 50。
游戏管理类头文件,具体功能后续会详细介绍。
mainwindow.h
// 游戏主类
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
// 重载按键事件
void keyPressEvent(QKeyEvent *event) override;
private:
Ui::MainWindow *ui;
// 定义地图
int map[50][13][13];
// 记录是否重新开始
bool isStart = true;
// 记录是否在战斗中,如果是战斗中则不能进行移动
bool isCombat = false;
// 记录角色的行列
int heroX = 0;
int heroY = 0;
// 定义地图label,展示当前层的地图图片
QLabel* labels[13][13];
// 定义角色信息label
QLabel *infoLabel;
// 角色名字
QLabel *nameLabel;
// 金币
QLabel *moneyLabel;
// 金币显示
QLabel *moneyValueLabel;
// 楼层
QLabel *floorLabel;
// 楼层显示
QLabel *floorValueLabel;
// 生命值
QLabel *bloodLabel;
// 攻击力
QLabel *attackLabel;
// 防御力
QLabel *defenseLabel;
// 蓝钥匙
QLabel *bKeyLabel;
// 黄钥匙
QLabel *yKeyLabel;
// 定义英雄
Hero *hero = new Hero();
//初始化 9小史莱姆
Monster *smallSlime = new Monster("小史莱姆", 20, 20, 20, 20, 9);
//初始化 8大史莱姆
Monster *bigSlime = new Monster("大史莱姆", 40, 40, 40, 40, 8);
//初始化 10 小蝙蝠
Monster *smallBat = new Monster("小蝙蝠", 60, 60, 60, 60, 10);
//初始化 11 骷髅战士
Monster *skeletonKnight = new Monster("骷髅战士", 70, 70, 70, 70, 11);
//初始化 18 大法师
Monster *exorcist = new Monster("大法师", 80, 80, 80, 80, 18);
// 初始化地图
void initMap();
// 修改地图
// type: 0-上,1-下,2-左,3-右
void updateMap(int type,int preValue = 0);
// 角色行动
void heroMove(int type,int preValue);
// 角色战斗
void heroCombat(int prevalue);
// 展示英雄信息
void showHeroInfo();
};
绘制地图
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 接受按键事件
this->grabKeyboard();
// 设置窗口大小
this->setFixedSize(14 * 60, 13 * 60);
// 初始化地图
initMap();
}
// 初始化地图
void MainWindow::initMap()
{
if(isStart)
{
// 将初始化地图防止在游戏类成员地图中,之后玩家操作,实时变换地图,操作的是这个成员变量地图
for(unsigned long k = 0 ; k < sizeof(Map::map) / sizeof(Map::map[0]) ; k++)
{
for(unsigned long i = 0 ; i < sizeof(Map::map[0]) / sizeof(Map::map[0][0]) ; i++)
{
for(unsigned long j = 0 ; j < sizeof(Map::map[0][0]) / sizeof(Map::map[0][0][0]) ; j++)
{
map[k][i][j] = Map::map[k][i][j];
}
}
}
// 用于英雄死亡,是否重新开始。
isStart = false;
}
// 绘制地图
for(unsigned long i = 0 ; i < sizeof(map[0]) / sizeof(map[0][0]) ; i++)
{
for(unsigned long j = 0 ; j < sizeof(map[0][0]) / sizeof(map[0][0][0]) ; j++)
{
// 每次更新一层,则需要刷新当前层的图片
labels[i][j] = new QLabel(this);
labels[i][j]->setFixedSize(60,60);
labels[i][j]->setPixmap(QPixmap(tr(":/%1.jpg").arg(map[hero->heroFloor][i][j])));
// 让图片在label中填充
labels[i][j]->setScaledContents(true);
// 设置label位置
labels[i][j]->move(j * 60,i * 60);
labels[i][j]->show();
// 检索到角色位置
if(map[hero->heroFloor][i][j] == 15)
{
heroX = i;
heroY = j;
}
}
}
// 绘制计分板
infoLabel = new QLabel(this);
// 设置label大小
infoLabel->setFixedSize(60,13 * 60);
infoLabel->setPixmap(QPixmap(":/info.jpg"));
// 让图片在label中填充
infoLabel->setScaledContents(true);
infoLabel->move(13 * 60, 0);
infoLabel->show();
// 显示英雄信息label
// 设置字体颜色
QPalette palette;
palette.setColor(QPalette::WindowText,Qt::white);
// 设置字体大小
QFont font;
font.setPointSize(10);
// 角色名字
nameLabel = new QLabel(this);
nameLabel->move(13 * 60 + 15, 100);
nameLabel->setPalette(palette);
nameLabel->setFont(font);
nameLabel->show();
// 金币
moneyLabel = new QLabel(this);
moneyLabel->move(13 * 60 + 13, 145);
moneyLabel->setPalette(palette);
moneyLabel->setFont(font);
moneyLabel->show();
// 金币显示
moneyValueLabel = new QLabel(this);
moneyValueLabel->move(13 * 60 + 23, 175);
moneyValueLabel->setPalette(palette);
moneyValueLabel->setFont(font);
moneyValueLabel->show();
// 楼层
floorLabel = new QLabel(this);
floorLabel->move(13 * 60 + 13, 198);
floorLabel->setPalette(palette);
floorLabel->setFont(font);
floorLabel->show();
// 楼层显示
floorValueLabel = new QLabel(this);
floorValueLabel->move(13 * 60 + 23, 225);
floorValueLabel->setPalette(palette);
floorValueLabel->setFont(font);
floorValueLabel->show();
// 生命值
bloodLabel = new QLabel(this);
bloodLabel->move(13 * 60 + 13, 355);
bloodLabel->setPalette(palette);
bloodLabel->setFont(font);
bloodLabel->show();
// 攻击力
attackLabel = new QLabel(this);
attackLabel->move(13 * 60 + 18, 510);
attackLabel->setPalette(palette);
attackLabel->setFont(font);
attackLabel->show();
// 防御力
defenseLabel = new QLabel(this);
defenseLabel->move(13 * 60 + 18, 560);
defenseLabel->setPalette(palette);
defenseLabel->setFont(font);
defenseLabel->show();
// 黄钥匙
yKeyLabel = new QLabel(this);
yKeyLabel->move(13 * 60 + 23, 680);
yKeyLabel->setPalette(palette);
yKeyLabel->setFont(font);
yKeyLabel->show();
// 蓝钥匙
bKeyLabel = new QLabel(this);
bKeyLabel->move(13 * 60 + 23, 750);
bKeyLabel->setPalette(palette);
bKeyLabel->setFont(font);
bKeyLabel->show();
}
定义英雄类
// 英雄类
// 英雄类的各个属性将展示在右边面板
class Hero
{
public:
Hero();
~Hero();
// 姓名
QString heroName = "勇者";
// 血量
int heroBlood = 100;
//攻击力
int heroAttack = 20;
//防御力
int heroDefense = 20;
//金钱
int heroMoney = 0;
//楼层
int heroFloor = 0;
//蓝钥匙
int heroBKey = 0;
//黄钥匙
int heroYKey = 0;
};
游戏类展示英雄信息
// 展示英雄信息
void MainWindow::showHeroInfo()
{
// 角色名字
nameLabel->setText(hero->heroName);
// 金币
moneyLabel->setText("金币");
// 金币显示
moneyValueLabel->setText(QString::number(hero->heroMoney));
// 楼层
floorLabel->setText("楼层");
// 楼层显示
floorValueLabel->setText(QString::number(hero->heroFloor));
// 生命值
bloodLabel->setText(QString::number(hero->heroBlood));
// 攻击力
attackLabel->setText(QString::number(hero->heroAttack));
// 防御力
defenseLabel->setText(QString::number(hero->heroDefense));
// 蓝钥匙
bKeyLabel->setText(QString::number(hero->heroBKey));
// 黄钥匙
yKeyLabel->setText(QString::number(hero->heroYKey));
}
定义怪物类
// 怪物类,不同的怪物有不同的参数属性
class Monster
{
public:
Monster(QString name,int boold,int attack ,int defense ,int money ,int type);
~Monster();
QString name; //姓名
int boold; //生命值
int attack; //攻击力
int defense; //防御力
int money; //金钱
int type; //类型 怪物类型 8大史莱姆 9小史莱姆 10小蝙蝠 11骷髅战士 18大法师
};
实现按键事件
// 重载按键事件,实现角色行动
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if(!isCombat)
{
// 记录类型
int type = 4;
// 记录即将行走的位置是什么
int preValue = 0;
switch (event->key())
{
case Qt::Key_Up: // 上
case Qt::Key_W:
type = 0;
preValue = map[hero->heroFloor][heroX - 1][heroY];
heroMove(type,preValue);
break;
case Qt::Key_Down: // 下
case Qt::Key_S:
type = 1;
preValue = map[hero->heroFloor][heroX + 1][heroY];
heroMove(type,preValue);
break;
case Qt::Key_Left: // 左
case Qt::Key_A:
type = 2;
preValue = map[hero->heroFloor][heroX][heroY - 1];
heroMove(type,preValue);
break;
case Qt::Key_Right: // 右
case Qt::Key_D:
type = 3;
preValue = map[hero->heroFloor][heroX][heroY + 1];
heroMove(type,preValue);
break;
default:
break;
}
//显示信息
showHeroInfo();
}
}
处理英雄行走
// 角色移动
void MainWindow::heroMove(int type, int preValue)
{
if(preValue == 0) // 墙
{
return;
}
else if(preValue == 1) // 空地
{
updateMap(type);
}
else if(preValue == 3 && hero->heroYKey > 0) // 黄门
{
updateMap(type);
hero->heroYKey--;
}
else if(preValue == 5) // 黄钥匙
{
updateMap(type);
hero->heroYKey++;
}
else if(preValue == 2 && hero->heroBKey > 0) // 蓝门
{
updateMap(type);
hero->heroBKey--;
}
else if(preValue == 4) // 黄钥匙
{
updateMap(type);
hero->heroBKey++;
}
else if(preValue == 6) // 红血瓶
{
updateMap(type);
hero->heroBlood += 100;
}
else if(preValue == 7) // 蓝血瓶
{
updateMap(type);
hero->heroBlood += 200;
}
else if(preValue == 13) // 红宝石
{
updateMap(type);
hero->heroAttack += 10;
}
else if(preValue == 14) // 蓝宝石
{
updateMap(type);
hero->heroDefense += 10;
}
else if(preValue == 16) // 上楼
{
hero->heroFloor++;
initMap();
}
else if(preValue == 19) // 下楼
{
hero->heroFloor--;
initMap();
}
else if(preValue == 8 || preValue == 9 || preValue == 10 || preValue == 11 || preValue == 18) // 怪物
{
// 英雄战斗
heroCombat(preValue);
// 实时修改地图
updateMap(type,preValue);
// 碰见怪物,攻击结束之后判断血量
if(hero->heroBlood <= 0)
{
// 英雄血量为空,判断失败
int select = QMessageBox::information(this,tr("游戏结束"),tr("是否重新开始?"),QMessageBox::Yes | QMessageBox::No);
if(select == QMessageBox::Yes)
{
// 重置英雄
hero = new Hero();
// 重新开始
isStart = true;
// 重置图像
initMap();
}
else
{
this->close();
}
}
}
}
人物战斗
// 角色战斗
// 角色战斗计算公式自己定义
void MainWindow::heroCombat(int prevalue)
{
switch (prevalue) {
case 8:
hero->heroBlood -= (bigSlime->boold - (hero->heroAttack - bigSlime->defense)) - hero->heroDefense;
hero->heroMoney += bigSlime->money;
break;
case 9:
hero->heroBlood -= (smallSlime->boold - (hero->heroAttack - smallSlime->defense)) - hero->heroDefense;
hero->heroMoney += smallSlime->money;
break;
case 10:
hero->heroBlood -= (smallBat->boold - (hero->heroAttack - smallBat->defense)) - hero->heroDefense;
hero->heroMoney += smallBat->money;
break;
case 11:
hero->heroBlood -= (skeletonKnight->boold - (hero->heroAttack - skeletonKnight->defense)) - hero->heroDefense;
hero->heroMoney += skeletonKnight->money;
break;
case 18:
hero->heroBlood -= (exorcist->boold - (hero->heroAttack - exorcist->defense)) - hero->heroDefense;
hero->heroMoney += exorcist->money;
break;
default:
break;
}
}
修改图片
人物运动时,当前位置图片和目标位置图片需要修改,目标位置变成英雄,当前位置变成空地。
// 修改地图
void MainWindow::updateMap(int type,int preValue)
{
// 修改地图,英雄向四周行动,则英雄原来的地方变成空地,下一个地方变成英雄
map[hero->heroFloor][heroX][heroY] = 1;
labels[heroX][heroY]->setPixmap(QPixmap(tr(":/%1.jpg").arg(map[hero->heroFloor][heroX][heroY])));
switch(type)
{
case 0:
heroX -= 1;
break;
case 1:
heroX += 1;
break;
case 2:
heroY -= 1;
break;
case 3:
heroY += 1;
break;
default:
break;
}
// 角色移动后下一个位置显示英雄
map[hero->heroFloor][heroX][heroY] = 15;
labels[heroX][heroY]->setPixmap(QPixmap(tr(":15.jpg")));
// 攻击怪物,实现图像闪烁
if(preValue == 8 || preValue == 9 || preValue == 10 || preValue == 11 || preValue == 18)
{
// 战斗中,记录战斗状态,在
isCombat = true;
// 定时器显示怪物图片
QTimer *timer1 = new QTimer(this);
connect(timer1,&QTimer::timeout,this,[=](){
labels[heroX][heroY]->setPixmap(QPixmap(tr(":/%1.jpg").arg(preValue)));
});
// 定时器显示英雄图片
QTimer *timer2 = new QTimer(this);
connect(timer2,&QTimer::timeout,this,[=](){
labels[heroX][heroY]->setPixmap(QPixmap(tr(":15.jpg")));
});
timer1->start(100);
QThread::msleep(50);
timer2->start(100);
// 500毫秒定时器,定时器结束,闪烁结束
QTimer *timer = new QTimer(this);
connect(timer,&QTimer::timeout,this,[=](){
// 时间到,定时器关闭,战斗关闭
timer1->stop();
timer2->stop();
timer->stop();
isCombat = false;
// 如果英雄战斗失败,则显示怪物图片
if(hero->heroBlood <= 0)
{
labels[heroX][heroY]->setPixmap(QPixmap(tr(":/%1.jpg").arg(preValue)));
}
// 英雄胜利,则显示英雄图片
else
{
map[hero->heroFloor][heroX][heroY] = 15;
labels[heroX][heroY]->setPixmap(QPixmap(tr(":15.jpg")));
}
});
timer->start(500);
}
}
至此,游戏基本功能已经实现。目前只是游戏初版,可以自己逐步优化。
游戏效果如下。
文章来源:https://www.toymoban.com/news/detail-403956.html
你知道的越多,你不知道的越多,下期再见。文章来源地址https://www.toymoban.com/news/detail-403956.html
到了这里,关于Qt 魔塔小游戏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!