STM32学习笔记十七:WS2812制作像素游戏屏-飞行射击游戏(7)探索动画之故事板,复杂动画

发布时间:2024年01月03日

要让物体沿着路径移动,必须同时修改X/Y两个值,用两个连续插值动画行不行?

在单片机这种单线程设备,两个TICK会前后脚进行修改,具有相同的时间跨度,所以似乎也是可以的。但是在支持多线程的设备,各动画对象的tick就不一定了。显示时可能会偏离路径。另外我们可能还会有更复杂的动画,比如,我们的任务要奔跑,那需要同时修改XY,同时人物奔跑时还有手脚动作的帧动画,可能显示血条的单次动画,等等等等,这些东西要是分开做的话,其同步可能会令人崩溃。

所以,我们需要一个播放组合型动画的能力。这里,我们借用 C# 的故事板概念,实现类似功能:

AnimationGroup 之间串行处理

AnimationGroup 内部的Animation 并行处理

当然,我们不可能完整实现C# 的故事板,只是最核心的最精简的能力。尽可能封装好,将来可移植复用即可。

1、定义动画组 AnimationGroup

AnimationGroup.h

/*
 * AnimationGroup.h
 *
 *  Created on: Dec 26, 2023
 *      Author: YoungMay
 */

#ifndef SRC_ANICOMP_ANIMATIONGROUP_H_
#define SRC_ANICOMP_ANIMATIONGROUP_H_
#include "Animation.h"

class AnimationGroup {
public:
	AnimationGroup();
	virtual ~AnimationGroup();

	void addItem(Animation *animation);
	void tick(uint32_t t);
	void start();
	uint8_t isValid = 0;
	uint32_t repeat = 1;
	uint8_t id;
private:
	ListNode *animationList;
};

#endif /* SRC_ANICOMP_ANIMATIONGROUP_H_ */

AnimationGroup.cpp

/*
 * AnimationGroup.cpp
 *
 *  Created on: Dec 26, 2023
 *      Author: YoungMay
 */

#include "AnimationGroup.h"

AnimationGroup::AnimationGroup() {
	animationList = ListCreate();

}

AnimationGroup::~AnimationGroup() {
	for (ListNode *node = animationList->next; node != animationList; node =
			node->next) {
		delete (Animation*) node->data;
	}
	ListDestory(animationList);
}

void AnimationGroup::addItem(Animation *animation) {
	ListPushBack(animationList, (LTDataType) animation);
}
void AnimationGroup::tick(uint32_t t) {
	isValid = 0;
	for (ListNode *node = animationList->next; node != animationList; node =
			node->next) {
		if (((Animation*) node->data)->isValid) {
			((Animation*) node->data)->tick(t);
		}
		isValid = isValid | ((Animation*) node->data)->isValid;
	}
}
void AnimationGroup::start() {
	isValid = 1;
	for (ListNode *node = animationList->next; node != animationList; node =
			node->next) {
		((Animation*) node->data)->start();
	}
}

注意tick里面的处理,每个animation都能执行到。所有animation结束后,该group结束。

2、定义故事板 AnimationStoryBoard

AnimationStoryBoard.h

/*
 * AnimationStoryBoard.h
 *
 *  Created on: Dec 26, 2023
 *      Author: YoungMay
 */

#ifndef SRC_ANICOMP_ANIMATIONSTORYBOARD_H_
#define SRC_ANICOMP_ANIMATIONSTORYBOARD_H_
#include "AnimationGroup.h"
class AnimationStoryBoard {
public:
	AnimationStoryBoard();
	virtual ~AnimationStoryBoard();

	void addItem(AnimationGroup *group);
	void tick(uint32_t t);
	void start();
	uint8_t isValid = 0;
private:
	ListNode *animationGroupList;
};

#endif /* SRC_ANICOMP_ANIMATIONSTORYBOARD_H_ */

AnimationStoryBoard.cpp

/*
 * AnimationStoryBoard.cpp
 *
 *  Created on: Dec 26, 2023
 *      Author: YoungMay
 */

#include "AnimationStoryBoard.h"

AnimationStoryBoard::AnimationStoryBoard() {
	animationGroupList = ListCreate();
}

AnimationStoryBoard::~AnimationStoryBoard() {
	for (ListNode *node = animationGroupList->next; node != animationGroupList;
			node = node->next) {
		delete (AnimationGroup*) node->data;
	}
	ListDestory(animationGroupList);
}

void AnimationStoryBoard::addItem(AnimationGroup *group) {
	ListPushBack(animationGroupList, (LTDataType) group);
}
void AnimationStoryBoard::tick(uint32_t t) {
	if (!isValid)
		return;
	isValid = 0;
	for (ListNode *node = animationGroupList->next; node != animationGroupList;
			node = node->next) {
		AnimationGroup *group = (AnimationGroup*) node->data;
		if (group->isValid) {
			group->tick(t);
			isValid = 1;
			if ((!group->isValid) && group->repeat > 1) {
				group->repeat--;
				group->start();
			}
			return;
		}
	}
}
void AnimationStoryBoard::start() {
	isValid = 1;
	for (ListNode *node = animationGroupList->next; node != animationGroupList;
			node = node->next) {
		((AnimationGroup*) node->data)->start();
	}
}

