当前位置: 首页 > news >正文

STM32学习笔记十五:WS2812制作像素游戏屏-飞行射击游戏(5)探索动画之帧动画

本章又是个重要的章节——动画。

动画,本质上时一系列静态的画面连续播放,欺骗人眼产生动画效果。这个原理自打十九世纪电影诞生开始,就从来没变过。

我们的游戏中也需要一些动画效果,比如,被击中时的受伤效果,击毁效果,血包的动画效果等等。这些动画分为两类:连续线性动画、离散的帧动画。

离散动画,就是在指定的时间点,将目标变量设定为特定的值。

连续动画,就是除了两个特定时间之外,通过插值算法为中间帧设定中间值。

这两者的时间轴都应不受系统处理能力的影响,所以,我们又想到了tick。

我们先从简单的开始,先做个帧动画。设定飞机被击中时,变为红色,1秒后恢复,单次动画不重复。

1、先定义一个动画基类:

Animation.h

/** Animation.h**  Created on: Dec 25, 2023*      Author: YoungMay*/#ifndef SRC_ANICOMP_ANIMATION_H_
#define SRC_ANICOMP_ANIMATION_H_
#include "stdint.h"
#include "../drivers/DList.h"
#include "../drivers/tools.h"typedef struct {uint32_t time;int value;
} AnimationData;class Animation {
public:Animation() {dataList = ListCreate();}virtual ~Animation() {ListDestory(dataList);}void addItem(uint32_t time, int value);virtual int tick(uint32_t t)=0;void start();uint8_t isValid = 0;int *bindAddress = NULL;
protected:ListNode *dataList;uint32_t totalTick;
};#endif /* SRC_ANICOMP_ANIMATION_H_ */

其中

各时间点的数据,保存在链表dataList中。

bindAddress是绑定的数据地址,到了指定时刻,我们就修改它。

 2、再定义一个离散动画类:

DispersedAnimation.h

/** DispersedAnimation.h**  Created on: Dec 25, 2023*      Author: YoungMay*/#ifndef SRC_ANICOMP_DISPERSEDANIMATION_H_
#define SRC_ANICOMP_DISPERSEDANIMATION_H_
#include "Animation.h"class DispersedAnimation: public Animation {
public:DispersedAnimation();~DispersedAnimation();int tick(uint32_t t);};#endif /* SRC_ANICOMP_DISPERSEDANIMATION_H_ */

 DispersedAnimation.cpp

/** DispersedAnimation.cpp**  Created on: Dec 25, 2023*      Author: YoungMay*/#include "DispersedAnimation.h"DispersedAnimation::DispersedAnimation() {// TODO Auto-generated constructor stub}DispersedAnimation::~DispersedAnimation() {// TODO Auto-generated destructor stub
}int DispersedAnimation::tick(uint32_t t) {totalTick += t;if (((AnimationData*) dataList->prev->data)->time < totalTick) {isValid = 0;if (bindAddress != NULL)*bindAddress = ((AnimationData*) dataList->prev->data)->value;return ((AnimationData*) dataList->prev->data)->value;}if (((AnimationData*) dataList->next->data)->time > totalTick) {if (bindAddress != NULL)*bindAddress = ((AnimationData*) dataList->next->data)->value;return ((AnimationData*) dataList->next->data)->value;}ListNode *node = dataList->next;while (((AnimationData*) node->next->data)->time < totalTick) {node = node->next;}if (bindAddress != NULL)*bindAddress = ((AnimationData*) node->data)->value;return ((AnimationData*) node->data)->value;
}

 动画类也有tick操作,我们把所有时间间隔都累加到了totalTick里面。

3、再看看怎么使用:

我们先在敌机的基类里面加上动画类 damageAnimation,让每个敌机都具备动画的能力。

class EnemyBase {
public:EnemyBase();virtual ~EnemyBase() {}virtual uint8_t tick(uint32_t t)=0;virtual void init()=0;virtual uint8_t show(void)=0;virtual uint8_t hitDetect(int x, int y)=0;ListNode *enemyBulletList;PlaneObject_t baseInfo;int HP;void hurt() {damageAnimation.start();}
protected:DispersedAnimation damageAnimation;ListNode *animationList;
};

animationList是用于保存所有动画的链表。动画damageAnimation 其实是可以在外层如enemyManager或者plane里面进行定义和注入的,但因为他与敌机强相关且其他类也不会用,所以直接在敌机类里面定义比较满足封装思想。

基类构造类里面完成链表初始化:

EnemyBase::EnemyBase() {baseInfo.x = ran_range(3 * PlaneXYScale, 29 * PlaneXYScale);baseInfo.y = 0;baseInfo.visiable = 1;animationList = ListCreate();ListPushBack(animationList, (LTDataType) &damageAnimation);
}

4、各种敌机本身颜色不一样,所以我们在各种敌机子类的初始化函数中,定义动画需要变得颜色:

void EnemyT1::init() {damageAnimation.addItem(0, 0xa02000);damageAnimation.addItem(1000, 0x208000);damageAnimation.bindAddress = &baseInfo.color;
}

5、最后在敌机的tick函数里面,遍历动画链表:

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

