注:在本篇博客中,对上一篇博客的飞机大战游戏进行了完善,但有很多细微的修改,由于篇幅原因,没有把所有代码列出来,大家需要仔细阅读,否则可能漏掉一些地方,导致编译错误或产生bug。
PS:如果大家有什么好的想法,比如想出什么新功能,可以在评论区留言。

简介

在之前的博客中,我们做过一个飞机大战游戏,但我后来重新看了一下代码,发现当时的代码质量太烂了。举一个例子:在设计敌人飞机时,我应该写一个AutoMoveComponent类,然后把这个组件和Plane一组合就够了,当时却傻傻乎乎重写了个Enemy类。痛定思痛,我今天打算把代码重构一下,同时增加一些功能,比如召唤友军等。加上召唤友军这个功能也是非常爽的,敌人毫无还手之力,不信看下游戏截图:

重构代码

重构后程序主要的类大概有这些:

首先,我们删掉Enemy类,新建AutoMoveComponent,AutoFireBulletsComponent,InputComponent这三个类。然后,我们就要把以前写在Plane类的功能抽出来分别放到三个类里。方法很简单,这里不再赘述,我们只来看一下修改后四个类的代码。

Plane

Plane.h:

#pragma once
#include"Actor.h"
class Plane :public Actor
{public:Plane(class Game* game, const Vector2& pos, bool IsEnemy = false);bool IsEnemy()const { return mIsEnemy; }
private:bool mIsEnemy;
};

可以看出,这里删去了大量实现细节的成员变量和函数,只增加了一个IsEnemy区分我方和敌方。
Plane.cpp的实现就更简单了,直接删去了ActorInput和UpdateActor这“两大巨头”,剩下的只有一个构造函数了。全部代码如下:

#include "Plane.h"
Plane::Plane(Game* game, const Vector2& pos,bool IsEnemy) :Actor(game),mIsEnemy(IsEnemy)
{SetPosition(pos);
}

AutoMoveComponent

这个类就是把以前Plane和Enemy类的代码相关部分移动到了这里。
AutoMoveComponent.h:

#pragma once
#include "Component.h"
class AutoMoveComponent :public Component
{public:AutoMoveComponent(Actor* owner, int updateOrder = 100);virtual void Update(float deltaTime);
private:Uint32 mMoveTicks;short mMove;
};

AutoMoveComponent.cpp:

#include "AutoMoveComponent.h"AutoMoveComponent::AutoMoveComponent(Actor* owner, int updateOrder) : Component(owner, updateOrder), mMoveTicks(SDL_GetTicks())
{mMove = 200 + rand() % 100;if (rand() % 2)mMove = -mMove;
}void AutoMoveComponent::Update(float deltaTime)
{Vector2 pos = mOwner->GetPosition();if (SDL_TICKS_PASSED(SDL_GetTicks(), mMoveTicks + 1000))//随机移动位置{mMoveTicks = SDL_GetTicks();mMove = 100 + rand() % 100;if (rand() % 2)mMove = -mMove;}pos.x += deltaTime * mMove;if (pos.x > 1024 - 50)pos.x = 1024 - 50;if (pos.x < 0)pos.x = 0;mOwner->SetPosition(pos);
}

AutoFireBulletsComponent

同上。
AutoFireBulletsComponent.h:

#pragma once
#include "Component.h"
class AutoFireBulletsComponent :public Component
{public:AutoFireBulletsComponent(Actor* owner, int updateOrder = 100);virtual void Update(float deltaTime);
private:Uint32 mTicks;
};

AutoFireBulletsComponent.cpp:

#include "AutoFireBulletsComponent.h"
#include"DrawRectangleComponent.h"
#include"Bullet.h"
#include"Plane.h"
AutoFireBulletsComponent::AutoFireBulletsComponent(Actor* owner, int updateOrder):Component(owner,updateOrder),mTicks(SDL_GetTicks())
{}void AutoFireBulletsComponent::Update(float deltaTime)
{if (SDL_TICKS_PASSED(SDL_GetTicks(), mTicks + 1000) && !(rand() % 25))//1秒发射子弹{Vector2 pos = mOwner->GetPosition();mTicks = SDL_GetTicks();pos.x += 20;pos.y += (((Plane*)mOwner)->IsEnemy() ? 40 : -40);new DrawRectangleComponent(new Bullet(mOwner->GetGame(), pos, ((Plane*)mOwner)->IsEnemy() ? 700 : -700) , Vector2(10, 20), 255, 0, 0, 0);}
}

