还记得上次在第十七章中为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制作像素游戏屏-飞行射