重写弹幕射击游戏的记录

article/2025/7/15 20:59:01

项目背景介绍

上大学之后我一直在学习游戏开发,最开始是直接使用Easyx这个绘图库做Dos下的游戏,当时学习了C++和数据结构之后正巧有个数据结构课程设计,就心生了要做这个游戏的想法。我算是那种有想法就想着去做的人(有时候也算是缺点,因为只注重了实现),于是在那次课程设计中完成了本项目的初始版本。

在初始版本中还是实现了挺多我对游戏功能的想法,不过代码的结构、规范化、性能存在着很大的问题,在经历了一些面试之后,我觉得我是得往这方面更深入研究。在还没有拿到offer的情况下,我打算使用已经学习和将要学习的知识来改进自己写过的代码,而这个弹幕射击游戏是第一个。我对项目改进所关注的点主要在于:代码的结构、规范性,程序的性能。

版本对比

截图对比:

原始版本

 

原始版本截图

 

 

改进版本截图

 

说明:从图中看,改进版本没有原始版本那么乱(其实是功能删减了很多)。

功能的改变:

想在新版本实现的功能:

插值计算 ok

轨迹管理 ok

存储记录 ok

全局信息 false

关卡管理 false

运动管理 ok

协程 false

敌人 子弹 导弹  技能 ok

游戏暂停 优化

 

相比原始版本未实现功能:

保存数据 false

debuff状态 false

攻击特效 false

溅射 false

敌人特性 false

玩家的升级  经验 hp  技能获取等 false

游戏提示 false

 

这些功能没有重新在新版本中实现是我觉得和一些其他功能有所重复,时间紧迫再实现也没多大意义。

项目结构的改变:

这次重新编写代码,仿照了Unity引擎脚本运行逻辑来设计对象模型结构,这样子游戏中出现的对象完全继承于基类方便了统一管理。相对比原始版本,消除了全局变量的做法,消除了动态对象分配的做法。

 

改进版本结构图1

 

改进版本结构图2(优化了一丢丢)

项目性能的改变:

 

原始版本性能参数

 

改进版本性能参数

说明:改进版本的性能上CPU占用减低了一丢丢,似乎没有多大影响,而且内存占用变大了,这是因为使用了对象池的原因。好了,这个只是暂时的,进一步的改进请看下面的性能优化过程。

 

改进遇到的问题

//遇到问题:2018年10月31日15:25:58  线段和圆的相交  感觉网上给出的方法是错误的

//解决方法:在网上实现的方法基础上进行了改进以达到要求。

 

//遇到问题的思考  内存池(其实应该是对象池,当时不懂)  先生成一定数量的某一类对象  但是我想生成一个敌人的内存池  敌人有不同种类

//即很多敌人类型是基础敌人基类的  该如何分配内存

//以下是解决方法:使用对象池 加定位new的方式分配对象空间 然后在使用到具体对象时在空间内创建类对象

//使用定位new在buff创建A类的对象a后会使用A类大小等同的字节来保存对象a的数据(包括虚基表)

//如果调用了显式析构函数 则虚基表会被释放 而其他的数据如果没有释放则不变

//即调用派生类的函数会调用到基类的函数 调用类成员变量则值不变

 

//遇到问题:2018年10月31日10:36:18 关于STL容器的迭代器尾++ 头-- 操作的问题

//那么如何在遍历的过程中进行增加删除呢

//做法:将操作转移到管理者中统一操作,因为管理者拥有迭代器成员变量记录链表操作对象,通过迭代器进行删除

 

//遇到的问题  从一个正在遍历的链表中转移一个元素到另外一个链表导致的链表遍历出错

//解决方法 类定义一个迭代器进行遍历操作 当回收时使迭代器--

 

//遇到问题 2018年10月30日20:46:33 将指向容器的迭代器做减操作时发生错误

//包括子弹的回收 特效的回收 怪物的回收

//解决方法:添加一个迭代器成员变量,在做减操作前进行判断。

 

//更复杂的一个问题:2018年10月31日11:07:42 在一个容器遍历的时候可能删除自身元素 同时遍历的操作过程中