InputComponent

同上。
InputComponent.h:

#pragma once
#include "Component.h"
class InputComponent :public Component
{public:InputComponent(Actor* owner, int updateOrder = 100);virtual void ProcessInput(const uint8_t* keyState);virtual void Update(float deltaTime);
private:short mPlaneDir;Uint32 mTick;
};

InputComponent.cpp:

#include "InputComponent.h"
#include"DrawRectangleComponent.h"
#include"Bullet.h"
InputComponent::InputComponent(Actor* owner, int updateOrder):Component(owner,updateOrder), mPlaneDir(0),mTick(SDL_GetTicks())
{}void InputComponent::ProcessInput(const uint8_t* keyState)
{mPlaneDir = 0;if (keyState[SDL_SCANCODE_RIGHT])mPlaneDir += 1;if (keyState[SDL_SCANCODE_LEFT])mPlaneDir -= 1;if (keyState[SDL_SCANCODE_SPACE] && SDL_TICKS_PASSED(SDL_GetTicks(), mTick + 300))//0.3秒发射一颗子弹{mTick = SDL_GetTicks();Vector2 pos = mOwner->GetPosition();pos.x += 20;pos.y -= 40;new DrawRectangleComponent(new Bullet(mOwner->GetGame(), pos, -700), Vector2(10, 20), 255, 0, 0, 0);}
}void InputComponent::Update(float deltaTime)
{Vector2 pos = mOwner->GetPosition();pos.x += mPlaneDir * 300 * deltaTime;if (pos.x < 0)pos.x = 0;if (pos.x > 1024 - 50)pos.x = 1024 - 50;mOwner->SetPosition(pos);
}

这样一来,代码质量就好多了。我们只需要把Game类中new出飞机的代码稍微一改就可以了。具体来说,需要添加多个组件,new出我方飞机的示例:

Plane* plane = new Plane(this, Vector2(492, 700));
new DrawPlaneComponent(plane);//绘制组件
new InputComponent(plane);//控制飞机组件

new出敌方飞机的示例:

Plane* enemy = new Plane(this, Vector2(rand() % 984, 10),true);
new DrawPlaneComponent(enemy);//绘制组件
new AutoFireBulletsComponent(enemy);//自动射击组件
new AutoMoveComponent(enemy);//自动移动组件

DrawPlaneComponent

由于博主还得上学,这篇博客断断续续写了三周左右,这个类不确定改没改,直接上代码,如果和以前的不一样,就用这个版本的。
DrawPlaneComponent.h:

#pragma once
#include"DrawComponent.h"
class DrawPlaneComponent :public DrawComponent
{public:DrawPlaneComponent(class Plane* actor, int drawOrder = 100);virtual void Draw(SDL_Renderer* renderer);
private:bool mIsEnemy;
};

DrawPlaneComponent.cpp:

#include "DrawPlaneComponent.h"
#include"Plane.h"
DrawPlaneComponent::DrawPlaneComponent(Plane* actor, int drawOrder) :DrawComponent(actor, drawOrder),mIsEnemy(actor->IsEnemy())
{}void DrawPlaneComponent::Draw(SDL_Renderer* renderer)
{SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);const Vector2& pos = mOwner->GetPosition();SDL_Rect rect = { pos.x,pos.y,50,30 };SDL_RenderFillRect(renderer, &rect);SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);rect = { (int)pos.x + 20,(int)pos.y - 20,10,20 };if (mIsEnemy)rect.y += 40;SDL_RenderFillRect(renderer, &rect);
}

修改代码

大框架的重构完成了,我们再来修改一下细节,优化效率。

使用list代替vector

首先,我们之前提到过,Game类中的一系列容器完全可以用list代替 ,效率更高,我们就直接把vector改成list,然后编译,把报错地方略微修改就可以。

飞机和石头单独存储

