当前位置: 首页 > 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)探索动画之插值动画

相关文章:

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

本章又是个重要的章节——动画。 动画&#xff0c;本质上时一系列静态的画面连续播放&#xff0c;欺骗人眼产生动画效果。这个原理自打十九世纪电影诞生开始&#xff0c;就从来没变过。 我们的游戏中也需要一些动画效果&#xff0c;比如&#xff0c;被击中时的受伤效果&#…...

期末复习(程序设计)

根据字符出现频率排序 【问题描述】 给定一个字符串 s &#xff0c;根据字符出现的 频率 对其进行降序排序。一个字符出现的频率是它出现在字符串中的次数。 返回已排序的字符串。 频率相同的的字符按ascii值降序排序。 s不包含空格、制表符、换行符等特殊字符。 【输入格…...

html-css-js移动端导航栏底部固定+i18n国际化全局

需求&#xff1a;要做一个移动端的仿照小程序的导航栏页面操作&#xff0c;但是这边加上了i18n国家化&#xff0c;由于页面切换的时候会导致国际化失效&#xff0c;所以写了这篇文章 1.效果 切换页面的时候中英文也会跟着改变&#xff0c;不会导致切换后回到默认的语言 2.实现…...

Ubuntu Linux 入门指南:面向初学者

目录 1. Ubuntu Linux 简介 Ubuntu 的由来 Ubuntu 与其他 Linux 发行版的比较 Debian&#xff1a; Fedora&#xff1a; openSUSE&#xff1a; Arch Linux&#xff1a; Linux Mint&#xff1a; 第二部分&#xff1a;安装 Ubuntu 1. 准备安装 系统需求 创建 Ubuntu 启…...

常见算法面试题目

前言 总结一些常见的算法题目&#xff0c;每一个题目写一行思路&#xff0c;方便大家复习。具体题目的来源是下面的网站。 剑指offer 剑指offe2 leetcode200题 leetcode 100题 leetcode150题 leetcode 75题 文章目录 前言二叉树非递归遍历牛客JZ31 栈的压入、弹出序列 (…...

PiflowX组件-JDBCWrite

JDBCWrite组件 组件说明 使用JDBC驱动向任意类型的关系型数据库写入数据。 计算引擎 flink 有界性 Sink: Batch Sink: Streaming Append & Upsert Mode 组件分组 Jdbc 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默…...

算法导论复习题目

这题需要考虑什么呢&#xff1f; 一换元&#xff0c;二要使用主方法猜出结果&#xff0c;三是证明的时候添加一个低阶项来消除 LC检索 C&#xff08;x&#xff09;是从上帝视角来看的成本 对C(x)的一个估计&#xff1a; 由两个部分组成&#xff0c;就相当于由以往的经验对未来…...

HTTPS协议详解

目录 前言 一、HTTPS协议 1、加密是什么 2、为什么要加密 二、常见加密方式 1、对称加密 2、非对称加密 三、数据摘要与数据指纹 1、数据摘要 2、数据指纹 四、HTTPS加密策略探究 1、只使用对称加密 2、只使用非对称加密 3、双方都使用非对称加密 4、对称加密非…...

菜鸟学习vue3笔记-vue3 router回顾

1、路由router pnpm i vue-router2、创建使用环境 1.src下创建 router文件夹、里面创建index.ts文件 //创建一个路由暴露出去//1.引入createRouter import { createRouter, createWebHistory } from "vue-router";// import Home from ../components/Home.vue//…...

Mybatis枚举类型处理和类型处理器

专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL Mybatis配置入门 Mybatis行为配置之Ⅰ—缓存 Mybatis行为配置…...

2023 NCTF writeup

CRYPTO Sign 直接给了fx,gx&#xff0c;等于私钥给了&#xff0c;直接套代码&#xff0c;具体可以参考&#xff1a; https://0xffff.one/d/1424 fx [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…...

golang的大杀器协程goroutine

在Golang中&#xff0c;协程&#xff08;Goroutine&#xff09;是轻量级的执行单元&#xff0c;用于实现并发编程。它是Golang语言的重要组成部分&#xff0c;提供了简洁、高效的方式来处理并发任务。 特点&#xff1a; 1&#xff09;轻量级&#xff1a;Go语言的协程是轻量级…...

[Angular] 笔记 9:list/detail 页面以及@Output

1. Output input 好比重力&#xff0c;向下传递数据&#xff0c;list 传给 detail&#xff0c;smart 组件传给 dumb 组件&#xff0c;父组件传给子组件。input 顾名思义&#xff0c;输入数据给组件。 output 与之相反&#xff0c;好比火箭&#xff0c;向上传递数据或事件。ou…...

Linux学习笔记(一)

如果有自己的物理服务器请先查看这篇文章 文章目录 网卡配置Linux基础指令ls:列出目录内容cd(mkdir.rmkdir): 切换文件夹(创建,删除操作)cp:复制文件或目录mv:文件/文件夹移动cat:查看文件vi:文件查看编辑man:查看命令手册more: 查看文件内容less : 查看文件内容 ps: 显示当前进…...

Python 爬虫 教程

python爬虫框架&#xff1a;Scrapyd&#xff0c;Feapder&#xff0c;Gerapy 参考文章&#xff1a; python爬虫工程师&#xff0c;如何从零开始部署ScrapydFeapderGerapy&#xff1f; - 知乎 神器&#xff01;五分钟完成大型爬虫项目 - 知乎 爬虫框架-feapder - 知乎 scrap…...

uniapp原生插件 - android原生插件打包流程 ( 避坑指南一)

【彩带- 避坑知识点】: 当时开发中安卓插件打包成功后&#xff0c;uniapp引用插件aar&#xff0c;用云打包 &#xff0c;总是提示不包含插件。原因是因为module的androidManifest.xml文件没有注册activity。 这一步 很重要&#xff0c;一定要注册。 --------------------------…...

搭建maven私服

maven maven简介 什么是maven&#xff1f; Maven这个单词来自于意第绪语&#xff08;犹太语&#xff09;&#xff0c;意为知识的积累。 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具软件。 Maven 除了以…...

EST-100身份证社保卡签批屏按捺终端PC版web版本http协议接口文档,支持web网页开发对接使用

<!DOCTYPE html><html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,initial-scale1.0"><title>演示DEMO</title><script type"text/…...

基于SpringBoot的毕业论文管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的毕业论文管理系统,java…...

iToF人脸识别

iToF(间接飞行时间)是一种测量光飞行时间的技术,主要应用于人脸识别。 iToF人脸识别技术在哪些场景下会用到 iToF人脸识别技术可以应用于许多场景,以下是一些常见的应用场景: 平安城市:在城市监控系统中,iToF人脸识别技术可以用于实时监控、目标检测和识别,以及异常行为…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...