STM32学习笔记十八:WS2812制作像素游戏屏-飞行射击游戏(8)探索游戏多样性,范围伤害模式

本文主要是介绍STM32学习笔记十八:WS2812制作像素游戏屏-飞行射击游戏(8)探索游戏多样性,范围伤害模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 前面我们的攻击手段比较单一,虽然已经分出了 EnemyT1 / EnemyT2 / EnemyT3, 但里面还是基本一样的。这回,我们尝试实现一些新的攻击方法,实现一些新的算法。

1、前面我们小飞机EnemyT1 的攻击方式是垂直向下发射子弹。

那么大飞机EnemyT2的攻击手段就更高级一些,我们让它能够瞄准玩家射击。

大飞机EnemyT2发射子弹是EnemyT2类的私有方法,想要让他能够瞄准玩家,就要知道玩家在哪里。还记得前面我们做的数据仓库么。这回用上了。

我们修改EnemyT2的createBulletObject函数:

void EnemyT2::createBulletObject(PlaneObject_t *target) {BulletObject_t *but1 = new BulletObject_t();but1->x = baseInfo.x - PlaneXYScale;but1->y = baseInfo.y + PlaneXYScale * 2;but1->visiable = 1;int sum = abs(target->x - but1->x) + abs(target->y - but1->y);but1->speedX = 300 * (target->x - but1->x) / sum;but1->speedY = 300 * (target->y - but1->y) / sum;getRainbowColor(&but1->color, 200);ListPushBack(enemyBulletList, (LTDataType) but1);
}uint8_t EnemyT2::tick(uint32_t t) {。。。if (fireTimer.tick(t)) {if (((PlaneObject_t*) DataBulk::GetInstance()->data1)->visiable)createBulletObject((PlaneObject_t*) DataBulk::GetInstance()->data1);if (((PlaneObject_t*) DataBulk::GetInstance()->data2)->visiable)createBulletObject((PlaneObject_t*) DataBulk::GetInstance()->data2);}。。。
}

注意确定子弹方向的算法:

    int sum = abs(target->x - but1->x) + abs(target->y - but1->y);

    but1->speedX = 300 * (target->x - but1->x) / sum;
    but1->speedY = 300 * (target->y - but1->y) / sum;

300是子弹速度。

简单通过相似三角形来确定方向和速度,不是很精确,但也足够用了。如果要精确的话,需要用三角函数算分速度,太复杂,没必要。

2、Boss肯定要更强大,我们要让他同时向多个方向发射散弹,同时发射一颗能追踪的导弹。

先给他发射散弹:

void EnemyT3::createBulletObject(int speedX) {PlaneObject_t *but1 = new PlaneObject_t();but1->x = baseInfo.x - PlaneXYScale * 2;but1->y = baseInfo.y + PlaneXYScale;but1->speedX = speedX;but1->speedY = 300 - abs(speedX);but1->visiable = 1;but1->color = 0xe01000;ListPushBack(enemyBulletList, (LTDataType) but1);
}uint8_t EnemyT3::tick(uint32_t t) {animationStoryBoard->tick(t);if (fireTimer.tick(t)) {createBulletObject(-120);createBulletObject(-70);createBulletObject(-30);createBulletObject(0);createBulletObject(30);createBulletObject(70);createBulletObject(120);}for (ListNode *node = animationList->next; node != animationList; node =node->next) {if (((Animation*) node->data)->isValid) {((Animation*) node->data)->tick(t);}}return 0;
}

追踪导弹不是直线运动,所以要单独一个链表 enemyRocketList,单独的算法:

