Qt版本-塔防游戏实现一_qt4 魔塔-程序员宅基地

技术标签: C++  Qt  塔防  游戏  

这个游戏来源于一篇较早的国外作品,不过原作是以Cocos2D为基础实现的,链接见下:

http://www.raywenderlich.com/37701/how-to-make-a-tower-defense-game-tutorial

这里呢,采用Qt5.1的库,进行移植了,这里就直接如主题了:


先看下游戏运行效果:



这里的图片,感谢原作者无私的资源,嘿嘿,借来用用

背景地图大小是480*320,这也是游戏界面的固定大小,setFixedSize(480, 320)

好了,看完效果,一步步走吧!


这是我的编译环境,去下最新版本的Qt就可以了,Qt4.x的不保证可以跑起来,没测过啦~,\(^o^)/~



1、绘制游戏背景,加载炮塔安放位置

创建一个工程,窗口采用QMainWindow或QWidget都可以,这里就用QWidget了,配上ui文件


设置好固定大小,就可以和ui文件说拜拜了,接下来基本上就都是绘制了


添加图片资源文件,这里的源代码在文章结束后会放出链接的,资源文件都在那了,友情设置0分,欢迎下载,给我长点分呗,hohoho~


然后就先画上背景图片呗~

重载MainWindow(就是QWidget)中的paintEvent函数:

void MainWindow::paintEvent(QPaintEvent *)
{
	QPainter painter(this);
	painter.drawPixmap(0, 0, QPixmap(":/image/Bg.png"));
}

这里就可以显示背景图片,效果图就不先放了

再来需要加载可以安放塔的位置,这个,原作者采用读取xml文件的,这里做测试,就直接先用数组替换了,那个xml文件的数值似乎有问题,因此这里我查找了下点,这里是我找的测试安放坐标点(这样貌似缺少弹性,以后再改为xml读取嘛,笑~)


关于TowerPosition应该有的信息见下:



这里,明显m_pos就是安放塔位置进行绘制的坐标点(左上角)

m_hasTower,用于表明该位置是否有塔

m_sprite则是保存绘制图片

需要的主要就是绘制方法,已经获得将来判断点击的点是否包含在该区域内,以决定是否可以安放塔

先查看下声明吧!

class TowerPosition
{
public:
	TowerPosition(QPoint pos, const QPixmap &sprite = QPixmap(":/image/open_spot.png"));

	void setHasTower(bool hasTower = true);
	bool hasTower() const;
	const QPoint centerPos() const;
	bool containPoint(const QPoint &pos) const;

	void draw(QPainter *painter) const;

private:
	QPoint		m_pos;
	bool		m_hasTower;
	QPixmap		m_sprite;

	static const QSize ms_fixedSize;
};
这里很简单,相应的实现也非常的直白,见下:

const QSize TowerPosition::ms_fixedSize(44, 44);

TowerPosition::TowerPosition(QPoint pos, const QPixmap &sprite/* = QPixmap(":/image/open_spot.png")*/)
	: m_pos(pos)
	, m_hasTower(false)
	, m_sprite(sprite)
{
}

const QPoint TowerPosition::centerPos() const
{
	QPoint offsetPoint(ms_fixedSize.width() / 2, ms_fixedSize.height() / 2);
	return m_pos + offsetPoint;
}

bool TowerPosition::containPoint(const QPoint &pos) const
{
	bool isXInHere = m_pos.x() < pos.x() && pos.x() < (m_pos.x() + ms_fixedSize.width());
	bool isYInHere = m_pos.y() < pos.y() && pos.y() < (m_pos.y() + ms_fixedSize.height());
	return isXInHere && isYInHere;
}

bool TowerPosition::hasTower() const
{
	return m_hasTower;
}

void TowerPosition::setHasTower(bool hasTower/* = true*/)
{
	m_hasTower = hasTower;
}

void TowerPosition::draw(QPainter *painter) const
{
	painter->drawPixmap(m_pos.x(), m_pos.y(), m_sprite);
}
之后所有对象信息,几乎都会包含这3个,坐标点,尺寸大小,图片信息,管理也都集中放在容器交给MainWindow管理


在MainWindow的private区域添加

QList<TowerPosition> m_towerPositionsList;

同时添加私有方法loadTowerPosition用于从XML文件中读取塔安放位置的信息(m_pos),不过目前从简

查看下实现:

