STM32学习笔记十三:WS2812制作像素游戏屏-飞行射击游戏(3)探索数据管理

本文主要是介绍STM32学习笔记十三:WS2812制作像素游戏屏-飞行射击游戏(3)探索数据管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这回,开始做敌机。

我们设定敌机有3中,小型,大型和BOSS,分别叫 EnemyT1 / EnemyT2 / EnemyT3。

先定义一个敌机基类:

EnemyBase.h

/** EnemyBase.h**  Created on: Dec 24, 2023*      Author: YoungMay*/#ifndef SRC_PLANE_ENEMYBASE_H_
#define SRC_PLANE_ENEMYBASE_H_
#include "../drivers/DList.h"
#include "PlaneDef.h"class EnemyBase {
public:EnemyBase();virtual ~EnemyBase() {}virtual uint8_t tick(uint32_t t)=0;virtual void init()=0;virtual uint8_t show(void)=0;ListNode *enemyBulletList;PlaneObject_t baseInfo;uint16_t HP;
};#endif /* SRC_PLANE_ENEMYBASE_H_ */

然后是三种敌机:

EnemyT1.h

/** EnemyT1.h**  Created on: Dec 24, 2023*      Author: YoungMay*/#ifndef SRC_PLANE_EnemyT1_H_
#define SRC_PLANE_EnemyT1_H_
#include "EnemyBase.h"class EnemyT1: public EnemyBase {
public:EnemyT1();~EnemyT1();uint8_t tick(uint32_t t);void init();uint8_t show(void);bool sharp[3][3] = {{ 1, 0, 1 },{ 1, 1, 1 },{ 0, 1, 0 } };};#endif /* SRC_PLANE_EnemyT1_H_ */

 EnemyT1.cpp

/** EnemyT1.cpp**  Created on: Dec 24, 2023*      Author: YoungMay*/#include "EnemyT1.h"EnemyT1::EnemyT1() {HP = 100;baseInfo.speed = 15;baseInfo.width = 3;baseInfo.height = 3;getRainbowColor(&baseInfo.color, 400);
}EnemyT1::~EnemyT1() {// TODO Auto-generated destructor stub
}void EnemyT1::init() {}uint8_t EnemyT1::tick(uint32_t t) {baseInfo.y += t * baseInfo.speed;return 0;
}uint8_t EnemyT1::show(void) {for (uint8_t y = 0; y < baseInfo.height; y++) {for (uint8_t x = 0; x < baseInfo.width; x++) {if (sharp[y][x])ws2812_pixel(x + baseInfo.x / PlaneXYScale - baseInfo.width / 2,y + baseInfo.y / PlaneXYScale - baseInfo.height / 2,baseInfo.color.R, baseInfo.color.G, baseInfo.color.B);}}return 0;
}

 EnemyT2.h

/** EnemyT2.h**  Created on: Dec 24, 2023*      Author: YoungMay*/#ifndef SRC_PLANE_EnemyT2_H_
#define SRC_PLANE_EnemyT2_H_
#include "EnemyBase.h"class EnemyT2: public EnemyBase {
public:EnemyT2();~EnemyT2();uint8_t tick(uint32_t t);void init();uint8_t show(void);bool sharp[5][5] = {{ 0, 1, 1, 1, 0 },{ 0, 0, 1, 0, 0 },{ 1, 1, 1, 1, 1 },{ 0, 1, 0, 1, 0 },{ 0, 1, 0, 1, 0 }};
};#endif /* SRC_PLANE_EnemyT2_H_ */

EnemyT3.h 

/** EnemyT3.h**  Created on: Dec 24, 2023*      Author: YoungMay*/#ifndef SRC_PLANE_EnemyT3_H_
#define SRC_PLANE_EnemyT3_H_
#include "EnemyBase.h"class EnemyT3: public EnemyBase {
public:EnemyT3();~EnemyT3();uint8_t tick(uint32_t t);void init();uint8_t show(void);bool sharp[5][9] = { { 0, 0, 1, 1, 1, 1, 1, 0, 0, },{ 0, 0, 0, 0, 1, 0, 0,	0, 0, }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, },{ 0, 0, 1, 0, 1, 0, 1, 0, 0, },{ 0, 0, 0, 0, 1, 0, 0, 0, 0, }};};#endif /* SRC_PLANE_EnemyT3_H_ */