class BulletManager {
public:BulletManager();virtual ~BulletManager();uint8_t tick(uint32_t t);void init();uint8_t show(void);ListNode *player1BulletList;ListNode *player2BulletList;ListNode *enemyBulletList;ListNode *enemyRocketList;
private:void tickOnce(ListNode *list, uint32_t t);void showOnce(ListNode *list);void destoryOnce(ListNode *list);void tickRocket(uint32_t t);
};
void BulletManager::tickRocket(uint32_t t) {for (ListNode *cur = enemyRocketList->next; cur != enemyRocketList; cur =cur->next) {PlaneObject_t *bullet = (PlaneObject_t*) (cur->data);PlaneObject_t *target =bullet->tag == 1 ? Player1BaseInfo : Player2BaseInfo;int sum = abs(target->x - bullet->x) + abs(target->y - bullet->y);bullet->speedX = 50 * (target->x - bullet->x) / sum;bullet->speedY = 50 * (target->y - bullet->y) / sum;bullet->x += bullet->speedX * t;bullet->y += bullet->speedY * t;}
}uint8_t BulletManager::tick(uint32_t t) {tickOnce(player1BulletList, t);tickOnce(player2BulletList, t);tickOnce(enemyBulletList, t);tickRocket(t);return 0;
}

然后发射导弹:

void EnemyT3::createRocketObject(int target) {PlaneObject_t *but1 = new PlaneObject_t();but1->x = baseInfo.x - PlaneXYScale * 2;but1->y = baseInfo.y + PlaneXYScale;but1->visiable = 1;but1->color = 0xe01000;but1->tag = target;ListPushBack(EnemyRocketList, (LTDataType) but1);
}
uint8_t EnemyT3::tick(uint32_t t) {animationStoryBoard->tick(t);if (fireTimer.tick(t)) {
。。。if (Player1BaseInfo->visiable)createRocketObject(1);if (Player2BaseInfo->visiable)createRocketObject(2);}
。。。return 0;
}

补充,为了方法,把数据的地址都在DataBulk里面管起来。

void Plane::init() {backGroundStar.init();bulletManager.init();player1.init(1);player1.bulletList = bulletManager.player1BulletList;enemyManager.init();DataBulk::GetInstance()->data1 = (intptr_t) &player1.baseInfo;DataBulk::GetInstance()->data2 = (intptr_t) &player2.baseInfo;DataBulk::GetInstance()->data3 = (intptr_t) bulletManager.player1BulletList;DataBulk::GetInstance()->data4 = (intptr_t) bulletManager.player2BulletList;DataBulk::GetInstance()->data5 = (intptr_t) bulletManager.enemyBulletList;DataBulk::GetInstance()->data6 = (intptr_t) bulletManager.enemyRocketList;}

添加宏:

PlaneDef.h

#define Player1BaseInfo ((PlaneObject_t*)DataBulk::GetInstance()->data1)
#define Player2BaseInfo ((PlaneObject_t*)DataBulk::GetInstance()->data2)
#define Player1BulletList ((ListNode*)DataBulk::GetInstance()->data3)
#define Player2BulletList ((ListNode*)DataBulk::GetInstance()->data4)
#define EnemyBulletList ((ListNode*)DataBulk::GetInstance()->data5)
#define EnemyRocketList ((ListNode*)DataBulk::GetInstance()->data6)

最后,给导弹就上生命周期:

typedef struct {int x;int y;int color;int speedX;int speedY;uint8_t visiable = 0;uint8_t width;uint8_t height;int life = 0x7fffffff;int tag;
} PlaneObject_t;
void BulletManager::tickRocket(uint32_t t) {for (ListNode *cur = enemyRocketList->next; cur != enemyRocketList; cur =cur->next) {PlaneObject_t *bullet = (PlaneObject_t*) (cur->data);bullet->life -= t;if (bullet->life < 0) {bullet->visiable = 0;continue;}PlaneObject_t *target =bullet->tag == 1 ? Player1BaseInfo : Player2BaseInfo;int sum = abs(target->x - bullet->x) + abs(target->y - bullet->y);bullet->speedX = 50 * (target->x - bullet->x) / sum;bullet->speedY = 50 * (target->y - bullet->y) / sum;bullet->x += bullet->speedX * t;bullet->y += bullet->speedY * t;}
}

三种敌机都有了足够的差异化了,不同的形状,不同的飞行方式,不同的攻击方式,甚至击毁效果都不一样。不枉我们把它们区分为3个不同的类。

下面再来考虑玩家的攻击方式。

原来只有子弹,我们给他加上炸弹和激光。

