?前面我们的攻击手段比较单一,虽然已经分出了 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制作像素游戏屏-飞行射击