C++ 游戏开发 打地鼠详解 (含.h文件和.cpp文件)

本文详述了一个基于C语言和EasyX库的打地鼠游戏实现过程,包括游戏规则、玩法、数据结构设计、游戏策略、关键函数的算法实现以及游戏流程。游戏具有不同难度模式,通过击中地鼠得分,错过则扣分,目标分数和生命值设定增加了挑战性。此外,游戏还包括排行榜功能,展示了分数和血量的实时更新,以及游戏成功或失败后的结果页面。
摘要由CSDN通过智能技术生成

基于easyx的打地鼠游戏 期末大作业

C语言版本请见 C语言 打地鼠游戏 超级详解,各个函数与算法,设计思路与流程

一、游戏简介
游戏简介:
《疯狂打地鼠》是一款经典的单机休闲益智类小游戏,调皮的小地鼠们又出来活动了,你需要做的就是将他们砸回洞中去!机械风+复古风的游戏画面,不一样的体验,趣味性十足!眼手并用消灭地鼠,不仅要求速度快,而且要准,能够锻炼反应能力和大脑协调能力。
游戏规则:
欢乐有趣的疯狂打地鼠游戏,拥有暴风锤,轻轻一点,即可以把地鼠打得满地找牙!首先可以选择游戏的难易程度,游戏开始时有一个目标分数和生命值,打中一只地鼠得1分,错过则生命 值减1,达到目标分游戏成功,生命值为0时游戏失败。
游戏策略:
根据想要的游戏体验,可以选择不同的难度,难度大的模式下地鼠速度会有所提升,因此在游戏过程中要保持全神贯注,不仅要求速度快,而且要准。
二、游戏玩法
游戏玩法:
地鼠会从一个个地洞中不经意的探出一个脑袋,我们要做的就是在地鼠出来的时候拖动鼠标控制击打的锤子,去打它,当打到地鼠时,会加分,地鼠逃走则会扣除生命值。达到目标分游戏成功,生命值为0时游戏失败。

流程图如下:
在这里插入图片描述
三、游戏设计实现
重要的数据结构介绍:
设计了两个类

MyDiShu类

1)定义地鼠坐标、图象、运动方向以及判断是否被击中的变量
2)定义显示地鼠和地鼠移动的函数
三、游戏设计实现
重要的数据结构介绍:

MyDiShu.h在这里插入图片描述
MyDiShu.cpp
在这里插入图片描述

MyDiShu ds[9]:定义地鼠对象数组,包含9只地鼠

Hammer类

Hammer.h
在这里插入图片描述
Hammer.cpp
在这里插入图片描述

1)定义锤子的坐标、图象、击中图象以及判断是否击中的变量
2)定义显示锤子的函数
在这里插入图片描述
整体关系如下: 主函数main位于“打地鼠1”中,是程序的入口。

在这里插入图片描述

1.set()

在这里插入图片描述

set()函数为初始化全局变量所用的函数,在整体的游戏程序之前(因为要设置游戏运行所要用的全局变量的默认值),但在讲解规则函数,选择难度函数之后,选择难度虽然会初始化target,max_miss,speed变量的值,但是并不需要set后面,因为set()是程序初始化时所用的默认变量。而我们选择页面是会改变这三个全局变量的。

2.锤子结构体内的draw()方法

在这里插入图片描述