在上一个版本里,飞机是没有单独存储在一个地方,判断子弹击中飞机,我们用的是RTTI(运行时类型信息)。但这样有一个缺点,大家可以看一下第二个运行示意图,有没有发现什么不足之处?没错,如果后期加入召唤友军功能,子弹数量急剧增多,遍历整个mActors效率非常低下。这样,我们可以添加一个单独的mPlanes容器,只需要遍历mPlanes就行,不需要RTTI。当然,这是一种以空间换时间的方法,所有的飞机都会被存储两份。类似地,对Stone类也做相同的处理。具体来说,我们在Game类中增加两个容器,mPlanes和mStones,然后类比AddActor和RemoveActor,添加AddXXX和RemoveXXX函数,不用考虑是否在更新。然后,在Plane和Stone类的构造函数中分别调用game类的AddPlane/Stone函数,重写析构函数,调用RemovePlane/Stone函数。
这样一来,还需要修改一下,将Stone类中判断与子弹碰撞的代码放到Bullet类中,单独遍历mStones,其它遍历的地方也进行相应的修改。修改后的Bullet::UpdateActor:

void Bullet::UpdateActor(float deltaTime)
{Vector2 pos = GetPosition();pos.y += mSpeed * deltaTime;SetPosition(pos);if (pos.y > 768 || pos.y < 0)SetState(EDead);for (auto i : GetGame()->mPlanes){if (i->IsEnemy()){Vector2 bPos = i->GetPosition();if (bPos.x - 10 < pos.x && bPos.x + 50 > pos.x && bPos.y + 50 > pos.y){SetState(EDead);i->SetState(EDead);}}else{Vector2 bPos = i->GetPosition();if (bPos.x - 10 < pos.x && bPos.x + 50 > pos.x && bPos.y < pos.y + 20 && bPos.y + 30 > pos.y){SetState(EDead);i->SetState(EDead);}}}for (auto i : GetGame()->mStones){Vector2 bPos = i->GetPosition();if (pos.x + 20 > bPos.x && pos.x < bPos.x + 50 && pos.y < bPos.y + 50){SetState(EDead);i->SetState(EDead);GetGame()->mStoneSpeed *= 1.02;}}
}

Stone::UpdateActor:

void Stone::UpdateActor(float deltaTime)
{Vector2 pos = GetPosition();pos.y += deltaTime * mSpeed;if (pos.y > 768)SetState(EDead);SetPosition(pos);for (auto i : GetGame()->mPlanes){if (!i->IsEnemy()){Vector2 bPos = i->GetPosition();if (bPos.x + 50 > pos.x && bPos.x < pos.x + 50 && bPos.y < pos.y + 50 && bPos.y + 30>pos.y){SetState(EDead);i->SetState(EDead);}}}
}

增加新功能

目前我就想出来一个召唤友军的功能,我们就先添加这一个功能。得益于以前代码的低耦合度,实现这个功能,根本不需要添加类,直接在想召唤的地方new出来,添加敌方飞机的那几个组件就行了。
我们可以在Game类中增加mFriendCount变量,记录友军数量。每new出一个友军就+1,每死亡一个友军-1。这部分代码可以在Game类的Add/RemovePlane函数中实现,此处略去。当然,这样一来,所有new飞机的地方都不再需要手动修改mFriendCount和mEnemyCount的数量了。
然后,在ProcessInput函数中加入以下代码,实现按下F键召唤友军:

if (state[SDL_SCANCODE_F])//召唤友军
{Plane* myfriend = new Plane(this, Vector2(rand() % 984, 700));new DrawPlaneComponent(myfriend);new AutoFireBulletsComponent(myfriend);new AutoMoveComponent(myfriend);
}

这样一来,就可以实现按下F键召唤友军了。如果想实现一些很好玩的画面,可以在游戏开始的时候new出大量友军和大量敌人,然后坐观虎斗