//遍历别的容器的元素并且可能把它删除 寻求解决的方法:如何在遍历时删除元素并确保遍历可以继续进行

//思路:如果没有删除 则一直遍历完成  如果删除了元素  则迭代器需要指向正确位置并判断是否可以进行步进操作

//难点:在遍历过程中是否删除元素无法告知遍历操作

//那么一个方法是可以让遍历中的执行操作返回是否删除元素  另外将遍历处理的工作移交给具备容器迭代器的对象进行

//处理 如此一来  它在处理过程中如果删除了元素  不会出现迭代器的++ --操作异常的情况

//最终选择的做法是将带有可能删除元素遍历操作专业到管理者中进行

 

//遇到问题的  不同的char类型之间的转换问题

//这个需要仔细看下博客。

 

//遇到问题:2018年10月30日18:03:45 EnemyIncubator的构造函数没有执行

//解决问题:其实是有执行  但敌人孵化器没有先构造 添加玩家时会将场景 敌人孵化器添加给子弹 空指针异常

 

//遇到问题:2018年10月31日19:46:16 函数执行之后不知道跳到哪里去了

//解决:2018年10月31日19:55:19 迭代器忘记了操作遍历

//遇到问题:2018年10月31日20:02:02 lambda表达式传递过来的值没有发生过变化

//解决:不是传过来的值没有变化,而是if语句之后添加了;导致每次返回了相同值

 

//特效的管理  无论是玩家管理还是场景特效管理者管理  都需要让特效获取指针执行管理

//为了让职责划分更清晰 所以才有场景特效管理者管理

//另一种思考:将子弹和特效绑定在一起管理 不过这种的话职责不清晰  而且管理不方便

//比如在特效产生前特效该干什么  子弹消除后子弹该干什么  假如什么都不干是否需要清除出队列

 

//遇到问题:如何将lambda表达式存储起来,供后面调用使用

//已经解决:  发现编译器的缺陷问题 类的模板函数目前还无法分文件实现

 

//又产生了和Bullet的相互调用

//进行尝试:2018年11月1日11:11:54 将会追踪的子弹添加到游戏中

//先测试数学几何模型

 

//问题:在写好场景转换之后遇到了Scene类无法识别的问题,导致gamemanager也出现报错

//A B相互包含  C继承B  然后在C中报错  C没有定义基类

//解决 使用指针  在需要调用GameManager类方法的源文件中添加GameManager的头文件

 

//遇到问题List的释放 后来发现其实不是不是这个问题  而且切换场景后没有重新调用GameManager的Run方法

//导致了继续执行原来场景的Updatae方法出现错误

性能优化过程

在完成改进版本功能编码之后对其进行性能优化,首先我们来看它现在的性能。首先设定:循环延时17ms(即保持每秒59帧这样子),CPU占用10.5%左右,内存25m左右。然后我们将循环延时去掉看最强的帧率能达到多少,帧率142左右滑动,CPU占用24.5%左右滑动,内存占用还是25m左右。

