上章我们做好了空间的比例尺,不至于物体定位出错。这次我们尝试一下时间间隔的同步。当然,游戏中需要同步时间的地方很多,这里仅仅涉及很小的一个点。

我们已经创造了玩家飞机,是时候让它能发射子弹了。

发射子弹,哪怕是密集如加特林,也需要有一个发射间隔。这个间隔如何做?显然是不可能用Hal_delay之类的等待函数。实际上,整个代码中都不会出现等待函数。假设我们需要保证每个玩家的每个子弹间隔都是400ms,同时还要考虑两个玩家并不是同时发射子弹,他们的间隔是独立的。另一方面,敌机也应该能发射子弹。

我们前面章节定义了tick入口函数,入参中携带了运行间隔时间。我们可以利用这个,为每种间隔定义自己的定时器。

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;

对象检查定时器是否到时间,如果没到时间,那该干嘛干嘛,如果已经到达时间了,那就干点其他啥。

现在可以在玩家属性了面加上这个间隔了。

private:
	IntervalAniTimer_t fireTimer = { 0, 400 };

插播:在实现发射子弹之前,我们要考虑子弹的数据结构。

子弹数据有几个特点:

1、数量不确定。

2、频繁的增删操作。

3、似乎没有随机访问的场景。

所以,使用链表比使用数组更合适。

没有现成的库,那就参考网上别人家的,手锤一个双向链表。

DList.h

#ifndef __SLIST_H__
#define __SLIST_H__

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "stdint.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef intptr_t LTDataType;
typedef struct ListNode {
	LTDataType data;
	struct ListNode *next;
	struct ListNode *prev;
} ListNode;

//创造节点
ListNode* BuyLTNode(LTDataType x);
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode *pHead);
// 双向链表打印
void ListPrint(ListNode *pHead, void (*callback)(LTDataType x));
// 双向链表尾插
void ListPushBack(ListNode *pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode *pHead);
// 双向链表头插
void ListPushFront(ListNode *pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode *pHead);
// 双向链表查找
ListNode* ListFind(ListNode *pHead, LTDataType x);

// 双向链表查找
ListNode* ListFindItem(ListNode *pHead, LTDataType y,
		uint8_t (*callback)(LTDataType x, LTDataType y));

// 双向链表在pos的前面进行插入
void ListInsert(ListNode *pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode *pos);

uint16_t ListCount(ListNode *pHead);

ListNode* ListGetNodeAt(ListNode *pHead, LTDataType idx);

#ifdef __cplusplus
}
#endif

#endif

DList.c

