# 自定义符卡
本教程适用于最新版 Touhou Little Maid 模组。
# 1. 需要的条件
- 基本的数据结构和计算机编程知识;
- 基本的
JavaScript
知识,熟悉简单的JavaScript
语法; - 高中及其以上的数学知识,熟悉各种基础函数的表达式;
- 必须的文本编辑工具,推荐
VSCode
,Atom
等工具;
# 2. 如何开始
- 游戏启动后,会在游戏主目录下的
config\touhou_little_maid\custom_spell_card
文件夹下生成默认的符卡文件; - 在此文件夹内创建新的文件,文件需要为
JavaScript
脚本文件,并按照后文介绍的格式书写; - 在游戏内使用
/spell reload
指令重载所有的符卡脚本; - 打开本模组的符卡标签页找到所书写的符卡。
# 3. 详细介绍
# 1. 创建文件
新建一个后缀名为 .js
的文件即可,其他部分无硬性要求,但为了规范统一,建议采用如下格式命名:
先找到想要还原符卡的英文名
通过 https://en.touhouwiki.net/wiki/List_of_Spell_Cards 可获取官方游戏中所有符卡的英文名
此处我们用
秋符「オータムスカイ」
举例,其英文名为Autumn Sign "Autumn Sky
;脚本命名就推荐书写为
autumn_sign.autumn_sky.js
。
# 2. 书写脚本
脚本中部分内容属于模式化的,可以直接复制粘贴进行使用,此处给出模板:
// 延迟执行逻辑的工具类
var Task = Java.type("com.github.tartaricacid.touhoulittlemaid.util.DelayedTask");
// 弹幕颜色枚举类
var Color = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.DanmakuColor");
// 弹幕类型枚举类
var Type = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.DanmakuType");
// 弹幕类
var Danmaku = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.script.EntityDanmakuWrapper");
// 三元双精度浮点向量类
var Vec3d = Java.type("com.github.tartaricacid.touhoulittlemaid.danmaku.script.Vec3dWrapper");
Java.asJSONCompatible({
// 符卡的 id,字符串,必需参数,不允许和其他符卡重名
// 推荐格式:资源域:某某符.符卡名,全部采用符卡英文名小写下划线格式书写
id: "demo:autumn_sign.autumn_sky",
// 非必须参数
// 如果没有此字段,则会自动依据符卡 id 生成语言文件的 Key
nameKey: "spell_card.demo:autumn_sign.autumn_sky.name"
// 非必须参数
// 可以通过此 key 书写语言文件,进而提供符卡的描述性文字
descriptionKey: "spell_card.demo:autumn_sign.autumn_sky.desc"
// 非必须参数
// 作者名称,字符串
author: "tartaric_acid",
// 非必须参数
// 版本,字符串
version: "1.0.0",
// 非必须参数
// 使用符卡后的冷却时间,整型数
cooldown: 250,
/**
* 执行的符卡逻辑,函数签名固定,会直接调用
* @param world 当前所处的世界
* @param entity 释放符卡的实体
*/
spellCard: function (world, entity) {
// 此处书写释放符卡后的具体逻辑,后面会细讲
}
});
作为示例,我们先写一个简单的朝视线方向发射的单发弹幕,这里仅给出符卡逻辑部分代码:
spellCard: function (world, entity) {
// 构建一个弹幕实例
// 其参数分别为:世界,发射它的实体,弹幕伤害值,弹幕重力值,弹幕类型,弹幕颜色
var danmaku = new Danmaku(world, entity, 2.0, 0.0, Type.PETAL, Color.MAGENTA);
// 弹幕的发射路径
// 其参数分别为:发射它的实体,发射的 Pitch(俯仰),发射的 Yaw(偏航)
// Pitch(俯仰)的偏移值,弹幕运动速度,弹幕不准确度
danmaku.shoot(entity, 0, entity.getYaw(), 0, 0.4, 0);
// 生成该弹幕
world.spawnDanmaku(danmaku);
}
书写好此代码后,游戏内运行重载符卡指令,即可在创造模式标签页找到该符卡。
现在我们稍微改下此符卡,想让其每间隔半秒发射一发单发弹幕,总共发射 10 次。此时我们需要用到 Task
类,在原来单发弹幕基础上进行些许修改,即可实现此功能:
spellCard: function (world, entity) {
// for 循环 10 次
for (var i = 0; i < 10; i++) {
// Task.add() 方法,有两个参数
// 第一个参数:一个无参函数,这里书写的是具体上个案例中弹幕弹幕的逻辑
// 第二个参数:延迟时间,这里写 i * 10 代表每 10 tick,也就是半秒执行一次该逻辑
Task.add(function () {
var danmaku = new Danmaku(world, entity, 2.0, 0.0, Type.PETAL, Color.MAGENTA);
danmaku.shoot(entity, 0, entity.getYaw(), 0, 0.4, 0);
world.spawnDanmaku(danmaku);
}, i * 10);
}
}
# 3. 方法列表
这里给出目前可用的所有方法的文档:
# Danmaku
类
弹幕本体,使用时需进行实例化,调用 World.spawnDanmaku(Danmaku danmaku)
方法进行实际的生成。
构造方法:
Danmaku(World world, EntityLivingBase thrower, Type type, Color color)
参数名 类型 说明 world
World
弹幕实体所处的世界 thrower
EntityLivingBase
弹幕的发射者 type
Type
弹幕类型,枚举类 color
Color
弹幕颜色,枚举类 Danmaku(World world, EntityLivingBase thrower,float damage, float gravity, Type type, Color color)
参数名 类型 说明 world
World
弹幕实体所处的世界 thrower
EntityLivingBase
弹幕的发射者 damage
float
弹幕的伤害值 gravity
float
弹幕的重力值,设置为 0,弹幕不受重力影响 type
Type
弹幕类型,枚举类 color
Color
弹幕颜色,枚举类 实例方法
void shoot(EntityLivingBase thrower, float pitch, float yaw, float pitchOffset, float velocity, float inaccuracy)
:弹幕发射方向的设置,无返回值
参数名 | 类型 | 说明 |
---|---|---|
thrower | EntityLivingBase | 弹幕的发射者 |
pitch | float | 弹幕的俯仰,单位为度 |
yaw | float | 弹幕的偏航,单位为度 |
pitchOffset | float | 弹幕的俯仰偏移量,最终弹幕的俯仰会按照 pitch + pitchOffset 角度进行运动 |
velocity | float | 弹幕运动速度 |
inaccuracy | float | 弹幕的不准确度,弹幕将会按照设置的方向,以高斯随机变量进行偏移,可用于设计一些泛自机狙弹幕 |
什么是俯仰(pitch)和偏航(yaw)?
来自 https://en.wikipedia.org/wiki/Aircraft_principal_axes 词条配图
void shoot(double x, double y, double z, float velocity, float inaccuracy)
:弹幕发射方向的设置,无返回值
参数名 | 类型 | 说明 |
---|---|---|
x y z | double | 弹幕射向的 x y z 坐标 |
velocity | float | 弹幕运动速度 |
inaccuracy | float | 弹幕的不准确度,弹幕将会按照设置的方向,以高斯随机变量进行偏移,可用于设计一些泛自机狙弹幕 |
void setType(Type type)
:设置弹幕类型,无返回值
参数名 | 类型 | 说明 |
---|---|---|
type | Type | 弹幕类型,枚举类 |
void setColor(Color color)
:设置弹幕颜色,无返回值
参数名 | 类型 | 说明 |
---|---|---|
color | Color | 弹幕颜色,枚举类 |
void setTicksExisted(int ticksExisted)
:设置弹幕存活时间,不设置时默认为 200 tick,无返回值
参数名 | 类型 | 说明 |
---|---|---|
ticksExisted | int | 弹幕存活时间 |
void setPosition(Vec3d vec3d)
:设置弹幕的位置,无返回值
参数名 | 类型 | 说明 |
---|---|---|
vec3d | Vec3d | 弹幕坐标 |
void setDamagesTerrain(boolean canDamages)
:设置魔炮弹幕是否会破坏地形,该参数仅对魔炮弹幕有效,无返回值
参数名 | 类型 | 说明 |
---|---|---|
canDamages | boolean | 默认为 false ,设置为 true ,会造成地形破坏和卡顿,谨慎使用 |
# Type
枚举类
弹幕的类型枚举
枚举名 | 说明 | 备注 |
---|---|---|
PELLET | 点弹 | |
BALL | 小玉 | |
ORBS | 环玉 | |
BIG_BALL | 中玉 | |
BUBBLE | 大玉 | |
HEART | 心弹 | |
JELLYBEAN | 椭弹 | |
AMULET | 札弹 | |
STAR | 星弹 | |
BIG_STAR | 大星弹 | |
PETAL | 米弹 | |
KNIFE | 刀弹 | |
MASTER_SPARK | 魔炮 | 会不停的释放爆炸效果 |
BULLET | 铳弹 | |
KUNAI | 苦无弹 | |
RAINDROP | 滴弹 | |
ARROWHEAD | 鳞弹 |
# Color
枚举类
弹幕的颜色枚举
枚举名 | 说明 |
---|---|
RED | 红色 |
ORANGE | 橙色 |
YELLOW | 黄色 |
LIME | 黄绿色 |
LIGHT_GREEN | 淡绿色 |
GREEN | 绿色 |
CYAN | 青色 |
LIGHT_BLUE | 淡蓝色 |
BLUE | 蓝色 |
PURPLE | 紫色 |
MAGENTA | 品红色 |
PINK | 粉色 |
GRAY | 灰色 |
# World
类
即 spellCard: function (world, entity)
里面传入的 world
变量所属的类型
void spawnDanmaku(Danmaku danmaku)
:在世界上生成传入的弹幕实例,无返回值
参数名 | 类型 | 说明 |
---|---|---|
danmaku | Danmaku | 想要生成的弹幕实例 |
# EntityLivingBase
类
即 spellCard: function (world, entity)
里面传入的 entity
变量所属的类型
float getYaw()
:获取该实体的偏航,单位为度,返回值为浮点数;
float getPitch()
:获取该实体的俯仰,单位为度,返回值为浮点数;
Vec3d getPos()
:获取该实体的坐标,返回值为 Vec3d
类;
# Task
类
一个用于延迟执行逻辑的类
void add(Function function, int delayedTick)
:依据设定的 tick 延时执行传入的函数式,无返回值
参数名 | 类型 | 说明 |
---|---|---|
function | Function | 无参函数式,函数式内仅能调用 world 和 entity 变量,或者创建内部变量,其他外部变量无法调用 |
delayedTick | int | 延迟的 tick 数 |
因为目前所用的 JavaScript
脚本解析器对新版本语法还存在一定的支持问题,function
变量可使用匿名函数来书写,其实例如下:
Task.add(function () {
// 具体的弹幕逻辑
}, delayedTick) // 变量 delayedTick 应为你想要设定的延迟时间
void add(Function<Integer> function, int delayedTick,int times)
:依据设定的 tick 延时执行传入的函数式,可以从外部额外传入一个 int
数据用于内部的函数式逻辑实现,无返回值
参数名 | 类型 | 说明 |
---|---|---|
function | Function<Integer> | 单 int 参数函数式,函数式内仅能调用 world 、entity 和 times 变量,或者创建内部变量,其他外部变量无法调用 |
delayedTick | int | 延迟的 tick 数 |
times | int | 外部传入的 int 数据,主要用于 for 循环传递次数 |
该方法实例如下:
Task.add(function (times) {
// 具体的弹幕逻辑
// 此时可额外使用 times 变量,该变量由下面的 times 参数传入
}, delayedTick, times) // 变量 delayedTick 应为你想要设定的延迟时间
# Vec3d
类
一个三元的浮点型向量类,主要用于一些简单的向量变换
构造方法
Vec3d(double x, double y, double z)
:依据传入的x y z
构建向量实例方法
double getX()
:获取该向量的 x 坐标,返回值为双精度浮点值double getY()
:获取该向量的 y 坐标,返回值为双精度浮点值double getZ()
:获取该向量的 z 坐标,返回值为双精度浮点值静态方法
Vec3d getRotationVector(double x, double y, double z, float yaw, double yOffset, EntityLivingBase entity)
:把以实体为原点的相对向量(x,y,z)
转换成绝对向量
参数名 | 类型 | 说明 |
---|---|---|
x y z | double | x y z 构建的相对向量 |
yaw | float | 向量的偏航,单位为度 |
yOffset | double | 该向量在 y 方向的偏移值 |
entity | EntityLivingBase | 偏转时参考的实体对象,最后向量的构建会参考该实体的朝向和坐标,从而构建成一个绝对向量 |