然后我们开始对在循环延时17ms的情况下进行优化:在主循环中sleep函数的调用占到了30%的时间,在绘制界面的函数中putimage函数是个大的性能花费,但是这里对它的改进不是我的主要工作,所以把背景给出掉。然后CPU占用下降到了4.2%左右(一个函数就这么夸张)。接下来我们看下我们最多能将CPU占用下降到多少,我在主循环中只保留了延时函数(17ms),好的,如果程序什么都不做,那么CPU占用为0.15这样子,说明我们现在还有很大的优化空间...接下里我们主要研究战斗场景的各函数花费占比,然后优化他们。我们先将开始游戏场景设置为战斗场景,这样子性能分析工具只测试部分调用的函数,避开了由于lambda表达式调用无法显示正确调用函数占用时间。好了,现在在游戏主循环中sleep函数的占比已经占到了42%了(几乎就用于睡觉了),其他函数调用微乎其微(就是我写的对象逻辑执行占用时间太少了)。Ok,那我的性能优化到此结束....好吧,开个玩笑,虽然现在逻辑运行占用时间少,但是还是有优化空间,当我们增大游戏中的怪物数量时,这个占用时间会慢慢升上去。把子弹,导弹,敌人的最大数量都调到10000000,每次产生的数量增大到1000(原本是1),我们现在再来看下各函数调用所占用时间...好吧,这玩笑开得有点过头了,我运行之后然后就去玩手游了...等了几分钟,终于等到了游戏分配好内存进入战斗,然后电脑游戏卡得实在是不显示了,而且性能分析工具在结束分析之后的很长一段时间没有给出分析结果...可能我得往下调整一下数量。我们还是把基数增长为100倍吧,不然这个对象池分配内存一开始耗费的时间太长了。对象数量设置为10000,每次产生的子弹、导弹、敌人为100,再进行测试。好吧,这样子也是界面卡得很慢,觉得不应该啊,这样子就承受不住了,先来看下性能分析的报表内容。这一次sleep函数调用占用为0.17%,说明根本没有时间睡觉了,进一步发现在玩家更新中,玩家所发射的子弹、导弹跟敌人的碰撞检测非常耗费时间。然后我直接运行程序发现是可以运行,但是随着敌人数量的不断增加,FPS逐渐减低到了1,然后基本上就是每2s移动的画面。为什么性能分析工具不用运行呢,带着疑问我继续进行测试,原因可能是性能分析工具本身需要占用一些资源,用于进行性能分析有点困难。照目前情况来看,100倍是ok的,稳定59帧率。重新搞成1000倍试一下,好吧,还是卡死,我想,如果能够在1000倍都正常运行,那可能是不错的。先回到10倍进行一下性能分析测试,发现在敌人的绘制函数中,名字的绘制是挺耗费时间的(相比其他几何图形绘制)。现在有了个疑惑,因为,发现了在游戏中子弹触发的碰撞检测要比导弹触发的碰撞检测要多,大约是子弹的四倍,但是在场景中导弹的数量要比子弹多,我们先来看触发碰撞检测部分代码。子弹的移动时间间隔也就是进行检测的时间间隔,但是子弹的移动是直线的,所以可以将移动时间间隔改大一些,然后发现导弹的移动时间间隔更小,这就更加让我疑惑,究竟是咋回事...然后修改一下倍数之后发现变回来了,导弹的碰撞要比子弹的多,但在碰撞检测中,调用敌人的碰撞检测函数,函数内部调用时间过长,得想办法消除部分花费时间,想法是直接获取敌人的位置信息然后使用碰撞函数判断。这样子修改之后确实有了较大的提升,并且我还将敌人的名字给隐藏了(这个绘图的相关优化我就不搞了)。然后将倍数改为了200发现是运行ok,但是FPS一跳一跳的,这个可能跟每2s发生一次导弹有关系,如果想稳定应该是去除发射导弹(当然游戏是需求的),我们先这么试试看FPS是否稳定,ok,不发射导弹FPS的跳动不大,上下跳动大概为2-3帧这样子,加导弹跳动不稳定,因为这导弹的数量基数挺大的。接下来设定最大对象为10000(比较小),但是产生倍数设置为500倍,然后,好吧,很快就万人同屏了,根本跑不动。还是设置个比较合理些的数字吧,设置2000这样子最大数量(更加小了),然后产生倍数还是为500吧(很快就可以达到上限了),接下来看效果,发现敌人数量产生了太多不太对劲,好吧,操作过程有点问题,没有修改到敌人的最大数量,现在设置它为2000,咦...敌人的数量还是有点迷,不会是哪里写错了吧...我决定要显示一下他们的数量出来。妈呀,居然产生了一万多的敌人在同一屏幕,怎么可能不卡呀...奇怪的是都没有设置那么大的对象池呀,这些敌人究竟是怎么产生的呢。带着这样子的疑惑先来看下子弹和导弹的产生是否会超越限制吧,emmm....经过排查发现其实敌人是没有超过2000的限制的(原来我使用的计数方法错误了)。那么目前来说就是,程序无法支撑2000个怪物同屏(起码来说很卡很卡),那么经过优化是否可以达到效果呢?应该如何进一步优化呢?我们可能还是得查看性能优化工具,发现调小了倍数之后发现,其实游戏逻辑执行已经很ok了,倒是渲染方面占用了不少的CPU(这块我就不做优化了)。