void MainWindow::loadTowerPositions()
{
	QPoint pos[] =
	{
		QPoint(65, 220),
		QPoint(155, 220),
		QPoint(245, 220),
		QPoint(335, 220),

		QPoint(100, 125),
		QPoint(195, 125),
		QPoint(280, 125),
		QPoint(370, 125),

		QPoint(80, 35),
		QPoint(170, 35),
		QPoint(260, 35),
		QPoint(350, 35)
	};
	int len	= sizeof(pos) / sizeof(pos[0]);

	for (int i = 0; i < len; ++i)
		m_towerPositionsList.push_back(pos[i]);
}
这里没有new,是因为TowerPosition结构简单,而且本身也只会被初始化一次,因此就这样愉快决定简单处理啦,哦也

然后再paintEvent中补上绘制就Ok了!


对了,还需要在MainWindow的构造中调用loadTowerPosition才可以,可以看下效果图啦~



终于完成第一步了,起码游戏界面现在看起来是那么回事了,想来应该是要添加一些家园卫士了,攻击塔搞起


2、攻击塔的初步实现

这里只是一个简单,简单,非常简单,不带减速的小小攻击塔,可以自由添加发挥~\(≧▽≦)/~啦啦啦

同样,先看下攻击塔有哪些必要属性:



首先有必备的3件套,坐标点,尺寸大小(这个是静态常量,其实就是图片的大小),图片

额外的属性,根据名字也很好猜测

m_attackRange,攻击范围,就是以塔的中心为原点,绘制一个圆,这个的半径就是攻击范围,默认为70

m_damage,塔的伤害值,后期用到,对敌人造成的费血,原作者看来相当不残忍,攻击力居然只有10点,好吧,我数值平衡很差啦~

m_fireRate,攻击平率,这里是用毫秒记,默认是1000ms,也就是1秒攻击1次


需要添加的方法,目前只有draw方法:

draw中干2件事,先绘制一个白色的圆,这是攻击范围(主要是方便测试观察),再绘制塔

这里的m_pos则表示塔的圆心

painter->save();
painter->setPen(Qt::white);
// 绘制攻击范围
painter->drawEllipse(m_pos, m_attackRange, m_attackRange);

// 绘制偏转坐标,由中心+偏移=左上
// 尺寸大小派上用场了,当然也可以直接获取图片大小,是一样的
static const QPoint offsetPoint(-ms_fixedSize.width() / 2, -ms_fixedSize.height() / 2);
// 绘制炮塔并选择炮塔
// 这里将坐标原点移到m_pos,绘制的适合,就要加上那个偏移点到左上角
painter->translate(m_pos);
painter->drawPixmap(offsetPoint, m_sprite);
painter->restore();

这里由于要改变painter一些设置,因此最好加上save/restore来作恢复使用

这样,就完成了攻击塔的绘制,赶快来解决下点击事件吧,看看效果吧!


为MainWindow重载mousePressEvent和添加canBuyTower处理函数(名字就该清晰了吧,后期需要用钱买,现在先免费)

同时为MainWindow添加成员

QList<Tower *> m_towersList; // 用来管理攻击塔的信息

重点查看下那两个方法呗:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
	QPoint pressPos = event->pos();
	auto it = m_towerPositionsList.begin();
	while (it != m_towerPositionsList.end())
	{
		if (canBuyTower() && it->containPoint(pressPos) && !it->hasTower())
		{
			it->setHasTower();
			Tower *tower = new Tower(it->centerPos(), this);
			m_towersList.push_back(tower);
			update();
			break;
		}

		++it;
	}
}

bool MainWindow::canBuyTower() const
{
	return true;
}

这里auto it = xxx.begin();是用到了C++11的新语法,这个随意啦,需要使用,在pro文件中添加

CONFIG += c++11

这里代码逻辑简单,遍历所有安放位置,点在安放位置 && 有钱(暂时肯定有啦~) && 没有别的塔

就添加入m_towerList管理中,看下效果吧!

还需要在paintEvent中添加相应绘制代码:

foreach (Tower *tower, m_towersList)
	tower->draw(&painter);
之后,可以看下效果图,点中区域,就好了:

现在塔也出来了,nice!

3、为敌人出现做准备,添加敌人行走路线,航点

塔防游戏路线其实不需要什么特别算法,针对本游戏更加简单,左上为入口,右下为基地,路线其实就是这么一个形状,先看效果图:

一共也就6个点,路线也是很正,都是直上直下,直男天下,哈哈

可惜不知道怎么了,原作者给的那几个点我用来都是有问题的,因此自己又重新测了下

下面是我添加的这几点:

