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)探索动画之帧动画
本章又是个重要的章节——动画。 动画,本质上时一系列静态的画面连续播放,欺骗人眼产生动画效果。这个原理自打十九世纪电影诞生开始,就从来没变过。 我们的游戏中也需要一些动画效果,比如,被击中时的受伤效果&#…...
期末复习(程序设计)
根据字符出现频率排序 【问题描述】 给定一个字符串 s ,根据字符出现的 频率 对其进行降序排序。一个字符出现的频率是它出现在字符串中的次数。 返回已排序的字符串。 频率相同的的字符按ascii值降序排序。 s不包含空格、制表符、换行符等特殊字符。 【输入格…...
html-css-js移动端导航栏底部固定+i18n国际化全局
需求:要做一个移动端的仿照小程序的导航栏页面操作,但是这边加上了i18n国家化,由于页面切换的时候会导致国际化失效,所以写了这篇文章 1.效果 切换页面的时候中英文也会跟着改变,不会导致切换后回到默认的语言 2.实现…...
Ubuntu Linux 入门指南:面向初学者
目录 1. Ubuntu Linux 简介 Ubuntu 的由来 Ubuntu 与其他 Linux 发行版的比较 Debian: Fedora: openSUSE: Arch Linux: Linux Mint: 第二部分:安装 Ubuntu 1. 准备安装 系统需求 创建 Ubuntu 启…...
常见算法面试题目
前言 总结一些常见的算法题目,每一个题目写一行思路,方便大家复习。具体题目的来源是下面的网站。 剑指offer 剑指offe2 leetcode200题 leetcode 100题 leetcode150题 leetcode 75题 文章目录 前言二叉树非递归遍历牛客JZ31 栈的压入、弹出序列 (…...
PiflowX组件-JDBCWrite
JDBCWrite组件 组件说明 使用JDBC驱动向任意类型的关系型数据库写入数据。 计算引擎 flink 有界性 Sink: Batch Sink: Streaming Append & Upsert Mode 组件分组 Jdbc 端口 Inport:默认端口 outport:默认端口 组件属性 名称展示名称默…...
算法导论复习题目
这题需要考虑什么呢? 一换元,二要使用主方法猜出结果,三是证明的时候添加一个低阶项来消除 LC检索 C(x)是从上帝视角来看的成本 对C(x)的一个估计: 由两个部分组成,就相当于由以往的经验对未来…...
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,等于私钥给了,直接套代码,具体可以参考: 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中,协程(Goroutine)是轻量级的执行单元,用于实现并发编程。它是Golang语言的重要组成部分,提供了简洁、高效的方式来处理并发任务。 特点: 1)轻量级:Go语言的协程是轻量级…...
[Angular] 笔记 9:list/detail 页面以及@Output
1. Output input 好比重力,向下传递数据,list 传给 detail,smart 组件传给 dumb 组件,父组件传给子组件。input 顾名思义,输入数据给组件。 output 与之相反,好比火箭,向上传递数据或事件。ou…...
Linux学习笔记(一)
如果有自己的物理服务器请先查看这篇文章 文章目录 网卡配置Linux基础指令ls:列出目录内容cd(mkdir.rmkdir): 切换文件夹(创建,删除操作)cp:复制文件或目录mv:文件/文件夹移动cat:查看文件vi:文件查看编辑man:查看命令手册more: 查看文件内容less : 查看文件内容 ps: 显示当前进…...
Python 爬虫 教程
python爬虫框架:Scrapyd,Feapder,Gerapy 参考文章: python爬虫工程师,如何从零开始部署ScrapydFeapderGerapy? - 知乎 神器!五分钟完成大型爬虫项目 - 知乎 爬虫框架-feapder - 知乎 scrap…...
uniapp原生插件 - android原生插件打包流程 ( 避坑指南一)
【彩带- 避坑知识点】: 当时开发中安卓插件打包成功后,uniapp引用插件aar,用云打包 ,总是提示不包含插件。原因是因为module的androidManifest.xml文件没有注册activity。 这一步 很重要,一定要注册。 --------------------------…...
搭建maven私服
maven maven简介 什么是maven? Maven这个单词来自于意第绪语(犹太语),意为知识的积累。 Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。 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人脸识别技术可以用于实时监控、目标检测和识别,以及异常行为…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