这个是实现时比较困难的函数之一,先总体来看,最外层的if,else分别是展示锤子的普通状态,展示锤子的落下状态。
if里面则是一直刷新锤子图,锤子跟随鼠标移动(锤子坐标的改变在逻辑函数中);else则用来展示锤子锤下时的图(20帧),并且击中的话还会显示击中效果图。
详细解释:
地鼠结构体内的hitted变量用于描述地鼠有没有被击中,但是这样会有一个问题,游戏时的某一个时间点上,只记录并操作了一只地鼠,所以当我们锤到地鼠时,ds[m]的m就已经发生变化了,但是我们用了一个变量flag来记录并持久化ds[m].hitted,然后在对flag进行判断和操作即可。如果没有flag变量,我们在按下锤子时锤子会垂下来,但是无论锤没锤到地鼠,击中效果图都会显示出来。
hitx,hity则是在游玩过程中,发现锤子锤下去后,移动锤子,击中效果也跟着锤子移动(改进前,击中效果的坐标始终等于锤子坐标)但这并不是我们的期望,所以增加这两个变量用于记录锤子锤中的坐标,并且锤子移动不会在改变这两个坐标的值,只有锤子再次击中地鼠时,才会改变这两个坐标的值。
展示锤子落下图并不在二级的if判断中是因为无论是否击中地鼠,只要按下鼠标左键,都应该展示锤子的落下效果图。
hitx,hity是全局变量,但是却用static声明在函数内部则是因为杨老师在上课讲到过的规范。这样用不仅符合代码规范,也在逻辑上行得通。
count则也是用到了上课学到的思想,记录循环跑的次数(也是帧数),来延长一个状态(锤子锤下的状态),如果不用count的话锤子锤下状态只显示一次(一帧)几乎无法察觉到锤子落下过,所以不可取,延迟锤子落下如果不用count方法而用sleep方法的话则会锤下时一切静止,等待锤子升起来后程序再运行,这并不是我们的初衷。

3.initgame()

要注意的是初始化的是地鼠的绝对坐标(其实就是与坑绑定),然后地鼠再次移动就是改变地鼠自己的的坐标,而不用到初始化时的坐标,这样就达到了了相对于坑移动的效果.
设定地鼠绝对坐标时比较麻烦,但是有规律可循,相邻地鼠之间的距离在同一行时固定的,所以可以每一行用一个for循环来赋值。最后3行9个地鼠,用三个for循环即可。

4.drawgame()

在这里插入图片描述

这个展示函数的前半部分,展示地鼠和背景这里需要技巧,需要先显示这行的地鼠再显示这行的背景图,这样一来,背景图就能将地鼠挡住,所以地鼠从洞中出来,其实只是地鼠遮住他上部分的背景图增多(露头部分),并且他自己被遮住的部分减少(藏在地里的部分)。
该函数的后半部分就是执行锤子的draw()方法(展示锤子两种状态和击中效果图),和展示文字部分(目标分,当前分,总血量,剩余血量),hammer.draw()上面已经讲过注意事项,和实现所用算法,文字部分也很简单,此处不再赘述。

5.canNotMoveUp() , canNotMoveDown().

在这里插入图片描述
在这里插入图片描述

判断是否无法向上或向下移动,需要注意的是边界判断,不应该只做边界的等值判断,而应该用>,<这种范围判断,如果用等值判断范围的话,那边界值必须是初始值+移动值的倍数,而这款游戏又可以选择速度,等值判断只会限制程序的拓展性,而且容易隐藏bug,所以我们用了范围判断。
老师上课讲到的判断,也都是一个范围判断,判断“大于或小于边界”时执行代码,而非“正好到达边界时”。

6.ctolGame()

在这里插入图片描述

if,else依据地鼠移动方向进入代码块,地鼠移动方向为上时只需移动后判断是否到顶即可,是的话方向取反向下即可。

外层else的情况,地鼠向下移动时,情况比较复杂,因为向下的情况有两种,
第一种是地鼠自然移动,向上到顶后又向下移动,缩回坑中的过程。
第二种情况是地鼠被被锤后位置被重置,且方向被置为向下
进入内层else后,判断地鼠是否被击中,如果没有击中,生命值减少,若击中了,不做多余操作并将记录值重置。
最后。无论上面哪种情况,都再刷新一个地鼠,要注意的是为了避免再次刷新在原位置(增加游戏难度),讲刷新写入循环中,只有刷新到新的位置才退出循环。

7.logic()

在这里插入图片描述

锤子击中的区域如下图所示,虽然理论上不是十分准确,(游戏是以玩家角度来看是三维的,而我们只做了二维判断(而且还是把锤子落下的椭圆外接长方形的区域,所以不是十分准确))
但是在视觉是契合的,锤子落下的区域击中了地鼠,那我们就认为是打到了地鼠,毕竟应该以玩家体验为前提和核心。
在这里插入图片描述