#include "DList.h"
// 创建返回链表的头结点.
ListNode* ListCreate() {
	ListNode *head = BuyLTNode(0);

	head->next = head; //循环列表创建头时头的首尾都指向自己
	head->prev = head;
	return head;
}
//创造节点
ListNode* BuyLTNode(LTDataType x) {
	ListNode *cur = (ListNode*) malloc(sizeof(ListNode));
	if (cur == NULL) {
		perror("malloc");
		exit(-1);
	}
	cur->data = x;
	return cur;

}
// 双向链表打印
void ListPrint(ListNode *pHead, void (*callback)(LTDataType x)) {
	assert(pHead);
	ListNode *cur = pHead->next;
	if (callback) {
		while (cur != pHead) {
			(*callback)(cur->data);
			cur = cur->next;
		}
	} else {
		while (cur != pHead) {
			printf("%d->", cur->data);
			cur = cur->next;
		}
		printf("head\n");
	}
}
// 双向链表尾插
void ListPushBack(ListNode *pHead, LTDataType x) {
	assert(pHead);
	ListNode *newnode = BuyLTNode(x);
	newnode->prev = pHead->prev; //要尾插的节点的prev指向原来的尾节点
	newnode->next = pHead; //要尾插的节点的next指向头
	pHead->prev->next = newnode; //原来的尾节点的next指向新尾
	pHead->prev = newnode; //头的prev指向新尾

}
// 双向链表尾删
void ListPopBack(ListNode *pHead) {
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode *tail = pHead->prev; //用一个指针保存尾巴
	tail->prev->next = pHead; //将倒数第二个节点的next指向头
	pHead->prev = tail->prev; //头节点的prev指向倒数第二节点
	free(tail);

}
// 双向链表头插
void ListPushFront(ListNode *pHead, LTDataType x) {
	assert(pHead);
	ListNode *newnode = BuyLTNode(x);
	newnode->next = pHead->next; //新空间的next指向原来的第一个数据
	newnode->prev = pHead; //新空间的prev指向头
	pHead->next->prev = newnode; //原来的的一个数据的prev指向newnode
	pHead->next = newnode; //头的next指向newnode
}
// 双向链表头删
void ListPopFront(ListNode *pHead) {
	assert(pHead);
	assert(pHead->next != pHead); //先判断链表中除了头有无其他数据
	ListNode *oldnode = pHead->next; //将要删除的数据的位置保存起来,以防后面丢失
	pHead->next = oldnode->next; //头的next指向第二个数据
	oldnode->next->prev = pHead; //第二个数据的prev指向头
	free(oldnode); //释放数据空间即可
}
// 双向链表查找
ListNode* ListFind(ListNode *pHead, LTDataType x) {
	if (pHead == NULL)
		return NULL;
	ListNode *cur = pHead->next;
	while (cur != pHead) {
		if (cur->data == x) {
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

ListNode* ListFindItem(ListNode *pHead, LTDataType y,
		uint8_t (*callback)(LTDataType x, LTDataType y)) {
	if (pHead == NULL)
		return NULL;
	ListNode *cur = pHead->next;
	while (cur != pHead) {
		if ((*callback)(cur->data, y)) {
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 双向链表在pos的前面进行插入
void ListInsert(ListNode *pos, LTDataType x) {
	assert(pos);
	//调整pos newnode pos前面的数据这三个空间的prev和next即可
	ListNode *newnode = BuyLTNode(x);
	ListNode *prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode *pos) {
	assert(pos);
	ListNode *prev = pos->prev;
	ListNode *next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;

}
// 双向链表销毁
void ListDestory(ListNode *pHead) {
	if (pHead == NULL)
		return;
	ListNode *cur = pHead->next;
	if (cur == NULL)
		return;
	ListNode *next = cur->next;
	while (cur != pHead) //先释放除头以外的所有节点,再释放头
	{
		free(cur);
		cur = next;
		next = next->next;
	}
	free(cur);
}

uint16_t ListCount(ListNode *pHead) {
	if (pHead == NULL)
		return 0;
	uint16_t c = 0;
	ListNode *cur = pHead->next;
	while (cur != pHead) {
		c++;
		cur = cur->next;
	}
	return c;
}

ListNode* ListGetNodeAt(ListNode *pHead, LTDataType idx) {
	ListNode *cur = pHead->next;
	for (uint16_t i = 0; i < idx; i++)
		cur = cur->next;
	return cur;
}

可以在玩家数据里面添加子弹的数据了:

class PlanePlayer {
public:
	PlanePlayer();
	~PlanePlayer();
	void init(uint8_t id);
	uint8_t tick(uint32_t t, uint8_t b1);
	uint8_t show(void);
	PlaneObject_t baseInfo;
	ListNode *bulletList;
private:
	IntervalAniTimer_t fireTimer = { 0, 400 };

};

然后在玩家的tick里面加上发射子弹的判断:

uint8_t PlanePlayer::tick(uint32_t t, uint8_t b1) {
	if (b1 & KEY_DOWN) {
		baseInfo.y += baseInfo.speed * t;
		if (baseInfo.y > 62 * PlaneXYScale)
			baseInfo.y = 62 * PlaneXYScale;
	}
	if (b1 & KEY_UP) {
		baseInfo.y -= baseInfo.speed * t;
		if (baseInfo.y < 5 * PlaneXYScale)
			baseInfo.y = 5 * PlaneXYScale;
	}
	if (b1 & KEY_LEFT) {
		baseInfo.x -= baseInfo.speed * t;
		if (baseInfo.x < 1 * PlaneXYScale)
			baseInfo.x = 1 * PlaneXYScale;
	}
	if (b1 & KEY_RIGHT) {
		baseInfo.x += baseInfo.speed * t;
		if (baseInfo.x > 30 * PlaneXYScale)
			baseInfo.x = 30 * PlaneXYScale;
	}

	if (b1 & KEY_BUTTON_C && fireTimer.tick(t)) {
		BulletObject_t *but = new BulletObject_t();
		but->x = baseInfo.x;
		but->y = baseInfo.y - 2;
		but->speedX = 0;
		but->speedY = -400;
		but->visiable = 1;
		getRainbowColor(&but->color, 150);
		ListPushBack(bulletList, (LTDataType) but);
	}
	return 0;
}

看看最终效果: 

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

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

12-30 06:02