EnemyT2.cpp和EnemyT3.cpp 也几乎雷同。

是不是可以抽基类方法?可以!

是不是可以用宏定义?可以!

那为什么不呢?后面再说。

现在各种对象类型开始多起来了,我们需要考虑怎么管管这么多东西。

传统C中,数据都是直接铺在内存空间中,依赖各种地址各种指针进行管理,只要考虑空间是否足够,考虑再合适的时候释放空间。所以,最好的方式是计算好各类数据可能需要的地址空间,然后把各类数据放到指定的位置。要用的时候,按地址进行访问就行了。

但游戏中不确定数量的东西有可能无法计算,一旦预留太多则会浪费空间。这在单片机这种小内存设备上是致命的。

所以本次项目,我尝试使用C++的面向对象技术。面向对象思想更符合游戏的逻辑。额,其实吧,是我本人多年JAVA开发经验,身上每个细胞都是一个独立对象,对这太熟了,偷乐。而对C++的面向对象不太熟,借此机会学习一下。

言归正传。

我们先把数据结构的定义都搬到一起,以方便后面的各种引用。

PlaneDef.h

/** PlaneDef.h**  Created on: Dec 22, 2023*      Author: YoungMay*/#ifndef SRC_MENU_PLANEOBJECT_H_
#define SRC_MENU_PLANEOBJECT_H_#include "../drivers/ws2812Frame.h"
#include "../drivers/tools.h"
#include "../drivers/keys.h"#define PlaneXYScale 10000typedef struct {int x;int y;RGBColor_TypeDef color;uint8_t speed;uint8_t visiable = 0;uint8_t width;uint8_t height;
} PlaneObject_t;typedef struct {int x;int y;int speedX;int speedY;RGBColor_TypeDef color;uint8_t visiable = 0;
} BulletObject_t;typedef struct {uint8_t type;int x;int y;int speedX;int speedY;uint16_t hp;uint8_t visiable = 0;
} EnemyObject_t;typedef struct {uint32_t lastTick = 0;uint32_t defaultSpan = 100;uint8_t tick(uint32_t tick) {if (lastTick > tick) {lastTick -= tick;return 0;} else {lastTick = defaultSpan;return 1;}}
} IntervalAniTimer_t;#endif /* SRC_MENU_PLANEOBJECT_H_ */

定义一个EnemyManager类管理所有敌机。

EnemyManager.h

/** EnemyManager.h**  Created on: Dec 23, 2023*      Author: YoungMay*/#ifndef SRC_PLANE_ENEMYMANAGER_H_
#define SRC_PLANE_ENEMYMANAGER_H_
#include "../drivers/DList.h"
#include "PlaneDef.h"
#include "EnemyT1.h"
#include "EnemyT2.h"
#include "EnemyT3.h"class EnemyManager {
public:EnemyManager();virtual ~EnemyManager();uint8_t tick(uint32_t t);void init();uint8_t show(void);ListNode *enemyList;ListNode *bulletList;private:IntervalAniTimer_t createTimer = { 0, 2000 };EnemyBase* createEnemyObject();uint16_t enemyTypeProportion[3] = { 120, 20, 3 };
};#endif /* SRC_PLANE_ENEMYMANAGER_H_ */

其中,我们用enemyList保存所有敌机实例,用bulletList管理所有敌机发射的子弹实例。

这样,我们在plane类的tick主函数中, 调用EnemyManager里面的tick方法,再由EnemyManager去调用每个enemy的tick方法。

后续,我们也是采用这一套逐级往下调用tick的方式,保证每个实例都能被执行tick,且在tick的入参中得到运行时间,已决定动作进行到哪里。

EnemyManager.cpp