接下里进行的是循环无延时的优化,我首先将循环延时给去掉,设定最大对象2000,倍数20,然后CPU占用为24.5%,内存占用25m,FPS:240左右这样子。改回来到倍数1看看,CPU、内存都差不多,FPS为380左右。现在把倍数改成20然后使用性能分析工具进行分析,结果发现,其实优化空间不大,主要还是受到了渲染的限制。

所以得出结论是,目前本项目可以容纳的游戏对象数量主要还是取决于渲染。假设减少渲染速率(就是添加循环延时,一般设置为60FPS每帧),循环中主要占用时间的是sleep函数和绘制函数,当敌人数量增加到一定程度时就会卡,如果想容纳更多的敌人,则需要优化渲染(这个我就不深入研究了)。其余的和敌人的碰撞检测也是非常耗费时间,想要减少这部分时间耗费可以减少检测或者优化检测。

资料以及请求

项目github地址:https://github.com/huanlingkeji/BarrageGame

项目gitee地址:https://gitee.com/huanlingkeji/BarrageGame

其他的参考学习资料

Git的学习推荐以下两个网站:

https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000

https://learngitbranching.js.org/?NODEMO


http://chatgpt.dhexx.cn/article/1deojYfq.shtml

相关文章

【Unity2D入门教程】简单制作战机弹幕射击游戏⑥最终回扩展其它范围的内容

制作分数和生命的UI: 由于我们前面没有做类似的UI所以这里教大伙一下基本思路: 首先我们创建一个canvas用来创建两个Text用来显示分数和生命的UI 蓝色的是分数黄色的是生命 我们创建一个scoreplay的脚本挂载在text上 using System.Collections; usi…

【Unity2D入门教程】简单制作战机弹幕射击游戏③C#编写 子弹Laser脚本

学习目标: 今天教大伙怎么设置子弹Laser的组件以及编写关于它的脚本 学习组件: 老规矩还是添加一下它的组件用capusle collider2D刚好它还是个圆柱体,别忘了勾选它的isTrigger Rigibody2D的Body Type调成是让它不受重力以及物理碰撞影响&a…

【Unity2D入门教程】简单制作战机弹幕射击游戏⑦番外篇扩展一个大型敌人

布置场景: 我们先按CrtlD复制一个之前创建好的Enemy Prefab,然后更改它的Sprite,我们拖一张素材的图片进来并改变它的Pixel Per Unit让它大其它敌机一倍 由于体型变大了之前的Coliider就不适用了我们要重新删掉之前的再创建一个新的 同样它的…

【Unity2D入门教程】简单制作战机弹幕射击游戏④C#编写 敌人按指定路径以及敌人生成点脚本

