Merge #62
@@ -30,9 +30,9 @@
|
||||
如果成功安装,那么以下内容的 markdown 预览应当是一个事件栏。
|
||||
|
||||
```rdview 7 5 1
|
||||
es.PlaySong 0 0[];
|
||||
es.SetCrotchetsPerBar_4 0 1[];
|
||||
eb.AddClassicBeat 0 2[tick=6];
|
||||
PlaySong 0 0[];
|
||||
SetCrotchetsPerBar 0 1[crotchetsPerBar=4];
|
||||
AddClassicBeat 0 2[tick=6];
|
||||
```
|
||||
|
||||
### RhythmBase.View
|
||||
|
||||
+4
-81
@@ -15,93 +15,15 @@
|
||||
<link rel="stylesheet" href="/style/dark.css">
|
||||
<link rel="stylesheet" href="/style/color.css">
|
||||
<link rel="stylesheet" href="/style/rdview.css">
|
||||
<link rel="stylesheet" href="/style/rdview-rdtk.css">
|
||||
<link rel="stylesheet" href="/style/otto.css">
|
||||
<link rel="stylesheet" href="/style/special.css">
|
||||
<script src="/script/config.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">加载中...</div>
|
||||
<script>
|
||||
$docsify = {
|
||||
name: "节奏医生编辑器教程",
|
||||
notFoundPage: "/404.md",
|
||||
homepage: "/pages/intro.md",
|
||||
loadSidebar: true,
|
||||
loadNavbar: true,
|
||||
coverpage: false,
|
||||
maxLevel: 5,
|
||||
subMaxLevel: 4,
|
||||
mergeNavbar: true,
|
||||
};
|
||||
</script>
|
||||
<script type="module">
|
||||
import "./script/rdview.js";
|
||||
import {render} from "./script/rdtkview_render.js";
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
loadRDView();
|
||||
const originalCodeRenderer = $docsify.markdown
|
||||
?.renderer?.code;
|
||||
function codeblockRender(code, lang) {
|
||||
if (lang.trim().startsWith("rdview")) {
|
||||
return RDViewRender(lang, code);
|
||||
}
|
||||
// else if (lang.trim().startsWith("rdtkview")) {
|
||||
// const args = lang.slice(8).trim().split(" ");
|
||||
// const width = parseInt(args[0]) || 8;
|
||||
// const height = parseInt(args[1]) || 4;
|
||||
// const scale = parseFloat(args[2]) || 2.0;
|
||||
// const result = render(code, width, height, scale);
|
||||
// result.toString = () => result;
|
||||
// return result;
|
||||
// }
|
||||
return originalCodeRenderer
|
||||
? originalCodeRenderer(code, lang)
|
||||
: `<pre data-lang="${lang}"><code>${
|
||||
escapeHtml(code)
|
||||
}</code></pre>`;
|
||||
}
|
||||
$docsify = {
|
||||
...$docsify,
|
||||
...{
|
||||
markdown: {
|
||||
renderer: {
|
||||
code: (code, lang) => {
|
||||
return codeblockRender(code, lang);
|
||||
},
|
||||
image: function (src, title, alt) {
|
||||
return `<div class="image-tip"><img class="medium-zoom-image" src="${src}" alt="${alt}">${
|
||||
alt ? `<p>${alt}</p>` : ""
|
||||
}</div>`;
|
||||
},
|
||||
},
|
||||
},
|
||||
switchLightDarkMode: {
|
||||
useSwitchMode: true,
|
||||
top: 130,
|
||||
right: 26,
|
||||
svgColor: "#7d7b75",
|
||||
},
|
||||
search: {
|
||||
noData: {
|
||||
"/": "没有找到结果!",
|
||||
},
|
||||
paths: "auto",
|
||||
placeholder: {
|
||||
"/": "搜索",
|
||||
},
|
||||
hideOtherSidebarContent: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<script type="module" src="./script/rdview-init.js"></script>
|
||||
<script type="module" src="./script/special.js"></script>
|
||||
<!-- <script src="/script/docsify.min.js"></script> -->
|
||||
<script src="/script/zoom-image.min.js"></script>
|
||||
@@ -110,6 +32,7 @@
|
||||
<!-- <script src="/script/docsify-copy-code.min.js"></script> -->
|
||||
<script src="/script/switchLightDarkMode.js"></script>
|
||||
<script src="/script/otto.js"></script>
|
||||
<script src="/script/custommethod-data.js"></script>
|
||||
<script src="/script/custommethod-search.js"></script>
|
||||
<!-- <script
|
||||
src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-yaml.js"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# RDView Playground
|
||||
<div class="rdview"><div class="canvas"></div><div class="editor"><div class="number"></div><textarea class="area" spellcheck="false" placeholder="Enter code here">
|
||||
rdview 8 5
|
||||
es.Comment 2[if=true];
|
||||
Comment 2[tab=Sounds,if=true];
|
||||
g 1 1 5 4 #0ff{
|
||||
eb.SetRowXs[rowxs=--x-xx];
|
||||
eb.AddClassicBeat 2[tick=3,swing=0.333,grayscale=100];
|
||||
eb.AddOneshotBeat 2 1[
|
||||
SetRowXs[rowxs=--x-xx];
|
||||
AddClassicBeat 2[tick=0.5,swing=0.333,grayscale=100];
|
||||
AddOneshotBeat 2 1[
|
||||
type=freezeshot subdivision,
|
||||
interval=2,
|
||||
delay=0.25,
|
||||
@@ -13,10 +13,10 @@ g 1 1 5 4 #0ff{
|
||||
loop=2,
|
||||
offset_grayscale=100
|
||||
];
|
||||
es.SayReadyGetSetGo_Get 0 2;
|
||||
es.SayReadyGetSetGo_Set 1 2;
|
||||
es.SayReadyGetSetGo_Go 2 2;
|
||||
es.SayReadyGetSetGo_GetSetGo 0 3;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 1 2[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 2 2[phraseToSay=JustSayGo];
|
||||
SayReadyGetSetGo 0 3[phraseToSay=SayGetSetGo];
|
||||
};
|
||||
</textarea></div></div>
|
||||
|
||||
|
||||
+18
-18
@@ -39,12 +39,12 @@
|
||||
**心爆 3** :三次及以上累计的按拍 / 三个及以上重叠的按拍产生的心爆。
|
||||
|
||||
```rdview 3 5
|
||||
eb.AddFreeTimeBeat 1 1[pulse=7];
|
||||
eb.AddFreeTimeBeat 1 2[pulse=7];
|
||||
eb.AddFreeTimeBeat 1 2[pulse=7];
|
||||
eb.AddFreeTimeBeat 1 3[pulse=7];
|
||||
eb.AddFreeTimeBeat 1 3[pulse=7];
|
||||
eb.AddFreeTimeBeat 1 3[pulse=7];
|
||||
AddFreeTimeBeat 1 1[pulse=6];
|
||||
AddFreeTimeBeat 1 2[pulse=6];
|
||||
AddFreeTimeBeat 1 2[pulse=6];
|
||||
AddFreeTimeBeat 1 3[pulse=6];
|
||||
AddFreeTimeBeat 1 3[pulse=6];
|
||||
AddFreeTimeBeat 1 3[pulse=6];
|
||||
```
|
||||
|
||||
### 设置心跳音量
|
||||
@@ -73,19 +73,19 @@ eb.AddFreeTimeBeat 1 3[pulse=7];
|
||||
```rdview 4 9
|
||||
g 1 1{
|
||||
g 1 0 0 1 #08f{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1.25 3 1.75 1 #08f{
|
||||
g{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1.5 5 1.5 1 #08f{
|
||||
g{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1.75 7 1.25 1 #08f{
|
||||
g{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
```
|
||||
|
||||
@@ -97,35 +97,35 @@ g 1.75 7 1.25 1 #08f{
|
||||
```rdview 7 9
|
||||
g 1 1 0.75 1 #08f{
|
||||
g 0 0 0.25{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1 3 1.25 1 #08f{
|
||||
g 0 0 0.75{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1 5 1.25{
|
||||
g 1.25 0 0 1 #08f{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 1 7 1.75 {
|
||||
g 1.75 0 0 1 #08f{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 4 1 1 1 #08f{
|
||||
g 0 0 0.5{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 4 3 1.5 1 #08f{
|
||||
g{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 4 5 1.5{
|
||||
g 1.5 0 0 1 #08f{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
g 4 7 2 {
|
||||
g 2 0 0 1 #08f{};
|
||||
eb.AddFreeTimeBeat [pulse=7];
|
||||
AddFreeTimeBeat [pulse=6];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
+25
-25
@@ -47,15 +47,15 @@
|
||||
首先,剧情模式出现两次五拍子机制,它们出现的位置在节奏上都是连续附点的,即两个心跳之间的间隔都是1.5拍,这样五拍子的有声拍长也是6,和普通的普通拍子是完全一致的。在这种连续附点的压迫感之下设置五拍,与音乐非常契合。
|
||||
|
||||
```rdview 14 4
|
||||
eb.AddClassicBeat 6 1[tick=6];
|
||||
eb.SetRowXs 1 2[rowxs=xx----];
|
||||
eb.AddFreeTimeBeat 3 2[pulse=1];
|
||||
eb.PulseFreeTimeBeat 4.5 2[pulse=9];
|
||||
eb.PulseFreeTimeBeat 6 2[pulse=9];
|
||||
eb.PulseFreeTimeBeat 7.5 2[pulse=9];
|
||||
eb.PulseFreeTimeBeat 9 2[pulse=9];
|
||||
eb.PulseFreeTimeBeat 10.5 2[pulse=9];
|
||||
eb.PulseFreeTimeBeat 12 2[pulse=9];
|
||||
AddClassicBeat 6 1[tick=6];
|
||||
SetRowXs 1 2[rowxs=xx----];
|
||||
AddFreeTimeBeat 3 2[pulse=1];
|
||||
PulseFreeTimeBeat 4.5 2[pulse=9];
|
||||
PulseFreeTimeBeat 6 2[pulse=9];
|
||||
PulseFreeTimeBeat 7.5 2[pulse=9];
|
||||
PulseFreeTimeBeat 9 2[pulse=9];
|
||||
PulseFreeTimeBeat 10.5 2[pulse=9];
|
||||
PulseFreeTimeBeat 12 2[pulse=9];
|
||||
```
|
||||
|
||||
而我们自选音乐时很多时候遇到的情况都是连续五个采音点,但间隔都是1拍,这种情况与其精准采音`xx----`反而不如额外增加一个不存在的采音变成`-x----`或者`-----x`,这样既不损失与音乐的契合,又能把节奏归化简单。
|
||||
@@ -89,22 +89,22 @@ eb.PulseFreeTimeBeat 12 2[pulse=9];
|
||||
**三、**每一条轨道的**节奏功能做出严格的区分**,如果场上本身就有多轨,不要给一条轨道设计过多差距太大的节奏类型。同时,非必要尽量不要增添太多轨道。让很多轨道展现出花里胡哨的节奏但却在同一拍按键也会让人感觉很牵强。
|
||||
|
||||
```rdview 14 4
|
||||
eb.AddClassicBeat 0 0[tick=1.5,swing=0.25];
|
||||
eb.AddClassicBeat 2 0[tick=3,swing=0.5];
|
||||
eb.AddClassicBeat 6 0[tick=3,hold=0.5,swing=0.5];
|
||||
eb.AddFreeTimeBeat 10 0[pulse=1];
|
||||
eb.PulseFreeTimeBeat 10.25[pulse=9];
|
||||
eb.PulseFreeTimeBeat 11[pulse=9];
|
||||
eb.PulseFreeTimeBeat 11.25[pulse=9];
|
||||
eb.PulseFreeTimeBeat 12[pulse=9];
|
||||
eb.PulseFreeTimeBeat 12.25[pulse=9];
|
||||
eb.PulseFreeTimeBeat 13[pulse=9];
|
||||
eb.AddClassicBeat 0 1[tick=6];
|
||||
eb.AddClassicBeat 7 1[tick=1.5,swing=0.25];
|
||||
eb.AddClassicBeat 9 1[tick=1.5,swing=0.25];
|
||||
eb.AddClassicBeat 0 2[tick=3,hold=0.5,swing=0.5];
|
||||
eb.AddClassicBeat 4 2[tick=3,swing=0.5];
|
||||
eb.AddClassicBeat 7 2[tick=6];
|
||||
AddClassicBeat 0 0[tick=1.5,swing=0.25];
|
||||
AddClassicBeat 2 0[tick=3,swing=0.5];
|
||||
AddClassicBeat 6 0[tick=3,hold=0.5,swing=0.5];
|
||||
AddFreeTimeBeat 10 0[pulse=1];
|
||||
PulseFreeTimeBeat 10.25[pulse=9];
|
||||
PulseFreeTimeBeat 11[pulse=9];
|
||||
PulseFreeTimeBeat 11.25[pulse=9];
|
||||
PulseFreeTimeBeat 12[pulse=9];
|
||||
PulseFreeTimeBeat 12.25[pulse=9];
|
||||
PulseFreeTimeBeat 13[pulse=9];
|
||||
AddClassicBeat 0 1[tick=6];
|
||||
AddClassicBeat 7 1[tick=1.5,swing=0.25];
|
||||
AddClassicBeat 9 1[tick=1.5,swing=0.25];
|
||||
AddClassicBeat 0 2[tick=3,hold=0.5,swing=0.5];
|
||||
AddClassicBeat 4 2[tick=3,swing=0.5];
|
||||
AddClassicBeat 7 2[tick=6];
|
||||
```
|
||||
|
||||
**四、**不要过度采音。更不要为了精准采音做出种类繁多,重复次数又少的自由拍子。节奏医生的游戏机制是和一般的音乐游戏不同的。音乐游戏是可以给每一个节奏点都设计按键(Note)的,但节奏医生却不能这样。节奏医生的本质是“提示拍 + 按键拍”的组合,**所有的提示拍全都应该按照没有按键的节奏点来设计**。如果像其他音乐游戏一样来设计节奏医生的谱面,得到的一定是塞爆谱。
|
||||
|
||||
+10
-10
@@ -39,25 +39,25 @@
|
||||
**创建文本递进**:如果想要把一整句歌词按歌词节奏一个一个地显示出来,就需要在文本中使用“`/`”将它们隔开。如果想要一个一个地覆盖掉之前显示的内容,就需要在文本中使用`回车键`将他们隔开。之后点击下面的“创建文本递进”,在工作区里面可以拖动右箭头事件来调整时机。如果你做好了又不想要了,那么再把下面的开启改成关闭即可,无需手动删除。如果你又反悔了,还是想要,再改成开启,又会恢复记录。
|
||||
|
||||
```rdview 8 3
|
||||
ea.FloatingText 1 1;
|
||||
ea.AdvanceText 2 1;
|
||||
ea.AdvanceText 3 1;
|
||||
ea.AdvanceText 4 1;
|
||||
ea.AdvanceText 5 1;
|
||||
ea.AdvanceText 6 1;
|
||||
ea.AdvanceText 7 1;
|
||||
FloatingText 1 1;
|
||||
AdvanceText 2 1;
|
||||
AdvanceText 3 1;
|
||||
AdvanceText 4 1;
|
||||
AdvanceText 5 1;
|
||||
AdvanceText 6 1;
|
||||
AdvanceText 7 1;
|
||||
```
|
||||
|
||||
音节的消失方式选择**淡出**就是慢慢消失,**立即**就是瞬间消失,音节显示的时长是按创建文本递进的最后一个事件往后数拍子的。
|
||||
|
||||
```rdview 13 3
|
||||
ea.FloatingText 1 1;
|
||||
FloatingText 1 1;
|
||||
g 3 1 4 1;
|
||||
g 7 1 0 1 #f80;
|
||||
ea.AdvanceText 3 1;
|
||||
AdvanceText 3 1;
|
||||
g 8 1 4 1;
|
||||
g 12 1 0 1 #f80;
|
||||
ea.AdvanceText 8 1;
|
||||
AdvanceText 8 1;
|
||||
```
|
||||
|
||||
## 更丰富的效果和技巧
|
||||
|
||||
@@ -93,6 +93,18 @@
|
||||
## 快捷查询
|
||||
|
||||
<div class="custommethods-search">
|
||||
<div class="cm-search">
|
||||
<input class="cm-query" type="text" placeholder="输入自定义方法名,如 ChangeCharacter" autocomplete="off">
|
||||
<ul class="cm-suggest"></ul>
|
||||
</div>
|
||||
<div class="cm-selected" hidden>
|
||||
<div class="cm-head">
|
||||
<span class="cm-name"></span>
|
||||
<span class="cm-desc"></span>
|
||||
</div>
|
||||
<div class="cm-row"></div>
|
||||
<code class="cm-preview"></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## 我知道你还想问什么
|
||||
|
||||
+20
-20
@@ -9,8 +9,8 @@
|
||||
如果不对其添加任何其他设置,那么轨道会延续上一个“切换为修饰拍子设置”的模式,当然你可以在“设置X标记”这个项目里面选择跳过节拍的形式,默认有三种情况,比如选`—x—x—x`轨道就会自动变成 `—x—x—x————♥` 的模式,像`4-1`这关的矿工一样,也就是在1,3,5,7拍有按压的动画效果。
|
||||
|
||||
```rdview 7 3
|
||||
eb.AddClassicBeat 3 1[tick=3,hold=0.3,swing=0.5];
|
||||
eb.SetRowXs 1 1[rowxs=---x--];
|
||||
AddClassicBeat 3 1[tick=0.5,hold=0.3,swing=0.5];
|
||||
SetRowXs 1 1[rowxs=---x--];
|
||||
```
|
||||
|
||||
另外长按拍子在编辑器里面也可以分解为自由节拍,使用自由节拍时,需将脉冲出现与脉冲消失分别构成两个合法的普通拍,也即所有脉冲出现的相隔时长相同,且所有脉冲消失的相隔时长相同。
|
||||
@@ -24,12 +24,12 @@ g 5 2 0 5 #f00{};
|
||||
g 5.5 3 0 4 #d20{};
|
||||
g 6 4 0 3 #b40{};
|
||||
g 6.5 5 0 2 #960{};
|
||||
eb.AddFreeTimeBeat 1 1[offset=0 1,hold=4];
|
||||
eb.PulseFreeTimeBeat 2 1[offset=0 2,hold=3.5,pulse=3];
|
||||
eb.PulseFreeTimeBeat 3 1[offset=0 3,hold=3,pulse=5];
|
||||
eb.PulseFreeTimeBeat 4 1[offset=0 4,hold=2.5,pulse=7];
|
||||
eb.AddClassicBeat 1 6[tick=3,swing=0.5];
|
||||
eb.AddClassicBeat 5 6[tick=1.5,swing=0.25];
|
||||
AddFreeTimeBeat 1 1[pulse=0,offset=0 1,hold=4];
|
||||
PulseFreeTimeBeat 2 1[offset=0 2,hold=3.5,action=Custom,customPulse=2];
|
||||
PulseFreeTimeBeat 3 1[offset=0 3,hold=3,action=Custom,customPulse=4];
|
||||
PulseFreeTimeBeat 4 1[offset=0 4,hold=2.5,action=Custom,customPulse=6];
|
||||
AddClassicBeat 1 6[tick=0.5,swing=2];
|
||||
AddClassicBeat 5 6[tick=0.25,swing=2];
|
||||
```
|
||||
|
||||
## 演示关卡
|
||||
@@ -41,23 +41,23 @@ eb.AddClassicBeat 5 6[tick=1.5,swing=0.25];
|
||||
**1、**在长按拍松手的时刻可以有其他的按拍。这在曾经的编辑器版本里是不允许的,目前官方修正了这一机制。
|
||||
|
||||
```rdview 9 4
|
||||
eb.AddClassicBeat 1 1[tick=6,hold=1];
|
||||
eb.AddClassicBeat 2 2[tick=6];
|
||||
AddClassicBeat 1 1[tick=1,hold=1];
|
||||
AddClassicBeat 2 2[tick=1];
|
||||
```
|
||||
|
||||
**2、**当同时存在多个长按拍子时,这些长按拍的长按时间段必须完全一致。也就是说,长按时间段之间不能是包含、交叉或首尾相连的关系。
|
||||
|
||||
```rdview 16 7
|
||||
eb.AddClassicBeat 1 1[tick=3,hold=1,swing=0.5];
|
||||
eb.AddClassicBeat 1 2[tick=3,hold=0.5,swing=0.5];
|
||||
eb.AddClassicBeat 6 1[tick=3,hold=1,swing=0.5];
|
||||
eb.AddClassicBeat 6.25 2[tick=3,hold=0.5,swing=0.5];
|
||||
eb.AddClassicBeat 11 1[tick=3,hold=1,swing=0.5];
|
||||
eb.AddClassicBeat 11.5 2[tick=3,hold=1,swing=0.5];
|
||||
eb.AddClassicBeat 1.5 4[tick=1.5,hold=1,swing=0.25];
|
||||
eb.AddClassicBeat 1 5[tick=3,hold=0.5,swing=0.5];
|
||||
eb.AddClassicBeat 6 4[tick=3,hold=1,swing=0.5];
|
||||
eb.AddClassicBeat 6.5 5[tick=3,hold=0.5,swing=0.5];
|
||||
AddClassicBeat 1 1[tick=0.5,hold=1,swing=0.5];
|
||||
AddClassicBeat 1 2[tick=0.5,hold=0.5,swing=0.5];
|
||||
AddClassicBeat 6 1[tick=0.5,hold=1,swing=0.5];
|
||||
AddClassicBeat 6.25 2[tick=0.5,hold=0.5,swing=0.5];
|
||||
AddClassicBeat 11 1[tick=0.5,hold=1,swing=0.5];
|
||||
AddClassicBeat 11.5 2[tick=0.5,hold=1,swing=0.5];
|
||||
AddClassicBeat 1.5 4[tick=0.5,hold=1,swing=0.25];
|
||||
AddClassicBeat 1 5[tick=0.5,hold=0.5,swing=0.5];
|
||||
AddClassicBeat 6 4[tick=0.5,hold=1,swing=0.5];
|
||||
AddClassicBeat 6.5 5[tick=0.5,hold=0.5,swing=0.5];
|
||||
```
|
||||
|
||||
**3、**凹兔的自动演示对于长按拍的处理十分诡异,可能会提前松掉长按拍子,但不必惊慌,这不是你的问题。
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
接下来在左下角点击紫色星星图标进入紫色的版块,在能看到波形结束的地方点一下,然后往下翻到**结束关卡**按钮,这样的按钮一共必须放三个。
|
||||
|
||||
```rdview 5 5
|
||||
ea.FinishLevel 1 1;
|
||||
ea.FinishLevel 2 2;
|
||||
ea.FinishLevel 3 3;
|
||||
FinishLevel 1 1;
|
||||
FinishLevel 2 2;
|
||||
FinishLevel 3 3;
|
||||
```
|
||||
|
||||
最后我们回到红色的版块,点击**关卡设置**,选择上方的导出,有几个空栏是必须要填写的:
|
||||
|
||||
+67
-67
@@ -7,13 +7,13 @@
|
||||
我们把一个普通的普通拍子分解为自由时长节拍后,可以看到它变成了一个大的`1`和六个小的`>` 。
|
||||
|
||||
```rdview 8 2
|
||||
eb.AddFreeTimeBeat 1 0[pulse=1];
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 5 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 6 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 7 0[pulse=9];
|
||||
AddFreeTimeBeat 1 0[pulse=0];
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
PulseFreeTimeBeat 3 0[action=Increment];
|
||||
PulseFreeTimeBeat 4 0[action=Increment];
|
||||
PulseFreeTimeBeat 5 0[action=Increment];
|
||||
PulseFreeTimeBeat 6 0[action=Increment];
|
||||
PulseFreeTimeBeat 7 0[action=Increment];
|
||||
```
|
||||
|
||||
我们选中数字1对应的节拍,发现这个是设置起始脉冲位置的。你甚至可以将它改为第七拍,然后把六个小的`>`删掉,这样就没有任何节拍提示。如果你设计的关卡是一个和节奏医生没什么关联的创意谱(梦幻联动?),且按键密度很大,则可以考虑这样使用。相比于工作区里面重叠摆放一大堆普通普通拍子,这样会看上去更清爽。但如果你设计的就是节奏医生,**请不要在完全无节奏提示的情况下直接响起第七拍**。
|
||||
@@ -23,20 +23,20 @@ eb.PulseFreeTimeBeat 7 0[pulse=9];
|
||||
剩下的大于号`>`即意味着心跳向右移动一格,数到7的时候则需要按拍。点选之后,把它改成 `<`,心跳还能向左移动一格;点击自定义,还可以任意指定到第几个节拍。每一组自由拍**必须使用较大的节拍作为开头**,较小的则数量不限,直到数到第七拍为止。
|
||||
|
||||
```rdview 10 2
|
||||
eb.AddFreeTimeBeat 1 0[pulse=1];
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 2.75 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3.5 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4.5 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 5 0[pulse=9];
|
||||
eb.AddFreeTimeBeat 5 0[pulse=1];
|
||||
eb.PulseFreeTimeBeat 6 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 6.75 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 7.5 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 8 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 8.5 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 9 0[pulse=9];
|
||||
AddFreeTimeBeat 1 0[pulse=0];
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
PulseFreeTimeBeat 2.75 0[action=Increment];
|
||||
PulseFreeTimeBeat 3.5 0[action=Increment];
|
||||
PulseFreeTimeBeat 4 0[action=Increment];
|
||||
PulseFreeTimeBeat 4.5 0[action=Increment];
|
||||
PulseFreeTimeBeat 5 0[action=Increment];
|
||||
AddFreeTimeBeat 5 0[pulse=0];
|
||||
PulseFreeTimeBeat 6 0[action=Increment];
|
||||
PulseFreeTimeBeat 6.75 0[action=Increment];
|
||||
PulseFreeTimeBeat 7.5 0[action=Increment];
|
||||
PulseFreeTimeBeat 8 0[action=Increment];
|
||||
PulseFreeTimeBeat 8.5 0[action=Increment];
|
||||
PulseFreeTimeBeat 9 0[action=Increment];
|
||||
```
|
||||
|
||||
如果你想让一串自由节拍最后不需要按拍,而是节奏直接消失,就把这一组自由节拍的最后一个点选**垃圾桶**符号。
|
||||
@@ -70,15 +70,15 @@ eb.PulseFreeTimeBeat 9 0[pulse=9];
|
||||
八拍子就更好理解了,我们直接额外增加一个节拍,让按拍的时机在第八拍响起的时候。但是轨道上是只能显示七个拍子的,所以我们依然要依赖**分解成自由拍**,以`2 1 2 3 4 5 6 7`的形式响起节拍。设置的方法如下图所示:
|
||||
|
||||
```rdview 10 3
|
||||
eb.AddClassicBeat 1 0[tick=6];
|
||||
eb.AddFreeTimeBeat 1 1[pulse=2];
|
||||
eb.PulseFreeTimeBeat 2 1[pulse=8];
|
||||
eb.PulseFreeTimeBeat 3 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 5 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 6 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 7 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 8 1[pulse=9];
|
||||
AddClassicBeat 1 0[tick=1];
|
||||
AddFreeTimeBeat 1 1[pulse=1];
|
||||
PulseFreeTimeBeat 2 1[action=Decrement];
|
||||
PulseFreeTimeBeat 3 1[action=Increment];
|
||||
PulseFreeTimeBeat 4 1[action=Increment];
|
||||
PulseFreeTimeBeat 5 1[action=Increment];
|
||||
PulseFreeTimeBeat 6 1[action=Increment];
|
||||
PulseFreeTimeBeat 7 1[action=Increment];
|
||||
PulseFreeTimeBeat 8 1[action=Increment];
|
||||
```
|
||||
|
||||
**如何提示**
|
||||
@@ -91,15 +91,15 @@ eb.PulseFreeTimeBeat 8 1[pulse=9];
|
||||
八拍子也可以是摇摆拍,只需要将所有偶数拍选择并拖动即可变成摇摆拍:
|
||||
|
||||
```rdview 10 3
|
||||
eb.AddClassicBeat 1 0[tick=6,swing=0.5];
|
||||
eb.AddFreeTimeBeat 1 1[pulse=2];
|
||||
eb.PulseFreeTimeBeat 2.5 1[pulse=8];
|
||||
eb.PulseFreeTimeBeat 3 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4.5 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 5 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 6.5 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 7 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 8.5 1[pulse=9];
|
||||
AddClassicBeat 1 0[tick=1,swing=0.5];
|
||||
AddFreeTimeBeat 1 1[pulse=1];
|
||||
PulseFreeTimeBeat 2.5 1[action=Decrement];
|
||||
PulseFreeTimeBeat 3 1[action=Increment];
|
||||
PulseFreeTimeBeat 4.5 1[action=Increment];
|
||||
PulseFreeTimeBeat 5 1[action=Increment];
|
||||
PulseFreeTimeBeat 6.5 1[action=Increment];
|
||||
PulseFreeTimeBeat 7 1[action=Increment];
|
||||
PulseFreeTimeBeat 8.5 1[action=Increment];
|
||||
```
|
||||
|
||||
在八拍子中,**激活拍子**与八拍子的对应关系如下图所示。不要给第1拍和第3拍设置X拍子,这样做它就相当于变回普通拍了。
|
||||
@@ -118,33 +118,33 @@ eb.PulseFreeTimeBeat 8.5 1[pulse=9];
|
||||
整数拍和附点拍是交错排列的,即`0.25<->0.375<->0.5<->0.75<->1<->1.5<->2<->3<->4`。附点变速拍可以将整拍长变成附点拍长,也可以反过来,但只能变化到排列里它**相邻**的拍长上。
|
||||
|
||||
```rdview 9 3
|
||||
eb.AddFreeTimeBeat 1 0;
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 0[pulse=9];
|
||||
AddFreeTimeBeat 1 0;
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
PulseFreeTimeBeat 3 0[action=Increment];
|
||||
PulseFreeTimeBeat 4 0[action=Increment];
|
||||
g 4 0 0.5 1 #08f[offset=0 0 #08f0]{
|
||||
eb.PulseFreeTimeBeat 1 0[pulse=9];
|
||||
PulseFreeTimeBeat 1 0[action=Increment];
|
||||
g 1 0 0.5 1 #000[offset=0 0 #0000]{
|
||||
g 0 0 0 0 #0000[offset=0.25]{
|
||||
eb.PulseFreeTimeBeat 0.75 0[pulse=9,offset=0.25];
|
||||
PulseFreeTimeBeat 0.75 0[action=Increment,offset=0.25];
|
||||
g 0.75 0 0.5 1 #0000[offset=0.25]{
|
||||
eb.PulseFreeTimeBeat 0.75 0[pulse=9];
|
||||
PulseFreeTimeBeat 0.75 0[action=Increment];
|
||||
g -1.75 0 3 1 #0000{};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
eb.AddFreeTimeBeat 1 1;
|
||||
eb.PulseFreeTimeBeat 2 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 1[pulse=9];
|
||||
AddFreeTimeBeat 1 1;
|
||||
PulseFreeTimeBeat 2 1[action=Increment];
|
||||
PulseFreeTimeBeat 3 1[action=Increment];
|
||||
PulseFreeTimeBeat 4 1[action=Increment];
|
||||
g 4 1 0.5 1 #80f[offset=0 0 #08f0]{
|
||||
eb.PulseFreeTimeBeat 1 0[pulse=9];
|
||||
PulseFreeTimeBeat 1 0[action=Increment];
|
||||
g 1 0 0.5 1 #000[offset=0 0 #0000]{
|
||||
g 0 0 0 0 #0000[offset=-0.5]{
|
||||
eb.PulseFreeTimeBeat 1.5 0[pulse=9];
|
||||
PulseFreeTimeBeat 1.5 0[action=Increment];
|
||||
g 1.5 0 0.5 1 #0000[offset=-0.5]{
|
||||
eb.PulseFreeTimeBeat 1.5 0[pulse=9];
|
||||
PulseFreeTimeBeat 1.5 0[action=Increment];
|
||||
g -2.5 0 4.5 1 #0000{};
|
||||
};
|
||||
};
|
||||
@@ -171,33 +171,33 @@ g 4 1 0.5 1 #80f[offset=0 0 #08f0]{
|
||||
变速普通拍的另一种形式是倍速变速拍,变速是拍长翻倍或减半,注意与附点变速拍区分。
|
||||
|
||||
```rdview 11 3
|
||||
eb.AddFreeTimeBeat 1 0;
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3 0[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 0[pulse=9];
|
||||
AddFreeTimeBeat 1 0;
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
PulseFreeTimeBeat 3 0[action=Increment];
|
||||
PulseFreeTimeBeat 4 0[action=Increment];
|
||||
g 4 0 0.5 1 #08f[offset=0 0 #08f0]{
|
||||
eb.PulseFreeTimeBeat 1 0[pulse=9];
|
||||
PulseFreeTimeBeat 1 0[action=Increment];
|
||||
g 1 0 0.5 1 #000[offset=0 0 #0000]{
|
||||
g 0 0 0 0 #0000[offset=0.5]{
|
||||
eb.PulseFreeTimeBeat 0.5 0[pulse=9];
|
||||
PulseFreeTimeBeat 0.5 0[action=Increment];
|
||||
g 0.5 0 0.5 1 #0000[offset=0.5]{
|
||||
eb.PulseFreeTimeBeat 0.5 0[pulse=9];
|
||||
PulseFreeTimeBeat 0.5 0[action=Increment];
|
||||
g -1.75 0 3 1 #0000{};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
eb.AddFreeTimeBeat 1 1;
|
||||
eb.PulseFreeTimeBeat 2 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 3 1[pulse=9];
|
||||
eb.PulseFreeTimeBeat 4 1[pulse=9];
|
||||
AddFreeTimeBeat 1 1;
|
||||
PulseFreeTimeBeat 2 1[action=Increment];
|
||||
PulseFreeTimeBeat 3 1[action=Increment];
|
||||
PulseFreeTimeBeat 4 1[action=Increment];
|
||||
g 4 1 0.5 1 #08f[offset=0 0 #08f0]{
|
||||
eb.PulseFreeTimeBeat 1 0[pulse=9];
|
||||
PulseFreeTimeBeat 1 0[action=Increment];
|
||||
g 1 0 0.5 1 #000[offset=0 0 #0000]{
|
||||
g 0 0 0 0 #0000[offset=-1]{
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
g 2 0 0.5 1 #0000[offset=-1]{
|
||||
eb.PulseFreeTimeBeat 2 0[pulse=9];
|
||||
PulseFreeTimeBeat 2 0[action=Increment];
|
||||
g -2.5 0 4.5 1 #0000{};
|
||||
};
|
||||
};
|
||||
|
||||
+119
-119
@@ -7,10 +7,10 @@
|
||||
我们前面说过,护士提示的 Rea 和 Get 对应脉冲,Dy 和 Set 对应按拍,Go 代表单发拍开始,这种规则当然不仅仅适用于普通单发拍。先来看一个简单的摇摆单发拍:
|
||||
|
||||
```rdview 11 3
|
||||
es.SayReadyGetSetGo_Get 1[];
|
||||
es.SayReadyGetSetGo_Set 2.5[];
|
||||
es.SayReadyGetSetGo_Go 5[];
|
||||
eb.AddOneshotBeat 5 1[loop=1,tick=1.5,interval=4];
|
||||
SayReadyGetSetGo 1[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 2.5[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 5[phraseToSay=JustSayGo];
|
||||
AddOneshotBeat 5 1[loop=1,tick=1.5,interval=4];
|
||||
```
|
||||
|
||||
可以看到,虽然 Get-Set 与 Set-Go 之间的间隔不同,但依然保持了上述的对应关系。实际上,这就是`4-4`使用的单发拍。
|
||||
@@ -18,31 +18,31 @@ eb.AddOneshotBeat 5 1[loop=1,tick=1.5,interval=4];
|
||||
在`2-3`中也出现了摇摆单发拍,可以试着自己理解一下这关的单发拍提示:
|
||||
|
||||
```rdview 10 2
|
||||
eb.AddOneshotBeat 1[];
|
||||
eb.AddOneshotBeat 3[];
|
||||
eb.AddOneshotBeat 5[tick=0.75];
|
||||
eb.AddOneshotBeat 6[tick=0.75];
|
||||
eb.AddOneshotBeat 7[tick=0.75];
|
||||
eb.AddOneshotBeat 8[tick=0.75];
|
||||
AddOneshotBeat 1[];
|
||||
AddOneshotBeat 3[];
|
||||
AddOneshotBeat 5[tick=0.75];
|
||||
AddOneshotBeat 6[tick=0.75];
|
||||
AddOneshotBeat 7[tick=0.75];
|
||||
AddOneshotBeat 8[tick=0.75];
|
||||
```
|
||||
|
||||
如果我们在此基础上更进一步,会发生什么呢?来看看`4-4N`中出现过的这段单发拍:
|
||||
|
||||
```rdview 12 4
|
||||
g 1 1 4 2[offset=4]{
|
||||
es.SayReadyGetSetGo_Rea 0 0[];
|
||||
es.SayReadyGetSetGo_Dy 0.75 1[];
|
||||
es.SayReadyGetSetGo_Get 1.5 0[];
|
||||
es.SayReadyGetSetGo_Set 2.25 1[];
|
||||
es.SayReadyGetSetGo_Go 4 0[];
|
||||
SayReadyGetSetGo 0 0[phraseToSay=JustSayRea];
|
||||
SayReadyGetSetGo 0.75 1[phraseToSay=JustSayDy];
|
||||
SayReadyGetSetGo 1.5 0[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 2.25 1[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 4 0[phraseToSay=JustSayGo];
|
||||
g 0 -1 0.75 3 #0f8[]{};
|
||||
g 1.5 -1 0.75 3 #0f8[]{};
|
||||
g 4 -1 0 3 #0f8[]{};
|
||||
};
|
||||
eb.AddOneshotBeat 5[tick=0.75];
|
||||
eb.AddOneshotBeat 6.5[tick=0.75];
|
||||
eb.AddOneshotBeat 9[tick=0.75];
|
||||
eb.AddOneshotBeat 10.5[tick=0.75];
|
||||
AddOneshotBeat 5[tick=0.75];
|
||||
AddOneshotBeat 6.5[tick=0.75];
|
||||
AddOneshotBeat 9[tick=0.75];
|
||||
AddOneshotBeat 10.5[tick=0.75];
|
||||
```
|
||||
|
||||
可以看到,图中的单发拍已不再局限于一个单发拍不断重复了,而是变成了若干个单发拍构成的单发拍组的不断重复,但前面讲过的与护士提示之间的对应关系仍保持不变。由此我们可以得到单发拍的核心规则:
|
||||
@@ -53,24 +53,24 @@ eb.AddOneshotBeat 10.5[tick=0.75];
|
||||
|
||||
```rdview 13 4
|
||||
g 1 1 4 2[offset=4]{
|
||||
es.SayReadyGetSetGo_Rea 0 0[];
|
||||
es.SayReadyGetSetGo_Dy 1 0[];
|
||||
es.SayReadyGetSetGo_Rea 2 0[];
|
||||
es.SayReadyGetSetGo_Dy 2.5 1[];
|
||||
es.SayReadyGetSetGo_Get 3 0[];
|
||||
es.SayReadyGetSetGo_Set 3.5 1[];
|
||||
es.SayReadyGetSetGo_Go 4 0[];
|
||||
SayReadyGetSetGo 0 0[phraseToSay=JustSayRea];
|
||||
SayReadyGetSetGo 1 0[phraseToSay=JustSayDy];
|
||||
SayReadyGetSetGo 2 0[phraseToSay=JustSayRea];
|
||||
SayReadyGetSetGo 2.5 1[phraseToSay=JustSayDy];
|
||||
SayReadyGetSetGo 3 0[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 3.5 1[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 4 0[phraseToSay=JustSayGo];
|
||||
g 0 -1 1 2 #0f8[]{};
|
||||
g 2 -1 0.5 3 #0f8[]{};
|
||||
g 3 -1 0.5 3 #0f8[]{};
|
||||
g 4 -1 0 3 #0f8[]{};
|
||||
};
|
||||
eb.AddOneshotBeat 5[];
|
||||
eb.AddOneshotBeat 7[tick=0.5];
|
||||
eb.AddOneshotBeat 8[tick=0.5];
|
||||
eb.AddOneshotBeat 9[];
|
||||
eb.AddOneshotBeat 11[tick=0.5];
|
||||
eb.AddOneshotBeat 12[tick=0.5];
|
||||
AddOneshotBeat 5[];
|
||||
AddOneshotBeat 7[tick=0.5];
|
||||
AddOneshotBeat 8[tick=0.5];
|
||||
AddOneshotBeat 9[];
|
||||
AddOneshotBeat 11[tick=0.5];
|
||||
AddOneshotBeat 12[tick=0.5];
|
||||
```
|
||||
|
||||
当你不确定一段单发拍提示是否正确时,可以把这段单发拍提示向后复制几份,首尾相连,如果发现所有的 Dy 和 Set 均对齐了按拍时机,就说明单发拍提示是正确的。
|
||||
@@ -84,13 +84,13 @@ eb.AddOneshotBeat 12[tick=0.5];
|
||||
以下单发拍均是合法的:
|
||||
|
||||
```rdview 9 2
|
||||
eb.AddOneshotBeat 1[];
|
||||
eb.AddOneshotBeat 3[];
|
||||
eb.AddOneshotBeat 3.5[tick=0.5];
|
||||
eb.AddOneshotBeat 4.75[tick=1.25];
|
||||
eb.AddOneshotBeat 5[];
|
||||
eb.AddOneshotBeat 5.75[tick=0.25];
|
||||
eb.AddOneshotBeat 7.5[tick=0.5];
|
||||
AddOneshotBeat 1[];
|
||||
AddOneshotBeat 3[];
|
||||
AddOneshotBeat 3.5[tick=0.5];
|
||||
AddOneshotBeat 4.75[tick=1.25];
|
||||
AddOneshotBeat 5[];
|
||||
AddOneshotBeat 5.75[tick=0.25];
|
||||
AddOneshotBeat 7.5[tick=0.5];
|
||||
```
|
||||

|
||||
|
||||
@@ -103,31 +103,31 @@ eb.AddOneshotBeat 7.5[tick=0.5];
|
||||
举个例子:Rea-Dy-Get-Set-Go 与 Rea-Dy--GetSet-Go,尽管两者的按拍时机相同,但不同的 Rea、Get 位置会给人带来不同的游玩体验。
|
||||
|
||||
```rdview 12 6
|
||||
eb.AddOneshotBeat 5[];
|
||||
eb.AddOneshotBeat 7.5[tick=0.5];
|
||||
eb.AddOneshotBeat 9[];
|
||||
eb.AddOneshotBeat 11.5[tick=0.5];
|
||||
AddOneshotBeat 5[];
|
||||
AddOneshotBeat 7.5[tick=0.5];
|
||||
AddOneshotBeat 9[];
|
||||
AddOneshotBeat 11.5[tick=0.5];
|
||||
g 1 1 4 1 [offset=4]{
|
||||
es.SayReadyGetSetGo_Rea 0[];
|
||||
es.SayReadyGetSetGo_Dy 1[];
|
||||
es.SayReadyGetSetGo_Get 2.5[];
|
||||
es.SayReadyGetSetGo_Set 3[];
|
||||
es.SayReadyGetSetGo_Go 4[];
|
||||
SayReadyGetSetGo 0[phraseToSay=JustSayRea];
|
||||
SayReadyGetSetGo 1[phraseToSay=JustSayDy];
|
||||
SayReadyGetSetGo 2.5[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 3[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 4[phraseToSay=JustSayGo];
|
||||
g 0 -1 1 2 #0f8[]{};
|
||||
g 2.5 -1 0.5 2 #0f8[]{};
|
||||
g 4 -1 0 2 #0f8[]{};
|
||||
};
|
||||
|
||||
eb.AddOneshotBeat 5 3[];
|
||||
eb.AddOneshotBeat 7 3[];
|
||||
eb.AddOneshotBeat 9 3[];
|
||||
eb.AddOneshotBeat 11 3[];
|
||||
AddOneshotBeat 5 3[];
|
||||
AddOneshotBeat 7 3[];
|
||||
AddOneshotBeat 9 3[];
|
||||
AddOneshotBeat 11 3[];
|
||||
g 1 4 4 1 [offset=4]{
|
||||
es.SayReadyGetSetGo_Rea 0[];
|
||||
es.SayReadyGetSetGo_Dy 1[];
|
||||
es.SayReadyGetSetGo_Get 2[];
|
||||
es.SayReadyGetSetGo_Set 3[];
|
||||
es.SayReadyGetSetGo_Go 4[];
|
||||
SayReadyGetSetGo 0[phraseToSay=JustSayRea];
|
||||
SayReadyGetSetGo 1[phraseToSay=JustSayDy];
|
||||
SayReadyGetSetGo 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 3[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 4[phraseToSay=JustSayGo];
|
||||
g 0 -1 1 2 #0f8[]{};
|
||||
g 2 -1 1 2 #0f8[]{};
|
||||
g 4 -1 0 2 #0f8[]{};
|
||||
@@ -143,13 +143,13 @@ g 1 4 4 1 [offset=4]{
|
||||
需要注意的是,跳过单发拍会跳过下一次**击拍**,而不是下一个循环节。
|
||||
|
||||
```rdview 7 4
|
||||
eb.AddOneshotBeat 1[type=skipshot];
|
||||
eb.AddOneshotBeat 5[];
|
||||
eb.AddOneshotBeat 1 2[type=skipshot];
|
||||
eb.AddOneshotBeat 3 2[tick=3];
|
||||
eb.AddOneshotBeat 3.75 2[tick=2.25];
|
||||
eb.AddOneshotBeat 4.5 2[tick=1.5];
|
||||
eb.AddOneshotBeat 5.25 2[tick=0.75];
|
||||
AddOneshotBeat 1[type=skipshot];
|
||||
AddOneshotBeat 5[];
|
||||
AddOneshotBeat 1 2[type=skipshot];
|
||||
AddOneshotBeat 3 2[tick=3];
|
||||
AddOneshotBeat 3.75 2[tick=2.25];
|
||||
AddOneshotBeat 4.5 2[tick=1.5];
|
||||
AddOneshotBeat 5.25 2[tick=0.75];
|
||||
```
|
||||
|
||||
另外,在剧情关卡中,跳过拍的位置如果按下空格会计算成失误,但编辑器中默认是不会失误的。
|
||||
@@ -167,10 +167,10 @@ eb.AddOneshotBeat 5.25 2[tick=0.75];
|
||||
除此之外,方块单发拍也需要护士提示。这种护士提示与普通单发拍的提示类似,唯一的不同点在于最后的 Go 被替换为了 One。
|
||||
|
||||
```rdview 5 3
|
||||
eb.AddOneshotBeat 3[];
|
||||
es.SayReadyGetSetGo_Get 1 1[];
|
||||
es.SayReadyGetSetGo_Set 2 1[];
|
||||
es.SayReadyGetSetGo_One 3 1[];
|
||||
AddOneshotBeat 3[];
|
||||
SayReadyGetSetGo 1 1[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 2 1[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 3 1[];
|
||||
```
|
||||
|
||||
如果相同拍长的方块单发拍连续出现了多次,无需为每次出现添加完整的护士提示。可以逐步减少护士提示,直到完全没有护士提示,只剩下特殊提示音。
|
||||
@@ -186,32 +186,32 @@ es.SayReadyGetSetGo_One 3 1[];
|
||||

|
||||
|
||||
```rdview 4 5
|
||||
eb.AddOneshotBeat 1[type=subdivision,subdivision=2];
|
||||
eb.AddOneshotBeat 1 1[type=subdivision,subdivision=3];
|
||||
eb.AddOneshotBeat 1 2[type=subdivision,subdivision=4];
|
||||
eb.AddOneshotBeat 1 3[type=subdivision,subdivision=5];
|
||||
AddOneshotBeat 1[type=subdivision,subdivision=2];
|
||||
AddOneshotBeat 1 1[type=subdivision,subdivision=3];
|
||||
AddOneshotBeat 1 2[type=subdivision,subdivision=4];
|
||||
AddOneshotBeat 1 3[type=subdivision,subdivision=5];
|
||||
```
|
||||
|
||||
在护士提示方面,不同于方块单发拍,细分拍需要在出拍前的半拍长开始数拍提示。
|
||||
|
||||
```rdview 13
|
||||
eb.AddOneshotBeat 1[type=subdivision,subdivision=2];
|
||||
AddOneshotBeat 1[type=subdivision,subdivision=2];
|
||||
g 1 1 0.5 1 #0f0[]{};
|
||||
g 1 1 1 1 #0f0[]{};
|
||||
g 0.5 1 0.5 1[offset=0.5]{
|
||||
es.SayReadyGetSetGo_2 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count2];
|
||||
};
|
||||
eb.AddOneshotBeat 4[tick=2,type=subdivision,subdivision=2,interval=4];
|
||||
AddOneshotBeat 4[tick=2,type=subdivision,subdivision=2,interval=4];
|
||||
g 4 1 1 1 #0f0[]{};
|
||||
g 4 1 2 1 #0f0[]{};
|
||||
g 3 1 1 1[offset=1]{
|
||||
es.SayReadyGetSetGo_2 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count2];
|
||||
};
|
||||
eb.AddOneshotBeat 9[tick=2,type=subdivision,subdivision=3,interval=4];
|
||||
AddOneshotBeat 9[tick=2,type=subdivision,subdivision=3,interval=4];
|
||||
g 9 1 1 1 #0f0[]{};
|
||||
g 9 1 2 1 #0f0[]{};
|
||||
g 8 1 1 1[offset=1]{
|
||||
es.SayReadyGetSetGo_3 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count3];
|
||||
};
|
||||
```
|
||||
|
||||
@@ -228,14 +228,14 @@ g 8 1 1 1[offset=1]{
|
||||
单独细分的提示**与正常单发拍的提示类似**,Rea Dy 或 Get Set 之间的间隔同样用于提示细分拍的拍长,且数字提示**和 Go! 提示一样不需要提前**。
|
||||
|
||||
```rdview 14 3
|
||||
eb.AddOneshotBeat 3[type=subdivision,subdivision=2];
|
||||
es.SayReadyGetSetGo_Get 1 1[];
|
||||
es.SayReadyGetSetGo_Set 2 1[];
|
||||
es.SayReadyGetSetGo_2 3 1[];
|
||||
eb.AddOneshotBeat 9[tick=2,type=subdivision,subdivision=4,interval=4];
|
||||
es.SayReadyGetSetGo_Get 5 1[];
|
||||
es.SayReadyGetSetGo_Set 7 1[];
|
||||
es.SayReadyGetSetGo_4 9 1[];
|
||||
AddOneshotBeat 3[type=subdivision,subdivision=2];
|
||||
SayReadyGetSetGo 1 1[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 2 1[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 3 1[phraseToSay=Count2];
|
||||
AddOneshotBeat 9[tick=2,type=subdivision,subdivision=4,interval=4];
|
||||
SayReadyGetSetGo 5 1[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 7 1[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 9 1[phraseToSay=Count4];
|
||||
```
|
||||
|
||||
## 冻结拍🧊
|
||||
@@ -249,14 +249,14 @@ es.SayReadyGetSetGo_4 9 1[];
|
||||

|
||||
|
||||
```rdview 8 2
|
||||
eb.AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
eb.AddOneshotBeat 5[type=freezeshot,delay=0.5,interval=2,tick=2];
|
||||
AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 5[type=freezeshot,delay=0.5,interval=2,tick=2];
|
||||
```
|
||||
|
||||
对于一个冻结拍而言,一共有五个关键的时间点:左十字,右十字,出拍点(绿线),原按拍点(蓝线),实际按拍点(黄线)。
|
||||
|
||||
```rdview 4 2
|
||||
eb.AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
```
|
||||
|
||||
- **左十字**和**右十字**会显示两次冰冻特效并分别播放不同的冰冻音效,用于预示拍延长的时间。
|
||||
@@ -274,20 +274,20 @@ eb.AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
- **左十字**必须和上一个普通单发拍的按拍位置重合。
|
||||
|
||||
```rdview 5 3
|
||||
eb.AddOneshotBeat 1[];
|
||||
eb.AddOneshotBeat 3[type=freezeshot,delay=0.5,interval=2];
|
||||
eb.AddOneshotBeat 1 1[grayscale=100,offset_grayscale=80];
|
||||
eb.AddOneshotBeat 3 1[grayscale=100,offset_grayscale=80];
|
||||
AddOneshotBeat 1[];
|
||||
AddOneshotBeat 3[type=freezeshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 1 1[grayscale=100,offset_grayscale=80];
|
||||
AddOneshotBeat 3 1[grayscale=100,offset_grayscale=80];
|
||||
```
|
||||
|
||||
如果需要在冻结拍上叠加多个拍子来凸显鼓点,可以将他们的按拍点放在同一位置。
|
||||
|
||||
```rdview 5 3
|
||||
eb.AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 2[type=freezeshot,delay=0.5,interval=2];
|
||||
g 2 -1 2 2 #0000[offset=0 1]{
|
||||
eb.AddOneshotBeat 0.5 1[];
|
||||
AddOneshotBeat 0.5 1[];
|
||||
g 0 -2 2 3 #0000[offset=0 1]{
|
||||
eb.AddOneshotBeat -1 3[tick=2.5];
|
||||
AddOneshotBeat -1 3[tick=2.5];
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -305,7 +305,7 @@ g 2 -1 2 2 #0000[offset=0 1]{
|
||||
类似地,对于一个灼热拍,一共有四个关键的时间点:左十字,右十字,出拍点(绿线),按拍点。为作区分且方便表述,将会把按拍点称为红线。
|
||||
|
||||
```rdview 5 2
|
||||
eb.AddOneshotBeat 3[tick=0.5,type=burnshot,interval=2];
|
||||
AddOneshotBeat 3[tick=0.5,type=burnshot,interval=2];
|
||||
```
|
||||
|
||||
- **左十字**和**右十字**会显示两次热浪特效并分别播放不同的灼热音效,用于预示拍实际的时间。
|
||||
@@ -320,20 +320,20 @@ eb.AddOneshotBeat 3[tick=0.5,type=burnshot,interval=2];
|
||||
- **左十字**必须和上一个普通单发拍的原定出拍位置重合。
|
||||
|
||||
```rdview 5 3
|
||||
eb.AddOneshotBeat 1[];
|
||||
eb.AddOneshotBeat 3[tick=0.5,type=burnshot,interval=2];
|
||||
eb.AddOneshotBeat 1 1[grayscale=100,offset_grayscale=80];
|
||||
eb.AddOneshotBeat 3 1[grayscale=100,offset_grayscale=80];
|
||||
AddOneshotBeat 1[];
|
||||
AddOneshotBeat 3[tick=0.5,type=burnshot,interval=2];
|
||||
AddOneshotBeat 1 1[grayscale=100,offset_grayscale=80];
|
||||
AddOneshotBeat 3 1[grayscale=100,offset_grayscale=80];
|
||||
```
|
||||
|
||||
如果需要在灼热拍上叠加多个拍子来凸显鼓点,可以将他们的按拍点放在同一位置。
|
||||
|
||||
```rdview 5 3
|
||||
eb.AddOneshotBeat 2[type=burnshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 2[type=burnshot,delay=0.5,interval=2];
|
||||
g 0 -1 3 2 #0000[offset=0 1]{
|
||||
eb.AddOneshotBeat 1.5 1[tick=1.5];
|
||||
AddOneshotBeat 1.5 1[tick=1.5];
|
||||
g 0 -1 3 3 #0000[offset=0 1]{
|
||||
eb.AddOneshotBeat 0.5 2[tick=2.5];
|
||||
AddOneshotBeat 0.5 2[tick=2.5];
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -387,32 +387,32 @@ g 0 -1 3 2 #0000[offset=0 1]{
|
||||
最后我们针对细分拍、冻结拍、灼热拍做一个技术总结:
|
||||
|
||||
```rdview 19 7
|
||||
eb.AddOneshotBeat 1[loop=1];
|
||||
eb.AddOneshotBeat 5[type=freezeshot,delay=0.5,interval=2];
|
||||
eb.AddOneshotBeat 1 1[loop=1];
|
||||
eb.AddOneshotBeat 5 1[];
|
||||
eb.AddOneshotBeat 1 2[loop=1];
|
||||
eb.AddOneshotBeat 5 2[tick=0.5,type=burnshot,interval=2];
|
||||
eb.AddOneshotBeat 1 4[loop=1];
|
||||
eb.AddOneshotBeat 5 4[tick=1,type=subdivision,subdivision=2,interval=2];
|
||||
eb.AddOneshotBeat 7 4[tick=0.5,loop=1,interval=1];
|
||||
eb.AddOneshotBeat 9 4[tick=0.5,type=subdivision,subdivision=2,interval=1];
|
||||
eb.AddOneshotBeat 10 4[tick=2];
|
||||
eb.AddOneshotBeat 14 4[tick=2,type=subdivision,subdivision=3,interval=4];
|
||||
AddOneshotBeat 1[loop=1];
|
||||
AddOneshotBeat 5[type=freezeshot,delay=0.5,interval=2];
|
||||
AddOneshotBeat 1 1[loop=1];
|
||||
AddOneshotBeat 5 1[];
|
||||
AddOneshotBeat 1 2[loop=1];
|
||||
AddOneshotBeat 5 2[tick=0.5,type=burnshot,interval=2];
|
||||
AddOneshotBeat 1 4[loop=1];
|
||||
AddOneshotBeat 5 4[tick=1,type=subdivision,subdivision=2,interval=2];
|
||||
AddOneshotBeat 7 4[tick=0.5,loop=1,interval=1];
|
||||
AddOneshotBeat 9 4[tick=0.5,type=subdivision,subdivision=2,interval=1];
|
||||
AddOneshotBeat 10 4[tick=2];
|
||||
AddOneshotBeat 14 4[tick=2,type=subdivision,subdivision=3,interval=4];
|
||||
g 5 5 0.5 1 #0f0[]{};
|
||||
g 5 5 1 1 #0f0[]{};
|
||||
g 4.5 5 0.5 1[offset=0.5]{
|
||||
es.SayReadyGetSetGo_2 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count2];
|
||||
};
|
||||
g 9 5 0.25 1 #0f0[]{};
|
||||
g 9 5 0.5 1 #0f0[]{};
|
||||
g 8.75 5 0.25 1[offset=0.25]{
|
||||
es.SayReadyGetSetGo_2 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count2];
|
||||
};
|
||||
g 14 5 1 1 #0f0[]{};
|
||||
g 14 5 2 1 #0f0[]{};
|
||||
g 13 5 1 1[offset=1]{
|
||||
es.SayReadyGetSetGo_2 0 1[];
|
||||
SayReadyGetSetGo 0 1[phraseToSay=Count2];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
+68
-68
@@ -15,14 +15,14 @@
|
||||
在“**循环**”的空栏里输入次数,这个单发拍就会被**额外**重复这么多次。当然了,直接输入循环次数的方法并不够方便,因为很多时候不听具体音乐自己也不知道该循环多少次。更方便的方法是,把鼠标移动到工作区的这个单发拍子上,右上角就会出现一个**红色的箭头符号**,按住它并拖动即可。
|
||||
|
||||
```rdview 7 3
|
||||
eb.AddOneshotBeat 1 1[tick=1,loop=2];
|
||||
AddOneshotBeat 1 1[tick=1,loop=2];
|
||||
```
|
||||
|
||||
在循环设置后,左上角又会多出一个“**间隔**”的设置,指的是两个绿线之间的时间长度。大多数时候,我们会将间隔设为拍长的两倍,这也就构成了一个普通单发拍子。但如果你想设计更为复杂的单发拍子节奏型,也可以试着将间隔改为其他数值:例如,把间隔改成`1`,拍长改成`0.75`,就变成了一种摇摆单发拍子。
|
||||
|
||||
```rdview 10 3
|
||||
eb.AddOneshotBeat 1 1[loop=1];
|
||||
eb.AddOneshotBeat 5 1[tick=0.75,loop=3,interval=1];
|
||||
AddOneshotBeat 1 1[loop=1];
|
||||
AddOneshotBeat 5 1[tick=0.75,loop=3,interval=1];
|
||||
```
|
||||
|
||||
配置好带有循环的单发拍之后,按一下“**分解为单发节拍**”,会发现它被拆散成了若干个没有循环的单发拍事件。分解后的单发拍更加便于修改,你可以为它们单独添加修饰符或额外的脉冲。
|
||||
@@ -42,80 +42,80 @@ eb.AddOneshotBeat 5 1[tick=0.75,loop=3,interval=1];
|
||||
将护士语音的拍长与单发拍的拍长保持一致,并将护士**喊出`Go!`的位置**与 **第一个普通单发拍的脉冲位置** 对齐即可,这样就完成了对一个普通单发拍的提示。
|
||||
|
||||
```rdview 16 12
|
||||
eb.AddOneshotBeat 5 1[tick=2];
|
||||
eb.AddOneshotBeat 9 1[tick=2];
|
||||
eb.AddOneshotBeat 13 1[tick=2];
|
||||
AddOneshotBeat 5 1[tick=2];
|
||||
AddOneshotBeat 9 1[tick=2];
|
||||
AddOneshotBeat 13 1[tick=2];
|
||||
g 1 2 4 1[offset=4 0]{
|
||||
g 0 -1 0 2 #0f08{
|
||||
es.SayReadyGetSetGo_Get 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayGet];
|
||||
};
|
||||
g 2 -1 0 2 #ff08{
|
||||
es.SayReadyGetSetGo_Set 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSaySet];
|
||||
};
|
||||
g 4 -1 0 2 #f008{
|
||||
es.SayReadyGetSetGo_Go 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayGo];
|
||||
};
|
||||
};
|
||||
eb.AddOneshotBeat 5 4;
|
||||
eb.AddOneshotBeat 7 4;
|
||||
eb.AddOneshotBeat 9 4;
|
||||
eb.AddOneshotBeat 11 4;
|
||||
eb.AddOneshotBeat 13 4;
|
||||
AddOneshotBeat 5 4;
|
||||
AddOneshotBeat 7 4;
|
||||
AddOneshotBeat 9 4;
|
||||
AddOneshotBeat 11 4;
|
||||
AddOneshotBeat 13 4;
|
||||
g 1 5 4 2[offset=4 0]{
|
||||
es.SayReadyGetSetGo_ReaDyGetSetGo 0 0;
|
||||
SayReadyGetSetGo 0 0[phraseToSay=SayReaDyGetSetGoNew];
|
||||
g 0 -1 0 3 #0f08{
|
||||
es.SayReadyGetSetGo_Rea 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayRea];
|
||||
};
|
||||
g 1 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Dy 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayDy];
|
||||
};
|
||||
g 2 -1 0 3 #0f08{
|
||||
es.SayReadyGetSetGo_Get 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayGet];
|
||||
};
|
||||
g 3 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Set 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSaySet];
|
||||
};
|
||||
g 4 -1 0 3 #F008{
|
||||
es.SayReadyGetSetGo_Go 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayGo];
|
||||
};
|
||||
};
|
||||
eb.AddOneshotBeat 5 8[tick=0.5];
|
||||
eb.AddOneshotBeat 6 8[tick=0.5];
|
||||
eb.AddOneshotBeat 7 8[tick=0.5];
|
||||
eb.AddOneshotBeat 8 8[tick=0.5];
|
||||
eb.AddOneshotBeat 9 8[tick=0.5];
|
||||
eb.AddOneshotBeat 10 8[tick=0.5];
|
||||
eb.AddOneshotBeat 11 8[tick=0.5];
|
||||
eb.AddOneshotBeat 12 8[tick=0.5];
|
||||
eb.AddOneshotBeat 13 8[tick=0.5];
|
||||
eb.AddOneshotBeat 14 8[tick=0.5];
|
||||
AddOneshotBeat 5 8[tick=0.5];
|
||||
AddOneshotBeat 6 8[tick=0.5];
|
||||
AddOneshotBeat 7 8[tick=0.5];
|
||||
AddOneshotBeat 8 8[tick=0.5];
|
||||
AddOneshotBeat 9 8[tick=0.5];
|
||||
AddOneshotBeat 10 8[tick=0.5];
|
||||
AddOneshotBeat 11 8[tick=0.5];
|
||||
AddOneshotBeat 12 8[tick=0.5];
|
||||
AddOneshotBeat 13 8[tick=0.5];
|
||||
AddOneshotBeat 14 8[tick=0.5];
|
||||
g 1 9 4 2[offset=4 0]{
|
||||
g 0 -1 0 2 #0f08{
|
||||
es.SayReadyGetSetGo_Rea 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayRea];
|
||||
};
|
||||
g 0.5 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Dy 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayDy];
|
||||
};
|
||||
g 1 -1 0 2 #0f08{
|
||||
es.SayReadyGetSetGo_Rea 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayRea];
|
||||
};
|
||||
g 1.5 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Dy 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayDy];
|
||||
};
|
||||
g 2 -1 0 2 #0f08{
|
||||
es.SayReadyGetSetGo_Rea 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayRea];
|
||||
};
|
||||
g 2.5 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Dy 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSayDy];
|
||||
};
|
||||
g 3 -1 0 2 #0f08{
|
||||
es.SayReadyGetSetGo_Get 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayGet];
|
||||
};
|
||||
g 3.5 -1 0 3 #ff08{
|
||||
es.SayReadyGetSetGo_Set 0 2;
|
||||
SayReadyGetSetGo 0 2[phraseToSay=JustSaySet];
|
||||
};
|
||||
g 4 -1 0 2 #F008{
|
||||
es.SayReadyGetSetGo_Go 0 1;
|
||||
SayReadyGetSetGo 0 1[phraseToSay=JustSayGo];
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -131,30 +131,30 @@ g 1 9 4 2[offset=4 0]{
|
||||
也可以让单发拍子在几种拍速之间来回切换,提示方法与前面所讲的相同。只需要注意一点:单发拍子的节奏型应该在**Go!的瞬间**完成切换,Go之前的护士提示不会影响当前正在进行的单发拍子,而 Go 之后也不应再出现前一种单发拍子的节拍。
|
||||
|
||||
```rdview 20 5
|
||||
eb.AddOneshotBeat 3 1;
|
||||
eb.AddOneshotBeat 5 1;
|
||||
eb.AddOneshotBeat 7 1[tick=0.5];
|
||||
eb.AddOneshotBeat 8 1[tick=0.5];
|
||||
eb.AddOneshotBeat 9 1[tick=0.5];
|
||||
eb.AddOneshotBeat 10 1[tick=0.5];
|
||||
eb.AddOneshotBeat 11 1;
|
||||
eb.AddOneshotBeat 13 1;
|
||||
eb.AddOneshotBeat 15 1[tick=0.75];
|
||||
eb.AddOneshotBeat 16 1[tick=0.75];
|
||||
eb.AddOneshotBeat 17 1[tick=0.75];
|
||||
eb.AddOneshotBeat 18 1[tick=0.75];
|
||||
es.SayReadyGetSetGo_Get 1 2;
|
||||
es.SayReadyGetSetGo_Set 2 2;
|
||||
es.SayReadyGetSetGo_Go 3 2;
|
||||
es.SayReadyGetSetGo_Get 6 2;
|
||||
es.SayReadyGetSetGo_Set 6.5 3;
|
||||
es.SayReadyGetSetGo_Go 7 2;
|
||||
es.SayReadyGetSetGo_Get 9 2;
|
||||
es.SayReadyGetSetGo_Set 10 2;
|
||||
es.SayReadyGetSetGo_Go 11 2;
|
||||
es.SayReadyGetSetGo_Get 14 2;
|
||||
es.SayReadyGetSetGo_Set 14.75 3;
|
||||
es.SayReadyGetSetGo_Go 15 2;
|
||||
AddOneshotBeat 3 1;
|
||||
AddOneshotBeat 5 1;
|
||||
AddOneshotBeat 7 1[tick=0.5];
|
||||
AddOneshotBeat 8 1[tick=0.5];
|
||||
AddOneshotBeat 9 1[tick=0.5];
|
||||
AddOneshotBeat 10 1[tick=0.5];
|
||||
AddOneshotBeat 11 1;
|
||||
AddOneshotBeat 13 1;
|
||||
AddOneshotBeat 15 1[tick=0.75];
|
||||
AddOneshotBeat 16 1[tick=0.75];
|
||||
AddOneshotBeat 17 1[tick=0.75];
|
||||
AddOneshotBeat 18 1[tick=0.75];
|
||||
SayReadyGetSetGo 1 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 2 2[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 3 2[phraseToSay=JustSayGo];
|
||||
SayReadyGetSetGo 6 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 6.5 3[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 7 2[phraseToSay=JustSayGo];
|
||||
SayReadyGetSetGo 9 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 10 2[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 11 2[phraseToSay=JustSayGo];
|
||||
SayReadyGetSetGo 14 2[phraseToSay=JustSayGet];
|
||||
SayReadyGetSetGo 14.75 3[phraseToSay=JustSaySet];
|
||||
SayReadyGetSetGo 15 2[phraseToSay=JustSayGo];
|
||||
```
|
||||
|
||||
一个单发拍子不可能无限循环下去,总会有停下来的时候,可以使用护士提示中的`Stop`告知玩家单发拍子的结束。通常情况下,应使`Stop`对齐单发拍子的**最后一次击拍位置**。
|
||||
@@ -164,12 +164,12 @@ es.SayReadyGetSetGo_Go 15 2;
|
||||
如果单发拍子的停止时机足够符合直觉,可以省略单发拍子的Stop,但必须保证玩家下意识多打出的拍子不会判Miss。最稳妥的方法永远是为所有单发拍子的停止都添加 Stop 提示。
|
||||
|
||||
```rdview 10 3
|
||||
eb.AddClassicBeat 2.5[tick=6];
|
||||
eb.AddOneshotBeat 1 1[];
|
||||
eb.AddOneshotBeat 3 1[];
|
||||
eb.AddOneshotBeat 5 1[];
|
||||
AddClassicBeat 2.5[tick=1];
|
||||
AddOneshotBeat 1 1[];
|
||||
AddOneshotBeat 3 1[];
|
||||
AddOneshotBeat 5 1[];
|
||||
g 7 1 1 1{
|
||||
eb.AddOneshotBeat[grayscale=100];
|
||||
AddOneshotBeat[grayscale=100];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
+190
-190
@@ -9,58 +9,58 @@
|
||||
```rdview 18 3
|
||||
g 1 0 4 2 #ff0{
|
||||
g 0 -1 0 3 #08f[]{};
|
||||
ea.MoveRow [tag=true];
|
||||
ea.MoveRow 1[tag=true];
|
||||
ea.MoveCamera 2[tag=true];
|
||||
ea.Flash 2.5[tag=true];
|
||||
ea.HideRow 0 1[tag=true];
|
||||
ea.SetVFXPreset 1.5 1[tag=true];
|
||||
ea.SetVFXPreset 2 1[tag=true];
|
||||
ea.HideRow 3 1[tag=true];
|
||||
MoveRow [tag=action];
|
||||
MoveRow 1[tag=action];
|
||||
MoveCamera 2[tag=action];
|
||||
Flash 2.5[tag=action];
|
||||
HideRow 0 1[tag=action];
|
||||
SetVFXPreset 1.5 1[tag=action];
|
||||
SetVFXPreset 2 1[tag=action];
|
||||
HideRow 3 1[tag=action];
|
||||
|
||||
g 5 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[offset_opacity=0];
|
||||
TagAction[offset_opacity=0,Tag=action];
|
||||
g 0 1 4 2 #ff0[opacity=0,offset_opacity=100]{
|
||||
g 0 -1 0 3 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
ea.MoveCamera 2[];
|
||||
ea.Flash 2.5[];
|
||||
ea.HideRow 0 1[];
|
||||
ea.SetVFXPreset 1.5 1[];
|
||||
ea.SetVFXPreset 2 1[];
|
||||
ea.HideRow 3 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
MoveCamera 2[];
|
||||
Flash 2.5[];
|
||||
HideRow 0 1[];
|
||||
SetVFXPreset 1.5 1[];
|
||||
SetVFXPreset 2 1[];
|
||||
HideRow 3 1[];
|
||||
};
|
||||
};
|
||||
g 9 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[offset_opacity=0,Tag=action];
|
||||
g 0 1 4 2 #ff0[opacity=0,offset_opacity=100]{
|
||||
g 0 -1 0 3 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
ea.MoveCamera 2[];
|
||||
ea.Flash 2.5[];
|
||||
ea.HideRow 0 1[];
|
||||
ea.SetVFXPreset 1.5 1[];
|
||||
ea.SetVFXPreset 2 1[];
|
||||
ea.HideRow 3 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
MoveCamera 2[];
|
||||
Flash 2.5[];
|
||||
HideRow 0 1[];
|
||||
SetVFXPreset 1.5 1[];
|
||||
SetVFXPreset 2 1[];
|
||||
HideRow 3 1[];
|
||||
};
|
||||
};
|
||||
g 13 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[offset_opacity=0,Tag=action];
|
||||
g 0 1 4 2 #ff0[opacity=0,offset_opacity=100]{
|
||||
g 0 -1 0 3 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
ea.MoveCamera 2[];
|
||||
ea.Flash 2.5[];
|
||||
ea.HideRow 0 1[];
|
||||
ea.SetVFXPreset 1.5 1[];
|
||||
ea.SetVFXPreset 2 1[];
|
||||
ea.HideRow 3 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
MoveCamera 2[];
|
||||
Flash 2.5[];
|
||||
HideRow 0 1[];
|
||||
SetVFXPreset 1.5 1[];
|
||||
SetVFXPreset 2 1[];
|
||||
HideRow 3 1[];
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -111,75 +111,75 @@ g 1 0 4 2 #ff0{
|
||||
```rdview 16 5
|
||||
g 1 1 2 1 #df0{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [tag=true];
|
||||
ea.MoveRow 1[tag=true];
|
||||
MoveRow [tag=move];
|
||||
MoveRow 1[tag=move];
|
||||
|
||||
g 4 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
};
|
||||
};
|
||||
g 6 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
};
|
||||
};
|
||||
g 8 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
};
|
||||
};
|
||||
g 10 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
g 0 -1 1 1 #0000[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
};
|
||||
};
|
||||
};
|
||||
g 1 3 3 1 #df0{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [tag=true];
|
||||
ea.MoveRow 1[tag=true];
|
||||
ea.TagAction 2[tag=true];
|
||||
MoveRow [tag=move];
|
||||
MoveRow 1[tag=move];
|
||||
TagAction 2[tag=move];
|
||||
g 4 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[offset_opacity=0];
|
||||
TagAction[offset_opacity=0,Tag=move];
|
||||
g 0 1 3 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
g 2 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[offset_opacity=0,Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
g 2 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[offset_opacity=0,Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
g 2 0 0 0 #0000[offset=0 -1]{
|
||||
ea.TagAction[];
|
||||
TagAction[offset_opacity=0,Tag=move];
|
||||
g 0 1 2 1 #ff0[opacity=20,offset_opacity=100]{
|
||||
g 0 -1 0 2 #08f[]{};
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
g -6 -4 1 5 #0000[]{};
|
||||
};
|
||||
};
|
||||
@@ -189,7 +189,7 @@ g 1 3 3 1 #df0{
|
||||
};
|
||||
};
|
||||
};
|
||||
ea.TagAction 12[];
|
||||
TagAction 12[];
|
||||
};
|
||||
```
|
||||
|
||||
@@ -211,143 +211,143 @@ g 1 3 3 1 #df0{
|
||||
|
||||
```rdview 19 4
|
||||
g 0 0 19 1 #f00[]{
|
||||
ea.Comment [];
|
||||
ea.FloatingText 0.5[];
|
||||
ea.FloatingText 1[];
|
||||
ea.FloatingText 1.5[];
|
||||
ea.FloatingText 2[];
|
||||
ea.FloatingText 2.5[];
|
||||
ea.FloatingText 3[];
|
||||
ea.FloatingText 3.5[];
|
||||
ea.FloatingText 4[];
|
||||
ea.AdvanceText 4.5[];
|
||||
ea.AdvanceText 5[];
|
||||
ea.AdvanceText 5.5[];
|
||||
ea.AdvanceText 6[];
|
||||
ea.AdvanceText 6.5[];
|
||||
ea.AdvanceText 7[];
|
||||
ea.AdvanceText 7.5[];
|
||||
ea.FloatingText 8[];
|
||||
ea.FloatingText 8.75[];
|
||||
ea.Comment 9.75[];
|
||||
ea.FloatingText 11.75[];
|
||||
ea.FloatingText 12.5[];
|
||||
ea.FloatingText 13.5[];
|
||||
ea.FloatingText 14[];
|
||||
ea.FloatingText 14.5[];
|
||||
ea.FloatingText 15[];
|
||||
ea.FloatingText 15.5[];
|
||||
ea.FloatingText 16[];
|
||||
ea.FloatingText 16.5[];
|
||||
ea.FloatingText 17[];
|
||||
ea.FloatingText 17.5[];
|
||||
Comment [color=ffff00];
|
||||
FloatingText 0.5[outlineColor=000000];
|
||||
FloatingText 1[outlineColor=000000];
|
||||
FloatingText 1.5[outlineColor=000000];
|
||||
FloatingText 2[outlineColor=000000];
|
||||
FloatingText 2.5[outlineColor=000000];
|
||||
FloatingText 3[outlineColor=000000];
|
||||
FloatingText 3.5[outlineColor=000000];
|
||||
FloatingText 4[outlineColor=000000];
|
||||
AdvanceText 4.5[];
|
||||
AdvanceText 5[];
|
||||
AdvanceText 5.5[];
|
||||
AdvanceText 6[];
|
||||
AdvanceText 6.5[];
|
||||
AdvanceText 7[];
|
||||
AdvanceText 7.5[];
|
||||
FloatingText 8[outlineColor=000000];
|
||||
FloatingText 8.75[outlineColor=000000];
|
||||
Comment 9.75[tab=Actions];
|
||||
FloatingText 11.75[outlineColor=000000];
|
||||
FloatingText 12.5[outlineColor=000000];
|
||||
FloatingText 13.5[outlineColor=000000];
|
||||
FloatingText 14[outlineColor=000000];
|
||||
FloatingText 14.5[outlineColor=000000];
|
||||
FloatingText 15[outlineColor=000000];
|
||||
FloatingText 15.5[outlineColor=000000];
|
||||
FloatingText 16[outlineColor=000000];
|
||||
FloatingText 16.5[outlineColor=000000];
|
||||
FloatingText 17[outlineColor=000000];
|
||||
FloatingText 17.5[outlineColor=000000];
|
||||
};
|
||||
g 0 1 6.25 1 #08f[]{
|
||||
ea.MoveCamera [];
|
||||
ea.MoveCamera 0.5[];
|
||||
ea.MoveCamera 1[];
|
||||
ea.MoveCamera 1.5[];
|
||||
ea.MoveCamera 2[];
|
||||
ea.MoveCamera 2.5[];
|
||||
ea.MoveCamera 3[];
|
||||
ea.MoveCamera 3.5[];
|
||||
ea.MoveCamera 4[];
|
||||
ea.MoveCamera 5[];
|
||||
ea.SetForeground 5.25[];
|
||||
MoveCamera [];
|
||||
MoveCamera 0.5[];
|
||||
MoveCamera 1[];
|
||||
MoveCamera 1.5[];
|
||||
MoveCamera 2[];
|
||||
MoveCamera 2.5[];
|
||||
MoveCamera 3[];
|
||||
MoveCamera 3.5[];
|
||||
MoveCamera 4[];
|
||||
MoveCamera 5[];
|
||||
SetForeground 5.25[];
|
||||
};
|
||||
ea.Comment 6.25 1[];
|
||||
ea.MoveRow 7.75 1[];
|
||||
ea.MoveRow 8 1[];
|
||||
ea.MoveRow 8.75 1[];
|
||||
ea.MoveCamera 12 1[];
|
||||
ea.SetForeground 12.25 1[];
|
||||
ea.FloatingText 14.5 1[];
|
||||
ea.FloatingText 15 1[];
|
||||
ea.FloatingText 15.5 1[];
|
||||
ea.FloatingText 16 1[];
|
||||
ea.FloatingText 16.5 1[];
|
||||
ea.SetVFXPreset 18 1[];
|
||||
Comment 6.25 1[tab=Actions];
|
||||
MoveRow 7.75 1[];
|
||||
MoveRow 8 1[];
|
||||
MoveRow 8.75 1[];
|
||||
MoveCamera 12 1[];
|
||||
SetForeground 12.25 1[];
|
||||
FloatingText 14.5 1[outlineColor=000000];
|
||||
FloatingText 15 1[outlineColor=000000];
|
||||
FloatingText 15.5 1[outlineColor=000000];
|
||||
FloatingText 16 1[outlineColor=000000];
|
||||
FloatingText 16.5 1[outlineColor=000000];
|
||||
SetVFXPreset 18 1[];
|
||||
g 0 2 13.875 1 #06f[]{
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow [];
|
||||
ea.Comment 1.75[];
|
||||
ea.MoveRow 2.75[];
|
||||
ea.MoveRow 3[];
|
||||
ea.MoveRow 4.5[];
|
||||
ea.MoveRow 4.75[];
|
||||
ea.MoveRow 5[];
|
||||
ea.MoveRow 5.25[];
|
||||
ea.MoveRow 6.75[];
|
||||
ea.MoveRow 7[];
|
||||
ea.MoveRow 7.5[];
|
||||
ea.MoveRow 7.75[];
|
||||
ea.MoveRow 8.5[];
|
||||
ea.MoveRow 10[];
|
||||
ea.MoveRow 10.25[];
|
||||
ea.HideRow 11.5[];
|
||||
ea.HideRow 11.5[];
|
||||
ea.MoveRow 12.5[];
|
||||
ea.MoveRow 12.75[];
|
||||
MoveRow [];
|
||||
MoveRow [];
|
||||
Comment 1.75[tab=Actions];
|
||||
MoveRow 2.75[];
|
||||
MoveRow 3[];
|
||||
MoveRow 4.5[];
|
||||
MoveRow 4.75[];
|
||||
MoveRow 5[];
|
||||
MoveRow 5.25[];
|
||||
MoveRow 6.75[];
|
||||
MoveRow 7[];
|
||||
MoveRow 7.5[];
|
||||
MoveRow 7.75[];
|
||||
MoveRow 8.5[];
|
||||
MoveRow 10[];
|
||||
MoveRow 10.25[];
|
||||
HideRow 11.5[];
|
||||
HideRow 11.5[];
|
||||
MoveRow 12.5[];
|
||||
MoveRow 12.75[];
|
||||
};
|
||||
ea.Comment 14 2[];
|
||||
Comment 14 2[tab=Actions];
|
||||
g 0 3 18 1 #f80[]{
|
||||
ea.FloatingText 0.5[];
|
||||
ea.AdvanceText 1[];
|
||||
ea.AdvanceText 1.5[];
|
||||
ea.AdvanceText 2.5[];
|
||||
ea.AdvanceText 3[];
|
||||
ea.AdvanceText 4.5[];
|
||||
ea.AdvanceText 5[];
|
||||
ea.AdvanceText 5.5[];
|
||||
ea.AdvanceText 6[];
|
||||
ea.AdvanceText 6.5[];
|
||||
ea.AdvanceText 7[];
|
||||
ea.AdvanceText 8[];
|
||||
ea.AdvanceText 10.5[];
|
||||
ea.AdvanceText 11.5[];
|
||||
ea.AdvanceText 12[];
|
||||
ea.AdvanceText 12.5[];
|
||||
ea.AdvanceText 13[];
|
||||
ea.AdvanceText 13.5[];
|
||||
ea.AdvanceText 14[];
|
||||
ea.AdvanceText 14.5[];
|
||||
ea.AdvanceText 15[];
|
||||
ea.AdvanceText 15.5[];
|
||||
ea.AdvanceText 16[];
|
||||
ea.AdvanceText 16.5[];
|
||||
ea.AdvanceText 17[];
|
||||
ea.AdvanceText 17.5[];
|
||||
FloatingText 0.5[outlineColor=000000];
|
||||
AdvanceText 1[];
|
||||
AdvanceText 1.5[];
|
||||
AdvanceText 2.5[];
|
||||
AdvanceText 3[];
|
||||
AdvanceText 4.5[];
|
||||
AdvanceText 5[];
|
||||
AdvanceText 5.5[];
|
||||
AdvanceText 6[];
|
||||
AdvanceText 6.5[];
|
||||
AdvanceText 7[];
|
||||
AdvanceText 8[];
|
||||
AdvanceText 10.5[];
|
||||
AdvanceText 11.5[];
|
||||
AdvanceText 12[];
|
||||
AdvanceText 12.5[];
|
||||
AdvanceText 13[];
|
||||
AdvanceText 13.5[];
|
||||
AdvanceText 14[];
|
||||
AdvanceText 14.5[];
|
||||
AdvanceText 15[];
|
||||
AdvanceText 15.5[];
|
||||
AdvanceText 16[];
|
||||
AdvanceText 16.5[];
|
||||
AdvanceText 17[];
|
||||
AdvanceText 17.5[];
|
||||
};
|
||||
```
|
||||
|
||||
2. 如果有一串同类型的事件紧密排布,例如每1/4格或者1/2格放一个,那么可以考虑不放在同一行,而是上下交错排开。
|
||||
|
||||
```rdview 16 4
|
||||
ea.MoveRow [];
|
||||
ea.MoveRow 1[];
|
||||
ea.MoveRow 2[];
|
||||
ea.MoveRow 4[];
|
||||
ea.MoveRow 5[];
|
||||
ea.MoveRow 6[];
|
||||
ea.MoveRow 8[];
|
||||
ea.MoveRow 9[];
|
||||
ea.MoveRow 10[];
|
||||
ea.MoveRow 12[];
|
||||
ea.MoveRow 13[];
|
||||
ea.MoveRow 14[];
|
||||
ea.MoveRow 0.5 1[];
|
||||
ea.MoveRow 1.5 1[];
|
||||
ea.MoveRow 3 1[];
|
||||
ea.MoveRow 4.5 1[];
|
||||
ea.MoveRow 5.5 1[];
|
||||
ea.MoveRow 7 1[];
|
||||
ea.MoveRow 8.5 1[];
|
||||
ea.MoveRow 9.5 1[];
|
||||
ea.MoveRow 11 1[];
|
||||
ea.MoveRow 12.5 1[];
|
||||
ea.MoveRow 13.5 1[];
|
||||
ea.MoveRow 15 1[];
|
||||
ea.Comment 4 3[];
|
||||
MoveRow [];
|
||||
MoveRow 1[];
|
||||
MoveRow 2[];
|
||||
MoveRow 4[];
|
||||
MoveRow 5[];
|
||||
MoveRow 6[];
|
||||
MoveRow 8[];
|
||||
MoveRow 9[];
|
||||
MoveRow 10[];
|
||||
MoveRow 12[];
|
||||
MoveRow 13[];
|
||||
MoveRow 14[];
|
||||
MoveRow 0.5 1[];
|
||||
MoveRow 1.5 1[];
|
||||
MoveRow 3 1[];
|
||||
MoveRow 4.5 1[];
|
||||
MoveRow 5.5 1[];
|
||||
MoveRow 7 1[];
|
||||
MoveRow 8.5 1[];
|
||||
MoveRow 9.5 1[];
|
||||
MoveRow 11 1[];
|
||||
MoveRow 12.5 1[];
|
||||
MoveRow 13.5 1[];
|
||||
MoveRow 15 1[];
|
||||
Comment 4 3[tab=Actions];
|
||||
```
|
||||
|
||||
3. 对于事件总体数量不太高的情况,如果有操作房间和轨道类型的特效,可以考虑把他们放在对应数量的行里,找起来会更加方便。
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,36 @@
|
||||
window.$docsify = {
|
||||
name: "节奏医生编辑器教程",
|
||||
notFoundPage: "/404.md",
|
||||
homepage: "/pages/intro.md",
|
||||
loadSidebar: true,
|
||||
loadNavbar: true,
|
||||
coverpage: false,
|
||||
maxLevel: 5,
|
||||
subMaxLevel: 4,
|
||||
mergeNavbar: true,
|
||||
markdown: {
|
||||
renderer: {
|
||||
image: function (src, title, alt) {
|
||||
return `<div class="image-tip"><img class="medium-zoom-image" src="${src}" alt="${alt}">${
|
||||
alt ? `<p>${alt}</p>` : ""
|
||||
}</div>`;
|
||||
},
|
||||
},
|
||||
},
|
||||
switchLightDarkMode: {
|
||||
useSwitchMode: true,
|
||||
top: 130,
|
||||
right: 26,
|
||||
svgColor: "#7d7b75",
|
||||
},
|
||||
search: {
|
||||
noData: {
|
||||
"/": "没有找到结果!",
|
||||
},
|
||||
paths: "auto",
|
||||
placeholder: {
|
||||
"/": "搜索",
|
||||
},
|
||||
hideOtherSidebarContent: true,
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
+612
-17
@@ -1,13 +1,111 @@
|
||||
|
||||
|
||||
|
||||
const customMethods = [
|
||||
{ type: 'mod',}
|
||||
]
|
||||
|
||||
const editDistance = (a, b) => {
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
(function () {
|
||||
"use strict";
|
||||
let ParameterType;
|
||||
(function (ParameterType) {
|
||||
ParameterType["String"] = "string";
|
||||
ParameterType["Number"] = "number";
|
||||
ParameterType["Boolean"] = "boolean";
|
||||
ParameterType["Enum"] = "enum";
|
||||
})(ParameterType || (ParameterType = {}));
|
||||
let MethodType;
|
||||
(function (MethodType) {
|
||||
MethodType["Mod"] = "mod";
|
||||
MethodType["Surgery"] = "surgery";
|
||||
MethodType["Comment"] = "comment";
|
||||
MethodType["CustomMethod"] = "custom_method";
|
||||
MethodType["Ink"] = "ink";
|
||||
MethodType["Variable"] = "variable";
|
||||
MethodType["Condition"] = "condition";
|
||||
MethodType["CustomClass"] = "custom_class";
|
||||
})(MethodType || (MethodType = {}));
|
||||
const enumTypeMap = {
|
||||
Characters: ["Logan", "Green", "Blue", "Yellow", "Purple", "Orange"],
|
||||
Size: ["Small", "Medium", "Large"],
|
||||
Shape: ["Circle", "Square", "Triangle"],
|
||||
};
|
||||
class NumericRange {
|
||||
constructor(min, max, isInteger = false) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.isInteger = isInteger;
|
||||
}
|
||||
}
|
||||
class Parameter {
|
||||
constructor(name, type, description, enumType, numericRange) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.enumType = enumType;
|
||||
this.numericRange = numericRange;
|
||||
}
|
||||
}
|
||||
class Method {
|
||||
constructor(name, type, description) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
class Mod extends Method {
|
||||
constructor(name, description) {
|
||||
super(name, MethodType.Mod, description);
|
||||
}
|
||||
}
|
||||
class Surgery extends Method {
|
||||
constructor(name, description) {
|
||||
super(name, MethodType.Surgery, description);
|
||||
}
|
||||
}
|
||||
class CommentMethod extends Method {
|
||||
constructor(name, description, parameters) {
|
||||
super(name, MethodType.Comment, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class InkMethod extends Method {
|
||||
constructor(name, description, parameters) {
|
||||
super(name, MethodType.Ink, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class CustomMethods extends Method {
|
||||
constructor(name, description, parameters) {
|
||||
super(name, MethodType.CustomMethod, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class VariableMethod extends Method {
|
||||
constructor(name, description, parameters) {
|
||||
super(name, MethodType.Variable, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class ConditionMethod extends Method {
|
||||
constructor(name, description, parameters) {
|
||||
super(name, MethodType.Condition, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class CustomClassMethod extends Method {
|
||||
constructor(name, description) {
|
||||
super(name, MethodType.CustomClass, description);
|
||||
}
|
||||
}
|
||||
function editDistance(a, b, caseSensitive = true) {
|
||||
if (a.length === 0)
|
||||
return b.length;
|
||||
if (b.length === 0)
|
||||
return a.length;
|
||||
const matrix = [];
|
||||
for (let i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
@@ -17,15 +115,512 @@ const editDistance = (a, b) => {
|
||||
}
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
const ac = a.charAt(j - 1);
|
||||
const bc = b.charAt(i - 1);
|
||||
if (caseSensitive ? bc === ac : bc.toLowerCase() === ac.toLowerCase()) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j - 1] + 1,
|
||||
Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1),
|
||||
);
|
||||
}
|
||||
else {
|
||||
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
}
|
||||
function parametersOf(method) {
|
||||
var _a;
|
||||
return (_a = method.parameters) !== null && _a !== void 0 ? _a : [];
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
// 数据:可直接在此编辑,或通过 window.CustomMethodSearch.setMethods(...) 覆盖
|
||||
// ---------------------------------------------------------------------
|
||||
let methods = [
|
||||
new Mod("wobblyLines", "让单发拍子的轨道抖动"),
|
||||
new Mod("invisibleHeart", "隐藏轨道上的心脏"),
|
||||
new CustomMethods("ChangeCharacter", "修改某条轨道对应的人物", [
|
||||
new Parameter("character", ParameterType.Enum, "Characters"),
|
||||
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
||||
]),
|
||||
new CustomMethods("DisableRowReflections", "开关某个房间的地面反射", [
|
||||
new Parameter("room", ParameterType.Number, "房间编号", undefined, new NumericRange(0, 3, true)),
|
||||
]),
|
||||
new CustomMethods("SetRowLength", "设置轨道长度", [
|
||||
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
||||
new Parameter("length", ParameterType.Number, "轨道长度", undefined, new NumericRange(1, 7, true)),
|
||||
]),
|
||||
new CommentMethod("trueCameraMove", "移动真实摄像头", [
|
||||
new Parameter("x", ParameterType.Number, "目标 X"),
|
||||
new Parameter("y", ParameterType.Number, "目标 Y"),
|
||||
new Parameter("angle", ParameterType.Number, "角度"),
|
||||
new Parameter("duration", ParameterType.Number, "时长"),
|
||||
new Parameter("ease", ParameterType.Enum, "缓动类型", "EaseType"),
|
||||
]),
|
||||
new CustomMethods("DemoAllTypes", "演示四种参数控件", [
|
||||
new Parameter("text", ParameterType.String, "文本"),
|
||||
new Parameter("count", ParameterType.Number, "数字"),
|
||||
new Parameter("enabled", ParameterType.Boolean, "开关"),
|
||||
new Parameter("character", ParameterType.Enum, "角色", "Characters"),
|
||||
new Parameter("color", ParameterType.Enum, "颜色"),
|
||||
]),
|
||||
];
|
||||
// 合并外置数据(script/custommethod-data.js),同名以内置为准
|
||||
function hydrateMethod(raw) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
const parameters = Array.isArray(raw.parameters)
|
||||
? raw.parameters.map((item) => {
|
||||
var _a, _b;
|
||||
return new Parameter(item.name, (_a = item.type) !== null && _a !== void 0 ? _a : ParameterType.String, (_b = item.description) !== null && _b !== void 0 ? _b : "", item.enumType, item.numericRange
|
||||
? new NumericRange(item.numericRange.min, item.numericRange.max, item.numericRange.isInteger)
|
||||
: undefined);
|
||||
})
|
||||
: [];
|
||||
switch (raw.type) {
|
||||
case MethodType.Mod: return new Mod(raw.name, (_a = raw.description) !== null && _a !== void 0 ? _a : "");
|
||||
case MethodType.Surgery: return new Surgery(raw.name, (_b = raw.description) !== null && _b !== void 0 ? _b : "");
|
||||
case MethodType.Comment: return new CommentMethod(raw.name, (_c = raw.description) !== null && _c !== void 0 ? _c : "", parameters);
|
||||
case MethodType.Ink: return new InkMethod(raw.name, (_d = raw.description) !== null && _d !== void 0 ? _d : "", parameters);
|
||||
case MethodType.Variable: return new VariableMethod(raw.name, (_e = raw.description) !== null && _e !== void 0 ? _e : "", parameters);
|
||||
case MethodType.Condition: return new ConditionMethod(raw.name, (_f = raw.description) !== null && _f !== void 0 ? _f : "", parameters);
|
||||
case MethodType.CustomClass: return new CustomClassMethod(raw.name, (_g = raw.description) !== null && _g !== void 0 ? _g : "");
|
||||
default: return new CustomMethods(raw.name, (_h = raw.description) !== null && _h !== void 0 ? _h : "", parameters);
|
||||
}
|
||||
}
|
||||
function loadExternalData() {
|
||||
const data = window.CustomMethodSearchData;
|
||||
if (!data)
|
||||
return;
|
||||
if (data.enumTypes) {
|
||||
for (const key of Object.keys(data.enumTypes)) {
|
||||
enumTypeMap[key] = data.enumTypes[key];
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.methods)) {
|
||||
const index = new Map();
|
||||
methods.forEach((method, i) => index.set(method.name.toLowerCase(), i));
|
||||
for (const raw of data.methods) {
|
||||
if (!raw || !raw.name)
|
||||
continue;
|
||||
const name = String(raw.name).toLowerCase();
|
||||
const hydrated = hydrateMethod(raw);
|
||||
if (index.has(name)) {
|
||||
methods[index.get(name)] = hydrated;
|
||||
}
|
||||
else {
|
||||
index.set(name, methods.length);
|
||||
methods.push(hydrated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadExternalData();
|
||||
// ---------------------------------------------------------------------
|
||||
// 匹配
|
||||
// ---------------------------------------------------------------------
|
||||
function searchMethods(query) {
|
||||
const q = query.trim();
|
||||
if (!q)
|
||||
return [];
|
||||
const ql = q.toLowerCase();
|
||||
const maxDistance = Math.max(2, Math.floor(q.length / 2));
|
||||
return methods
|
||||
.map((method) => {
|
||||
const name = method.name;
|
||||
const lower = name.toLowerCase();
|
||||
let score;
|
||||
if (name === q) {
|
||||
score = 0;
|
||||
}
|
||||
else if (lower.startsWith(ql)) {
|
||||
score = 1;
|
||||
}
|
||||
else if (lower.indexOf(ql) >= 0) {
|
||||
score = 2;
|
||||
}
|
||||
else {
|
||||
score = 3 + editDistance(name, q, false);
|
||||
}
|
||||
return { method, score };
|
||||
})
|
||||
.filter((item) => item.score <= 2 || item.score <= 3 + maxDistance)
|
||||
.sort((a, b) => a.score - b.score || a.method.name.localeCompare(b.method.name))
|
||||
.slice(0, 8)
|
||||
.map((item) => item.method);
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
// DOM 构建
|
||||
// ---------------------------------------------------------------------
|
||||
function el(tag, className) {
|
||||
const node = document.createElement(tag);
|
||||
if (className)
|
||||
node.className = className;
|
||||
return node;
|
||||
}
|
||||
function resolveEnumOptions(parameter) {
|
||||
if (parameter.enumType && enumTypeMap[parameter.enumType]) {
|
||||
return enumTypeMap[parameter.enumType];
|
||||
}
|
||||
// 兼容把枚举类型名写在 description 的情况
|
||||
if (enumTypeMap[parameter.description]) {
|
||||
return enumTypeMap[parameter.description];
|
||||
}
|
||||
if (enumTypeMap[parameter.name]) {
|
||||
return enumTypeMap[parameter.name];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
// 布尔值可用 true/false,也可用布尔变量 b0~b9
|
||||
const BOOLEAN_OPTIONS = ["true", "false"];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
BOOLEAN_OPTIONS.push("b" + i);
|
||||
}
|
||||
function isNumberVariable(value) {
|
||||
// 数字位置允许的变量:i0~i9(整型)、f0~f9(浮点)、b0~b9(布尔)
|
||||
return /^[ifb][0-9]$/.test(value);
|
||||
}
|
||||
function makeCombo(parameter, options, onChange) {
|
||||
const combo = el("span", "cm-combo");
|
||||
const input = el("input", "cm-input cm-combo-input");
|
||||
input.type = "text";
|
||||
input.placeholder = parameter.name;
|
||||
input.autocomplete = "off";
|
||||
const list = el("ul", "cm-combo-list");
|
||||
list.hidden = true;
|
||||
function populate() {
|
||||
list.textContent = "";
|
||||
if (options.length === 0) {
|
||||
list.hidden = true;
|
||||
return;
|
||||
}
|
||||
const query = input.value.trim();
|
||||
const ql = query.toLowerCase();
|
||||
const maxDistance = Math.max(1, Math.floor(query.length / 2));
|
||||
const shown = (query
|
||||
? options.filter((option) => option.toLowerCase().indexOf(ql) >= 0 ||
|
||||
editDistance(option, query, false) <= maxDistance)
|
||||
: options).slice(0, 10);
|
||||
if (shown.length === 0) {
|
||||
list.hidden = true;
|
||||
return;
|
||||
}
|
||||
for (const option of shown) {
|
||||
const item = el("li", "cm-combo-item");
|
||||
item.textContent = option;
|
||||
item.title = option;
|
||||
item.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
input.value = option;
|
||||
list.hidden = true;
|
||||
onChange();
|
||||
});
|
||||
list.appendChild(item);
|
||||
}
|
||||
list.hidden = false;
|
||||
}
|
||||
input.addEventListener("input", () => {
|
||||
populate();
|
||||
onChange();
|
||||
});
|
||||
input.addEventListener("focus", populate);
|
||||
input.addEventListener("blur", () => {
|
||||
window.setTimeout(() => { list.hidden = true; }, 120);
|
||||
});
|
||||
combo.appendChild(input);
|
||||
combo.appendChild(list);
|
||||
return combo;
|
||||
}
|
||||
function makeControl(parameter, onChange) {
|
||||
switch (parameter.type) {
|
||||
case ParameterType.Boolean: {
|
||||
return makeCombo(parameter, BOOLEAN_OPTIONS, onChange);
|
||||
}
|
||||
case ParameterType.Enum: {
|
||||
// 可输入的下拉框:输入时用编辑距离筛选候选项
|
||||
return makeCombo(parameter, resolveEnumOptions(parameter), onChange);
|
||||
}
|
||||
case ParameterType.Number: {
|
||||
// 普通输入框,合法性由 readParameter 校验(非数字标红)
|
||||
const number = el("input", "cm-input");
|
||||
number.type = "text";
|
||||
number.inputMode = "decimal";
|
||||
number.placeholder = parameter.name;
|
||||
number.addEventListener("input", onChange);
|
||||
return number;
|
||||
}
|
||||
default: {
|
||||
const text = el("input", "cm-input");
|
||||
text.type = "text";
|
||||
text.placeholder = parameter.name;
|
||||
text.addEventListener("input", onChange);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
function readParameter(parameter, control) {
|
||||
switch (parameter.type) {
|
||||
case ParameterType.Boolean: {
|
||||
const input = control.querySelector("input");
|
||||
const value = input.value.trim();
|
||||
const valid = value === "true" || value === "false" || /^b[0-9]$/.test(value);
|
||||
return { text: value || "<布尔>", valid, invalid: value.length > 0 && !valid, control };
|
||||
}
|
||||
case ParameterType.Enum: {
|
||||
const input = control.querySelector("input");
|
||||
const value = input.value.trim();
|
||||
const options = resolveEnumOptions(parameter);
|
||||
const valid = options.length > 0
|
||||
? options.some((option) => option.toLowerCase() === value.toLowerCase())
|
||||
: value.length > 0;
|
||||
return { text: value || "<选项>", valid, invalid: value.length > 0 && !valid, control };
|
||||
}
|
||||
case ParameterType.Number: {
|
||||
const input = control;
|
||||
const value = input.value.trim();
|
||||
if (value.length === 0) {
|
||||
return { text: "<数字>", valid: false, invalid: false, control };
|
||||
}
|
||||
if (isNumberVariable(value)) {
|
||||
return { text: value, valid: true, invalid: false, control };
|
||||
}
|
||||
const number = Number(value);
|
||||
let valid = isFinite(number);
|
||||
const range = parameter.numericRange;
|
||||
if (valid && range) {
|
||||
if (range.isInteger && !Number.isInteger(number))
|
||||
valid = false;
|
||||
if (number < range.min || number > range.max)
|
||||
valid = false;
|
||||
}
|
||||
return { text: value, valid, invalid: !valid, control };
|
||||
}
|
||||
default: {
|
||||
const input = control;
|
||||
const value = input.value.trim();
|
||||
return { text: value ? `str:${value}` : "<文本>", valid: value.length > 0, invalid: false, control };
|
||||
}
|
||||
}
|
||||
}
|
||||
function stripStringPrefix(text) {
|
||||
return text.startsWith("str:") ? text.slice(4) : text;
|
||||
}
|
||||
function formatMethod(method, states) {
|
||||
const values = states.map((state) => state.text);
|
||||
const args = values.join(", ");
|
||||
switch (method.type) {
|
||||
case MethodType.Comment:
|
||||
return `()=>${method.name}(${args})`;
|
||||
case MethodType.Ink:
|
||||
return `DOTHIS{${method.name}(${args})}`;
|
||||
case MethodType.Variable:
|
||||
return `${method.name} = ${values.length ? stripStringPrefix(values[0]) : ""}`;
|
||||
case MethodType.Mod:
|
||||
return `"mods": [..., "${method.name}"]`;
|
||||
case MethodType.CustomClass:
|
||||
return `"customClass": "${method.name}"`;
|
||||
case MethodType.Condition:
|
||||
return values.length ? `${method.name}(${args})` : method.name;
|
||||
case MethodType.Surgery:
|
||||
return method.name;
|
||||
default:
|
||||
return values.length ? `${method.name}(${args})` : method.name;
|
||||
}
|
||||
}
|
||||
function copyText(text) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
yield navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// fallthrough to execCommand
|
||||
}
|
||||
try {
|
||||
const area = document.createElement("textarea");
|
||||
area.value = text;
|
||||
area.style.position = "fixed";
|
||||
area.style.opacity = "0";
|
||||
document.body.appendChild(area);
|
||||
area.select();
|
||||
const ok = document.execCommand("copy");
|
||||
document.body.removeChild(area);
|
||||
return ok;
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
function renderContainer(container) {
|
||||
if (container.dataset.cmReady === "1")
|
||||
return;
|
||||
container.dataset.cmReady = "1";
|
||||
// 优先使用 custommethod.md 里写好的骨架;缺失时再动态生成
|
||||
let query = container.querySelector(".cm-query");
|
||||
let suggest = container.querySelector(".cm-suggest");
|
||||
let selected = container.querySelector(".cm-selected");
|
||||
let nameEl = container.querySelector(".cm-name");
|
||||
let descEl = container.querySelector(".cm-desc");
|
||||
let row = container.querySelector(".cm-row");
|
||||
let preview = container.querySelector(".cm-preview");
|
||||
if (!query || !suggest || !selected || !nameEl || !descEl || !row || !preview) {
|
||||
container.textContent = "";
|
||||
const searchBar = el("div", "cm-search");
|
||||
query = el("input", "cm-query");
|
||||
query.type = "text";
|
||||
query.placeholder = "输入自定义方法名,如 ChangeCharacter";
|
||||
query.autocomplete = "off";
|
||||
suggest = el("ul", "cm-suggest");
|
||||
searchBar.appendChild(query);
|
||||
searchBar.appendChild(suggest);
|
||||
selected = el("div", "cm-selected");
|
||||
selected.hidden = true;
|
||||
const head = el("div", "cm-head");
|
||||
nameEl = el("span", "cm-name");
|
||||
descEl = el("span", "cm-desc");
|
||||
head.appendChild(nameEl);
|
||||
head.appendChild(descEl);
|
||||
row = el("div", "cm-row");
|
||||
preview = el("code", "cm-preview");
|
||||
selected.appendChild(head);
|
||||
selected.appendChild(row);
|
||||
selected.appendChild(preview);
|
||||
container.appendChild(searchBar);
|
||||
container.appendChild(selected);
|
||||
}
|
||||
const queryEl = query;
|
||||
const suggestEl = suggest;
|
||||
const selectedEl = selected;
|
||||
const nameNode = nameEl;
|
||||
const descNode = descEl;
|
||||
const rowEl = row;
|
||||
const previewEl = preview;
|
||||
const copyBtn = el("button", "cm-copy");
|
||||
copyBtn.type = "button";
|
||||
copyBtn.textContent = "复制";
|
||||
copyBtn.hidden = true;
|
||||
copyBtn.title = "复制生成的调用代码";
|
||||
selectedEl.appendChild(copyBtn);
|
||||
copyBtn.addEventListener("click", () => {
|
||||
copyText(previewEl.textContent).then((ok) => {
|
||||
copyBtn.textContent = ok ? "已复制" : "复制失败";
|
||||
window.setTimeout(() => { copyBtn.textContent = "复制"; }, 1200);
|
||||
});
|
||||
});
|
||||
function clearSuggest() {
|
||||
suggestEl.textContent = "";
|
||||
}
|
||||
function selectMethod(method) {
|
||||
clearSuggest();
|
||||
queryEl.value = method.name;
|
||||
selectedEl.hidden = false;
|
||||
nameNode.textContent = method.name;
|
||||
descNode.textContent = method.description;
|
||||
rowEl.textContent = "";
|
||||
previewEl.textContent = "";
|
||||
copyBtn.hidden = true;
|
||||
const parameters = parametersOf(method);
|
||||
const controls = [];
|
||||
function updatePreview() {
|
||||
const states = controls.map((item) => readParameter(item.parameter, item.control));
|
||||
states.forEach((state) => state.control.classList.toggle("cm-invalid", state.invalid));
|
||||
previewEl.textContent = formatMethod(method, states);
|
||||
copyBtn.hidden = !states.every((state) => state.valid);
|
||||
}
|
||||
if (parameters.length === 0) {
|
||||
const empty = el("span", "cm-empty");
|
||||
empty.textContent = "该方法没有参数";
|
||||
rowEl.appendChild(empty);
|
||||
updatePreview();
|
||||
return;
|
||||
}
|
||||
for (const parameter of parameters) {
|
||||
const field = el("label", "cm-param");
|
||||
field.title = parameter.description;
|
||||
const label = el("span", "cm-param-name");
|
||||
label.textContent = parameter.name;
|
||||
const control = makeControl(parameter, updatePreview);
|
||||
field.appendChild(label);
|
||||
field.appendChild(control);
|
||||
rowEl.appendChild(field);
|
||||
controls.push({ parameter, control });
|
||||
}
|
||||
updatePreview();
|
||||
}
|
||||
function updateSuggestions() {
|
||||
clearSuggest();
|
||||
const matches = searchMethods(queryEl.value);
|
||||
for (const method of matches) {
|
||||
const item = el("li", "cm-suggest-item");
|
||||
const nameSpan = el("span", "cm-suggest-name");
|
||||
nameSpan.textContent = method.name;
|
||||
const descSpan = el("span", "cm-suggest-desc");
|
||||
descSpan.textContent = method.description;
|
||||
item.appendChild(nameSpan);
|
||||
item.appendChild(descSpan);
|
||||
item.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
selectMethod(method);
|
||||
});
|
||||
suggestEl.appendChild(item);
|
||||
}
|
||||
}
|
||||
queryEl.addEventListener("input", () => {
|
||||
updateSuggestions();
|
||||
selectedEl.hidden = true;
|
||||
});
|
||||
queryEl.addEventListener("focus", updateSuggestions);
|
||||
queryEl.addEventListener("blur", () => {
|
||||
window.setTimeout(clearSuggest, 120);
|
||||
});
|
||||
if (queryEl.value) {
|
||||
updateSuggestions();
|
||||
}
|
||||
}
|
||||
function initAll() {
|
||||
const containers = document.querySelectorAll(".custommethods-search");
|
||||
containers.forEach((container) => renderContainer(container));
|
||||
}
|
||||
function register() {
|
||||
const docsify = window.$docsify;
|
||||
if (docsify) {
|
||||
docsify.plugins = [].concat(docsify.plugins, (hook) => {
|
||||
hook.doneEach(() => initAll());
|
||||
});
|
||||
}
|
||||
else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => initAll());
|
||||
}
|
||||
else {
|
||||
initAll();
|
||||
}
|
||||
}
|
||||
window.CustomMethodSearch = {
|
||||
search: searchMethods,
|
||||
editDistance,
|
||||
renderAll: initAll,
|
||||
getMethods: () => methods,
|
||||
setMethods: (list) => {
|
||||
methods = list;
|
||||
initAll();
|
||||
},
|
||||
Parameter,
|
||||
ParameterType,
|
||||
Method,
|
||||
MethodType,
|
||||
Mod,
|
||||
Surgery,
|
||||
CommentMethod,
|
||||
InkMethod,
|
||||
CustomMethods,
|
||||
VariableMethod,
|
||||
ConditionMethod,
|
||||
CustomClassMethod,
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", register);
|
||||
}
|
||||
else {
|
||||
register();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,8 +1,114 @@
|
||||
class CustomMethods{
|
||||
type: "mod"|"surgery"|"comment"|"custom_method";
|
||||
}
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function editDistance(a: String, b: String): number {
|
||||
enum ParameterType {
|
||||
String = "string",
|
||||
Number = "number",
|
||||
Boolean = "boolean",
|
||||
Enum = "enum",
|
||||
}
|
||||
enum MethodType {
|
||||
Mod = "mod",
|
||||
Surgery = "surgery",
|
||||
Comment = "comment",
|
||||
CustomMethod = "custom_method",
|
||||
Ink = "ink",
|
||||
Variable = "variable",
|
||||
Condition = "condition",
|
||||
CustomClass = "custom_class",
|
||||
}
|
||||
|
||||
const enumTypeMap: Record<string, string[]> = {
|
||||
Characters: ["Logan", "Green", "Blue", "Yellow", "Purple", "Orange"],
|
||||
Size: ["Small", "Medium", "Large"],
|
||||
Shape: ["Circle", "Square", "Triangle"],
|
||||
};
|
||||
class NumericRange {
|
||||
min: number;
|
||||
max: number;
|
||||
isInteger: boolean;
|
||||
public constructor(min: number, max: number, isInteger: boolean = false) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.isInteger = isInteger;
|
||||
}
|
||||
}
|
||||
class Parameter {
|
||||
name: string;
|
||||
type: ParameterType;
|
||||
enumType?: string;
|
||||
numericRange?: NumericRange;
|
||||
description: string;
|
||||
public constructor(name: string, type: ParameterType, description: string, enumType?: string, numericRange?: NumericRange) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.enumType = enumType;
|
||||
this.numericRange = numericRange;
|
||||
}
|
||||
}
|
||||
class Method {
|
||||
name: string;
|
||||
type: MethodType;
|
||||
description: string;
|
||||
public constructor(name: string, type: MethodType, description: string) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
class Mod extends Method {
|
||||
public constructor(name: string, description: string) {
|
||||
super(name, MethodType.Mod, description);
|
||||
}
|
||||
}
|
||||
class Surgery extends Method {
|
||||
public constructor(name: string, description: string) {
|
||||
super(name, MethodType.Surgery, description);
|
||||
}
|
||||
}
|
||||
class CommentMethod extends Method {
|
||||
parameters: Parameter[];
|
||||
public constructor(name: string, description: string, parameters: Parameter[]) {
|
||||
super(name, MethodType.Comment, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class InkMethod extends Method {
|
||||
parameters: Parameter[];
|
||||
public constructor(name: string, description: string, parameters: Parameter[]) {
|
||||
super(name, MethodType.Ink, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class CustomMethods extends Method {
|
||||
parameters: Parameter[];
|
||||
public constructor(name: string, description: string, parameters: Parameter[]) {
|
||||
super(name, MethodType.CustomMethod, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class VariableMethod extends Method {
|
||||
parameters: Parameter[];
|
||||
public constructor(name: string, description: string, parameters: Parameter[]) {
|
||||
super(name, MethodType.Variable, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class ConditionMethod extends Method {
|
||||
parameters: Parameter[];
|
||||
public constructor(name: string, description: string, parameters: Parameter[]) {
|
||||
super(name, MethodType.Condition, description);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
class CustomClassMethod extends Method {
|
||||
public constructor(name: string, description: string) {
|
||||
super(name, MethodType.CustomClass, description);
|
||||
}
|
||||
}
|
||||
|
||||
function editDistance(a: string, b: string, caseSensitive: boolean = true): number {
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
const matrix: number[][] = [];
|
||||
@@ -14,7 +120,9 @@ function editDistance(a: String, b: String): number {
|
||||
}
|
||||
for (let i = 1; i <= b.length; i++) {
|
||||
for (let j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
const ac = a.charAt(j - 1);
|
||||
const bc = b.charAt(i - 1);
|
||||
if (caseSensitive ? bc === ac : bc.toLowerCase() === ac.toLowerCase()) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
@@ -25,8 +133,529 @@ function editDistance(a: String, b: String): number {
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
}
|
||||
|
||||
function parametersOf(method: Method): Parameter[] {
|
||||
return (method as CommentMethod | InkMethod | CustomMethods | VariableMethod | ConditionMethod).parameters ?? [];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 数据:可直接在此编辑,或通过 window.CustomMethodSearch.setMethods(...) 覆盖
|
||||
// ---------------------------------------------------------------------
|
||||
let methods: Method[] = [
|
||||
new Mod("wobblyLines", "让单发拍子的轨道抖动"),
|
||||
new Mod("invisibleHeart", "隐藏轨道上的心脏"),
|
||||
new CustomMethods("ChangeCharacter", "修改某条轨道对应的人物", [
|
||||
new Parameter("character", ParameterType.Enum, "Characters"),
|
||||
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
||||
]),
|
||||
new CustomMethods("DisableRowReflections", "开关某个房间的地面反射", [
|
||||
new Parameter("room", ParameterType.Number, "房间编号", undefined, new NumericRange(0, 3, true)),
|
||||
]),
|
||||
new CustomMethods("SetRowLength", "设置轨道长度", [
|
||||
new Parameter("row", ParameterType.Number, "轨道编号", undefined, new NumericRange(0, 15, true)),
|
||||
new Parameter("length", ParameterType.Number, "轨道长度", undefined, new NumericRange(1, 7, true)),
|
||||
]),
|
||||
new CommentMethod("trueCameraMove", "移动真实摄像头", [
|
||||
new Parameter("x", ParameterType.Number, "目标 X"),
|
||||
new Parameter("y", ParameterType.Number, "目标 Y"),
|
||||
new Parameter("angle", ParameterType.Number, "角度"),
|
||||
new Parameter("duration", ParameterType.Number, "时长"),
|
||||
new Parameter("ease", ParameterType.Enum, "缓动类型", "EaseType"),
|
||||
]),
|
||||
new CustomMethods("DemoAllTypes", "演示四种参数控件", [
|
||||
new Parameter("text", ParameterType.String, "文本"),
|
||||
new Parameter("count", ParameterType.Number, "数字"),
|
||||
new Parameter("enabled", ParameterType.Boolean, "开关"),
|
||||
new Parameter("character", ParameterType.Enum, "角色", "Characters"),
|
||||
new Parameter("color", ParameterType.Enum, "颜色"),
|
||||
]),
|
||||
];
|
||||
|
||||
export { editDistance };
|
||||
// 合并外置数据(script/custommethod-data.js),同名以内置为准
|
||||
function hydrateMethod(raw: any): Method {
|
||||
const parameters: Parameter[] = Array.isArray(raw.parameters)
|
||||
? raw.parameters.map((item: any) => new Parameter(
|
||||
item.name,
|
||||
item.type ?? ParameterType.String,
|
||||
item.description ?? "",
|
||||
item.enumType,
|
||||
item.numericRange
|
||||
? new NumericRange(item.numericRange.min, item.numericRange.max, item.numericRange.isInteger)
|
||||
: undefined,
|
||||
))
|
||||
: [];
|
||||
switch (raw.type) {
|
||||
case MethodType.Mod: return new Mod(raw.name, raw.description ?? "");
|
||||
case MethodType.Surgery: return new Surgery(raw.name, raw.description ?? "");
|
||||
case MethodType.Comment: return new CommentMethod(raw.name, raw.description ?? "", parameters);
|
||||
case MethodType.Ink: return new InkMethod(raw.name, raw.description ?? "", parameters);
|
||||
case MethodType.Variable: return new VariableMethod(raw.name, raw.description ?? "", parameters);
|
||||
case MethodType.Condition: return new ConditionMethod(raw.name, raw.description ?? "", parameters);
|
||||
case MethodType.CustomClass: return new CustomClassMethod(raw.name, raw.description ?? "");
|
||||
default: return new CustomMethods(raw.name, raw.description ?? "", parameters);
|
||||
}
|
||||
}
|
||||
|
||||
function loadExternalData(): void {
|
||||
const data = (window as any).CustomMethodSearchData;
|
||||
if (!data) return;
|
||||
if (data.enumTypes) {
|
||||
for (const key of Object.keys(data.enumTypes)) {
|
||||
enumTypeMap[key] = data.enumTypes[key];
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.methods)) {
|
||||
const index = new Map<string, number>();
|
||||
methods.forEach((method, i) => index.set(method.name.toLowerCase(), i));
|
||||
for (const raw of data.methods) {
|
||||
if (!raw || !raw.name) continue;
|
||||
const name = String(raw.name).toLowerCase();
|
||||
const hydrated = hydrateMethod(raw);
|
||||
if (index.has(name)) {
|
||||
methods[index.get(name)!] = hydrated;
|
||||
} else {
|
||||
index.set(name, methods.length);
|
||||
methods.push(hydrated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadExternalData();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 匹配
|
||||
// ---------------------------------------------------------------------
|
||||
function searchMethods(query: string): Method[] {
|
||||
const q = query.trim();
|
||||
if (!q) return [];
|
||||
const ql = q.toLowerCase();
|
||||
const maxDistance = Math.max(2, Math.floor(q.length / 2));
|
||||
return methods
|
||||
.map((method) => {
|
||||
const name = method.name;
|
||||
const lower = name.toLowerCase();
|
||||
let score: number;
|
||||
if (name === q) {
|
||||
score = 0;
|
||||
} else if (lower.startsWith(ql)) {
|
||||
score = 1;
|
||||
} else if (lower.indexOf(ql) >= 0) {
|
||||
score = 2;
|
||||
} else {
|
||||
score = 3 + editDistance(name, q, false);
|
||||
}
|
||||
return { method, score };
|
||||
})
|
||||
.filter((item) => item.score <= 2 || item.score <= 3 + maxDistance)
|
||||
.sort((a, b) => a.score - b.score || a.method.name.localeCompare(b.method.name))
|
||||
.slice(0, 8)
|
||||
.map((item) => item.method);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// DOM 构建
|
||||
// ---------------------------------------------------------------------
|
||||
function el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string): HTMLElementTagNameMap[K] {
|
||||
const node = document.createElement(tag);
|
||||
if (className) node.className = className;
|
||||
return node;
|
||||
}
|
||||
|
||||
function resolveEnumOptions(parameter: Parameter): string[] {
|
||||
if (parameter.enumType && enumTypeMap[parameter.enumType]) {
|
||||
return enumTypeMap[parameter.enumType];
|
||||
}
|
||||
// 兼容把枚举类型名写在 description 的情况
|
||||
if (enumTypeMap[parameter.description]) {
|
||||
return enumTypeMap[parameter.description];
|
||||
}
|
||||
if (enumTypeMap[parameter.name]) {
|
||||
return enumTypeMap[parameter.name];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// 布尔值可用 true/false,也可用布尔变量 b0~b9
|
||||
const BOOLEAN_OPTIONS: string[] = ["true", "false"];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
BOOLEAN_OPTIONS.push("b" + i);
|
||||
}
|
||||
|
||||
function isNumberVariable(value: string): boolean {
|
||||
// 数字位置允许的变量:i0~i9(整型)、f0~f9(浮点)、b0~b9(布尔)
|
||||
return /^[ifb][0-9]$/.test(value);
|
||||
}
|
||||
|
||||
function makeCombo(parameter: Parameter, options: string[], onChange: () => void): HTMLElement {
|
||||
const combo = el("span", "cm-combo");
|
||||
const input = el("input", "cm-input cm-combo-input") as HTMLInputElement;
|
||||
input.type = "text";
|
||||
input.placeholder = parameter.name;
|
||||
input.autocomplete = "off";
|
||||
const list = el("ul", "cm-combo-list");
|
||||
list.hidden = true;
|
||||
|
||||
function populate(): void {
|
||||
list.textContent = "";
|
||||
if (options.length === 0) {
|
||||
list.hidden = true;
|
||||
return;
|
||||
}
|
||||
const query = input.value.trim();
|
||||
const ql = query.toLowerCase();
|
||||
const maxDistance = Math.max(1, Math.floor(query.length / 2));
|
||||
const shown = (query
|
||||
? options.filter((option) =>
|
||||
option.toLowerCase().indexOf(ql) >= 0 ||
|
||||
editDistance(option, query, false) <= maxDistance)
|
||||
: options
|
||||
).slice(0, 10);
|
||||
if (shown.length === 0) {
|
||||
list.hidden = true;
|
||||
return;
|
||||
}
|
||||
for (const option of shown) {
|
||||
const item = el("li", "cm-combo-item");
|
||||
item.textContent = option;
|
||||
item.title = option;
|
||||
item.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
input.value = option;
|
||||
list.hidden = true;
|
||||
onChange();
|
||||
});
|
||||
list.appendChild(item);
|
||||
}
|
||||
list.hidden = false;
|
||||
}
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
populate();
|
||||
onChange();
|
||||
});
|
||||
input.addEventListener("focus", populate);
|
||||
input.addEventListener("blur", () => {
|
||||
window.setTimeout(() => { list.hidden = true; }, 120);
|
||||
});
|
||||
|
||||
combo.appendChild(input);
|
||||
combo.appendChild(list);
|
||||
return combo;
|
||||
}
|
||||
|
||||
function makeControl(parameter: Parameter, onChange: () => void): HTMLElement {
|
||||
switch (parameter.type) {
|
||||
case ParameterType.Boolean: {
|
||||
return makeCombo(parameter, BOOLEAN_OPTIONS, onChange);
|
||||
}
|
||||
case ParameterType.Enum: {
|
||||
// 可输入的下拉框:输入时用编辑距离筛选候选项
|
||||
return makeCombo(parameter, resolveEnumOptions(parameter), onChange);
|
||||
}
|
||||
case ParameterType.Number: {
|
||||
// 普通输入框,合法性由 readParameter 校验(非数字标红)
|
||||
const number = el("input", "cm-input") as HTMLInputElement;
|
||||
number.type = "text";
|
||||
number.inputMode = "decimal";
|
||||
number.placeholder = parameter.name;
|
||||
number.addEventListener("input", onChange);
|
||||
return number;
|
||||
}
|
||||
default: {
|
||||
const text = el("input", "cm-input") as HTMLInputElement;
|
||||
text.type = "text";
|
||||
text.placeholder = parameter.name;
|
||||
text.addEventListener("input", onChange);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ParameterState {
|
||||
text: string;
|
||||
valid: boolean;
|
||||
invalid: boolean;
|
||||
control: HTMLElement;
|
||||
}
|
||||
|
||||
function readParameter(parameter: Parameter, control: HTMLElement): ParameterState {
|
||||
switch (parameter.type) {
|
||||
case ParameterType.Boolean: {
|
||||
const input = control.querySelector("input") as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const valid = value === "true" || value === "false" || /^b[0-9]$/.test(value);
|
||||
return { text: value || "<布尔>", valid, invalid: value.length > 0 && !valid, control };
|
||||
}
|
||||
case ParameterType.Enum: {
|
||||
const input = control.querySelector("input") as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
const options = resolveEnumOptions(parameter);
|
||||
const valid = options.length > 0
|
||||
? options.some((option) => option.toLowerCase() === value.toLowerCase())
|
||||
: value.length > 0;
|
||||
return { text: value || "<选项>", valid, invalid: value.length > 0 && !valid, control };
|
||||
}
|
||||
case ParameterType.Number: {
|
||||
const input = control as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
if (value.length === 0) {
|
||||
return { text: "<数字>", valid: false, invalid: false, control };
|
||||
}
|
||||
if (isNumberVariable(value)) {
|
||||
return { text: value, valid: true, invalid: false, control };
|
||||
}
|
||||
const number = Number(value);
|
||||
let valid = isFinite(number);
|
||||
const range = parameter.numericRange;
|
||||
if (valid && range) {
|
||||
if (range.isInteger && !Number.isInteger(number)) valid = false;
|
||||
if (number < range.min || number > range.max) valid = false;
|
||||
}
|
||||
return { text: value, valid, invalid: !valid, control };
|
||||
}
|
||||
default: {
|
||||
const input = control as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
return { text: value ? `str:${value}` : "<文本>", valid: value.length > 0, invalid: false, control };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stripStringPrefix(text: string): string {
|
||||
return text.startsWith("str:") ? text.slice(4) : text;
|
||||
}
|
||||
|
||||
function formatMethod(method: Method, states: ParameterState[]): string {
|
||||
const values = states.map((state) => state.text);
|
||||
const args = values.join(", ");
|
||||
switch (method.type) {
|
||||
case MethodType.Comment:
|
||||
return `()=>${method.name}(${args})`;
|
||||
case MethodType.Ink:
|
||||
return `DOTHIS{${method.name}(${args})}`;
|
||||
case MethodType.Variable:
|
||||
return `${method.name} = ${values.length ? stripStringPrefix(values[0]) : ""}`;
|
||||
case MethodType.Mod:
|
||||
return `"mods": [..., "${method.name}"]`;
|
||||
case MethodType.CustomClass:
|
||||
return `"customClass": "${method.name}"`;
|
||||
case MethodType.Condition:
|
||||
return values.length ? `${method.name}(${args})` : method.name;
|
||||
case MethodType.Surgery:
|
||||
return method.name;
|
||||
default:
|
||||
return values.length ? `${method.name}(${args})` : method.name;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyText(text: string): Promise<boolean> {
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
// fallthrough to execCommand
|
||||
}
|
||||
try {
|
||||
const area = document.createElement("textarea");
|
||||
area.value = text;
|
||||
area.style.position = "fixed";
|
||||
area.style.opacity = "0";
|
||||
document.body.appendChild(area);
|
||||
area.select();
|
||||
const ok = document.execCommand("copy");
|
||||
document.body.removeChild(area);
|
||||
return ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderContainer(container: HTMLElement): void {
|
||||
if (container.dataset.cmReady === "1") return;
|
||||
container.dataset.cmReady = "1";
|
||||
|
||||
// 优先使用 custommethod.md 里写好的骨架;缺失时再动态生成
|
||||
let query = container.querySelector<HTMLInputElement>(".cm-query");
|
||||
let suggest = container.querySelector<HTMLUListElement>(".cm-suggest");
|
||||
let selected = container.querySelector<HTMLElement>(".cm-selected");
|
||||
let nameEl = container.querySelector<HTMLElement>(".cm-name");
|
||||
let descEl = container.querySelector<HTMLElement>(".cm-desc");
|
||||
let row = container.querySelector<HTMLElement>(".cm-row");
|
||||
let preview = container.querySelector<HTMLElement>(".cm-preview");
|
||||
|
||||
if (!query || !suggest || !selected || !nameEl || !descEl || !row || !preview) {
|
||||
container.textContent = "";
|
||||
const searchBar = el("div", "cm-search");
|
||||
query = el("input", "cm-query") as HTMLInputElement;
|
||||
query.type = "text";
|
||||
query.placeholder = "输入自定义方法名,如 ChangeCharacter";
|
||||
query.autocomplete = "off";
|
||||
suggest = el("ul", "cm-suggest");
|
||||
searchBar.appendChild(query);
|
||||
searchBar.appendChild(suggest);
|
||||
selected = el("div", "cm-selected");
|
||||
selected.hidden = true;
|
||||
const head = el("div", "cm-head");
|
||||
nameEl = el("span", "cm-name");
|
||||
descEl = el("span", "cm-desc");
|
||||
head.appendChild(nameEl);
|
||||
head.appendChild(descEl);
|
||||
row = el("div", "cm-row");
|
||||
preview = el("code", "cm-preview");
|
||||
selected.appendChild(head);
|
||||
selected.appendChild(row);
|
||||
selected.appendChild(preview);
|
||||
container.appendChild(searchBar);
|
||||
container.appendChild(selected);
|
||||
}
|
||||
|
||||
const queryEl = query;
|
||||
const suggestEl = suggest;
|
||||
const selectedEl = selected;
|
||||
const nameNode = nameEl;
|
||||
const descNode = descEl;
|
||||
const rowEl = row;
|
||||
const previewEl = preview;
|
||||
|
||||
const copyBtn = el("button", "cm-copy") as HTMLButtonElement;
|
||||
copyBtn.type = "button";
|
||||
copyBtn.textContent = "复制";
|
||||
copyBtn.hidden = true;
|
||||
copyBtn.title = "复制生成的调用代码";
|
||||
selectedEl.appendChild(copyBtn);
|
||||
copyBtn.addEventListener("click", () => {
|
||||
copyText(previewEl.textContent).then((ok) => {
|
||||
copyBtn.textContent = ok ? "已复制" : "复制失败";
|
||||
window.setTimeout(() => { copyBtn.textContent = "复制"; }, 1200);
|
||||
});
|
||||
});
|
||||
|
||||
function clearSuggest(): void {
|
||||
suggestEl.textContent = "";
|
||||
}
|
||||
|
||||
function selectMethod(method: Method): void {
|
||||
clearSuggest();
|
||||
queryEl.value = method.name;
|
||||
selectedEl.hidden = false;
|
||||
nameNode.textContent = method.name;
|
||||
descNode.textContent = method.description;
|
||||
rowEl.textContent = "";
|
||||
previewEl.textContent = "";
|
||||
copyBtn.hidden = true;
|
||||
|
||||
const parameters = parametersOf(method);
|
||||
const controls: { parameter: Parameter; control: HTMLElement }[] = [];
|
||||
|
||||
function updatePreview(): void {
|
||||
const states = controls.map((item) => readParameter(item.parameter, item.control));
|
||||
states.forEach((state) => state.control.classList.toggle("cm-invalid", state.invalid));
|
||||
previewEl.textContent = formatMethod(method, states);
|
||||
copyBtn.hidden = !states.every((state) => state.valid);
|
||||
}
|
||||
|
||||
if (parameters.length === 0) {
|
||||
const empty = el("span", "cm-empty");
|
||||
empty.textContent = "该方法没有参数";
|
||||
rowEl.appendChild(empty);
|
||||
updatePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const parameter of parameters) {
|
||||
const field = el("label", "cm-param");
|
||||
field.title = parameter.description;
|
||||
const label = el("span", "cm-param-name");
|
||||
label.textContent = parameter.name;
|
||||
const control = makeControl(parameter, updatePreview);
|
||||
field.appendChild(label);
|
||||
field.appendChild(control);
|
||||
rowEl.appendChild(field);
|
||||
controls.push({ parameter, control });
|
||||
}
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
function updateSuggestions(): void {
|
||||
clearSuggest();
|
||||
const matches = searchMethods(queryEl.value);
|
||||
for (const method of matches) {
|
||||
const item = el("li", "cm-suggest-item");
|
||||
const nameSpan = el("span", "cm-suggest-name");
|
||||
nameSpan.textContent = method.name;
|
||||
const descSpan = el("span", "cm-suggest-desc");
|
||||
descSpan.textContent = method.description;
|
||||
item.appendChild(nameSpan);
|
||||
item.appendChild(descSpan);
|
||||
item.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
selectMethod(method);
|
||||
});
|
||||
suggestEl.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
queryEl.addEventListener("input", () => {
|
||||
updateSuggestions();
|
||||
selectedEl.hidden = true;
|
||||
});
|
||||
queryEl.addEventListener("focus", updateSuggestions);
|
||||
queryEl.addEventListener("blur", () => {
|
||||
window.setTimeout(clearSuggest, 120);
|
||||
});
|
||||
|
||||
if (queryEl.value) {
|
||||
updateSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
function initAll(): void {
|
||||
const containers = document.querySelectorAll<HTMLElement>(".custommethods-search");
|
||||
containers.forEach((container) => renderContainer(container));
|
||||
}
|
||||
|
||||
function register(): void {
|
||||
const docsify = (window as any).$docsify;
|
||||
if (docsify) {
|
||||
docsify.plugins = ([] as any[]).concat(docsify.plugins, (hook: any) => {
|
||||
hook.doneEach(() => initAll());
|
||||
});
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => initAll());
|
||||
} else {
|
||||
initAll();
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).CustomMethodSearch = {
|
||||
search: searchMethods,
|
||||
editDistance,
|
||||
renderAll: initAll,
|
||||
getMethods: () => methods,
|
||||
setMethods: (list: Method[]) => {
|
||||
methods = list;
|
||||
initAll();
|
||||
},
|
||||
Parameter,
|
||||
ParameterType,
|
||||
Method,
|
||||
MethodType,
|
||||
Mod,
|
||||
Surgery,
|
||||
CommentMethod,
|
||||
InkMethod,
|
||||
CustomMethods,
|
||||
VariableMethod,
|
||||
ConditionMethod,
|
||||
CustomClassMethod,
|
||||
};
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", register);
|
||||
} else {
|
||||
register();
|
||||
}
|
||||
})();
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
export declare class IconStyle {
|
||||
enabled: boolean;
|
||||
active: boolean;
|
||||
hover: boolean;
|
||||
scale: number;
|
||||
showDuration: boolean;
|
||||
}
|
||||
|
||||
export interface HTMLLayerElement extends HTMLDivElement {
|
||||
onStateChange?: (style: IconStyle) => void;
|
||||
filterData: unknown;
|
||||
updateFilter: () => void;
|
||||
}
|
||||
|
||||
export interface HTMLEventElement extends HTMLDivElement {
|
||||
onStateChange: (style: IconStyle) => void;
|
||||
eventStyle: IconStyle;
|
||||
eventData: object;
|
||||
moveTo: (x: number, y: number) => void;
|
||||
}
|
||||
|
||||
export declare function createElementEvent(
|
||||
obj: any,
|
||||
style: IconStyle,
|
||||
): HTMLEventElement | null;
|
||||
|
||||
export declare function colorOf(tab: string): any;
|
||||
+956
-382
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
import "./rdview.js";
|
||||
import "./rdtkview_render.js";
|
||||
import "./rdview_render.js";
|
||||
|
||||
window.loadRDView();
|
||||
+304
-575
File diff suppressed because it is too large
Load Diff
+285
-608
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
window.$docsify.plugins = [].concat(window.$docsify.plugins, (hook) => {
|
||||
hook.doneEach(() => {
|
||||
if (typeof window.RDViewRender !== "function") {
|
||||
return;
|
||||
}
|
||||
const codeBlocks = document.querySelectorAll("pre[data-lang]");
|
||||
codeBlocks.forEach((codeBlock) => {
|
||||
const lang = codeBlock.getAttribute("data-lang") || "";
|
||||
if (!lang.trim().startsWith("rdview")) {
|
||||
return;
|
||||
}
|
||||
const codeEl = codeBlock.querySelector("code");
|
||||
const code = codeEl ? codeEl.innerText : codeBlock.innerText;
|
||||
let node;
|
||||
try {
|
||||
node = window.RDViewRender(lang, code);
|
||||
} catch (e) {
|
||||
console.error("rdview render failed:", lang, e);
|
||||
return;
|
||||
}
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const parent = document.createElement("div");
|
||||
parent.className = "rdtk-rdview-wrap";
|
||||
parent.appendChild(node);
|
||||
codeBlock.replaceWith(parent);
|
||||
});
|
||||
});
|
||||
});
|
||||
+5
-8
@@ -228,7 +228,7 @@ function rdview() {
|
||||
function initCanvas() {
|
||||
const lang = textarea.value.split("\n")[0];
|
||||
const code = textarea.value.split("\n").slice(1).join("\n");
|
||||
rdcanvas.innerHTML = RDViewRender(lang, code);
|
||||
rdcanvas.replaceChildren(RDViewRender(lang, code));
|
||||
}
|
||||
initCanvas();
|
||||
initLineNumbers();
|
||||
@@ -322,10 +322,11 @@ function rdview2() {
|
||||
}
|
||||
const eventStyle = { "scale": 2.0, "showDuration": false };
|
||||
let maxy = 0;
|
||||
const elemPromises = [];
|
||||
const elems = [];
|
||||
for (let obj of objs) {
|
||||
const style = { ...eventStyle, enabled: obj.active ?? true };
|
||||
const elem = createElementEvent(obj, style).then((elem) => {
|
||||
const elem = createElementEvent(obj, style);
|
||||
if (!elem) continue;
|
||||
elem.eventStyle = style;
|
||||
elem.style.position = "absolute";
|
||||
elem.style.left = `${
|
||||
@@ -363,15 +364,11 @@ function rdview2() {
|
||||
eStyle.active = false;
|
||||
elem.onStateChange(eStyle);
|
||||
});
|
||||
return elem;
|
||||
});
|
||||
elemPromises.push(elem);
|
||||
elems.push(elem);
|
||||
}
|
||||
Promise.all(elemPromises).then((elems) => {
|
||||
elems.forEach((elem) => {
|
||||
rdcanvas.appendChild(elem);
|
||||
});
|
||||
});
|
||||
rdcanvas.style.position = "relative";
|
||||
rdcanvas.style.height = `${maxy + 4 * 14 * eventStyle.scale}px`;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
@@ -14,6 +15,7 @@
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"files": [
|
||||
"rdview.ts"
|
||||
"rdview.ts",
|
||||
"custommethod-search.ts"
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
.rdtk-rdview-wrap {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rdtk-rdview {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.rdtk-event {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
z-index: 0;
|
||||
transition: transform 0.3s, filter 0.3s;
|
||||
}
|
||||
|
||||
.rdtk-event.hover {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.rdtk-group {
|
||||
position: absolute;
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
transition: transform 0.3s, filter 0.3s, outline-color 0.3s;
|
||||
}
|
||||
|
||||
.rdtk-group.hover {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.rdtk-condition {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.rdtk-condition.condition-true {
|
||||
background-image: url(./assets/condition-true.png);
|
||||
}
|
||||
|
||||
.rdtk-condition.condition-false {
|
||||
background-image: url(./assets/condition-false.png);
|
||||
}
|
||||
|
||||
.rdtk-condition.condition-both {
|
||||
background-image: url(./assets/condition-both.png);
|
||||
}
|
||||
|
||||
.rdtk-unknown {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rdtk-error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -267,3 +267,222 @@ div.decoration-depth {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.custommethods-search {
|
||||
position: relative;
|
||||
margin: 16px 0;
|
||||
|
||||
& .cm-search {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& .cm-query {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-family: d9c, d9, monospace;
|
||||
font-size: 15px;
|
||||
color: inherit;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 6px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #3b8e59;
|
||||
}
|
||||
}
|
||||
|
||||
& .cm-suggest {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: calc(100% + 4px);
|
||||
z-index: 30;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
list-style: none;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& .cm-suggest-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, 0.16);
|
||||
}
|
||||
}
|
||||
|
||||
& .cm-suggest-name {
|
||||
font-family: d9c, d9, monospace;
|
||||
}
|
||||
|
||||
& .cm-suggest-desc {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
& .cm-selected {
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 6px;
|
||||
background-color: rgba(127, 127, 127, 0.06);
|
||||
}
|
||||
|
||||
& .cm-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-right: 56px;
|
||||
}
|
||||
|
||||
& .cm-name {
|
||||
font-family: d9c, d9, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
& .cm-desc {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
& .cm-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
& .cm-param {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
& .cm-param-name {
|
||||
font-family: d9c, d9, monospace;
|
||||
font-size: 13px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
& .cm-input {
|
||||
padding: 4px 6px;
|
||||
font-size: 14px;
|
||||
color: inherit;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #3b8e59;
|
||||
}
|
||||
}
|
||||
|
||||
& select.cm-input {
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
& input.cm-input[type="number"] {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
& input.cm-input[type="text"] {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
& .cm-preview {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
padding: 8px 10px;
|
||||
font-family: d9c, d9, monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background-color: rgba(127, 127, 127, 0.12);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
& .cm-empty {
|
||||
font-size: 13px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
& .cm-input.cm-invalid,
|
||||
& .cm-combo.cm-invalid .cm-input {
|
||||
border-color: #e5484d;
|
||||
box-shadow: 0 0 0 1px #e5484d;
|
||||
}
|
||||
|
||||
& .cm-combo {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
& .cm-combo-input {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
& .cm-combo-list {
|
||||
position: absolute;
|
||||
top: calc(100% + 2px);
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
z-index: 40;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
list-style: none;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
& .cm-combo-item {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, 0.16);
|
||||
}
|
||||
}
|
||||
|
||||
& .cm-copy {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid var(--background-border-color);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #3b8e59;
|
||||
border-color: #3b8e59;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user