sscharter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +21 -0
- data/README.md +30 -0
- data/Rakefile +12 -0
- data/exe/sscharter +13 -0
- data/lib/sscharter/chart.rb +58 -0
- data/lib/sscharter/cli.rb +192 -0
- data/lib/sscharter/utils.rb +41 -0
- data/lib/sscharter/version.rb +7 -0
- data/lib/sscharter.rb +587 -0
- data/tutorial/tutorial.md +1019 -0
- metadata +142 -0
@@ -0,0 +1,1019 @@
|
|
1
|
+
# Tutorial for sscharter
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
Sscharter is a Ruby DSL serving as a charting tool for
|
6
|
+
[Sunniesnow](https://sunniesnow.github.io).
|
7
|
+
Different from other charting tools, sscharter does not have a GUI.
|
8
|
+
|
9
|
+
By using a programming language, charters can write charts more creatively.
|
10
|
+
If the chart has many patterns, it can be much more efficient than using a GUI charting tool.
|
11
|
+
However, programming languages can be difficult to learn.
|
12
|
+
|
13
|
+
## Prerequisites
|
14
|
+
|
15
|
+
Here are some prerequisites for using sscharter.
|
16
|
+
Check all of them before continuing this tutorial:
|
17
|
+
|
18
|
+
- You need to have a basic knowledge of how to use a command line interface (CLI).
|
19
|
+
If you do not, you may find [this introduction](https://tutorial.djangogirls.org/en/intro_to_command_line/) helpful.
|
20
|
+
- You need to [install Ruby](https://www.ruby-lang.org/en/documentation/installation/).
|
21
|
+
- You need to has a basic understanding of how to program.
|
22
|
+
If you have programmed before, you will be fine because Ruby is a easy-to-begin language.
|
23
|
+
If you have not, [this](https://pine.fm/LearnToProgram/)
|
24
|
+
is a good place to start.
|
25
|
+
- You need a basic audio editing software and a basic text editing software.
|
26
|
+
If you do not have any, you can use
|
27
|
+
[Audacity](https://www.audacityteam.org/) and
|
28
|
+
[Visual Studio Code](https://code.visualstudio.com/).
|
29
|
+
- You need to be familiar with the game Sunniesnow
|
30
|
+
(which I assume you do because otherwise you would not be reading this now).
|
31
|
+
Better, you should be familiar with how a level file of Sunniesnow is structured
|
32
|
+
by reading [the documentation](https://sunniesnow.github.io/doc/).
|
33
|
+
|
34
|
+
## Install sscharter
|
35
|
+
|
36
|
+
Assume you have installed Ruby.
|
37
|
+
Then, run
|
38
|
+
|
39
|
+
```shell
|
40
|
+
gem install sscharter
|
41
|
+
```
|
42
|
+
|
43
|
+
You are now good to go!
|
44
|
+
|
45
|
+
## Step 0: decide the music
|
46
|
+
|
47
|
+
You need to decide the music you want to chart for!
|
48
|
+
Because you are assumed to be familiar with rhythm games,
|
49
|
+
I do not need to tell you what kinds of music are suitable for rhythm games.
|
50
|
+
However, I need to remind you that before you start charting, either
|
51
|
+
|
52
|
+
- you make sure the artist announced publicly that anyone can chart for the music
|
53
|
+
or permitted some sorts of usage that covers charting, or
|
54
|
+
- you got contact with the artist,
|
55
|
+
explained to them that you want to chart for the music and how you would distribute your chart,
|
56
|
+
and got their permission.
|
57
|
+
|
58
|
+
If you did not do any of the above, you need to keep in mind that you are a bad boy
|
59
|
+
and may face some sort of consequences
|
60
|
+
(I do not know. I am not a lawyer).
|
61
|
+
|
62
|
+
For this tutorial, I will use
|
63
|
+
[*Big-D* by Shaolin Dub](https://freemusicarchive.org/music/Shaolin_Dub/joint-force/big-d/).
|
64
|
+
It is licensed under [CC-BY-NC-ND-4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/).
|
65
|
+
You can download it from the link or use the following command
|
66
|
+
(if you have installed [cURL](https://curl.se/)).
|
67
|
+
By downloading it, you accept its license.
|
68
|
+
|
69
|
+
```shell
|
70
|
+
curl https://files.freemusicarchive.org/storage-freemusicarchive-org/tracks/MQ8JO0BqKl2UADzKg74rwoY7mapqBT4uWpQYciTJ.mp3 -o 'Shaolin Dub - Big-D.mp3'
|
71
|
+
```
|
72
|
+
|
73
|
+
## Step 1: create a project
|
74
|
+
|
75
|
+
If you have already [installed sscharter](#install-sscharter)
|
76
|
+
and downloaded the audio file for your music using the command above,
|
77
|
+
you can now create a project for your chart by running the following command:
|
78
|
+
|
79
|
+
```shell
|
80
|
+
sscharter init big-d 'Shaolin Dub - Big-D.mp3'
|
81
|
+
```
|
82
|
+
|
83
|
+
The parameter `big-d` is the path to the project directory,
|
84
|
+
and its basename will be set as the name of the project
|
85
|
+
(but you are free to change it later).
|
86
|
+
The parameter `Shaolin Dub - Big-D.mp3` is the path to the audio file.
|
87
|
+
You can continue to add more parameters to the command
|
88
|
+
to include more files that you want t o include in the final level file.
|
89
|
+
See [the documentation](https://sunniesnow.github.io/doc/)
|
90
|
+
for what you may want to include.
|
91
|
+
If you forget to include some of the files that you want to include
|
92
|
+
at this step, you can add them later whenever you want.
|
93
|
+
|
94
|
+
After running the command, the audio file will be copied into the project directory,
|
95
|
+
and it is no longer needed for the rest of the tutorial.
|
96
|
+
You may delete it if you want.
|
97
|
+
|
98
|
+
Open the directory `big-d`, and you will see this directory structure:
|
99
|
+
|
100
|
+
```plain
|
101
|
+
big-d
|
102
|
+
├── files
|
103
|
+
│ └── Shaolin Dub - Big-D.mp3
|
104
|
+
├── Gemfile
|
105
|
+
├── .gitignore
|
106
|
+
├── Rakefile
|
107
|
+
├── README.md
|
108
|
+
├── src
|
109
|
+
│ └── master.rb
|
110
|
+
└── .sscharter.yml
|
111
|
+
```
|
112
|
+
|
113
|
+
Here are some explanation for each of them.
|
114
|
+
|
115
|
+
- `Gemfile` specifies the gem dependencies of this project.
|
116
|
+
By default your project depends on sscharter, bundler, and rake.
|
117
|
+
You can add more dependencies if you want.
|
118
|
+
For more information about it,
|
119
|
+
see [the official documentation](https://bundler.io/man/gemfile.5.html).
|
120
|
+
- `.gitignore` specifies the files that should not be tracked by Git.
|
121
|
+
If you do not use Git as the version manager for your project, it does nothing.
|
122
|
+
- `Rakefile` contains some tasks that can be run by [Rake](https://ruby.github.io/rake/).
|
123
|
+
- `README.md` is the README file of your project.
|
124
|
+
- `.sscharter.yml` is the sscharter configuration for your project.
|
125
|
+
- `files` is the directory whose files will be included in the final level file.
|
126
|
+
You can change the directory in `.sscharter.yml`.
|
127
|
+
- `src` is the directory that contains the source codes of your project.
|
128
|
+
You can change the directory in `.sscharter.yml`.
|
129
|
+
|
130
|
+
Open `.sscharter.yml` using your text editor.
|
131
|
+
Here are the contents that you should see:
|
132
|
+
|
133
|
+
```yaml
|
134
|
+
---
|
135
|
+
project_name: big-d
|
136
|
+
build_dir: build
|
137
|
+
files_dir: files
|
138
|
+
sources_dir: src
|
139
|
+
include:
|
140
|
+
- README.md
|
141
|
+
```
|
142
|
+
|
143
|
+
Here are some explanation for each of them.
|
144
|
+
|
145
|
+
- `project_name` is the name of your project.
|
146
|
+
It will be the filename without extension of the generated level file.
|
147
|
+
- `build_dir` is the directory that will contain the generated level file.
|
148
|
+
- `files_dir` is the directory whose files will be included in the final level file.
|
149
|
+
- `sources_dir` is the directory that contains the source codes of your project.
|
150
|
+
- `include` is the list of files outside `files_dir` that will be included in the final level file.
|
151
|
+
You can use wildcard characters in the filenames.
|
152
|
+
|
153
|
+
You can also use your text editor to open the file `src/master.rb` for what a source codes file look like:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# frozen_string_literal: true
|
157
|
+
|
158
|
+
Sunniesnow::Charter.open 'master' do
|
159
|
+
|
160
|
+
title 'The title of the music'
|
161
|
+
artist 'The artist of the music'
|
162
|
+
charter 'Your name'
|
163
|
+
difficulty_name 'Master'
|
164
|
+
difficulty_color '#8c68f3'
|
165
|
+
difficulty '12'
|
166
|
+
|
167
|
+
offset 0
|
168
|
+
bpm 120
|
169
|
+
|
170
|
+
tp_chain 0, 0, 1 do
|
171
|
+
t -50, 0, 'hello'
|
172
|
+
b 1 # proceed by 1 beat
|
173
|
+
t 50, 0, 'world'
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
You will learn what these codes mean soon later!
|
180
|
+
|
181
|
+
## Step 2: set up workflow
|
182
|
+
|
183
|
+
Now, in the project directory, run
|
184
|
+
|
185
|
+
```shell
|
186
|
+
bundle install
|
187
|
+
```
|
188
|
+
|
189
|
+
It should be done quickly if your network is good.
|
190
|
+
Change the gem source in `Gemfile` if you have trouble retrieving from the default source.
|
191
|
+
|
192
|
+
After that, run
|
193
|
+
|
194
|
+
```shell
|
195
|
+
rake
|
196
|
+
```
|
197
|
+
|
198
|
+
Then, you should see a file `build/big-d.ssc` generated.
|
199
|
+
This file is a level file of Sunniesnow.
|
200
|
+
You can upload it on the webpage of Sunniesnow to play it.
|
201
|
+
|
202
|
+
However, it is kind of inconvenient if you have to run `rake`
|
203
|
+
and reupload the level file every time you make a change to the source codes.
|
204
|
+
You can use the following command to make it more convenient:
|
205
|
+
|
206
|
+
```shell
|
207
|
+
rake serve
|
208
|
+
```
|
209
|
+
|
210
|
+
This command will open the Sunniesnow webpage in your browser for you.
|
211
|
+
The `online` field of Sunniesnow is already filled with the address to the generated level file.
|
212
|
+
Every time you save changes to the source codes,
|
213
|
+
the program will automatically rebuild the level file.
|
214
|
+
You just need to hit "load" again to reload the level file.
|
215
|
+
|
216
|
+
The port of the local server is 8011 by default.
|
217
|
+
If you need to change the port to 1314 for example, you need to run
|
218
|
+
|
219
|
+
```shell
|
220
|
+
bundle exec sscharter serve 1314
|
221
|
+
```
|
222
|
+
|
223
|
+
## What does each line in `src/master.rb` mean?
|
224
|
+
|
225
|
+
Now, you are ready to write the chart!
|
226
|
+
Open `src/master.rb` using your text editor.
|
227
|
+
Here I explain what does each line mean.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
Sunniesnow::Charter.open 'master' do
|
231
|
+
# ...
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
This means that you are opening a chart called "master".
|
236
|
+
This will lead to a chart file called `master.json` in the generated level file.
|
237
|
+
You can open multiple charts in a single source codes file,
|
238
|
+
and you can also open the same chart in multiple source codes files
|
239
|
+
(but in that case the loading order matters).
|
240
|
+
|
241
|
+
The contents inside the `do`...`end` block are the contents of the chart.
|
242
|
+
You can write anything you like there.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
title 'The title of the music'
|
246
|
+
artist 'The artist of the music'
|
247
|
+
charter 'Your name'
|
248
|
+
difficulty_name 'Master'
|
249
|
+
difficulty_color '#8c68f3'
|
250
|
+
difficulty '12'
|
251
|
+
```
|
252
|
+
|
253
|
+
These lines are the metadata of the chart.
|
254
|
+
They already explains pretty much themselves.
|
255
|
+
Just fill them in!
|
256
|
+
|
257
|
+
By the way, there is a trick about the `difficulty_color` for your convenience.
|
258
|
+
If you want default difficulty colors from Lyrica, you can use one of these:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
difficulty_color :easy
|
262
|
+
difficulty_color :normal
|
263
|
+
difficulty_color :hard
|
264
|
+
difficulty_color :master
|
265
|
+
difficulty_color :special
|
266
|
+
```
|
267
|
+
|
268
|
+
Then, here comes the offset and BPM part:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
offset 0
|
272
|
+
bpm 120
|
273
|
+
```
|
274
|
+
|
275
|
+
This means:
|
276
|
+
|
277
|
+
- The zeroth beat of the music starts at time equaling to 0 seconds.
|
278
|
+
- The BPM of the music starting at the zeroth beat is 120.
|
279
|
+
|
280
|
+
You can change the BPM later at any beat by using `bpm`.
|
281
|
+
You can also use `offset` multiple times, but every time you run `offset`,
|
282
|
+
the beat is reset to zero, and you have to set the BPM over again.
|
283
|
+
|
284
|
+
Now here comes the main part:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
tp_chain 0, 0, 1 do
|
288
|
+
t -50, 0, 'hello'
|
289
|
+
b 1 # proceed by 1 beat
|
290
|
+
t 50, 0, 'world'
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
This means:
|
295
|
+
|
296
|
+
- Start a tip point chain.
|
297
|
+
The tip point spawns at:
|
298
|
+
- coordinates $(0,0)$ relative to the first tip-pointable event in the chain, and
|
299
|
+
- time 1 second before the first tip-pointable event in the chain.
|
300
|
+
- Add a tap note at coordinates $(-50,0)$ to the tip point chain.
|
301
|
+
The tap note has text "hello" on it.
|
302
|
+
- Proceed by 1 beat.
|
303
|
+
Now the current beat is 1 instead of 0.
|
304
|
+
- Add a tap note at coordinates $(50,0)$ to the tip point chain.
|
305
|
+
The tap note has text "world" on it.
|
306
|
+
- End the tip point chain.
|
307
|
+
|
308
|
+
They are actually very straightforward!
|
309
|
+
|
310
|
+
Now, you are ready to write your own chart.
|
311
|
+
|
312
|
+
## Step 3: write the chart metadata
|
313
|
+
|
314
|
+
Delete all contents inside the outmost `do`...`end` block.
|
315
|
+
Then, write the metadata of the chart:
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
title 'Big-D'
|
319
|
+
artist 'Shaolin Dub'
|
320
|
+
charter 'UlyssesZhan' # replace this with your name
|
321
|
+
difficulty_name 'Master'
|
322
|
+
difficulty_color :master
|
323
|
+
difficulty '10'
|
324
|
+
```
|
325
|
+
|
326
|
+
You are then writing the chart for the master difficulty,
|
327
|
+
with difficulty being 10.
|
328
|
+
|
329
|
+
Now, the whole source codes file should look like this:
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
# frozen_string_literal: true
|
333
|
+
|
334
|
+
Sunniesnow::Charter.open 'master' do
|
335
|
+
|
336
|
+
title 'Big-D'
|
337
|
+
artist 'Shaolin Dub'
|
338
|
+
charter 'UlyssesZhan' # replace this with your name
|
339
|
+
difficulty_name 'Master'
|
340
|
+
difficulty_color :master
|
341
|
+
difficulty '10'
|
342
|
+
|
343
|
+
end
|
344
|
+
```
|
345
|
+
|
346
|
+
## Step 4: determine the offset and BPM
|
347
|
+
|
348
|
+
Now, you need to determine the offset and BPM of the music
|
349
|
+
and write the information into the source codes file.
|
350
|
+
Use your favorite audio editing software to open the music
|
351
|
+
(I use Audacity here).
|
352
|
+
|
353
|
+
To determine them, inside a section of the music with consistent BPM,
|
354
|
+
find a beat that is clear to recognize (up to milliseconds precision for the best).
|
355
|
+
|
356
|
+
![First clear beat](https://i.imgur.com/rI5UXgm.png)
|
357
|
+
|
358
|
+
Then, find the time of the beat.
|
359
|
+
You can read off the time at the "Audio Position" field in Audacity,
|
360
|
+
which says 7.023 seconds here.
|
361
|
+
|
362
|
+
Then, find the last beat that is clear to recognize.
|
363
|
+
|
364
|
+
![Last clear beat](https://i.imgur.com/eJw6ZaJ.png)
|
365
|
+
|
366
|
+
Then, find the time of the beat.
|
367
|
+
It is 180.071 seconds.
|
368
|
+
|
369
|
+
You now need to know how many beats are there between the two beats.
|
370
|
+
You can first approximately measure the length of, say, 8 beats,
|
371
|
+
and then divide the length between the two beats by the length of 8 beats
|
372
|
+
to estimate how many "8 beats" are there.
|
373
|
+
Because we can easily hear that number of beats modulo 4 is 2,
|
374
|
+
we can use the fact to check our result of calculation
|
375
|
+
(the result of the division should be close to something point 25 or something point 75).
|
376
|
+
The result should be approximately $49.75$.
|
377
|
+
Then, we can calculate the BPM now:
|
378
|
+
|
379
|
+
```math
|
380
|
+
60\,\mathrm{s/min}\cdot
|
381
|
+
\frac{49.75\times8\,\mathrm{beats}}{180.071\,\mathrm s-7.023\,\mathrm s}
|
382
|
+
\approx137.996\,\mathrm{beats/min}.
|
383
|
+
```
|
384
|
+
|
385
|
+
So the BPM is approximately 138.
|
386
|
+
|
387
|
+
To get the offset, we just need to extrapolate the time of the zeroth beat.
|
388
|
+
We can count that the first clear beat we chose is the 16th beat,
|
389
|
+
so we can use the BPM and the time of the 16th beat to extrapolate the time of the zeroth beat:
|
390
|
+
|
391
|
+
```math
|
392
|
+
7.023\,\mathrm s-16\,\mathrm{beats}\cdot
|
393
|
+
\frac{60\,\mathrm{s/min}}{138\,\mathrm{beats/min}}
|
394
|
+
\approx0.066\,\mathrm s.
|
395
|
+
```
|
396
|
+
|
397
|
+
Now that we got the offset and BPM, we can write them into the source codes file:
|
398
|
+
|
399
|
+
```ruby
|
400
|
+
offset 0.066
|
401
|
+
bpm 138
|
402
|
+
```
|
403
|
+
|
404
|
+
The whole source codes file should now look like this:
|
405
|
+
|
406
|
+
```ruby
|
407
|
+
# frozen_string_literal: true
|
408
|
+
|
409
|
+
Sunniesnow::Charter.open 'master' do
|
410
|
+
|
411
|
+
title 'Big-D'
|
412
|
+
artist 'Shaolin Dub'
|
413
|
+
charter 'UlyssesZhan' # replace this with your name
|
414
|
+
difficulty_name 'Master'
|
415
|
+
difficulty_color :master
|
416
|
+
difficulty '10'
|
417
|
+
|
418
|
+
offset 0.066
|
419
|
+
bpm 138
|
420
|
+
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
424
|
+
## Step 5: write the chart
|
425
|
+
|
426
|
+
Now, you are ready to write the main part of the chart!
|
427
|
+
|
428
|
+
### Write notes
|
429
|
+
|
430
|
+
Write the following code:
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
hold -25, 0, 1/2r # a hold note at (-25,0) with the duration of 1/2 beats
|
434
|
+
beat 3/4r # go to beat 3/4
|
435
|
+
hold 25, 0, 1/2r # a hold note at (25,0) with the duration of 1/2 beats
|
436
|
+
beat 1/4r + 2 # go to beat 3
|
437
|
+
hold 0, 25, 1/2r # a hold note at (0,25) with the duration of 1/2 beats
|
438
|
+
beat 1 # go to beat 4
|
439
|
+
|
440
|
+
hold -25, -25, 1/2r
|
441
|
+
beat 3/4r
|
442
|
+
hold 25, -25, 1/2r
|
443
|
+
beat 1/4r + 2
|
444
|
+
hold 0, -50, 1/2r
|
445
|
+
beat 1
|
446
|
+
```
|
447
|
+
|
448
|
+
By writing these, you added 6 hold notes to the chart.
|
449
|
+
Each `hold` adds a hold note to the chart.
|
450
|
+
The first argument is the $x$-coordinate of the hold note,
|
451
|
+
the second argument is the $y$-coordinate of the hold note,
|
452
|
+
and the third argument is the duration of the hold note in beats.
|
453
|
+
There is an optional fourth argument, which is the text on the hold note
|
454
|
+
(defaults to empty string).
|
455
|
+
Each `beat` call proceeds the current beat by the argument you specifies.
|
456
|
+
|
457
|
+
> Here is a notice for those who are not familiar with Ruby:
|
458
|
+
expression like `1/2r` means the rational number $1/2$.
|
459
|
+
In sscharter, you need to specify beats in integers or rational numbers
|
460
|
+
to avoid rounding errors.
|
461
|
+
Here is an example of how rational numbers avoid rounding errors:
|
462
|
+
>
|
463
|
+
> ```ruby
|
464
|
+
> 1/10r + 1/5r == 3/10r # => true
|
465
|
+
> 0.1 + 0.2 == 0.3 # => false
|
466
|
+
> ```
|
467
|
+
>
|
468
|
+
> The rounding error would not be huge even if being added up many times,
|
469
|
+
but it is still better to avoid it.
|
470
|
+
>
|
471
|
+
> If you are tired of appending `r` to every rational number, you may use
|
472
|
+
>
|
473
|
+
> ```ruby
|
474
|
+
> Integer.alias_method :/, :quo
|
475
|
+
> ```
|
476
|
+
>
|
477
|
+
> to make the division operation between integers automatically return rational numbers.
|
478
|
+
|
479
|
+
You can also abbreviate `hold` to `h` and `beat` to `b`, like this:
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
h -25, 0, 1/2r; b 3/4r
|
483
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
484
|
+
h 0, 25, 1/2r; b 1
|
485
|
+
```
|
486
|
+
|
487
|
+
> Here is another notice for those who are not familiar with Ruby.
|
488
|
+
In Ruby, the semicolon `;` can be used to separate multiple statements in a single line.
|
489
|
+
Therefore, `h -25, 0, 1/2r; b 3/4r` is equivalent to
|
490
|
+
>
|
491
|
+
> ```ruby
|
492
|
+
> h -25, 0, 1/2r
|
493
|
+
> b 3/4r
|
494
|
+
> ```
|
495
|
+
|
496
|
+
Here is a list of all the note types you can use:
|
497
|
+
|
498
|
+
| Note type | Syntax | Abbreviation |
|
499
|
+
|-|-|-|
|
500
|
+
| tap | `tap x, y, text=""` | `t` |
|
501
|
+
| hold | `hold x, y, duration_beats, text=""` | `h` |
|
502
|
+
| drag | `drag x, y` | `d` |
|
503
|
+
| flick | `flick x, y, direction, text=""` | `f` |
|
504
|
+
| background note | `bg_note x, y, duration_beats=0, text=""` | |
|
505
|
+
|
506
|
+
Here are some notices:
|
507
|
+
|
508
|
+
- The direction of a flick note can be a number specifying the angle in **radians**
|
509
|
+
(zero is to the right, and increasing angle is counterclockwise),
|
510
|
+
or it can be one of the following 8 symbols:
|
511
|
+
`:right`, `:up_right`, `:up`, `:up_left`, `:left`, `:down_left`, `:down`, `:down_right`.
|
512
|
+
- The duration of a background note can be zero, but that of a hold note cannot.
|
513
|
+
|
514
|
+
> Here is another notice for those who are not familiar with Ruby.
|
515
|
+
> In Ruby, the parentheses around the arguments of a method call can be omitted.
|
516
|
+
> Therefore, `tap 25, 0, 'hi'` is equivalent to `tap(25, 0, 'hi')`.
|
517
|
+
|
518
|
+
Now, the whole source codes file should look like this:
|
519
|
+
|
520
|
+
```ruby
|
521
|
+
# frozen_string_literal: true
|
522
|
+
|
523
|
+
Sunniesnow::Charter.open 'master' do
|
524
|
+
|
525
|
+
title 'Big-D'
|
526
|
+
artist 'Shaolin Dub'
|
527
|
+
charter 'UlyssesZhan' # replace this with your name
|
528
|
+
difficulty_name 'Master'
|
529
|
+
difficulty_color :master
|
530
|
+
difficulty '10'
|
531
|
+
|
532
|
+
offset 0.066
|
533
|
+
bpm 138
|
534
|
+
|
535
|
+
h -25, 0, 1/2r; b 3/4r
|
536
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
537
|
+
h 0, 25, 1/2r; b 1
|
538
|
+
|
539
|
+
h -25, -25, 1/2r; b 3/4r
|
540
|
+
h 25, -25, 1/2r; b 1/4r + 2
|
541
|
+
h 0, -50, 1/2r; b 1
|
542
|
+
|
543
|
+
end
|
544
|
+
```
|
545
|
+
|
546
|
+
You may build the level file and play it on Sunniesnow to see what you have done!
|
547
|
+
If everything is alright, you should see a chart like this:
|
548
|
+
|
549
|
+
![The chart](https://i.imgur.com/zPUtgGr.png)
|
550
|
+
|
551
|
+
If you see something similar, congratulations!
|
552
|
+
|
553
|
+
### Background patterns
|
554
|
+
|
555
|
+
There are totally 7 different kinds of background patterns:
|
556
|
+
|
557
|
+
| Pattern | Syntax |
|
558
|
+
|-|-|
|
559
|
+
| big text | `big_text duration_beats=0, text` |
|
560
|
+
| grid | `grid duration_beats=0` |
|
561
|
+
| hexagon | `hexagon duration_beats=0` |
|
562
|
+
| checkerboard | `checkerboard duration_beats=0` |
|
563
|
+
| diamond grid | `diamond_grid duration_beats=0` |
|
564
|
+
| pentagon | `pentagon duration_beats=0` |
|
565
|
+
| turntable | `turntable duration_beats=0` |
|
566
|
+
|
567
|
+
Most of them only has one optional argument, specifying the duration.
|
568
|
+
The big text is different in that it needs an additional required argument,
|
569
|
+
specifying the contents of the texts.
|
570
|
+
|
571
|
+
The mathematical details about the shapes of these patterns are
|
572
|
+
specified in [the documentation](https://sunniesnow.github.io/doc/chart.html).
|
573
|
+
|
574
|
+
You may now try adding a grid pattern to the chart:
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
offset 0.066
|
578
|
+
bpm 138
|
579
|
+
|
580
|
+
grid 16 # add a grid pattern with duration 16 beats
|
581
|
+
|
582
|
+
# notes...
|
583
|
+
h -25, 0, 1/2r; b 3/4r
|
584
|
+
```
|
585
|
+
|
586
|
+
### Groups, duplicates, and transformations
|
587
|
+
|
588
|
+
You have already written the chart for the first 8 beats of *Big-D*.
|
589
|
+
Then, you want to have the same for the next 8 beats.
|
590
|
+
How would you do that?
|
591
|
+
|
592
|
+
Well, an obvious solution is to copy and paste the code you have written.
|
593
|
+
However, this has some drawbacks:
|
594
|
+
|
595
|
+
- If at some point you want to change the pattern,
|
596
|
+
you have to change it at multiple places.
|
597
|
+
- When you read the codes, it would not be obvious at a glance that
|
598
|
+
"hey, this part is the same as that part!"
|
599
|
+
|
600
|
+
Come on, you are programming! Are not there some better ways?
|
601
|
+
Fortunately, there are, and there are multiple ways to do that.
|
602
|
+
|
603
|
+
First, you can define a method, and call that method twice:
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
def pattern # the method name `pattern` is arbitrary, set what you like
|
607
|
+
h -25, 0, 1/2r; b 3/4r
|
608
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
609
|
+
h 0, 25, 1/2r; b 1
|
610
|
+
|
611
|
+
h -25, -25, 1/2r; b 3/4r
|
612
|
+
h 25, -25, 1/2r; b 1/4r + 2
|
613
|
+
h 0, -50, 1/2r; b 1
|
614
|
+
end
|
615
|
+
pattern
|
616
|
+
pattern
|
617
|
+
```
|
618
|
+
|
619
|
+
Another way of doing this is to create a group of notes and duplicate them.
|
620
|
+
You can write these:
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
notes = group do # the variable name `notes` is arbitrary, set what you like
|
624
|
+
h -25, 0, 1/2r; b 3/4r
|
625
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
626
|
+
h 0, 25, 1/2r; b 1
|
627
|
+
|
628
|
+
h -25, -25, 1/2r; b 3/4r
|
629
|
+
h 25, -25, 1/2r; b 1/4r + 2
|
630
|
+
h 0, -50, 1/2r; b 1
|
631
|
+
end
|
632
|
+
new_notes = duplicate notes # duplicate them to get a group of new notes
|
633
|
+
transform(new_notes) { beat_translate 8 } # translate those new notes by 8 beats
|
634
|
+
b 8 # calling `transform` does not affect current beat, so proceed by 8 beats now
|
635
|
+
```
|
636
|
+
|
637
|
+
In this example, the variable `notes` is assigned the return value of `group`,
|
638
|
+
which is actually an array of the events inside the code block in the call of `group`.
|
639
|
+
The variable `new_notes` is assigned the return value of `duplicate`,
|
640
|
+
which is another array of other events, which are copies of the events in `notes`.
|
641
|
+
|
642
|
+
> In Ruby, code blocks can be written as a `do`...`end` block **or** a curly-enclosed block (`{`...`}`).
|
643
|
+
They have equivalent effects but have different precedences,
|
644
|
+
and by convention, if the code block contains only one line of codes,
|
645
|
+
people use a curly-enclosed block to make it seem more compact.
|
646
|
+
|
647
|
+
Transformations are more powerful than just translating in time.
|
648
|
+
There are many other transformations you can apply to notes.
|
649
|
+
Here is a list:
|
650
|
+
|
651
|
+
| Syntax | Meaning |
|
652
|
+
|-|-|
|
653
|
+
| `translate dx, dy` | translating in space by vector $(\mathtt{dx},\mathtt{dy})$ |
|
654
|
+
| `horizontal_flip` | flipping horizontally |
|
655
|
+
| `vertical_flip` | flipping vertically |
|
656
|
+
| `rotate angle` | rotating by `angle` (in radians) counterclockwise |
|
657
|
+
| `scale sx, sy=sx` | scaling by `sx` in $x$ and by `sy` in $y$; negative values are also valid |
|
658
|
+
| `compound_linear xx, xy, yx, yy` | applying a linear transformation specified by numbers in a matrix |
|
659
|
+
| `beat_translate delta_beat` | translating in time by `delta_beat` beats |
|
660
|
+
|
661
|
+
> For those who are not familiar with linear algebra,
|
662
|
+
some of the transformations on a 2-dimensional shape can be specified by a $2\times2$ matrix.
|
663
|
+
Those transformations are called **linear transformations**.
|
664
|
+
The matrix is called the **transformation matrix**.
|
665
|
+
The transformation is done like this:
|
666
|
+
>
|
667
|
+
> ```math
|
668
|
+
> x'=\mathtt{xx}\cdot x+\mathtt{xy}\cdot y,\quad
|
669
|
+
> y'=\mathtt{yx}\cdot x+\mathtt{yy}\cdot y.
|
670
|
+
> ```
|
671
|
+
|
672
|
+
*Notice*: When doing spatial transformations,
|
673
|
+
you do not need to worry about the direction of flick notes.
|
674
|
+
They are taken care of as well.
|
675
|
+
|
676
|
+
*Another notice*: For background patterns,
|
677
|
+
they are ignored when doing spatial transformations.
|
678
|
+
They are still affected by temporal transformations, though.
|
679
|
+
|
680
|
+
Therefore, you can also apply, say, a horizontal flip on the duplicated notes like this:
|
681
|
+
|
682
|
+
```ruby
|
683
|
+
notes = group do
|
684
|
+
# ...
|
685
|
+
end
|
686
|
+
transform duplicate notes do
|
687
|
+
horizontal_flip
|
688
|
+
beat_translate 8
|
689
|
+
end
|
690
|
+
```
|
691
|
+
|
692
|
+
Transformations can also be used to bulk edit notes.
|
693
|
+
Imagine that you have already written a bunch of notes with their coordinates,
|
694
|
+
but you then decided them move them (say, translating by vector $(25,25)$).
|
695
|
+
You can use `transform` to do that:
|
696
|
+
|
697
|
+
```ruby
|
698
|
+
notes = group do
|
699
|
+
# ...
|
700
|
+
end
|
701
|
+
transform(notes) { translate 25, 25 }
|
702
|
+
```
|
703
|
+
|
704
|
+
### Navigate among beats
|
705
|
+
|
706
|
+
You have already learned how to use `beat` to proceed the current beat
|
707
|
+
by a certain amount of beats.
|
708
|
+
It can also navigate backwards by using negative numbers.
|
709
|
+
|
710
|
+
You can get the current beat number by using `beat` without arguments:
|
711
|
+
|
712
|
+
```ruby
|
713
|
+
offset 0.066 # using `offset` will reset the current beat to zero
|
714
|
+
p beat # this will output 0 in the terminal, because the current beat is 0
|
715
|
+
p b # `b` is an abbreviation of `beat`, so this will also output 0
|
716
|
+
beat 1 # proceed the current beat by 1 beat
|
717
|
+
p beat # this will output 1 in the terminal, because the current beat is 1
|
718
|
+
b 3/4r # proceed the current beat by 3/4 beats
|
719
|
+
p b # this will output 7/4 in the terminal, because the current beat is 7/4
|
720
|
+
b -1/2r # go back by 1/2 beats
|
721
|
+
p b # outputs 5/4
|
722
|
+
b -2 # go back by 2 beats, even if the current beat is only 5/4
|
723
|
+
p b # outputs -3/4
|
724
|
+
```
|
725
|
+
|
726
|
+
> In Ruby, the method `p` is used to output any object in the terminal
|
727
|
+
in a human-readable form.
|
728
|
+
You can use it to debug your program.
|
729
|
+
|
730
|
+
You can use `beat!` (whose abbreviation is `b!`) to set the current beat directly,
|
731
|
+
instead of specifying the amount of beats to proceed relative to the current beat:
|
732
|
+
|
733
|
+
```ruby
|
734
|
+
beat! 10 # set the current beat to 10
|
735
|
+
p beat # outputs 10
|
736
|
+
b! -5 # set the current beat to -5
|
737
|
+
p b # outputs -5
|
738
|
+
p beat! # this outputs -5 because you may also use `beat!` without arguments to get the current beat
|
739
|
+
p b! # this also outputs -5
|
740
|
+
```
|
741
|
+
|
742
|
+
You can use `time_at` to get the time (in seconds) of a certain beat:
|
743
|
+
|
744
|
+
```ruby
|
745
|
+
offset 0.1
|
746
|
+
bpm 120
|
747
|
+
p time_at 0 # outputs 0.1, meaning that beat 0 is at 0.1 seconds
|
748
|
+
p time_at 1 # outputs 0.6, meaning that beat 1 is at 0.6 seconds
|
749
|
+
b! 10
|
750
|
+
p time_at # when using `time_at` without arguments, it will use the current beat; outputs 5.1
|
751
|
+
|
752
|
+
offset 0.2 # this resets current beat to 0
|
753
|
+
bpm 120 # calling `offset` clears BPM info, so you need to set it again
|
754
|
+
p time_at # outputs 0.2, meaning that beat 0 is at 0.2 seconds
|
755
|
+
```
|
756
|
+
|
757
|
+
Having `beat!`, you can now travel back to some point in time
|
758
|
+
to write some notes there:
|
759
|
+
|
760
|
+
```ruby
|
761
|
+
checkpoint = b
|
762
|
+
|
763
|
+
# write something here...
|
764
|
+
|
765
|
+
b! checkpoint # travel back to the checkpoint
|
766
|
+
|
767
|
+
# write something here...
|
768
|
+
```
|
769
|
+
|
770
|
+
However, there is another way of doing this.
|
771
|
+
You can encapsulate the first set of notes in a `group` that does
|
772
|
+
*not preserve beat*,
|
773
|
+
which means that the current beat is set to how it was at the start of the group when the group finishes,
|
774
|
+
like this:
|
775
|
+
|
776
|
+
```ruby
|
777
|
+
group preserve_beat: false do
|
778
|
+
# write something here...
|
779
|
+
end
|
780
|
+
# write something here...
|
781
|
+
```
|
782
|
+
|
783
|
+
### BPM changes
|
784
|
+
|
785
|
+
*Notice*: because *Big-D* does not have BPM changes,
|
786
|
+
this part of the tutorial will not be used in the actual chart of *Big-D*.
|
787
|
+
You may still try it out, though.
|
788
|
+
|
789
|
+
You can use `bpm` to set the BPM starting at the current beat.
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
offset 0
|
793
|
+
bpm 120
|
794
|
+
p time_at # outputs 0.0
|
795
|
+
b 1 # proceed by 1 beat
|
796
|
+
p time_at # outputs 0.5
|
797
|
+
bpm 60 # set the BPM starting at the current beat to 60
|
798
|
+
b 1
|
799
|
+
p time_at # outputs 1.5
|
800
|
+
```
|
801
|
+
|
802
|
+
As we can see, BPM changes only affect the beats **after** they were set.
|
803
|
+
Beats before the BPM changes are not affected.
|
804
|
+
However, this means that you cannot get the time before the first BPM you set:
|
805
|
+
|
806
|
+
```ruby
|
807
|
+
offset 0
|
808
|
+
bpm 120
|
809
|
+
p time_at -1 # raises an error!
|
810
|
+
```
|
811
|
+
|
812
|
+
### Tip points
|
813
|
+
|
814
|
+
Sscharter regards tap, hold, drag, and flick as *tip-pointable* events.
|
815
|
+
Note that although Sunniesnow also regards background notes as tip-pointable,
|
816
|
+
sscharter does not.
|
817
|
+
Tip-pointable events can be connected by tip points,
|
818
|
+
and one tip-pointable event can be connected by at most one tip point.
|
819
|
+
A set of events that are connected by the same tip point compose a *tip point chain*.
|
820
|
+
|
821
|
+
There are two methods for you to write tip points:
|
822
|
+
`tip_point_chain` and `tip_point_drop`,
|
823
|
+
and they are respectively abbreviated as `tp_chain` and `tp_drop`.
|
824
|
+
Before introducing them, I need to first explain how to specify
|
825
|
+
where and when a tip point spawns.
|
826
|
+
There are two ways to specify where the tip point spawns:
|
827
|
+
|
828
|
+
- Specifying the coordinates relative to the first tip-pointable event in the tip point chain.
|
829
|
+
This is the default way.
|
830
|
+
You specify the $x$-coordinate and $y$-coordinate
|
831
|
+
by the first two arguments of `tip_point_chain` or `tip_point_drop`.
|
832
|
+
- Specifying the absolute coordinates of the position where the tip point spawns.
|
833
|
+
To specify the position in this way,
|
834
|
+
you need to add a keyword argument `relative: false` to the call of `tip_point_chain` or `tip_point_drop`.
|
835
|
+
|
836
|
+
There are four ways to specify when the tip point spawns:
|
837
|
+
|
838
|
+
- Specifying how much time, in seconds,
|
839
|
+
before the first tip-pointable event in the tip point chain the tip point will have spawned.
|
840
|
+
This is the default way.
|
841
|
+
The time is specified by the third argument in the call of `tip_point_chain` or `tip_point_drop`.
|
842
|
+
- Specifying the speed of the tip point between its spawning and the first tip-pointable event in the tip point chain.
|
843
|
+
To specify the speed, you need to use the keyword argument `speed:`
|
844
|
+
in the call of `tip_point_chain` or `tip_point_drop`.
|
845
|
+
- Specifying how many beats before the first tip-pointable event
|
846
|
+
in the tip point chain the tip point will have spawned.
|
847
|
+
To specify the beats, you need to use the keyword argument `relative_beat:`
|
848
|
+
in the call of `tip_point_chain` or `tip_point_drop`.
|
849
|
+
- Specifying the beat speed (unit length per beat)
|
850
|
+
of the tip point between its spawning and the first tip-pointable event in the tip point chain.
|
851
|
+
To specify the beat speed, you need to use the keyword argument `beat_speed:`
|
852
|
+
in the call of `tip_point_chain` or `tip_point_drop`.
|
853
|
+
The beat speed may be specified as a float number,
|
854
|
+
and this is the only case where something related to beats is not preferred to be specified as a rational number.
|
855
|
+
|
856
|
+
We can categorize the four ways into a table:
|
857
|
+
|
858
|
+
<table>
|
859
|
+
<tr>
|
860
|
+
<th>Method</th>
|
861
|
+
<td><code>tip_point_chain</code></td>
|
862
|
+
<td><code>tip_point_drop</code></td>
|
863
|
+
</tr>
|
864
|
+
<tr>
|
865
|
+
<th><code>relative_time</code></th>
|
866
|
+
<td><code>tip_point_chain x=0, y=0, relative_time=0.0, relative: true</code></td>
|
867
|
+
<td><code>tip_point_drop x=0, y=0, relative_time=0.0, relative: true</code></td>
|
868
|
+
</tr>
|
869
|
+
<tr>
|
870
|
+
<th><code>speed</code></th>
|
871
|
+
<td><code>tip_point_chain x=0, y=0, relative: true, speed:</code></td>
|
872
|
+
<td><code>tip_point_drop x=0, y=0, relative: true, speed:</code></td>
|
873
|
+
</tr>
|
874
|
+
<tr>
|
875
|
+
<th><code>relative_beat</code></th>
|
876
|
+
<td><code>tip_point_chain x=0, y=0, relative: true, relative_beat:</code></td>
|
877
|
+
<td><code>tip_point_drop x=0, y=0, relative: true, relative_beat:</code></td>
|
878
|
+
</tr>
|
879
|
+
<tr>
|
880
|
+
<th><code>beat_speed</code></th>
|
881
|
+
<td><code>tip_point_chain x=0, y=0, relative: true, beat_speed:</code></td>
|
882
|
+
<td><code>tip_point_drop x=0, y=0, relative: true, beat_speed:</code></td>
|
883
|
+
</tr>
|
884
|
+
</table>
|
885
|
+
|
886
|
+
You can only choose one of the four ways to specify the position and time of the spawning of a tip point.
|
887
|
+
If you specify more than one of them, an error will be raised.
|
888
|
+
|
889
|
+
> In Ruby, keyword arguments are specified after the normal arguments.
|
890
|
+
You need to explicitly write the name of the keyword argument followed by a colon
|
891
|
+
in the method call.
|
892
|
+
For example, in the expression
|
893
|
+
>
|
894
|
+
> ```ruby
|
895
|
+
> tp_chain 0, 100, speed: 100 do
|
896
|
+
> # ...
|
897
|
+
> end
|
898
|
+
> ```
|
899
|
+
>
|
900
|
+
> the argument `speed:` is a keyword argument in the call of `tp_chain`.
|
901
|
+
The value of the keyword argument is `100`.
|
902
|
+
|
903
|
+
Knowing how to specify the position and time of the spawning of a tip point,
|
904
|
+
then creating tip points is just as easy as filling events in the code block
|
905
|
+
in the call of `tip_point_chain` or `tip_point_drop`.
|
906
|
+
|
907
|
+
Now I need to explain the difference of `tip_point_chain` and `tip_point_drop`.
|
908
|
+
`tip_point_chain` creates only one tip point chain,
|
909
|
+
and the tip point connects every tip-pointable event in the code block;
|
910
|
+
`tip_point_drop` creates as many tip point chains as the number of tip-pointable events in the code block,
|
911
|
+
and each tip point chain connects only one tip-pointable event.
|
912
|
+
|
913
|
+
As an example, let's connect the notes in the first 8 beats of *Big-D* by tip points:
|
914
|
+
|
915
|
+
```ruby
|
916
|
+
tp_chain 0, 100, speed: 100 do
|
917
|
+
h -25, 0, 1/2r; b 3/4r
|
918
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
919
|
+
h 0, 25, 1/2r; b 1
|
920
|
+
|
921
|
+
h -25, -25, 1/2r; b 3/4r
|
922
|
+
h 25, -25, 1/2r; b 1/4r + 2
|
923
|
+
h 0, -50, 1/2r; b 1
|
924
|
+
end
|
925
|
+
```
|
926
|
+
|
927
|
+
If you want to duplicate those tip-pointed notes, you do not need to encapsulate them with another layer of `group`.
|
928
|
+
The return value of `tip_point_chain` or `tip_point_drop`
|
929
|
+
is already an array of the events in the code block.
|
930
|
+
Therefore, you can just do this:
|
931
|
+
|
932
|
+
```ruby
|
933
|
+
notes = tp_chain 0, 100, speed: 100 do
|
934
|
+
# notes...
|
935
|
+
end
|
936
|
+
transform duplicate notes do
|
937
|
+
# transforms...
|
938
|
+
end
|
939
|
+
```
|
940
|
+
|
941
|
+
*Tips*:
|
942
|
+
Just like `group`, `tip_point_chain` and `tip_point_drop`
|
943
|
+
may also not preserve beat.
|
944
|
+
You can let them not preserve beat by specifying the keyword argument `preserve_beat: false`:
|
945
|
+
|
946
|
+
```ruby
|
947
|
+
tp_chain 0, 100, speed: 100, preserve_beat: false do
|
948
|
+
# notes...
|
949
|
+
end
|
950
|
+
```
|
951
|
+
|
952
|
+
Now, the whole source codes file should look like this:
|
953
|
+
|
954
|
+
```ruby
|
955
|
+
# frozen_string_literal: true
|
956
|
+
|
957
|
+
Sunniesnow::Charter.open 'master' do
|
958
|
+
|
959
|
+
title 'Big-D'
|
960
|
+
artist 'Shaolin Dub'
|
961
|
+
charter 'UlyssesZhan' # replace this with your name
|
962
|
+
difficulty_name 'Master'
|
963
|
+
difficulty_color :master
|
964
|
+
difficulty '10'
|
965
|
+
|
966
|
+
offset 0.066
|
967
|
+
bpm 138
|
968
|
+
|
969
|
+
grid 16
|
970
|
+
|
971
|
+
notes = tp_chain 0, 100, speed: 100 do
|
972
|
+
h -25, 0, 1/2r; b 3/4r
|
973
|
+
h 25, 0, 1/2r; b 1/4r + 2
|
974
|
+
h 0, 25, 1/2r; b 1
|
975
|
+
|
976
|
+
h -25, -25, 1/2r; b 3/4r
|
977
|
+
h 25, -25, 1/2r; b 1/4r + 2
|
978
|
+
h 0, -50, 1/2r; b 1
|
979
|
+
end
|
980
|
+
transform duplicate notes do
|
981
|
+
horizontal_flip
|
982
|
+
beat_translate 8
|
983
|
+
end
|
984
|
+
|
985
|
+
end
|
986
|
+
```
|
987
|
+
|
988
|
+
You may build the level file and play it on Sunniesnow to see what you have done!
|
989
|
+
|
990
|
+
![Second chart](https://i.imgur.com/ksu5sVW.png)
|
991
|
+
|
992
|
+
*Notice*:
|
993
|
+
When duplicating tip-pointed events,
|
994
|
+
the duplicated events are not connected by the same tip point as the original events.
|
995
|
+
However, if two original events are connected by the same tip point,
|
996
|
+
their duplicates are also connected by the same tip point, too.
|
997
|
+
|
998
|
+
## Advanced charting techniques
|
999
|
+
|
1000
|
+
TODO.
|
1001
|
+
|
1002
|
+
## Step 6: review your chart and write the README
|
1003
|
+
|
1004
|
+
Now, you have finished writing the chart!
|
1005
|
+
Please always play the chart yourself on Sunniesnow to see if it is enjoyable to play it.
|
1006
|
+
You should also try playing it in mirror mode (horizontal flip and/or vertical flip)
|
1007
|
+
so that you do not apply too much of your charting memories to playing the chart.
|
1008
|
+
|
1009
|
+
Then, it is time to finish writing the README.
|
1010
|
+
A template for the README file should have already been generated for you by sscharter.
|
1011
|
+
Use your text editor to edit it!
|
1012
|
+
|
1013
|
+
## Step 7: distribute the level file
|
1014
|
+
|
1015
|
+
Congratulations! You finished your chart.
|
1016
|
+
Now, you can distribute the level file to people who may want to play it.
|
1017
|
+
|
1018
|
+
If you want your chart to be available as an online level of Sunniesnow,
|
1019
|
+
you need to [contact the author of Sunniesnow](mailto:UlyssesZhan <ulysseszhan@gmail.com>).
|