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

2024-01-03 10:33:31

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

文章来源:https://blog.csdn.net/vvind/article/details/135356131
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。