前言: 我们前面忘记设置的当敌机和子弹碰到特定的位置(指屏幕外的)就会自动销毁 挂载的脚本Sherred如下 using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shredder : MonoBehaviour {private…

【Unity2D入门教程】简单制作战机弹幕射击游戏⑤C#编写 背景滚动移动以及增加粒子特效

学习目标: 上期结束后我们游戏的主体就已经差不多了,剩下的就要给游戏的真实质感以及可玩性上下大点功夫了,今天我们就从背景滚动移动以及增加粒子特效上出发。 背景滚动制作: 为了让游戏更具真实性的就是让玩家从视觉上感受飞机…

【Unity2D入门教程】简单制作战机弹幕射击游戏② C#编写 Player和Enemy脚本

学习目标: 上期的水平大伙看到我已经写好了Enemy和Player的脚本了,现在就把脚本教给大伙,话不多说搞的不丑 学习内容: 首先是Player的脚本(之前没发现CSDN有这个代码段的,我的我的) using Sy…

【Unity2D入门教程】简单制作战机弹幕射击游戏① 导入素材

学习目标: 今天我们制作一款类似银河战机的游戏导入素材并给它设置一下背景,把素材做成预设体等等 首先先上网找到我们需要的素材 Unity Asset Store - The Best Assets for Game MakingDiscover the best assets for game making. Choose from our mas…

弹幕射击游戏中旋转矩形碰撞检测的算法描述

分离轴法是根据两个多边形的几何中心在任意矢量方向的法线上的投影存在交叉的条件来做出的方法[32]。换而言之,如果可以找出这样一个方向,将两个多边形投影在此方向的法线上的投影不交叉,则说明碰撞未发生,如图5-3所示。 图中A、B…

Cocos2d-x 简单弹幕射击游戏

开发环境 Win10, Cocos2d-x v3.16, Visual Studio 2017, Visual Studio Code 项目阐述 这个游戏是一个小型的弹幕游戏。玩家将控制pipi美,在躲避pop子的子弹之余,射杀pop子。名字打算叫“pop子的深邃黑暗幻想”。 游戏的设计主要借鉴了东方系列的弹幕游…

程序员职业规划和学习规划

程序员职业规划路线 技术体系 阿里程序员等级 学习规划

程序员职业发展规划

程序员职业生涯发展到一定的程度都会面临着职业发展方向选择的问题,随着年龄的增长,面对日新月异的代码,感到力不从心,更年轻的程序员层出不穷,这些都是促使程序员向另一个方向进行转型,那么,程…

程序员10年职业规划

从事Dotnet程序开发工作近10年了,从开始的月薪3k的小程序员菜鸟,到现在年薪60w的项目总经理,从战战兢兢的去各个公司应聘,到现在开始面试那些战战兢兢的小程序员,回想起这近十年来的经验,看着还是朝气蓬勃的…

女生做软件测试的职业规划,来说说女程序员的职业规划要怎么做

如果你是一名女程序员,你一定会为如何规划自己的职业生涯而感到苦恼。本期乔布 女程序员的职业规划 要怎么做。 关键词: 女程序员的职业规划 总体来说,女程序员的职业规划路线主要有四种:技术线路;业务路线&#xff1b…

女程序员的职业规划

女程序员的职业规划 引子 很多姐妹发微博评论留言给我,说希望我写一篇关于女程序员职业规划的文章。很惭愧,我自己的职业规划都还没亲自验证完毕,coder之路还在继续,在此仅仅分享我的所闻、所见、所学、所思,有…

Android程序媛大厂拧螺丝,未来职业如何规划?

作者:程军 上周有一位朋友咨询我职业规划问题。 我先介绍一下背景,她工作 2 年,985 本科毕业,目前在一家互联网大厂任职 Android工程师。 她主要咨询了我 4 个困惑问题,比如大厂拧螺丝怎么破?全年 996 没…

女神节-女性程序员有哪些好的职业发展路线

1、前言 祝天下作所有的女生节日快乐,在今天这个日子里,多陪陪自己的爱人、母亲,有时候不一定要礼物,真诚的对待和陪伴就是对她最好的爱。 最近为公司招聘测试岗位,岗位需求最好是女生,因为领导觉得在测…

一名女程序员的职业规划

前言: 在IT这个行业做了有几年了,身边的一些朋友有一部分已经转行了,也有部分正在为转行打基础做准备。所以我最近也在考虑这个问题,是应该继续做技术开发还是转产品之类的,还是直接转行做其他的职业。写下这篇博文&a…

c语言之运算符号

c语言的运算符有很多,希望做这份总结能够充分的利用这些运算符,编写出自己想要达到的效果 -----------------------------这是虚线还是实线,傻傻的分不清楚--------------------------------- 算数操作符 ###逻辑操作符 ###按位操作符 …

c语言运算符大全极其意义,C语言运算符大全

.. ;. C语言运算符大全 C语言的内部运算符很丰富,运算符是告诉编译程序执行特定算术或逻辑操作的符号。C语言有三大运算符:算术、关系与逻辑、位操作。另外,C还有一些特殊的运算符,用于完成一些特殊的任务。 2.6.1算术运算符 表2-…