if里面运用了两矩形碰撞判断,减少了代码量,提高了可读性,当锤子落下并且该地鼠并未被击中时,我们讲地鼠的位置重置(即锤下地鼠它直接消失),并且讲该地鼠移动方向置为向下,方便一会进入ctolGame()判断。refreshDiShu()则是讲地鼠的位置重置,因为共九个地鼠,如果写判断的话要写9个(优化后可以写成3个),但这依然不符合上课杨老师讲到的代码的可读性,复用性。拓展性。logic()只需逻辑部分,地鼠位置重置只需交给另一个函数即可。并且下次其他函数如果需要地鼠刷新,例如后续增加某功能,需要用到该函数,就重用了该部分代码,增加了程序的扩展性,一举三得。
最下面的三个if,完全可以只用一个变量,Win,结束时判断是否胜利即可,但是这并不符合代码的可读性,并且如果想添加后续功能,例如进入下一阶段,这既不是胜利,也不是失败,所以这又限制了拓展性,所以我们还是使用了这种写法。

8.readRecordFile(),WriteRecordFile()

在这里插入图片描述

readRecordFile()需要注意的不多,按照倒数第二堂课讲的迷宫存储读取方法使用即可,
但是WriteRecordFile()则需要在原有掌握的基础上加入排行榜功能。
排行榜采用了传统的三个位置,Top1,Top2,Top3,排行榜的排行规则是击中地鼠数量最多的排名最高,地鼠击中数量相同的,血量高的排名更高。符合通常的游戏排行榜规则。
具体算法:
第一个for循环:
用两个数组分别存储排行榜前三名的分数与血量,游玩结束后,将本次分数与剩余血量存于两数组的第四个(下标3),然后先进行遍历数组,分数相同的情况下,如果血量还比排行榜上的血量高,那么变成为排行榜上的记录。break也要注意添加,否则替换多个。
第二个for循环:
用了一个简单的冒泡排序,没有用快速排序或者其他时间复杂度低的排序,因为数组本身只有四个元素,时间上几乎没有差别,其他间复杂度低的排序通常代码量比较大,写在这里会显得“头重脚轻”,所以此处使用冒泡排序。
冒泡排序通过分数将四个人排序,注意此处不可以写等号,写等号的话分数上没什么影响,可能会导致血量上排名变乱。

9.drawRules()

在这里插入图片描述

这个函数只需要注意增加一个while循环用_getch()方法读取缓冲区中的字符,,防止通过键盘方式跳过规则时,缓冲区里留下字符,在后面选择难度时又自动被读进去。导致程序出错,或者难度选择出错。

10.choose()

在这里插入图片描述

用到了上课学到的键盘输入模型,即循环中不断检测缓冲区里是否有字符,有的话则进入switch判断。
其实这个函数也可以用桶(哈希)的思想,用三个数组保存目标分,血量,速度,然后直接
变量=对应数组名输入这样可以空间换时间,将时间复杂度从O(n)->O(1),但是这样代码的可读性变差,不直观,并且n本身只有4,所以我们并不这样做,但也确实是经过思考,权衡利弊后使用了下面的这种写法。

四、游戏实现
见cpp

五、游戏效果
1)游戏规则画面
在这里插入图片描述

2)菜单画面(难度选择或退出游戏)
在这里插入图片描述

3)开始游戏(该模式是疯狂模式,可以观察到目标得分和初始血量比较苛刻)
在这里插入图片描述

4)击中地鼠(该模式是简单模式,可以看到目标得分和初始血量比较客观)
在这里插入图片描述

图片解释:
地鼠被击中后图片直接归位(地鼠图片被置于下方,被背景图覆盖),但锤子下降状态和锤子击中的反馈效果还继续存在,所以这张截图中没有地鼠,只能看到锤子下降状态和锤子击中的效果,实际的游戏过程中,因为这两种效果存留的时间也比较短,地鼠被锤没后,因为视觉残留效果,玩家能明显的感受锤子是锤到了地鼠的,并且因为地鼠直接被锤没了而感受到“到打击感”,但如果地鼠被锤后依然存在,然后与这两种效果存在时间相同(默认20帧中间帧),玩家体验会差很多。因为锤完后地鼠没有快速消失,这样就失去了“打击感”。并且是“尸体”存在一段时间后自动消失,而不是被锤时候消失,显得很违和。

5)达到目标分,游戏成功
在这里插入图片描述