/** EnemyManager.cpp**  Created on: Dec 23, 2023*      Author: YoungMay*/#include "EnemyManager.h"EnemyManager::EnemyManager() {enemyList = ListCreate();}EnemyManager::~EnemyManager() {for (ListNode *cur = enemyList->next; cur != enemyList; cur = cur->next) {delete ((EnemyObject_t*) (cur->data));}ListDestory(enemyList);
}void EnemyManager::init() {}EnemyBase* EnemyManager::createEnemyObject() {uint8_t ran = ran_seq(3, enemyTypeProportion);switch (ran) {case 0:return new EnemyT1();case 1:return new EnemyT2();case 2:return new EnemyT3();}return NULL;
}
uint8_t EnemyManager::tick(uint32_t t) {if (createTimer.tick(t)) {EnemyBase *enemy = createEnemyObject();ListPushBack(enemyList, (LTDataType) enemy);}for (ListNode *cur = enemyList->next; cur != enemyList; cur = cur->next) {EnemyBase *enemy = ((EnemyBase*) (cur->data));enemy->tick(t);}return 0;
}uint8_t EnemyManager::show(void) {ListNode *cur = enemyList->next;while (cur != enemyList) {EnemyBase *enemy = ((EnemyBase*) (cur->data));if (!enemy->baseInfo.visiable) {delete enemy;ListErase(cur);} else {enemy->show();}cur = cur->next;}return 0;
}

注意1:其中用到一个工具方法:

每种敌机出现的机率不一样的。我们初定为120:20:3

enemyTypeProportion[3] = { 120, 20, 3 }

封装一个函数来实现它:

int ran_seq(int count, uint16_t *seqs) {uint32_t i, sum = 0;for (i = 0; i < count; i++) {sum += seqs[i];}uint32_t ran = ran_max(sum);sum = 0;for (i = 0; i < count; i++) {sum += seqs[i];if (ran < sum) {return i;}}return 0;
}

注意2:

enemyList 由enemyManager来管理,在enemyManager构造函数中初始化。

bulletList却不是,它是在bulletManager中进行管理的。只是把地址传过去,以方便使用。

现在我们总结一下整个数据管理。

1、玩家数据最复杂,最个性化,所以有自己独立的类、属性等。自行管理。

2、差异较小的且无什操作的对象,我们放在管理类中对数据和方法进行统一管理。如星空背景、子弹,并不是每颗星星都是一个对象,而仅仅是BackGroundStar类中的一条数据。

3、介于二者之间的,属性具有一致性,但动作存在差异性的对象,划归不同的类,但放在同一个管理类中进行统一管理。如敌机,分3个类,但放在一个enemyList 中。回答上面问题,虽然三种敌机的实现基本一致,但是分开,是为了将来他们可以有不同的动作逻辑(tick)和不同的显示方法(show)。

4、不定数量的对象,放在队列中,该队列应放在该对象的Manager类中。跨队对象类型的数据访问,可以把队列地址传递给对方。

看看效果:

STM32学习笔记十三:WS2812制作像素游戏屏-飞行射击

似乎不太好,我们优化一下:

1、BOSS 飞行速度慢,开始出现时,只是冒出一点小头,我们要让他先冲出来,再减速。

EnemyT3.cpp

EnemyT3::EnemyT3() {HP = 10000;baseInfo.speed = 50;baseInfo.width = 9;baseInfo.height = 5;getRainbowColor(&baseInfo.color, 600);
}uint8_t EnemyT3::tick(uint32_t t) {baseInfo.y += t * baseInfo.speed;if (baseInfo.y > 5 * PlaneXYScale) {baseInfo.speed = 1;}if (baseInfo.y > 64 * PlaneXYScale)baseInfo.visiable = 0;return 0;
}

2、BOSS虽然小几率出现,但是仍有可能短时间出现多个。所以,我们改为中小飞机按比例随机出现,而BOSS定时1分钟出现。为BOSS的出现单独设置一个定时器。

EnemyManager.h

