STM32学习笔记二十一:WS2812制作像素游戏屏-飞行射击游戏(11)探索游戏脚本

news2024/11/14 1:06:37

 还记得上次在第十七章中为BOSS创建的路径动画吧。我们写了一大坨的代码来描述BOSS的运动路径,但凡是写过几年代码的人都不会干出这样的事情。-_-!

没办法,谁叫那时候还没有脚本呢。这章就来补齐这块短板。

脚本属于配置化的一种,你可以把脚本当成配置文件来看待。要做脚本,优先要确定脚本的格式。常用的格式有:

1、二进制格式。优势是最节约空间,非常适合单片机这种嵌入式设备。劣势是无法直接阅读,不需要另外写个编解码工具来生成配置文件。

2、文本行格式。优势是可阅读,空间也可接受。劣势是修改容易出错,尽量不要有对齐要求,否则用起来可能要崩溃。

3、JSON或XML。优势是方便阅读方便编辑,不需要专用工具。劣势是需要空间较大。

本项目中,采用2+3方式,即按行保存压缩的JSON串。

脚本主要起两个作用,一是描述物体运动的轨迹,如前面BOSS的运动动画。另一个是制作关卡剧本。

先来搞第一个:

1、现有弄个JSON解析工具。以前做MQTT时用过一个:《【嵌入式项目应用】__cJSON在单片机的使用》

下载cJSON.h 和cJSON.c,并放到项目中。

 2、统一所有敌机的TICK,先尝试有没有故事板可以运行。如果故事板运行结束,则按原有固定向下运行。