void MainWindow::addWayPoints()
{
	WayPoint *wayPoint1 = new WayPoint(QPoint(420, 285));
	m_wayPointsList.push_back(wayPoint1);

	WayPoint *wayPoint2 = new WayPoint(QPoint(35, 285));
	m_wayPointsList.push_back(wayPoint2);
	wayPoint2->setNextWayPoint(wayPoint1);

	WayPoint *wayPoint3 = new WayPoint(QPoint(35, 195));
	m_wayPointsList.push_back(wayPoint3);
	wayPoint3->setNextWayPoint(wayPoint2);

	WayPoint *wayPoint4 = new WayPoint(QPoint(445, 195));
	m_wayPointsList.push_back(wayPoint4);
	wayPoint4->setNextWayPoint(wayPoint3);

	WayPoint *wayPoint5 = new WayPoint(QPoint(445, 100));
	m_wayPointsList.push_back(wayPoint5);
	wayPoint5->setNextWayPoint(wayPoint4);

	WayPoint *wayPoint6 = new WayPoint(QPoint(35, 100));
	m_wayPointsList.push_back(wayPoint6);
	wayPoint6->setNextWayPoint(wayPoint5);
}

这里用一个小封装的WayPoint来存储节点,WayPoint行为像是一个逆序链表,第一点其实是基地,(我比较懒,没有修改,其实觉得用Qt自带容器就可以了,而且为毛要逆序,顺序不就可以了),那个setNextWayPoint,其实存放的也是后一个节点,只不过节点6才是起始节点,蛋疼ing,对于WayPoint不解释了,也是那几点,在MainWindow中也需要管理:

1.声明:

// 敌人移动的航线
class WayPoint
{
public:
	WayPoint(QPoint pos);

	void setNextWayPoint(WayPoint *nextPoint);
	WayPoint* nextWayPoint() const;
	const QPoint pos() const;

	void draw(QPainter *painter) const;

private:
	QPoint m_pos;
	WayPoint * m_nextWayPoint;
};

2.实现

WayPoint::WayPoint(QPoint pos)
	: m_pos(pos)
	, m_nextWayPoint(NULL)
{
}

void WayPoint::setNextWayPoint(WayPoint *nextPoint)
{
	m_nextWayPoint = nextPoint;
}

WayPoint* WayPoint::nextWayPoint() const
{
	return m_nextWayPoint;
}

const QPoint WayPoint::pos() const
{
	return m_pos;
}

void WayPoint::draw(QPainter *painter) const
{
	painter->save();
	painter->setPen(QColor(0, 255, 0));
	painter->drawEllipse(m_pos, 6, 6);
	painter->drawEllipse(m_pos, 2, 2);

	if (m_nextWayPoint)
		painter->drawLine(m_pos, m_nextWayPoint->m_pos);
	painter->restore();
}

3.MainWindow中添加

QList<WayPoint *> m_wayPointsList;	// 在paintEvent中需要进行绘制,那个类似的foreach(xxx) xxx.draw(xxx)
void addWayPoints();			// 在构造函数中调用

这几步曲几乎是后续元素添加的顺序

这里航点是由一个两个圆套起来显示的,敌人会和航点圆心做碰撞检查,碰撞到了,就要开始调换方向,向下一个原点进发


好了,今天太晚了,先写到这里,之后继续~

未完Go on!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/satanzw/article/details/10418063

智能推荐

linux中cgroup的简单使用_cgroup使用-程序员宅基地

文章浏览阅读2.5k次,点赞26次,收藏34次。Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去。_cgroup使用

Halcon缺陷检测之(Blob+差分法&&模板匹配+差分法)_工业视觉检测开发blob-程序员宅基地

文章浏览阅读8.9k次,点赞27次,收藏166次。一、缺陷检测概述  缺陷检测是机器视觉重要的应用方向之一,由于在制造产品的过程中,表面缺陷的产生往往是不可避免的,故机器视觉的缺陷检测有较大的市场需求。熟练掌握缺陷检测是视觉工程师的必要技能。  在工业视觉检测当中,常见的工业视觉检测表面缺陷有划伤、划痕、辊印、凹坑、粗糙、波纹等外观缺陷,此外还有像一些非金属产品表面的夹杂、破损、污点,以及纸张表面的色差、压痕等。  相比于人工检测,基于机器视觉的检测有如下优点:①能24小时不间断工作②检测速度快,准确率高③检测精度高④不受外界因素的干扰,检测_工业视觉检测开发blob

openGauss洗冤录 之 copy from_copy to or from a file is prohibited for security -程序员宅基地

