要让物体沿着路径移动,必须同时修改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)探索游戏多样性,范围伤害模式