class EnemyManager {
public:EnemyManager();virtual ~EnemyManager();uint8_t tick(uint32_t t);void init();uint8_t show(void);ListNode *enemyList;ListNode *bulletList;private:IntervalAniTimer_t createTimer = { 0, 2000 };IntervalAniTimer_t createBossTimer = { 30000,60000};EnemyBase* createEnemyObject();uint16_t enemyTypeProportion[3] = { 120, 20 };
};

EnemyManager.cpp

EnemyBase* EnemyManager::createEnemyObject() {uint8_t ran = ran_seq(2, enemyTypeProportion);switch (ran) {case 0:return new EnemyT1();case 1:return new EnemyT2();}return NULL;
}
uint8_t EnemyManager::tick(uint32_t t) {if (createTimer.tick(t)) {EnemyBase *enemy = createEnemyObject();ListPushBack(enemyList, (LTDataType) enemy);}if (createBossTimer.tick(t)) {EnemyBase *enemy = new EnemyT3();ListPushBack(enemyList, (LTDataType) enemy);}for (ListNode *cur = enemyList->next; cur != enemyList; cur = cur->next) {EnemyBase *enemy = ((EnemyBase*) (cur->data));enemy->tick(t);}return 0;
}

数据管理都在各自的manager类里面了,但是跨manager的数据访问怎么办?我们做一个单例的数据仓库类,保留各种数据地址指针。当然,这个仓库可不光为了本飞行射击游戏使用,其他应用也是可以用的。

1、创建数据仓库DataBulk,这是个单例。

DataBulk.h

/** DataBulk.h**  Created on: Dec 27, 2023*      Author: YoungMay*/#ifndef SRC_DRIVERS_DATABULK_H_
#define SRC_DRIVERS_DATABULK_H_
#include "stddef.h"
#include "stdint.h"class DataBulk {
private:static DataBulk *p;
public:DataBulk();virtual ~DataBulk();static DataBulk* GetInstance() //定义一个公有函数,可以获取这个唯一的实例,并且在需要时创建该实例。{if (p == NULL)  //判断是否第一次调用p = new DataBulk;return p;}intptr_t data1;intptr_t data2;
};#endif /* SRC_DRIVERS_DATABULK_H_ */

在main.cpp里面进行初始化

/* USER CODE BEGIN 0 */
DataBulk *DataBulk::p = nullptr;
/* USER CODE END 0 */

现在还不知道需要哪些数据,先把玩家数据弄进去吧,毕竟最有可能用到。

在plane的init函数中,把地址存进去:

void Plane::init() {。。。DataBulk::GetInstance()->data1 = (intptr_t) &player1.baseInfo;DataBulk::GetInstance()->data2 = (intptr_t) &player2.baseInfo;}

宏定义简化访问:

#define Player1BaseInfo ((PlaneObject_t*)DataBulk::GetInstance()->data[0])
#define Player2BaseInfo ((PlaneObject_t*)DataBulk::GetInstance()->data[1])

OK.期待下一章。

STM32学习笔记十四:WS2812制作像素游戏屏-飞行射击游戏(4)探索碰撞检测

这篇关于STM32学习笔记十三:WS2812制作像素游戏屏-飞行射击游戏(3)探索数据管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/555662

相关文章

opencv实现像素统计的示例代码

《opencv实现像素统计的示例代码》本文介绍了OpenCV中统计图像像素信息的常用方法和函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 统计像素值的基本信息2. 统计像素值的直方图3. 统计像素值的总和4. 统计非零像素的数量

使用Python制作一个PDF批量加密工具

《使用Python制作一个PDF批量加密工具》PDF批量加密‌是一种保护PDF文件安全性的方法,通过为多个PDF文件设置相同的密码,防止未经授权的用户访问这些文件,下面我们来看看如何使用Python制... 目录1.简介2.运行效果3.相关源码1.简介一个python写的PDF批量加密工具。PDF批量加密

Python开发围棋游戏的实例代码(实现全部功能)

《Python开发围棋游戏的实例代码(实现全部功能)》围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的... 目录1. 围棋游戏概述1.1 游戏规则1.2 游戏设计思路2. 环境准备3. 创建棋盘3.1 棋盘类

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]