图片会在一定时间后自动消失,没有像菜单那样引入鼠标或键盘判断来确认玩家是否跳过这个结果画面,因为以我的游玩经验,绝大部分玩家是不喜欢结算页面需要确认的,几乎都是在游戏结束时就连续的按按键来结束这个画面,所以为了避免结果画面的审美疲劳,和减轻玩家负担,我则让这个页面显示一段时间后自动结束,另一个结果画面同理,排行榜画面也是同理,因为排行榜上的分大概率是很久不变的,所以也是类似于“多次看到,厌倦了的”画面,但当玩家需要看时(自己打破了纪录),还是停留了一段时间来给玩家参考。(排行榜画面停留的时间大于结果画面的停留时间,符合玩家需求。)

6)生命值为0,游戏失败
(在(5)解释过,不再赘述。)
在这里插入图片描述

7)排行榜
在这里插入图片描述

(为什么固定显示一段时间 ,在(5)解释过,不再赘述;排行榜算法,在上面解释过,不再赘述。)
文字颜色黄色则是显示当前页面为排行榜(醒目),绿色有正向,积极的韵味所以用来描述排行第几比较合适,分数则是用了红色,因为红色不仅有醒目的作用,而且有厉害,强者的感觉,所以用来展示排行榜上大神们的分数,和剩余血量。
文字颜色黄色则是显示当前页面为排行榜(醒目),绿色有正向,积极的韵味所以用来描述排行第几比较合适,分数则是用了红色,因为红色不仅有醒目的作用,而且有厉害,强者的感觉,所以用来展示排行榜上大神们的分数,和剩余血量。

六、分工及总结

  1. 分工
    本次小组作业的完成大致分为两个阶段:
    设计阶段:
    1)每个人分别搜集资料,选择一个想做的游戏
    2)积极讨论选题,分析实现性和游戏的可玩性
    3)最终决定一起完成疯狂打地鼠这个经典小游戏
    实现阶段:
    XXX
    1)主要负责编码实现,具体函数及算法的设计与实现
    2)游戏功能的创新与实现
    3)录制游戏视频讲解
    4)改进实验报告,增加函数与算法解释部分
    XXX
    1)游戏框架的设计和代码编写
    2)提供游戏规则,策略,与设计难度等级等
    3)负责图片查找,P图,图片改进
    4)完成实验报告
    XXX
    1)提供游戏创意思路
    2)查找并提供图片和音乐素材,并修改素材为可使用版本
    3)游戏测试,并提供改进与增加功能的意见与思路
    4)完成实验报告

2.总结
项目日志:
Day1 各自搜集资料,寻找合适的游戏项目
Day2 讨论选题,最终敲定《疯狂打地鼠》这一游戏项目,明确分工
Day3 完成素材的搜集,并对处理加工素材
Day4 实现游戏框架的底层代码,实现动态地鼠
Day5 实现锤子敲击,重置地鼠的功能
Day6 添加记录并显示得分,生命值,目标分,初始生命值的功能
Day7 添加游戏规则和菜单页面,实现难度选择功能
Day8 添加排行榜功能
Day9 测试并改善代码,提高代码复用性,优化各功能
Day10 书写,并完成实验报告
Day11 修改实验报告,完成视频讲解的录制。

概述性总结:

本次小组作业,我们首先积极讨论了游戏的选题,及选题的难度是否合适,最后我们根据时间(因为在布置之前就开始准备,所以有两周多的时间),小组能力(ACM副会长,ACM技术部部员,和会P图找素材的大佬),所以我们认为我们完全有能力实现一个原创(原创代码与算法)的游戏,最后确定了做“打地鼠”这款具有童年回忆的游戏,于是我们在网上找到素材后,第一时间打好了框架,并开始了具体代码的编写,但是整个过程也并不是一帆风顺,在很多地方也陷入过困难,(比如地鼠显示,需要用背景图遮盖的方式来隐藏地鼠,并且再通过地鼠隐藏背景图的方式体现地鼠出来,在锤子落下图和击中效果图,击中地鼠得分,地鼠重置上,也几次碰壁,总是实现了一个功能后,另一个变出了问题,但好在最后都解决了)。正是一次次的debug才使我们成长,正所谓“不能杀死你的,会使你更强大”,最后在各个组员的努力下终于完成了一个有完整功能的打地鼠游戏。
心得与收获:
1)通过这个课程学习到了面向过程逐步分解的方式思考问题,数据、逻辑、控制相分离,降低函数内代码耦合性,尽量一个函数一个功能等思想,着重代码可读性,复用性,拓展性等,大作业也是通过这样的方式编写了渐进式代码。
2)根据上课学习到的各种思想与方法,和实现某些功能的具体代码,在这些的基础上,我们又添加了自己的东西,既巩固了习得的知识,又提高了自己的代码能力和思考能力。
3)在完成大作业的过程中,可以说是困难重重,但是成功没有捷径,通过我们的齐心协力,成功完成了此次大作业,这不仅锻炼了我们的代码能力,更锻炼了我们的沟通协作,小组协同能力,很理解老师要求至少两人一组的良苦用心。