文章浏览阅读634次。对于copy功能PostgreSQL从9.2.4到16devel是否有过优化?或者openGauss是否持续合并或优化PostgreSQL的copy功能,这方面我没有去考证过。单纯从测试结果上看,openGauss的copy性能要略逊于PostgreSQL。当然,可能是我水平有限,所以希望各位openGauss的专家、老师集思广益,还openGauss一个真实的COPY FROM文件导入性能。(大家可以回复优化方案,我这边去做验证)_copy to or from a file is prohibited for security concerns

基于springboot的体育馆使用预约系统_基于springboot的体育馆预约管理系统-程序员宅基地

文章浏览阅读1.1k次,点赞23次,收藏27次。基于springboot的体育馆使用预约系统_基于springboot的体育馆预约管理系统

Spring、SpringBoot常见面试题与答案_spring和springboot的常见面试题-程序员宅基地

文章浏览阅读390次。SpringSpring Bean 的作用域有哪些?它的注册方式有几种?Spring 容器中管理一个或多个 Bean,这些 Bean 的定义表示为 BeanDefinition 对象,具体包含以下重要信息:Bean 的实际实现类;Bean 的引用或者依赖项;Bean 的作用范围;singleton:单例(默认);prototype:原型,每次调用bean都会创建新实例;request:每次http请求都会创建新的bean;session:同一个http session共享一个bean_spring和springboot的常见面试题

openstack认证服务(认证组件)3_openstack 认证服务-程序员宅基地

文章浏览阅读1.9k次。Openstack认证服务(认证组件)3_openstack 认证服务

随便推点

R语言实用案例分析-1_r语言案例分析-程序员宅基地

文章浏览阅读2.2w次,点赞10次,收藏63次。在日常生活和实际应用当中,我们经常会用到统计方面的知识,比如求最大值,求平均值等等。R语言是一门统计学语言,他可以方便的完成统计相关的计算,下面我们就来看一个相关案例。1. 背景最近西安交大大数据专业二班,开设了Java和大数据技术课程,班级人数共100人。2. 需求通过R语言完成该100位同学学号的生成,同时使用R语言模拟生成Java和大数据技术成绩,成绩满分为100,需要满足正_r语言案例分析

Java知识体系总结(2024版),这一次带你搞懂Spring代理创建过程-程序员宅基地

文章浏览阅读639次,点赞11次,收藏26次。虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。一个人可以走的很快,但一群人才能走的更远。

车辆动力学及在Unity、UE4中的实现_unity 车辆动力学模型-程序员宅基地

文章浏览阅读3.9k次,点赞9次,收藏53次。受力分析直线行驶时的车轮受力如下:水平方向上,所受合力为:F=Ft+Fw+FfF=F_t+F_w+F_fF=Ft​+Fw​+Ff​其中,FtF_tFt​为牵引力,FwF_wFw​为空气阻力,FfF_fFf​为滚动阻力,下面我们将逐个介绍。驱动力先来说扭矩,扭矩是使物体发生旋转的一个特殊力矩,等于力和力臂的乘积,单位为N∙mN∙mN∙m:设驱动轴的扭矩为TtT_tTt​,车轮半径为rrr,那么牵引力:Ft=Tt⁄rF_t=T_t⁄rFt​=Tt​⁄r如何求得驱动轴扭矩TtT_tTt​呢?_unity 车辆动力学模型

在vue中使用web3.js开发以太坊dapp_如何使用web3和vue.js创建你的第一个以太坊dapp-程序员宅基地

文章浏览阅读1.8w次,点赞2次,收藏65次。前端如何使用以太坊智能合约方法这里讲的是前端与MetaMask之间的交互文中涉及到的官方文档web3.js 1.0中文手册MetaMask官方文档web3.js文件链接:https://pan.baidu.com/s/1_mPT-ZcQ9GU_U1CVhBKpLA提取码:cbey//在vue中安装web3npm install web3 --save//在main.js引入import Web3 from 'web3'Vue.prototype.Web3 = Web3一、唤起Me_如何使用web3和vue.js创建你的第一个以太坊dapp

Python:太阳花的绘制_绘制一个直径随机的太阳花-程序员宅基地

文章浏览阅读701次。绘制一个太阳花的图形。from turtle import *color("red",'yellow')begin_fill()while True: forward(200) left(170) if abs(pos())<1: breakend_fill()done()_绘制一个直径随机的太阳花

Linux常用命令(1)_code=exited, status=0/success-程序员宅基地

文章浏览阅读348次。Linux常用命令(1)切换到ROOT用户(su - root)[liu@localhost ~]$ su - root密码:[root@localhost ~]查看IP地址(ifconfig)[root@localhost ~]# ifconfigens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.100.47 netmask 255.255.255.0 broad_code=exited, status=0/success

推荐文章

热门文章

相关标签