注意tick里面的处理,只处理第一个未结束的animationGroup。

好了,现在可以设置BOSS的飞行路径。

?分为两个group.先从屏幕外面飞下来,然后绕8字形循环飞行。

1、修改BOSS,添加一个故事板 animationStoryBoard

class EnemyT3: public EnemyBase {
public:
	EnemyT3();
	~EnemyT3();
	uint8_t tick(uint32_t t);
	void init();
	uint8_t show(void);
	uint8_t hitDetect(int x, int y, int damage);
	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, } };
private:
	AnimationStoryBoard *animationStoryBoard;
	IntervalAniTimer_t fireTimer = { 2000, 6000 };
	void createBulletObject();
};

?2、在BOSS初始化 init 函数中灌入数据:


void EnemyT3::init() {
	damageAnimation.addItem(0, 0xa02000);
	damageAnimation.addItem(1000, 0x604000);

	explodeAnimation.addItem(0, 1);
	explodeAnimation.addItem(200, 2);
	explodeAnimation.addItem(400, 3);
	explodeAnimation.addItem(600, 4);
	explodeAnimation.addItem(800, 5);
	explodeAnimation.addItem(1000, 6);
	explodeAnimation.addItem(1200, 7);
	explodeAnimation.addItem(1400, 100);

	animationStoryBoard = new AnimationStoryBoard();
	AnimationGroup *group1 = new AnimationGroup();

	ContinuousAnimation *ani1X = new ContinuousAnimation();
	ContinuousAnimation *ani1Y = new ContinuousAnimation();
	ani1X->bindAddress = &baseInfo.x;
	ani1Y->bindAddress = &baseInfo.y;
	group1->addItem(ani1X);
	group1->addItem(ani1Y);
	ani1X->addItem(0, 20 * PlaneXYScale);
	ani1Y->addItem(0, 0 * PlaneXYScale);
	ani1X->addItem(2000, 20 * PlaneXYScale);
	ani1Y->addItem(2000, 10 * PlaneXYScale);
	animationStoryBoard->addItem(group1);

	AnimationGroup *group2 = new AnimationGroup();
	ContinuousAnimation *ani2X = new ContinuousAnimation();
	ContinuousAnimation *ani2Y = new ContinuousAnimation();
	ani2X->bindAddress = &baseInfo.x;
	ani2Y->bindAddress = &baseInfo.y;
	group2->addItem(ani2X);
	group2->addItem(ani2Y);
	ani2X->addItem(0, 20 * PlaneXYScale);
	ani2Y->addItem(0, 10 * PlaneXYScale);
	ani2X->addItem(5658, 12 * PlaneXYScale);
	ani2Y->addItem(5658, 15 * PlaneXYScale);
	ani2X->addItem(8658, 7 * PlaneXYScale);
	ani2Y->addItem(8658, 15 * PlaneXYScale);
	ani2X->addItem(10356, 5 * PlaneXYScale);
	ani2Y->addItem(10356, 16 * PlaneXYScale);
	ani2X->addItem(10956, 5 * PlaneXYScale);
	ani2Y->addItem(10956, 14 * PlaneXYScale);
	ani2X->addItem(12654, 7 * PlaneXYScale);
	ani2Y->addItem(12654, 10 * PlaneXYScale);
	ani2X->addItem(15654, 12 * PlaneXYScale);
	ani2Y->addItem(15654, 10 * PlaneXYScale);
	ani2X->addItem(21312, 20 * PlaneXYScale);
	ani2Y->addItem(21312, 15 * PlaneXYScale);
	ani2X->addItem(24312, 25 * PlaneXYScale);
	ani2Y->addItem(24312, 15 * PlaneXYScale);
	ani2X->addItem(26010, 27 * PlaneXYScale);
	ani2Y->addItem(26010, 16 * PlaneXYScale);
	ani2X->addItem(26610, 27 * PlaneXYScale);
	ani2Y->addItem(26610, 14 * PlaneXYScale);
	ani2X->addItem(28308, 25 * PlaneXYScale);
	ani2Y->addItem(28308, 10 * PlaneXYScale);
	ani2X->addItem(31308, 20 * PlaneXYScale);
	ani2Y->addItem(31308, 10 * PlaneXYScale);
	group2->repeat = 1000000;
	animationStoryBoard->addItem(group2);
	animationStoryBoard->start();
}

3、最后在BOSS的tick里面加上故事板的调用。

uint8_t EnemyT3::tick(uint32_t t) {
	if (explodeState == 0)
		baseInfo.y += t * baseInfo.speed;
	if (baseInfo.y > 64 * PlaneXYScale)
		baseInfo.visiable = 0;
	if (fireTimer.tick(t))
		createBulletObject();
	animationStoryBoard->tick(t);
	for (ListNode *node = animationList->next; node != animationList; node =
			node->next) {
		if (((Animation*) node->data)->isValid) {
			((Animation*) node->data)->tick(t);
		}
	}
	return 0;
}

好了。看看效果:

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

故事板功能可以做得非常强大,通过各种animation和group的组合,可以实现复杂的动画。还可以自行扩展各种group的触发条件,如前置后置条件,同步异步等。

大致思路如此,要做成什么样就看需求了。

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

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