uint8_t EnemyT1::tick(uint32_t t) {
	if (animationStoryBoard->isValid) {
		animationStoryBoard->tick(t);
	} else {
		if (explodeState == 0)
			baseInfo.y += t * baseInfo.speedY;
		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;
}

3、把原来BOSS的运行路径转为JSON串:

[
	{
		"r": 1,
		"i": [
			{				"c": 0,						"x": 200000,				"y": 0			},
			{				"c": 2000,				"x": 200000,				"y": 100000			}
		]
	},
	{
		"r": 2,
		"i": [
			{				"c": 0,						"x": 200000,				"y": 100000		},
			{				"c": 3772,				"x": 120000,				"y": 150000		},
			{				"c": 5772,				"x":  70000,				"y": 150000		},
			{				"c": 6904,				"x":  50000,				"y": 160000		},
			{				"c": 7304,				"x":  50000,				"y": 140000		},
			{				"c": 8436,				"x":  70000,				"y": 100000		},
			{				"c": 10436,				"x": 120000,				"y": 100000		},
			{				"c": 14208,				"x": 200000,				"y": 150000		},
			{				"c": 16208,				"x": 250000,				"y": 150000		},
			{				"c": 17340,				"x": 270000,				"y": 160000		},
			{				"c": 17740,				"x": 270000,				"y": 140000		},
			{				"c": 18872,				"x": 250000,				"y": 100000		},
			{				"c": 20872,				"x": 200000,				"y": 100000		}
		]
	}
]

r 为 group的重复次数

c为时间点

x,y为坐标值。应提前 * PlaneXYScale

4、对JSON进行压缩转义,然后用字符串来保存。

/*
 * PlaneScript.h
 *
 *  Created on: Dec 28, 2023
 *      Author: YoungMay
 */

#ifndef SRC_PLANE_PLANESCRIPT_H_
#define SRC_PLANE_PLANESCRIPT_H_

const char *PlaneLevelScript[] =
		{
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":200000,\"y\":0},{\"c\":2000,\"x\":200000,\"y\":100000}]},{\"r\":2,\"i\":[{\"c\":0,\"x\":200000,\"y\":100000},{\"c\":3772,\"x\":120000,\"y\":150000},{\"c\":5772,\"x\":70000,\"y\":150000},{\"c\":6904,\"x\":50000,\"y\":160000},{\"c\":7304,\"x\":50000,\"y\":140000},{\"c\":8436,\"x\":70000,\"y\":100000},{\"c\":10436,\"x\":120000,\"y\":100000},{\"c\":14208,\"x\":200000,\"y\":150000},{\"c\":16208,\"x\":250000,\"y\":150000},{\"c\":17340,\"x\":270000,\"y\":160000},{\"c\":17740,\"x\":270000,\"y\":140000},{\"c\":18872,\"x\":250000,\"y\":100000},{\"c\":20872,\"x\":200000,\"y\":100000}]}]"

		};

#endif /* SRC_PLANE_PLANESCRIPT_H_ */

5、在故事板中添加脚本解析方法。

void AnimationStoryBoard::loadScript(const char *value, int *bindAddX,
		int *bindAddY) {
	cJSON *root = cJSON_Parse(value);
	int gcount = cJSON_GetArraySize(root);
	for (int i = 0; i < gcount; i++) {
		cJSON *jGroup = cJSON_GetArrayItem(root, i);
		AnimationGroup *group = new AnimationGroup();
		ContinuousAnimation *aniX = new ContinuousAnimation();
		ContinuousAnimation *aniY = new ContinuousAnimation();
		aniX->bindAddress = bindAddX;
		aniY->bindAddress = bindAddY;
		group->addItem(aniX);
		group->addItem(aniY);
		group->repeat = cJSON_GetObjectItem(jGroup, "r")->valueint;
		cJSON *iGroup = cJSON_GetObjectItem(jGroup, "i");
		int icount = cJSON_GetArraySize(iGroup);
		for (int j = 0; j < icount; j++) {
			cJSON *items = cJSON_GetArrayItem(iGroup, j);
			int time = cJSON_GetObjectItem(items, "c")->valueint;
			int x = cJSON_GetObjectItem(items, "x")->valueint;
			int y = cJSON_GetObjectItem(items, "y")->valueint;

			aniX->addItem(time, x);
			aniY->addItem(time, y);

		}
		ListPushBack(animationGroupList, (LTDataType) group);
	}
}

这就ok了。

脚本的另一个作用是制作关卡。

我们通过脚本定义不同时间或者不同位置(滚轴游戏),出现不同的敌人,也可以定义出现一些地形物体,加上碰撞,可以做到类似地图的效果。

我们来试着做个排队行进的小飞机。方式是几个飞机绑定相同的线路,错开相同间隔出现。

1、先定义飞机的飞行线路

对应的JSON:

[
	{
		"r": 1,
		"i": [
			{				"c": 0,						"x": 200000,				"y": 0			},
			{				"c": 4000,				"x": 200000,				"y": 400000	},
			{				"c": 5000,				"x": 100000,				"y": 400000	},
			{				"c": 7000,				"x": 100000,				"y": 200000	},
			{				"c": 7500,				"x": 150000,				"y": 200000	},
		]
	}
]

再加一个与之对称的。

2、加入数组

/*
 * PlaneScript.h
 *
 *  Created on: Dec 28, 2023
 *      Author: YoungMay
 */

#ifndef SRC_PLANE_PLANESCRIPT_H_
#define SRC_PLANE_PLANESCRIPT_H_

static const char *PlaneLevelScript[] =
		{
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":200000,\"y\":0},{\"c\":2000,\"x\":200000,\"y\":100000}]},{\"r\":2,\"i\":[{\"c\":0,\"x\":200000,\"y\":100000},{\"c\":3772,\"x\":120000,\"y\":150000},{\"c\":5772,\"x\":70000,\"y\":150000},{\"c\":6904,\"x\":50000,\"y\":160000},{\"c\":7304,\"x\":50000,\"y\":140000},{\"c\":8436,\"x\":70000,\"y\":100000},{\"c\":10436,\"x\":120000,\"y\":100000},{\"c\":14208,\"x\":200000,\"y\":150000},{\"c\":16208,\"x\":250000,\"y\":150000},{\"c\":17340,\"x\":270000,\"y\":160000},{\"c\":17740,\"x\":270000,\"y\":140000},{\"c\":18872,\"x\":250000,\"y\":100000},{\"c\":20872,\"x\":200000,\"y\":100000}]}]",
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":210000,\"y\":0},{\"c\":4000,\"x\":210000,\"y\":400000},{\"c\":5000,\"x\":100000,\"y\":400000},{\"c\":7000,\"x\":100000,\"y\":200000},{\"c\":7500,\"x\":160000,\"y\":200000}]}]",
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":100000,\"y\":0},{\"c\":4000,\"x\":100000,\"y\":400000},{\"c\":5000,\"x\":210000,\"y\":400000},{\"c\":7000,\"x\":210000,\"y\":200000},{\"c\":7500,\"x\":160000,\"y\":200000}]}]",
};


#endif /* SRC_PLANE_PLANESCRIPT_H_ */

3、设置关卡剧本,关卡剧本我们换一种方法,不是因为这样更好,仅仅是为了多测试几种方式。如果将来要做更复杂,则应该用文件方式保存在flash里面。

/*
 * PlaneScript.h
 *
 *  Created on: Dec 28, 2023
 *      Author: YoungMay
 */

#ifndef SRC_PLANE_PLANESCRIPT_H_
#define SRC_PLANE_PLANESCRIPT_H_

static const char *PlaneLevelScript[] =
		{
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":200000,\"y\":0},{\"c\":2000,\"x\":200000,\"y\":100000}]},{\"r\":2,\"i\":[{\"c\":0,\"x\":200000,\"y\":100000},{\"c\":3772,\"x\":120000,\"y\":150000},{\"c\":5772,\"x\":70000,\"y\":150000},{\"c\":6904,\"x\":50000,\"y\":160000},{\"c\":7304,\"x\":50000,\"y\":140000},{\"c\":8436,\"x\":70000,\"y\":100000},{\"c\":10436,\"x\":120000,\"y\":100000},{\"c\":14208,\"x\":200000,\"y\":150000},{\"c\":16208,\"x\":250000,\"y\":150000},{\"c\":17340,\"x\":270000,\"y\":160000},{\"c\":17740,\"x\":270000,\"y\":140000},{\"c\":18872,\"x\":250000,\"y\":100000},{\"c\":20872,\"x\":200000,\"y\":100000}]}]",
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":210000,\"y\":0},{\"c\":4000,\"x\":210000,\"y\":400000},{\"c\":5000,\"x\":100000,\"y\":400000},{\"c\":7000,\"x\":100000,\"y\":200000},{\"c\":7500,\"x\":160000,\"y\":200000}]}]",
				"[{\"r\":1,\"i\":[{\"c\":0,\"x\":100000,\"y\":0},{\"c\":4000,\"x\":100000,\"y\":400000},{\"c\":5000,\"x\":210000,\"y\":400000},{\"c\":7000,\"x\":210000,\"y\":200000},{\"c\":7500,\"x\":160000,\"y\":200000}]}]", };

typedef struct {
	uint32_t createTime;
	uint8_t objType;
	uint8_t bindScript;
} StageScript_t;

#define StageScriptsCount 13
const StageScript_t StageScripts[] = {
		{1000,		0,		1},
		{1600,		0,		1},
		{2200,		0,		1},
		{2800,		0,		1},
		{3400,		0,		1},
		{4000,		0,		1},
		{11000,		0,		2},
		{11600,		0,		2},
		{12200,		0,		2},
		{12800,		0,		2},
		{13400,		0,		2},
		{14000,		0,		2},
		{20000,		0,		1},};

#endif /* SRC_PLANE_PLANESCRIPT_H_ */

4、修改EnemyManager管理类敌机创造的逻辑,加上脚本。

uint8_t EnemyManager::tick(uint32_t t) {
	if (stageScriptPoint < StageScriptsCount) {
		stageTime += t;
		while (stageScriptPoint < StageScriptsCount
				&& StageScripts[stageScriptPoint].createTime < stageTime) {
			EnemyBase *enemy = createEnemyObject(
					StageScripts[stageScriptPoint].objType);
			enemy->init();
			enemy->animationStoryBoard->loadScript(
					PlaneLevelScript[StageScripts[stageScriptPoint].bindScript],
					&enemy->baseInfo.x, &enemy->baseInfo.y);
			enemy->animationStoryBoard->start();
			ListPushBack(enemyList, (LTDataType) enemy);
			stageScriptPoint++;
		}
	} else {
		if (createTimer.tick(t)) {
			EnemyBase *enemy = createEnemyObject(
					ran_seq(2, enemyTypeProportion));
			enemy->init();
			ListPushBack(enemyList, (LTDataType) enemy);
		}

		if (createBossTimer.tick(t)) {
			createTimer.defaultSpan -= 100;
			EnemyBase *enemy = new EnemyT3();
			enemy->init();
			enemy->animationStoryBoard->loadScript(PlaneLevelScript[0],
					&enemy->baseInfo.x, &enemy->baseInfo.y);
			enemy->animationStoryBoard->start();
			ListPushBack(enemyList, (LTDataType) enemy);
		}
	}

	for (ListNode *cur = enemyList->next; cur != enemyList; cur = cur->next) {
		EnemyBase *enemy = ((EnemyBase*) (cur->data));
		enemy->tick(t);
	}
	return 0;
}

TIPS:花点时间设计脚本,就可以做出沙罗曼蛇那样的闯关游戏了。

 看看效果吧

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1360806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XSKY SDS 产品率先获得 OceanBase V4 新版本认证

近日&#xff0c;北京奥星贝斯科技有限公司&#xff08;简称&#xff1a;OceanBase&#xff09;与北京星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY 星辰天合&#xff09;顺利完成产品兼容性认证。 XSKY 的高性能全闪存储以及混闪存储&#xff0c;与 OceanBase V…

LayaAir3最稳定的版本来了,3.0.11或将成为3.0分支的终结版

在LayaAir1与LayaAir2的发版策略中&#xff0c;存在一个BUG&#xff0c;就是永远都在新的版本上修复上一个版本的问题。这就导致了在高速迭代的过程中&#xff0c;很难有一个持续维护的小版本分支。也就是说直到引擎停止了功能更新的最后几个版本&#xff0c;才能真正步入稳定版…

leetcode算法题之递归--深度优先搜索总结

文章目录 1.全排列2.子集 1.全排列 全排列 class Solution {vector<vector<int>> ret;vector<int> path;bool check[7];//标记nums数组某个下标是否已访问&#xff0c;剪枝使用 public:vector<vector<int>> permute(vector<int>& n…

工智能基础知识总结-- 什么是Seq2Seq

什么是Seq2Seq Seq2Seq模型,全称Sequence to sequence,由Encoder和Decoder两个部分组成,每部分都是一个RNNCell(RNN、LSTM、GRU等)结构。Encoder将一个序列编码为一个固定长度的语义向量,Decoder将该语义向量解码为另一个序列。输入序列和输出序列都可以是不定长序列。 S…

124基于matlab的禁忌搜索算法和蚁群优化算法优化TSP路径

基于matlab的禁忌搜索算法和蚁群优化算法优化TSP路径&#xff0c;动态输出路径规划过程及输出最小距离。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。需要直接拍下&#xff0c;拍下后发邮箱。标价为程序价格&#xff0c;不包含售后。程序保证可直接运行。 …

18款Visual Studio实用插件(更新)

前言 俗话说的好工欲善其事必先利其器&#xff0c;安装一些Visual Studio实用插件对自己日常的开发和工作效率能够大大的提升&#xff0c;避免996从选一款好的IDE实用插件开始。以下是我认为比较实用的Visual Studio插件希望对大家有用&#xff0c;大家有更好的插件推荐可在文…

引导过程的解析以及如何利用systemd去管理nginx

bios加电自检------mbr--------grub-------加载内核文件------启动第一个进程 bios的主要作用&#xff1a;检测硬件是否正常&#xff0c;然后根据bios中的启动项设置&#xff0c;去找内核文件 boot开机启动项顺序&#xff0c;你可以把内核文件放在何处&#xff1f; 1.硬盘 …

什么是谐波减速机?日本Harmonic哈默纳科谐波减速机有哪些优点?

一、什么是谐波减速机&#xff1f; 谐波减速装置最早期被叫做“strain wave gearing”&#xff0c;直译过来为“应变波齿轮”。其后被HarmonicDrive Systems 公司大规模商业实用化后&#xff0c;经过二次翻译后&#xff0c;中文名称才将其称为“谐波齿轮传动”。 谐波减速机是…

免费的GPT4来了,你还不知道吗?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

odoo17 | 视图操作按钮

前言 到目前为止&#xff0c;我们主要通过声明字段和视图来构建我们的模块。在上一章中&#xff0c;我们刚刚通过计算字段和onchanges引入了业务逻辑。在任何真实的业务场景中&#xff0c;我们都会希望将一些业务逻辑链接到操作按钮。在我们的房地产示例中&#xff0c;我们希望…

【数据分析】指数移动平均线的直观解释

slavahead 一、介绍 在时间序列分析中&#xff0c;通常需要通过考虑先前的值来了解序列的趋势方向。序列中下一个值的近似可以通过多种方式执行&#xff0c;包括使用简单基线或构建高级机器学习模型。 指数&#xff08;加权&#xff09;移动平均线是这两种方法之间的稳健权衡。…

FreeRTOS——内存管理知识总结及实战

1 freeRTOS动态创建与静态创建 动态创建&#xff1a;从FreeRTOS 管理的内存堆中申请创建对象所需的内存&#xff0c;在对象删除后&#xff0c; 这块内存释放回FreeRTOS管理的内存堆中 静态创建&#xff1a;需用户提供各种内存空间&#xff0c;并且使用静态方式占用的内存空间一…

Leetcode2965. 找出缺失和重复的数字

Every day a Leetcode 题目来源&#xff1a;2965. 找出缺失和重复的数字 解法1&#xff1a;哈希 用哈希表统计数组 grid 中各元素的出现次数&#xff0c;其中出现次数为 2 的记为 a。 统计数组 grid 的元素之和为 sum。 数组 grid 其中的值在 [1, n2] 范围内&#xff0c;…

CentOS 8 8.5.2111 网络在线安装系统 —— 筑梦之路

之前写过一篇关于centos 8 官方停止更新维护后解决yum源问题的文章&#xff1a; CentOS 8 停止维护后换可用yum源——筑梦之路_http://ftp.iij.ad.jp/pub/linux/centos-vault/8.5.21-CSDN博客 由于centos 8 dvd的镜像比较大&#xff0c;有时候我们根本不需要去下载一个10G以上…

Android两个APP之间跳转+手机图标修改

APP之间跳转 两个APP之间跳转同样使用Intent进行跳转&#xff0c;将需要跳转的APP都下载到手机中&#xff0c;通过主APP调用需要跳转的APP包名进行跳转。 最好在其中加上try-catch语句&#xff0c;当需要跳转的APP不存在时进行错误抓取。 代码如下&#xff1a; Intent mInten…

netcore 时间戳转换成时间日期格式

直接上代码&#xff1a; var date new DateTime(2013, 6, 2, 8, 0, 0); // 要转换的日期 var timestamp (long)(date.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds; Console.WriteLine("Timestamp: " timestamp); return View(); …

KBDLT2.DLL文件丢失,软件或游戏无法启动,如何解决,试试这些方法,亲测有效

KBDLT2.DLL是Windows操作系统中的一个重要的动态链接库文件&#xff0c;它主要用于支持系统的键盘布局功能&#xff0c;尤其是与立陶宛语相关的键盘布局。 如果KBDLT2.DLL文件缺失或损坏&#xff0c;可能会导致一些问题。例如&#xff0c;当你试图使用立陶宛语键盘布局时&#…

【开源】轻松实现车牌检测与识别:yolov8+paddleocr【python源码+数据集】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

第7章-第1节-Java中的异常处理

1、异常Exception概述&#xff1a; 1&#xff09;、异常的概念&#xff1a; 现实生活中万物在发展和变化会出现各种各样不正常的现象。 例如&#xff1a;人的成长过程中会生病。 实际工作中&#xff0c;遇到的情况不可能是非常完美的。 比如&#xff1a;你写的某个模块&…