TIPS:由于什么时候执行tick无法确定,可能非常接近的时间点不会执行,直接就跳过了。所以用于做显示的动画可以接受,毕竟跳过就跳过了,显示最终效果即可,但如果用来修改某些影响流程的状态值的话,需要小心一些,需要有足够的时间间隔,确保能tick进去。

 同样的方法,我们再加上其他敌机类型的受伤效果,玩家被击中的效果等,不再累述。

飞机被击毁时,直接消失不见了,这不太合适,所以我们再给它加个击毁的动画。可以用类似前面焰火程序做个爆炸开来的样子。

还是用帧动画。

1、添加爆炸的动画explodeAnimation属性,添加爆炸阶段状态码explodeState。

class EnemyBase {
public:EnemyBase();virtual ~EnemyBase() {}virtual uint8_t tick(uint32_t t)=0;virtual void init()=0;virtual uint8_t show(void)=0;virtual uint8_t hitDetect(int x, int y, int damage)=0;ListNode *enemyBulletList;PlaneObject_t baseInfo;int HP;protected:DispersedAnimation damageAnimation;DispersedAnimation explodeAnimation;ListNode *animationList;int explodeState = 0;
};

2、给explodeAnimation灌入数据

void EnemyT1::init() {damageAnimation.addItem(0, 0xa02000);damageAnimation.addItem(1000, 0x208000);explodeAnimation.addItem(0, 1);explodeAnimation.addItem(200, 2);explodeAnimation.addItem(400, 3);explodeAnimation.addItem(600, 4);explodeAnimation.addItem(800, 100);}

3、根据状态码explodeState显示不同的爆炸形态

const int8_t Explode_X[] = { -1, 0, 1, 1, 1, 0, -1, -1 };
const int8_t Explode_Y[] = { -1, -1, -1, 0, 1, 1, 1, 0 };uint8_t EnemyT1::show(void) {if (explodeState) {for (uint8_t j = 0; j < 8; j++) {ws2812_pixel(baseInfo.x / PlaneXYScale + Explode_X[j] * explodeState,baseInfo.y / PlaneXYScale + Explode_Y[j] * explodeState,240, 20, 0);}if (explodeState == 100) {baseInfo.visiable = 0;}} else {for (uint8_t y = 0; y < baseInfo.height; y++) {for (uint8_t x = 0; x < baseInfo.width; x++) {if (sharp[y][x])ws2812_pixel(x + baseInfo.x / PlaneXYScale - baseInfo.width / 2,y + baseInfo.y / PlaneXYScale - baseInfo.height / 2,(baseInfo.color >> 16) & 0xff,(baseInfo.color >> 8) & 0xff,baseInfo.color & 0xff);}}}return 0;
}

4、原来血量为0时就直接消失了,现在还要再保留一下显示爆炸,而且这段时间也不能再动了。所以,对原来消失的和tick的逻辑做点小改动。

uint8_t EnemyT1::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();}for (ListNode *node = animationList->next; node != animationList; node =node->next) {if (((Animation*) node->data)->isValid) {((Animation*) node->data)->tick(t);}}return 0;
}
uint8_t EnemyT1::hitDetect(int x, int y, int damage) {if (explodeState)return 0;int a = (x - baseInfo.x) / 100;int b = (y - baseInfo.y) / 100;int c = 180; // 1.5 * 10000 / 100 // 碰撞圈子略大一点,uint8_t res = (a * a + b * b < c * c) ? 1 : 0;if (res) {HP -= damage;if (HP < 0) {explodeState = 1;explodeAnimation.start();} elsedamageAnimation.start();}return res;
}

嗯,还有补充T2和T3的爆炸效果。T2炸的范围更大一点,而T3可以爆出两朵大花。

好了,我们看看效果:

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

STM32学习笔记十六:WS2812制作像素游戏屏-飞行射击游戏(6)探索动画之插值动画

http://www.lryc.cn/news/271292.html

相关文章:

  • 期末复习(程序设计)
  • html-css-js移动端导航栏底部固定+i18n国际化全局
  • Ubuntu Linux 入门指南:面向初学者
  • 常见算法面试题目
  • PiflowX组件-JDBCWrite
  • 算法导论复习题目
  • HTTPS协议详解
  • 菜鸟学习vue3笔记-vue3 router回顾
  • Mybatis枚举类型处理和类型处理器
  • 2023 NCTF writeup
  • golang的大杀器协程goroutine
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • Linux学习笔记(一)
  • Python 爬虫 教程
  • uniapp原生插件 - android原生插件打包流程 ( 避坑指南一)
  • 搭建maven私服
  • EST-100身份证社保卡签批屏按捺终端PC版web版本http协议接口文档,支持web网页开发对接使用
  • 基于SpringBoot的毕业论文管理系统
  • iToF人脸识别
  • Django开发3
  • MS2358:96KHz、24bit 音频 ADC
  • 【Android12】Android Framework系列---tombstone墓碑生成机制
  • 中间件系列 - Redis入门到实战(原理篇)
  • P2249 【深基13.例1】查找
  • linux常用shell脚本
  • Rust学习笔记005:结构体 struct
  • maven中dependencyManagement标签
  • SparkStreaming与Kafka整合
  • openwrt源码编译
  • 【Leetcode Sheet】Weekly Practice 22