C++游戏编程教程(七)——改进飞机大战游戏相关推荐

  1. python飞机大战游戏高级_05.python实现飞机大战游戏

    python的核心编程跟着学习完了,终于到了第一个实战项目的演练,很是激动. 按着黑马老师指导的思路,代码主要分成两个模块主模块plane_main.py和工具模块plane_sprites.py. ...

  2. 飞机大战游戏python_基于Python的飞机大战游戏

    基于 Python 的飞机大战游戏 杨铠 [期刊名称] <电脑迷> [年 ( 卷 ), 期] 2017(000)021 [摘要] 我们每天都享受到科技带来的好处 , 了解计算机编程对每个人 ...

  3. iPhone游戏编程教程一步步教你游戏开发

    这是此系列教程的第一部分,我将从最基本开始教大家怎样编写一个iPhone游戏.众所周知,OpenGL和Quartz的学习不是那么简单的.本教程将简化开发的过程而不会使用这两种技术.我们将使用UIIma ...

  4. 飞机大战游戏python_使用python完成飞机大战游戏

    听说做不了飞机大战都不算入门一个编程语言,今儿我们就来完成飞机大战的制作 分析 1.创建窗口:首先需要定义一个游戏运行的窗口(pygame)用来展示游戏的界面 2.移动飞机:能够使飞机通过键盘移动并发 ...

  5. 用Java编写飞机大战游戏

    飞机大战(Plane War)是一款非常受欢迎的小游戏,它通过增加玩家的难度和挑战性,促使玩家不断提高自己的操作能力和反应速度,并在升级过程中逐步拓展游戏世界的规模和内容.本文将介绍如何使用Java编 ...

  6. Python+pygame飞机大战游戏

    转载自IIronMan 的博客 Python:飞机大战游戏1:前期准备 Python:飞机大战游戏2:pygame 快速入门 Python:飞机大战游戏3:框架搭建 Python:飞机大战游戏4:背景 ...

  7. 鸿蒙开发实例 | ArkUI JS飞机大战游戏开发

    本篇介绍使用ArkUI JS框架开发一款基于鸿蒙操作系统的飞机大战游戏.这款飞机大战游戏是在普通单机游戏的基础上添加了鸿蒙操作系统分布式支持,使游戏可以同时使用多台鸿蒙操作系统设备,为游戏玩家提供分布 ...

  8. 【iVX 初级工程师培训教程 10篇文拿证】05 画布及飞机大战游戏制作

    目录 [iVX 初级工程师培训教程 10篇文拿证]01 了解 iVX 完成新年贺卡 [iVX 初级工程师培训教程 10篇文拿证]02 数值绑定及自适应网站制作 [iVX 初级工程师培训教程 10篇文拿 ...

  9. 基于Java的飞机大战游戏的设计与实现论文

    源码下载 http://www.byamd.xyz/hui-zong-1/ 摘 要 现如今,随着智能手机的兴起与普及,加上4G(the 4th Generation mobile communicat ...

最新文章

  1. 【目标检测】yolo系列:从yolov1到yolov5之YOLOv2详解及复现
  2. c#endread怎么打印出来_c# – Socket.EndRead 0字节意味着断开连接?
  3. 二十一、Hadoop学记笔记————kafka的初识
  4. mysql随机选择记录表_Mysql表中取随机记录
  5. 127.0.0.1与localhost的区别
  6. 实操代码带你理解CSS中的常用选择器(你值得掌握!)
  7. java面向对象编程集合边框_第六章使用java实现面向对象-集合框架
  8. excel表头_「Excel技巧」关于Excel表格打印常见的四个问题,你会解决吗?
  9. 【编辑器】Vim学习笔记
  10. 赛尔笔记 | 自然语言处理中的迁移学习(下)
  11. 《统计会犯错——如何避免数据分析中的统计陷阱》—第1章构建置信区间
  12. AI2019下载Adobe Illustrator CC2019安装教程
  13. win10 蓝牙搜不到设备怎么办
  14. MySQL实战第二十二讲-MySQL有哪些“饮鸩止渴”提高性能的方法?
  15. 2020.8.13 京东Android开发二面
  16. arm64 中的__virt_to_phys
  17. 怎么提高程序的可修改性
  18. 干货!#黑客大会PPT资源#,快来领取吧!
  19. 2021年7月世界编程语言排行
  20. 会员积分体系付费会员的运营优化方法

热门文章

  1. 项目组内个角色的职责说明,仅针对当前项目组,其他项目组慎用,呵呵
  2. antd的联级选择器异步调用编辑回显_react-uplod-img 是一个基于 React antd组件的图片上传组件...
  3. 设计模式:里氏替换原则(详解)
  4. Android获取SD卡总容量,可用大小,机身内存总容量及可用大小的系统方法
  5. OPENGLES 绘制纹理带黑圈pre-multiplying
  6. 常用地图投影转换公式 (转载)
  7. 串口控制74HC164C语言,51单片机74HC164串口控制数码管显示
  8. Excel中的三种平均值算法
  9. 拓扑排序介绍及其应用
  10. 如何将应用隐藏成一个计算机,局域网中如何隐藏自己的计算机