最后附上源码:

MyDiShu.h

#pragma once
#include <graphics.h>

class MyDiShu {
   
public:
	int	x, y;//地鼠坐标
	IMAGE img, img_y;//地鼠图
	int dir;//记录地鼠运动方向 1-UP 0-DOWN
	bool hitted;//地鼠是否击中过

	MyDiShu();

	~MyDiShu();

	void draw();//显示地鼠
	void moveUp(int speed);
	void moveDown(int speed);
};

Hammer.h

#pragma once
#include <graphics.h>

class Hammer {
   
public:
	int x, y;
	bool hit;
	IMAGE img, img_y;
	IMAGE img_hit, img_hit_y;
	IMAGE img_thitted, img_thitted_mask;
	Hammer();
	~Hammer();
	void draw(bool hitted, int frame);//显示锤子
};//锤子类

MyDiShu.cpp

#include"MyDiShu.h"
#include <graphics.h>
#include<conio.h>


	MyDiShu::MyDiShu() {
   }
	MyDiShu::~MyDiShu() {
   }

	void MyDiShu::draw()//显示地鼠
	{
   
		putimage(x, y, &img_y, SRCAND);
		putimage(x, y, &img, SRCPAINT);
	}
	void MyDiShu::moveUp(int speed)
	{
   
		y -= speed;//每次移动speed个像素
	}
	void MyDiShu::moveDown(int speed)
	{
   
		y += speed;//每次移动speed个像素
	}

Hammer.cpp

#include"Hammer.h"
#include <graphics.h>
#include<conio.h>

int x, y;
bool hit;
IMAGE img, img_y;
IMAGE img_hit, img_hit_y;
IMAGE img_thitted,img_thitted_mask;

Hammer::Hammer(){
   }
Hammer::~Hammer() {
   }

void Hammer::draw(bool hitted,int frame)//显示锤子
{
   
	bool flag = false;
	bool musicPlay;
	if (hit == false) {
   
		putimage(x, y, &img_y, SRCAND);
		putimage(x, y, &img, SRCPAINT);
	}
	else /*(hit == true)*/ {
   //锤子向下打的状态
		//static bool flag;//标志是否打击中了,实则用持久化ds[m].hitted(该值会因为地鼠的改变而无法查询,所以用flag来记录)
		static int hitx, hity;//用于击中效果坐标的持久化,位置等于锤子锤下去时一瞬间的位置,为了击中效果图不随着锤子位置变化而变化,故加入此两个变量用于持久化坐标。
		if (hitted == true) {
   
			flag = true;
			hitx = x - 5;//5为横坐标偏移量;
			hity = y + 40;//40为纵坐标坐标偏移量;
		}
		if (flag == true) {
   //如果打中了,锤子是向下状态时,那么播放击中图片
			mciSendString(_T("close hitmusic"), NULL, 0, NULL);//先关闭之前播放的本音乐
			mciSendString(_T("play hitmusic"), NULL, 0, NULL);//播放音乐文件

			putimage(hitx, hity, &img_thitted_mask, NOTSRCERASE);
			putimage(hitx, hity, &img_thitted, SRCINVERT);
		}
		putimage(x, y, &img_hit_y, SRCAND);
		putimage(x, y, &img_hit, SRCPAINT);
		static int count = 0;//count技术,每
		count++;
		if (count == frame
  • 16
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值