子弹击中就没有了,而炸弹和激光属于范围攻击模式,其伤害是持续性的,而且与时间有关系。先做炸弹。

设定炸弹效果为半径=7的圆,碰撞检测销毁敌方子弹,对敌机持续造成 time * 1 的伤害。每玩家只有一个在生效。

先在像素屏驱动里面补充一个单片机上常用的画圆方法,Bresenham算法。具体原理不再赘述,可自行网上搜索。

不用三角函数的函数就是好函数。


void ws2812_Fill_Circle(uint16_t x0, uint16_t y0, uint8_t r, uint32_t color) {int x = 0, y = r, d;d = 3 - 2 * r;while (x <= y) {ws2812_fill(x0 - x, y0 - y, x * 2, 1, (color & 0xff0000) >> 16,(color & 0xff00) >> 8, color & 0xff);ws2812_fill(x0 - x, y0 + y - 1, x * 2, 1, (color & 0xff0000) >> 16,(color & 0xff00) >> 8, color & 0xff);ws2812_fill(x0 - y, y0 - x, 1, x * 2, (color & 0xff0000) >> 16,(color & 0xff00) >> 8, color & 0xff);ws2812_fill(x0 + y - 1, y0 - x, 1, x * 2, (color & 0xff0000) >> 16,(color & 0xff00) >> 8, color & 0xff);if (d < 0) {d = d + 4 * x + 6;} else {d = d + 4 * (x - y) + 10;y--;}x++;}ws2812_fill(x0 - y, y0 - y, y * 2, y * 2, (color & 0xff0000) >> 16,(color & 0xff00) >> 8, color & 0xff);
}

1、定义一个结构 EffectObject_t 保存效果信息:

typedef struct {int type;int x;int y;int life = 0x7fffffff;
} EffectObject_t;

2、在玩家类里面加上爆炸效果 effectObject :

class PlanePlayer {
public:PlanePlayer();~PlanePlayer();void init(uint8_t id);uint8_t tick(uint32_t t, uint8_t b1);uint8_t show(void);uint8_t hitDetect(int x, int y, int damage);uint8_t hitEffectDetect(int x, int y, int r);
。。。EffectObject_t *effectObject = NULL;int HP;
private:
。。。
};

3、在plane.cpp里面加上 爆炸效果的碰撞检测遍历:

void Plane::checkEffectCollision(uint32_t t, PlanePlayer *player) {if (player->effectObject == NULL)return;for (ListNode *enemy = enemyManager.enemyList->next;enemy != enemyManager.enemyList; enemy = enemy->next) {EnemyBase *ene = (EnemyBase*) enemy->data;if (ene->explodeState)continue;uint8_t res = player->hitEffectDetect(ene->baseInfo.x, ene->baseInfo.y,(ene->baseInfo.width + ene->baseInfo.height) / 3);if (res) {ene->HP -= res * t;ene->hurt();}}for (ListNode *enemyBul = bulletManager.enemyBulletList->next;enemyBul != bulletManager.enemyBulletList; enemyBul =enemyBul->next) {PlaneObject_t *bul = (PlaneObject_t*) enemyBul->data;uint8_t res = player->hitEffectDetect(bul->x, bul->y, 1);if (res) {bul->visiable = 0;}}for (ListNode *enemyBul = bulletManager.enemyRocketList->next;enemyBul != bulletManager.enemyRocketList; enemyBul =enemyBul->next) {PlaneObject_t *bul = (PlaneObject_t*) enemyBul->data;uint8_t res = player->hitEffectDetect(bul->x, bul->y, 1);if (res) {bul->visiable = 0;}}
}

注意,敌机、敌方子弹、敌方导弹都有可能被爆炸摧毁。

4、在玩家类里加上碰撞检测:

uint8_t PlanePlayer::hitEffectDetect(int x, int y, int r) {switch (effectObject->type) {case 1: {int a = (x - effectObject->x) / 100;int b = (y - effectObject->y) / 100;int c = (r + 10) * 100;return (a * a + b * b < c * c) ? 1 : 0;}}return 0;
}

5、显示玩家的,顺手显示爆炸效果:

uint8_t PlanePlayer::show(void) {if (effectObject != NULL) {ws2812_Fill_Circle(effectObject->x / PlaneXYScale,effectObject->y / PlaneXYScale, 10, 0x801000);}for (uint8_t y = 0; y < 5; y++) {for (uint8_t x = 0; x < 5; x++) {if (PlaneSharp[y][x])ws2812_pixel(x + baseInfo.x / PlaneXYScale - 2,y + baseInfo.y / PlaneXYScale - 2,(baseInfo.color >> 16) & 0xff,(baseInfo.color >> 8) & 0xff, baseInfo.color & 0xff);}}return 0;
}

TODO:其实爆炸小时不应该和显示玩家纠结在一起。如果有多种特殊武器,还是单独写个函数显示为好。

 好了,对于激光来说,激光要跟随玩家移动。增加激光特效,只要增加碰撞检测和显示两部分就行了。

激光的碰撞检测:

uint8_t PlanePlayer::hitEffectDetect(int x, int y, int r) {switch (effectObject->type) {case 1: {int a = (x - effectObject->x) / 100;int b = (y - effectObject->y) / 100;int c = (r + 10) * 100;return (a * a + b * b < c * c) ? 1 : 0;}case 2:return (x / PlaneXYScale == baseInfo.x / PlaneXYScale) ? 1 : 0;}return 0;
}

 激光的显示:

uint8_t PlanePlayer::show(void) {if (effectObject != NULL) {if (effectObject->type == 1) {ws2812_Fill_Circle(effectObject->x / PlaneXYScale,effectObject->y / PlaneXYScale, 10, 0x801000);} else {ws2812_fill(baseInfo.x / PlaneXYScale, 0, 1,baseInfo.y / PlaneXYScale, 0, 100, 200);}}for (uint8_t y = 0; y < 5; y++) {for (uint8_t x = 0; x < 5; x++) {if (PlaneSharp[y][x])ws2812_pixel(x + baseInfo.x / PlaneXYScale - 2,y + baseInfo.y / PlaneXYScale - 2,(baseInfo.color >> 16) & 0xff,(baseInfo.color >> 8) & 0xff, baseInfo.color & 0xff);}}return 0;
}

 比较一下,加一个激光特效,只要两行代码。0到1不容易,1到2快的飞起。

 最后,在玩家操作里面加上炸弹和激光:

uint8_t PlanePlayer::tick(uint32_t t, uint8_t b1) {
。。。if (effectObject == NULL) {if (b1 & KEY_BUTTON_D) {effectObject = new EffectObject_t();effectObject->x = baseInfo.x;effectObject->y =(baseInfo.y > 25 * PlaneXYScale) ?baseInfo.y - 25 * PlaneXYScale : 0;effectObject->type = 1;effectObject->life = 4000;} else if (b1 & KEY_BUTTON_A) {effectObject = new EffectObject_t();effectObject->x = baseInfo.x;effectObject->y = baseInfo.y;effectObject->type = 2;effectObject->life = 4000;}} else {effectObject->life -= t;if (effectObject->life < 0) {delete effectObject;effectObject = NULL;}}。。。return 0;
}

看看效果: 

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

STM32学习笔记十九:WS2812制作像素游戏屏-飞行射击游戏(9)探索道具系统

这篇关于STM32学习笔记十八:WS2812制作像素游戏屏-飞行射击游戏(8)探索游戏多样性,范围伤害模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

springboot项目打jar制作成镜像并指定配置文件位置方式

《springboot项目打jar制作成镜像并指定配置文件位置方式》:本文主要介绍springboot项目打jar制作成镜像并指定配置文件位置方式,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录一、上传jar到服务器二、编写dockerfile三、新建对应配置文件所存放的数据卷目录四、将配置文

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

使用Python实现获取屏幕像素颜色值

《使用Python实现获取屏幕像素颜色值》这篇文章主要为大家详细介绍了如何使用Python实现获取屏幕像素颜色值,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、一个小工具,按住F10键,颜色值会跟着显示。完整代码import tkinter as tkimport pyau

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R