-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
688 lines (657 loc) · 68.1 KB
/
Copy pathindex.html
File metadata and controls
688 lines (657 loc) · 68.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ピクセルの手ざわりのつくりかた — Babylon Lite platformer デモ解説</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DotGothic16&family=Zen+Kaku+Gothic+New:wght@400;500;700&family=M+PLUS+1+Code:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #F5F1E6;
--panel: #FFFFFF;
--ink: #2B2536;
--ink-soft: #675F72;
--line: #E4DDCC;
--line-strong: #D3C9B2;
--sky: #3E8FD0; --sky-deep: #245D95; --sky-bg: #E9F2FB;
--coin: #EDAF23; --coin-deep: #B07713; --coin-bg: #FBF1D6;
--brick: #C85A34; --brick-deep: #98401F; --brick-bg: #FAEadf;
--pipe: #46A64B; --pipe-deep: #2B7A31; --pipe-bg: #E7F4E8;
--lava: #E5602A; --lava-deep: #AF3B12; --lava-bg: #FBEadf;
--grape: #7E5AC8; --grape-deep: #533A93; --grape-bg: #F0EAFA;
--night: #2A2447;
--font-display: 'DotGothic16', 'Zen Kaku Gothic New', sans-serif;
--font-body: 'Zen Kaku Gothic New', 'Hiragino Kaku Gothic ProN', sans-serif;
--font-code: 'M PLUS 1 Code', monospace;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
font-size: 15.5px;
line-height: 1.95;
/* 折り返せない長い語(識別子・ファイル名など)が箱を突き破るのを防ぐ最後の砦 */
overflow-wrap: break-word;
}
a { color: var(--sky-deep); }
a:focus-visible, button:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; border-radius: 4px; }
::selection { background: #FCE39A; }
/* ── ヒーロー ─────────────────────────── */
.hero {
background:
repeating-linear-gradient(0deg, rgba(43,37,54,.05) 0 1px, transparent 1px 4px),
linear-gradient(180deg, #BFE0F5 0%, #E4F1FB 62%, var(--sky-bg) 100%);
border-bottom: 3px solid var(--line-strong);
padding: 64px 24px 52px;
}
.hero-inner {
max-width: 1000px; margin: 0 auto;
display: grid; grid-template-columns: 1fr auto; gap: 40px; align-items: center;
}
.eyebrow {
font-family: var(--font-display);
font-size: 13px; letter-spacing: .22em;
color: var(--sky-deep); margin: 0 0 14px;
}
h1 {
font-family: var(--font-display);
font-weight: 400;
font-size: clamp(26px, 4.3vw, 41px);
line-height: 1.45; margin: 0 0 14px; letter-spacing: .01em;
}
.hero p.sub { margin: 0; max-width: 560px; color: #4C4757; }
.stats { display: flex; flex-wrap: wrap; gap: 10px 34px; margin-top: 26px; font-family: var(--font-code); }
.stat b { display: block; font-size: 24px; line-height: 1.3; font-weight: 700; }
.stat span { font-size: 12px; color: var(--ink-soft); letter-spacing: .06em; }
.tiles { display: grid; grid-template-columns: repeat(2, 66px); gap: 18px; }
.tiles svg { filter: drop-shadow(0 4px 0 rgba(43,37,54,.14)); }
.tiles svg:nth-child(1) { animation: bob 4.6s ease-in-out infinite; }
.tiles svg:nth-child(2) { animation: bob 4.6s ease-in-out .7s infinite; }
.tiles svg:nth-child(3) { animation: bob 4.6s ease-in-out 1.4s infinite; }
.tiles svg:nth-child(4) { animation: bob 4.6s ease-in-out 2.1s infinite; }
@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
/* ── レイアウト ───────────────────────── */
.layout {
max-width: 1060px; margin: 0 auto;
padding: 44px 24px 90px;
display: grid; grid-template-columns: 224px minmax(0, 1fr); gap: 46px;
}
nav.toc { position: sticky; top: 20px; align-self: start; font-size: 13.5px; }
nav.toc .toc-title { font-family: var(--font-display); font-size: 12px; letter-spacing: .2em; color: var(--ink-soft); margin: 0 0 10px 8px; }
nav.toc a {
display: flex; align-items: center; gap: 9px;
padding: 5px 8px; border-radius: 6px;
color: var(--ink-soft); text-decoration: none; line-height: 1.5;
}
nav.toc a:hover { background: var(--panel); color: var(--ink); }
.chip { width: 10px; height: 10px; border-radius: 2px; flex: none; border: 1px solid rgba(0,0,0,.18); }
main { min-width: 0; }
section { margin: 0 0 78px; scroll-margin-top: 26px; }
.ch { font-family: var(--font-display); font-size: 12px; letter-spacing: .2em; color: var(--ink-soft); margin: 0 0 6px; }
h2 {
font-family: var(--font-display); font-weight: 400;
font-size: 25px; line-height: 1.4;
display: flex; align-items: center; gap: 12px;
margin: 0 0 10px;
}
h2 svg { flex: none; }
h3 { font-size: 16.5px; margin: 34px 0 8px; }
p { margin: 0 0 16px; }
.lede { color: var(--ink-soft); margin-bottom: 22px; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 20px 22px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.term { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; }
.term b { display: block; font-size: 14.5px; margin-bottom: 4px; }
.term p { font-size: 13.5px; color: var(--ink-soft); margin: 0; line-height: 1.8; }
.callout { border: 1px solid var(--line); border-left: 5px solid var(--coin); background: var(--coin-bg); border-radius: 8px; padding: 14px 18px; margin: 18px 0; }
.callout.sky { border-left-color: var(--sky); background: var(--sky-bg); }
.callout.pipe { border-left-color: var(--pipe); background: var(--pipe-bg); }
.callout.grape { border-left-color: var(--grape); background: var(--grape-bg); }
.callout b { font-size: 14.5px; }
.callout p { margin: 4px 0 0; font-size: 14px; }
pre.mermaid {
background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
padding: 18px 12px; margin: 20px 0;
display: flex; overflow-x: auto;
font-family: var(--font-code); font-size: 13px; color: var(--ink-soft);
}
/* 中央寄せは justify-content ではなく margin:auto で行う。
justify-content:center は図が箱より広いとき左側のはみ出しがスクロールで
到達できなくなり、図の左半分が永久に見えなくなる。 */
pre.mermaid > svg { margin: auto; max-width: 100%; height: auto; }
pre.code {
font-family: var(--font-code); font-size: 13px; line-height: 1.75;
background: var(--night); color: #EDE7F6;
border-radius: 10px; padding: 18px 22px; overflow-x: auto; margin: 18px 0;
}
pre.code .cm { color: #9A8FC0; }
pre.code .kw { color: #F0B429; }
pre.code .st { color: #7FD0A0; }
code.inline { font-family: var(--font-code); font-size: .88em; background: #EEE7D8; border-radius: 5px; padding: 1px 7px; }
.tablescroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; margin: 18px 0; }
th { background: #F0EAD9; text-align: left; font-weight: 700; }
th, td { padding: 9px 14px; border-bottom: 1px solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: none; }
td.num { font-family: var(--font-code); text-align: right; white-space: nowrap; }
tr.gh td { font-family: var(--font-display); font-size: 12.5px; letter-spacing: .12em; padding: 7px 14px; }
kbd { font-family: var(--font-code); font-size: 12.5px; background: var(--panel); border: 1px solid var(--line-strong); border-bottom-width: 3px; border-radius: 6px; padding: 1px 9px; white-space: nowrap; }
footer { border-top: 3px solid var(--line-strong); margin-top: 30px; padding: 28px 24px 60px; }
footer .inner { max-width: 1060px; margin: 0 auto; font-size: 13.5px; color: var(--ink-soft); }
figure.fig { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 20px 14px 12px; margin: 20px 0; }
figure.fig figcaption { text-align: center; font-size: 13px; color: var(--ink-soft); margin-top: 10px; line-height: 1.75; padding: 0 12px; }
@media (max-width: 920px) {
/* minmax(0, 1fr) + nav.toc の min-width:0 が必須。
素の 1fr は「中身の min-content 以上」を暗黙の下限に持つため、横並びになった
目次リンクの合計幅(約1900px)まで列が広がり、同じ列にいる main もろとも
画面幅を突き破って本文が切れる。 */
.layout { grid-template-columns: minmax(0, 1fr); gap: 26px; padding-top: 20px; }
nav.toc { position: sticky; top: 0; z-index: 10; min-width: 0; background: var(--bg); border-bottom: 1px solid var(--line); padding: 10px 0; }
/* 横長の図を画面幅に収めると 13.5px の文字が 3〜4px まで潰れて読めなくなる。
モバイルでは縮小せず原寸のまま、箱の中を横スクロールさせて読ませる。 */
pre.mermaid > svg { max-width: none; flex: none; }
nav.toc .toc-title { display: none; }
nav.toc .links { display: flex; overflow-x: auto; gap: 4px; padding-bottom: 4px; }
nav.toc a { white-space: nowrap; }
.hero-inner { grid-template-columns: minmax(0, 1fr); }
.hero-inner > *, .grid2 > *, .grid3 > * { min-width: 0; }
.tiles { grid-template-columns: repeat(4, 50px); }
.grid2, .grid3 { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
.tiles svg { animation: none; }
html { scroll-behavior: auto; }
}
</style>
</head>
<body>
<header class="hero">
<div class="hero-inner">
<div>
<a href="../index.html" class="backlink" style="display:inline-block; margin:0 0 12px; font-family:var(--font-display); font-size:12px; letter-spacing:.14em; color:inherit; opacity:.75">← デモ解説一覧へ戻る</a>
<p class="eyebrow">BABYLON LITE 公式デモを読む</p>
<h1>ピクセルの手ざわりを、<br>1フレームずつ分解する。</h1>
<p class="sub">WebGPU 専用の軽量 2D スプライトエンジン「Babylon Lite」に付属する、マリオ風横スクロールアクションのデモ。ジャンプの気持ちよさから、実行時に生成される溶岩や CRT の走査線まで、全 18 ファイルのしくみを初心者向けにやさしく解説します。</p>
<div class="stats">
<div class="stat"><b>18</b><span>ファイル</span></div>
<div class="stat"><b>約4,900</b><span>行のコード</span></div>
<div class="stat"><b>3</b><span>エリア(草原・洞窟・城)</span></div>
<div class="stat"><b>0</b><span>音声ファイル(合成)</span></div>
</div>
</div>
<div class="tiles" aria-hidden="true">
<!-- ?ブロック -->
<svg viewBox="0 0 100 100" width="66"><rect x="6" y="6" width="88" height="88" rx="7" fill="#EDAF23" stroke="#B07713" stroke-width="4"/><rect x="14" y="14" width="72" height="72" rx="4" fill="none" stroke="#F6D06A" stroke-width="3"/><g fill="#8A5A0E"><rect x="18" y="18" width="8" height="8"/><rect x="74" y="18" width="8" height="8"/><rect x="18" y="74" width="8" height="8"/><rect x="74" y="74" width="8" height="8"/></g><text x="50" y="68" fill="#8A5A0E" font-family="'DotGothic16',sans-serif" font-size="52" text-anchor="middle">?</text></svg>
<!-- レンガ -->
<svg viewBox="0 0 100 100" width="66"><rect x="6" y="6" width="88" height="88" rx="6" fill="#C85A34" stroke="#98401F" stroke-width="4"/><g stroke="#98401F" stroke-width="3"><line x1="6" y1="31" x2="94" y2="31"/><line x1="6" y1="56" x2="94" y2="56"/><line x1="6" y1="81" x2="94" y2="81"/><line x1="50" y1="6" x2="50" y2="31"/><line x1="28" y1="31" x2="28" y2="56"/><line x1="72" y1="31" x2="72" y2="56"/><line x1="50" y1="56" x2="50" y2="81"/></g></svg>
<!-- キノコ -->
<svg viewBox="0 0 100 100" width="66"><path d="M14,58 A36,36 0 0 1 86,58 Z" fill="#D8402F" stroke="#9B2C1E" stroke-width="4"/><circle cx="34" cy="44" r="8" fill="#FBEadf"/><circle cx="66" cy="44" r="8" fill="#FBEadf"/><rect x="34" y="58" width="32" height="30" rx="5" fill="#F1E2C4" stroke="#9B2C1E" stroke-width="4"/><circle cx="44" cy="72" r="3.4" fill="#2B2536"/><circle cx="56" cy="72" r="3.4" fill="#2B2536"/></svg>
<!-- スター -->
<svg viewBox="0 0 100 100" width="66"><polygon points="50,8 62,38 94,38 68,58 78,90 50,70 22,90 32,58 6,38 38,38" fill="#EDAF23" stroke="#B07713" stroke-width="4" stroke-linejoin="round"/><circle cx="42" cy="52" r="3.4" fill="#2B2536"/><circle cx="58" cy="52" r="3.4" fill="#2B2536"/></svg>
</div>
</div>
</header>
<div class="layout">
<nav class="toc" aria-label="目次">
<p class="toc-title">もくじ</p>
<div class="links">
<a href="#intro"><span class="chip" style="background:var(--sky)"></span>このデモとは</a>
<a href="#play"><span class="chip" style="background:var(--coin)"></span>あそびかた</a>
<a href="#glossary"><span class="chip" style="background:var(--grape)"></span>用語ミニ辞典</a>
<a href="#arch"><span class="chip" style="background:var(--night)"></span>全体の地図</a>
<a href="#boot"><span class="chip" style="background:var(--pipe)"></span>起動の流れ</a>
<a href="#level"><span class="chip" style="background:var(--brick)"></span>レベルのつくりかた</a>
<a href="#physics"><span class="chip" style="background:var(--sky)"></span>動きの物理</a>
<a href="#feel"><span class="chip" style="background:var(--coin)"></span>ジャンプの手ざわり</a>
<a href="#loop"><span class="chip" style="background:var(--night)"></span>毎フレームの仕事</a>
<a href="#enemies"><span class="chip" style="background:var(--pipe)"></span>敵とパワーアップ</a>
<a href="#sprites"><span class="chip" style="background:var(--brick)"></span>スプライトと背景</a>
<a href="#procedural"><span class="chip" style="background:var(--lava)"></span>描かずに作るエフェクト</a>
<a href="#crt"><span class="chip" style="background:var(--grape)"></span>CRTブラウン管</a>
<a href="#audio"><span class="chip" style="background:var(--sky)"></span>音を合成する</a>
<a href="#themes"><span class="chip" style="background:var(--coin)"></span>設計テーマと歴史書</a>
<a href="#files"><span class="chip" style="background:var(--night)"></span>ファイル一覧</a>
</div>
</nav>
<main>
<section id="intro">
<p class="ch">第1章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#EDAF23" stroke="#B07713" stroke-width="5"/><text x="50" y="70" fill="#8A5A0E" font-family="'DotGothic16',sans-serif" font-size="56" text-anchor="middle">?</text></svg>このデモとは</h2>
<p class="lede">「マリオみたいなゲームって、どう作られているの?」に、動くコードで答えてくれるサンプルです。</p>
<p>このデモは、Babylon.js ファミリーの新しい WebGPU 専用ランタイム <b>Babylon Lite</b> の公式リポジトリに含まれる、横スクロールアクションのサンプルです。3D は一切使わず、<b>2D スプライト(絵札)を並べるだけ</b>の描画で、草原→洞窟→城の3エリアを走り抜けます。ブロックを叩き、スライムを踏み、キノコで大きくなり、スターで無敵になり、最後は城のボスと戦います。画面全体には昔のテレビ(ブラウン管)の走査線がのっていて、レトロゲームの空気そのものです。</p>
<p>プログラムとしての見どころは、次の3点です。</p>
<div class="grid3">
<div class="term"><b>🎮 手ざわり優先の物理</b><p>ジャンプの高さはボタンを押した長さで変わり、崖から落ちた直後や着地の直前でもジャンプが「効く」よう、わざと甘さを仕込んでいます。</p></div>
<div class="term"><b>✨ 描かずに作る絵</b><p>溶岩・松明の光・炎・火花・CRT は画像を用意せず、時間と座標だけからシェーダで生成。スプライトは CC0 のフリー素材です。</p></div>
<div class="term"><b>🚪 エリアを積み替える</b><p>巨大な1枚マップを持たず、エリアごとにタイルと敵をまるごと入れ替え。土管に入ると次のエリアへワープします。</p></div>
</div>
</section>
<section id="play">
<p class="ch">第2章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><path d="M18,58 A32,32 0 0 1 82,58 Z" fill="#D8402F" stroke="#9B2C1E" stroke-width="5"/><rect x="36" y="58" width="28" height="28" rx="4" fill="#F1E2C4" stroke="#9B2C1E" stroke-width="5"/></svg>あそびかた</h2>
<p class="lede">コードを読む前に、まず遊んでみるのがいちばんの近道です。キーボードでもタッチでも遊べます。</p>
<div class="callout sky"><b>🎮 ブラウザで遊ぶ</b><p><a href="https://babylonjs.com/lite-demos/demo-platformer.html" target="_blank" rel="noopener">babylonjs.com のライブデモを開く →</a>(WebGPU 対応ブラウザが必要です)</p></div>
<div class="tablescroll">
<table>
<tr><th style="width:42%">操作</th><th>すること</th></tr>
<tr><td><kbd>←</kbd> <kbd>→</kbd> / <kbd>A</kbd> <kbd>D</kbd></td><td>歩く・走る(後述のダッシュと合わせて加速)</td></tr>
<tr><td><kbd>Space</kbd> / <kbd>Z</kbd> / <kbd>↑</kbd> / <kbd>W</kbd> / <kbd>K</kbd></td><td>ジャンプ(長く押すほど高く跳ぶ)</td></tr>
<tr><td><kbd>Shift</kbd> / <kbd>X</kbd> / <kbd>J</kbd></td><td>ダッシュ。ファイアフラワー取得中はこれで火の玉を撃つ</td></tr>
<tr><td><kbd>↓</kbd> / <kbd>S</kbd></td><td>しゃがむ/土管の上で下入力するとワープ</td></tr>
<tr><td><kbd>Enter</kbd> / <kbd>R</kbd></td><td>スタート/やられたあとのリスタート</td></tr>
<tr><td>タッチ操作</td><td>画面に十字キーと A(ジャンプ)・B(ダッシュ)ボタンが出る。マルチタッチ対応</td></tr>
</table>
</div>
<div class="callout sky"><b>💡 気持ちよさの正体</b><p>「ジャンプを長押しすると高く跳ぶ」「走ってからジャンプすると遠くへ跳ぶ」——この2つは第7章・第8章で分解します。触っているうちに体が覚える“手ざわり”が、実は数個の数字でチューニングされています。</p></div>
</section>
<section id="glossary">
<p class="ch">第3章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><polygon points="50,10 61,40 92,40 67,59 76,90 50,71 24,90 33,59 8,40 39,40" fill="#7E5AC8" stroke="#533A93" stroke-width="5" stroke-linejoin="round"/></svg>用語ミニ辞典</h2>
<p class="lede">この先の章で出てくることばを先にまとめて紹介します。分からなくなったらここに戻ってきてください。</p>
<div class="grid2">
<div class="term"><b>スプライト</b><p>2Dの「絵札」。この世界は3Dの立体を使わず、四角い絵札を画面に何百枚も並べて描きます。地面も敵も背景も、ぜんぶスプライトです。</p></div>
<div class="term"><b>テクスチャアトラス</b><p>たくさんの小さな絵を1枚の大きな画像にまとめたもの(シール台紙)。切り替えが減って描画が速くなります。このデモは付属の PNG シートを読み込みます。</p></div>
<div class="term"><b>タイル / タイルマップ</b><p>世界を格子状のマス(タイル)で表す方式。このデモのレベルは、記号を並べた文字の地図から組み立てられます。</p></div>
<div class="term"><b>AABB</b><p>「軸に平行な長方形」。当たり判定を回転しない四角形どうしの重なりで判定する、いちばん基本の方法です。</p></div>
<div class="term"><b>軸分離の衝突判定</b><p>横の動きと縦の動きを別々に処理して壁や床にめり込ませない工夫。まず X を動かして壁で止め、次に Y を動かして床で止めます。</p></div>
<div class="term"><b>コヨーテタイム</b><p>崖から足が離れた「直後」の短い時間だけ、まだジャンプを受け付ける甘さ。崖ギリギリの操作が快適になります。</p></div>
<div class="term"><b>ジャンプバッファ</b><p>着地する「少し前」に押したジャンプを覚えておき、着地した瞬間に発動させるしくみ。連続ジャンプがつっかえません。</p></div>
<div class="term"><b>可変ジャンプ</b><p>ボタンを押している間だけ重力を弱めることで、長押し=高く・ちょい押し=低く、を実現するテクニック。</p></div>
<div class="term"><b>パララックス</b><p>遠くの背景はゆっくり、近くの背景は速く流すことで奥行きを感じさせる手法。空・雲・遠い丘・近い丘の4層で表現します。</p></div>
<div class="term"><b>シェーダ / WGSL</b><p>GPU の上で動く小さなプログラム。WGSL は WebGPU 用の言語で、溶岩・CRT・スターの虹色など「時間と座標から絵を作る」処理を担います。</p></div>
<div class="term"><b>ゲームジュース</b><p>火花・スコア表示・破片など、あってもなくても遊べるけれど“気持ちよさ”を足す演出のこと。英語で juice(ジュース)。</p></div>
<div class="term"><b>ルックアヘッド・スケジューラ</b><p>音楽を正確なリズムで鳴らすため、少し先の音を前もって予約していく方式。ブラウザのタイマーのブレを避けられます。</p></div>
</div>
</section>
<section id="arch">
<p class="ch">第4章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#2A2447" stroke="#161129" stroke-width="5"/><g stroke="#7E5AC8" stroke-width="5" stroke-linecap="round"><line x1="26" y1="34" x2="74" y2="34"/><line x1="26" y1="52" x2="74" y2="52"/><line x1="26" y1="70" x2="58" y2="70"/></g></svg>全体の地図</h2>
<p class="lede">エントリポイントの <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer.ts" target="_blank" rel="noopener"><code class="inline">platformer.ts</code></a> はほんの数十行。エンジンを起こして <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a> に「あとはよろしく」と渡すだけです。実装のほとんどは巨大な <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a> と、その周りの専門部品にあります。</p>
<pre class="mermaid">
flowchart TD
ENTRY["platformer.ts<br/>エンジン起動 → game へ委譲"]
GAME["game.ts(2,211行)<br/>状態機械・毎フレームの司令塔"]
subgraph DATA["データ層 ─ 世界の設計図"]
direction LR
LV["level<br/>文字の地図→タイル"]
ENT["entities<br/>敵・ブロック・効果の型"]
FR["frames / constants<br/>物理値・寸法・フレーム名"]
end
subgraph SYS["システム層 ─ 手ざわりと入出力"]
direction LR
PHY["physics<br/>AABB衝突"]
INP["input<br/>キー・タッチ・押下判定"]
AUD["audio<br/>WebAudio合成"]
HUD["hud<br/>DOMのスコア表示"]
end
subgraph FX["描画・エフェクト層 ─ 見た目"]
direction LR
ATL["atlas<br/>スプライト読込"]
PAR["parallax<br/>4層背景"]
PROC["lava / lantern / fire /<br/>portal / juice"]
CRT["crt<br/>ブラウン管仕上げ"]
end
ENTRY --> GAME
GAME --> DATA
GAME --> SYS
GAME --> FX
</pre>
<p>矢印は「上が下を利用する」依存の向きです。<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a> がすべてを束ねる中枢で、Minecraft デモが「薄い指揮者+部品」だったのとは対照的に、<b>厚い中枢+専門部品</b>という形をしています。横スクロールアクションは「毎フレーム、たくさんの物が同時に絡み合う」ため、状態機械(タイトル→レディ→プレイ中→クリア→…)を1か所にまとめる設計が読みやすい、という判断です。</p>
<div class="callout pipe"><b>🔍 読みはじめるなら</b><p>おすすめは、①<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/frames.ts" target="_blank" rel="noopener"><code class="inline">frames.ts</code></a>(物理定数 PHYS と寸法)→ ②<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/level.ts" target="_blank" rel="noopener"><code class="inline">level.ts</code></a>(文字の地図)→ ③<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/physics.ts" target="_blank" rel="noopener"><code class="inline">physics.ts</code></a>(当たり判定)→ ④<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a> のプレイ中ループ。小さな部品を先に押さえると、巨大な game.ts が「知ってる部品の組み合わせ」に見えてきます。</p></div>
</section>
<section id="boot">
<p class="ch">第5章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="20" y="10" width="60" height="80" rx="8" fill="#46A64B" stroke="#2B7A31" stroke-width="5"/><rect x="12" y="10" width="76" height="22" rx="6" fill="#46A64B" stroke="#2B7A31" stroke-width="5"/></svg>起動の流れ</h2>
<p class="lede">ページを開いてから世界が動きだすまで、プログラムは短いステップを順番に進みます。エントリの <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer.ts" target="_blank" rel="noopener"><code class="inline">platformer.ts</code></a> の仕事はここだけです。</p>
<pre class="mermaid">
flowchart TD
A["① canvas を取得"] --> B["② 読み込み進捗を表示<br/>スプライトシートの取得量を見積もる"]
B --> C["③ エンジン生成<br/>Babylon Lite の2Dスプライト描画"]
C --> D["④ startGame → 世界を用意<br/>スプライトシート読込・レベルcompile・部品生成"]
D --> E["⑤ フレームループ開始!"]
E -.失敗したら.-> X["canvas に赤いエラー画面<br/>等幅フォントで原因を表示"]
</pre>
<p>エントリポイントは <code class="inline">try / catch</code> で全体を包み、もし起動に失敗したら、真っ赤なオーバーレイを canvas に直接描いて原因を等幅フォントで表示します。WebGPU 非対応など「そもそも動かない環境」でも、真っ白な画面ではなく理由が出るようにという配慮です。</p>
<div class="callout"><b>🧱 スプライトは“同梱”</b><p>Minecraft デモがテクスチャを実行時に fetch していたのに対し、このデモは CC0 のスプライトシートをリポジトリに同梱しています。ネットワークに出ずに済むぶん、起動が安定します。読み込み進捗バーは、そのシート取得の目安(約100万バイト)を見せています。</p></div>
</section>
<section id="level">
<p class="ch">第6章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="6" fill="#C85A34" stroke="#98401F" stroke-width="5"/><g stroke="#98401F" stroke-width="4"><line x1="6" y1="35" x2="94" y2="35"/><line x1="6" y1="65" x2="94" y2="65"/><line x1="50" y1="6" x2="50" y2="35"/><line x1="28" y1="35" x2="28" y2="65"/><line x1="72" y1="35" x2="72" y2="65"/></g></svg>レベルのつくりかた</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/level.ts" target="_blank" rel="noopener"><code class="inline">level.ts</code></a>。レベルは、記号を並べた<b>文字の地図(ASCIIタイルマップ)</b>として書かれています。人間が目で見て編集でき、コードが読み取ってタイルや敵に変換します。</p>
<pre class="code"><span class="cm">// 高さは全エリア共通で 14 行(ROWS = 14)。横幅はエリアごとに違う</span>
<span class="cm">// 一例(イメージ):</span>
"........................"
"...?..BB?B.............."
"..........o.o.o........."
"......=====....g...n...."
"###############..####..F"</pre>
<p>1文字が1マス(タイル)に対応します。<code class="inline">compileGrid()</code> がこの地図を1文字ずつ走査し、床の当たり判定・地形スプライト・叩けるブロック・コイン・敵・ハザードへ振り分けます。地面タイルには、隣を見て「左端/右端/中央」の絵を自動で選ぶ処理(<code class="inline">groundFrame()</code>)が入っていて、継ぎ目が自然につながります。</p>
<h3>記号の凡例</h3>
<div class="tablescroll">
<table>
<tr><th style="width:16%">記号</th><th>意味</th></tr>
<tr><td class="num"><code class="inline">#</code></td><td>地面(草・土)</td></tr>
<tr><td class="num"><code class="inline">=</code></td><td>浮き足場(石の箱)</td></tr>
<tr><td class="num"><code class="inline">B</code></td><td>レンガブロック(叩くと壊れる)</td></tr>
<tr><td class="num"><code class="inline">?</code></td><td>?ブロック(コイン入り)</td></tr>
<tr><td class="num"><code class="inline">M / S</code></td><td>キノコ入り/スター入りブロック</td></tr>
<tr><td class="num"><code class="inline">o</code></td><td>コイン</td></tr>
<tr><td class="num"><code class="inline">g / n / f</code></td><td>スライム/カタツムリ/飛ぶ敵</td></tr>
<tr><td class="num"><code class="inline">^</code></td><td>トゲ(ハザード)</td></tr>
<tr><td class="num"><code class="inline">p / F</code></td><td>プレイヤー開始位置/ゴール旗</td></tr>
</table>
</div>
<h3>1枚マップではなく「3つのエリア」</h3>
<p>世界はひとつの巨大マップではなく、独立した3つのエリアに分かれています。土管に入ると、いまのエリアのタイル・地形・敵をまるごと片づけ、次のエリアのデータで詰め直します。共有の巨大格子を持たないので、切り替えのコストが軽いのが利点です。</p>
<div class="tablescroll">
<table>
<tr><th>エリア</th><th style="width:18%">横幅</th><th>ふんいき</th></tr>
<tr><td>1-1 オーバーワールド</td><td class="num">124 列</td><td>青空と丘の草原。基本の操作を覚えるステージ</td></tr>
<tr><td>1-2 ケイブ</td><td class="num">57 列</td><td>暗い洞窟。ランタンの光と溶岩</td></tr>
<tr><td>1-3 キャッスル</td><td class="num">30 列</td><td>城。溶岩地帯とボス戦</td></tr>
</table>
</div>
</section>
<section id="physics">
<p class="ch">第7章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><circle cx="50" cy="50" r="42" fill="#3E8FD0" stroke="#245D95" stroke-width="5"/><path d="M50,20 Q30,40 50,54 Q70,68 50,80" fill="none" stroke="#E9F2FB" stroke-width="6" stroke-linecap="round"/></svg>動きの物理</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/physics.ts" target="_blank" rel="noopener"><code class="inline">physics.ts</code></a>(当たり判定)と <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/frames.ts" target="_blank" rel="noopener"><code class="inline">frames.ts</code></a> の <code class="inline">PHYS</code>(数字のつまみ)。マリオらしい走りと跳びは、たった15個ほどの数字で決まっています。</p>
<h3>プレイヤーを動かす2段構え</h3>
<p>毎フレーム、まず入力から横方向の加速・摩擦を計算し、重力で縦の速度を足します。そして <code class="inline">moveAndCollide()</code> が、その速度で四角い当たり箱を動かしながら固体タイルにぶつけて止めます。ポイントは<b>横(X)と縦(Y)を別々に処理する「軸分離」</b>。まず横に動かして壁で止め、次に縦に動かして床・天井で止めるので、角にめり込みません。落ちる床のような<b>一方通行の足場</b>もここで扱い、「上から乗るときだけ」ぶつかるよう判定します。</p>
<h3>PHYS ── 手ざわりのつまみ一覧</h3>
<div class="tablescroll">
<table>
<tr><th>つまみ</th><th style="width:20%">値</th><th>意味</th></tr>
<tr><td>gravity</td><td class="num">2600</td><td>ふだんの重力(下向きの加速)</td></tr>
<tr><td>jumpHoldGravity</td><td class="num">1300</td><td>ジャンプ長押し中の弱い重力(=高く跳ぶ)</td></tr>
<tr><td>jumpSpeed</td><td class="num">884</td><td>ジャンプの初速</td></tr>
<tr><td>maxFall</td><td class="num">1200</td><td>落下速度の上限(速すぎてすり抜けない)</td></tr>
<tr><td>maxWalk / maxRun</td><td class="num">360 / 560</td><td>歩き/ダッシュの最高速</td></tr>
<tr><td>walkAccel / runAccel</td><td class="num">2600 / 3200</td><td>歩き/ダッシュの加速の強さ</td></tr>
<tr><td>airAccel</td><td class="num">1600</td><td>空中での方向転換のしにくさ</td></tr>
<tr><td>groundFriction</td><td class="num">2400</td><td>地上の摩擦(離すと止まる速さ)</td></tr>
<tr><td>stompBounce</td><td class="num">760</td><td>敵を踏んだときの跳ね返り</td></tr>
<tr><td>coyote</td><td class="num">0.09</td><td>崖を離れてもジャンプできる猶予(秒)</td></tr>
<tr><td>jumpBuffer</td><td class="num">0.11</td><td>着地前のジャンプ入力を覚える時間(秒)</td></tr>
<tr><td>enemySpeed / shellSpeed</td><td class="num">95 / 620</td><td>敵の歩く速さ/蹴った甲羅の速さ</td></tr>
</table>
</div>
<div class="callout sky"><b>📏 TILE = 70 という単位</b><p>寸法の基準 <code class="inline">TILE</code> は 70。これは画面のピクセルではなく<b>ワールド座標の単位</b>です。レベルの高さ(14行 × 70 = 980)が画面の高さにちょうど収まるよう、描画のときに一括で拡大・縮小します。だから、どんな画面サイズでも「見える範囲のマス数」は変わりません。</p></div>
</section>
<section id="feel">
<p class="ch">第8章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#EDAF23" stroke="#B07713" stroke-width="5"/><path d="M30,66 L30,42 L50,30 L70,42 L70,66" fill="none" stroke="#8A5A0E" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/></svg>ジャンプの手ざわり</h2>
<p class="lede">このデモがいちばん丁寧に作り込んでいるのが「ジャンプの気持ちよさ」です。正体は3つの小さな甘さ——<b>可変ジャンプ・コヨーテタイム・ジャンプバッファ</b>。名作アクションはたいてい、これを仕込んでいます。</p>
<h3>① 可変ジャンプ ── 押した長さで高さが変わる</h3>
<p>ジャンプ中、<b>ボタンを押している間だけ重力を弱め</b>(2600 → 1300)、離すと通常の重力に戻します。長く押せば長く浮いて高く、ちょい押しなら低く跳ねる。1本のボタンで高さを無段階に操れる、シンプルで強力な仕掛けです。</p>
<h3>② コヨーテ & ③ バッファ ── 「効いてほしい」を先読みする</h3>
<p>人間は崖の端でジャンプすると、たいてい少し遅れてボタンを押します。また着地の直前に次のジャンプを押しがちです。厳密に判定すると、そのどちらも「不発」になってストレスになります。そこで:</p>
<figure class="fig">
<svg viewBox="0 0 640 300" width="640" style="max-width:100%;height:auto;display:block;margin:0 auto" role="img" aria-label="コヨーテタイムとジャンプバッファの時間軸" font-family="'Zen Kaku Gothic New',sans-serif" font-size="12.5">
<!-- コヨーテ -->
<text x="20" y="34" fill="#2B2536" font-size="13" font-weight="700">① コヨーテタイム(0.09秒)</text>
<line x1="20" y1="70" x2="620" y2="70" stroke="#D3C9B2" stroke-width="2"/>
<!-- ground block -->
<rect x="20" y="70" width="220" height="40" fill="#C85A34" stroke="#98401F" stroke-width="1.5"/>
<text x="130" y="96" fill="#FBEadf" font-size="12" text-anchor="middle">地面</text>
<!-- coyote window -->
<rect x="240" y="46" width="86" height="24" fill="#FCE39A" stroke="#B07713" stroke-width="1.5"/>
<text x="283" y="63" fill="#8A5A0E" font-size="11.5" text-anchor="middle">まだ跳べる</text>
<!-- player marker leaving ledge -->
<circle cx="240" cy="58" r="9" fill="#3E8FD0" stroke="#245D95" stroke-width="2"/>
<text x="240" y="132" fill="#675F72" font-size="11.5" text-anchor="middle">崖を離れた瞬間</text>
<path d="M326,58 L360,58" stroke="#98401F" stroke-width="2" marker-end="url(#ar)"/>
<text x="470" y="63" fill="#675F72" font-size="12" text-anchor="middle">この後に押すと不発(落下)</text>
<!-- バッファ -->
<text x="20" y="196" fill="#2B2536" font-size="13" font-weight="700">② ジャンプバッファ(0.11秒)</text>
<line x1="20" y1="248" x2="620" y2="248" stroke="#D3C9B2" stroke-width="2"/>
<rect x="360" y="248" width="260" height="34" fill="#C85A34" stroke="#98401F" stroke-width="1.5"/>
<text x="490" y="270" fill="#FBEadf" font-size="12" text-anchor="middle">着地する床</text>
<!-- buffer window before landing -->
<rect x="266" y="226" width="94" height="22" fill="#E9F2FB" stroke="#245D95" stroke-width="1.5"/>
<text x="313" y="242" fill="#245D95" font-size="11.5" text-anchor="middle">押下を記憶</text>
<text x="230" y="222" fill="#675F72" font-size="11.5" text-anchor="end">早すぎる入力</text>
<circle cx="266" cy="237" r="8" fill="#EDAF23" stroke="#B07713" stroke-width="2"/>
<path d="M360,237 L360,248" stroke="#245D95" stroke-width="0"/>
<circle cx="360" cy="248" r="9" fill="#46A64B" stroke="#2B7A31" stroke-width="2"/>
<text x="360" y="298" fill="#2B7A31" font-size="11.5" text-anchor="middle">着地=その場で発動</text>
<defs><marker id="ar" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#98401F"/></marker></defs>
</svg>
<figcaption>コヨーテは「崖を離れた後」の猶予、バッファは「着地する前」の記憶。前後どちらのズレも吸収するので、操作が指に吸いつく。</figcaption>
</figure>
<p>ちなみに <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/input.ts" target="_blank" rel="noopener"><code class="inline">input.ts</code></a> 自体は「押した瞬間」を1回だけ拾うシンプルな押下検出(<code class="inline">jumpPressed</code>)に徹していて、コヨーテとバッファの<b>時間の管理は game.ts のプレイ中ループが担当</b>します。役割がきれいに分かれているのも読みどころです。</p>
</section>
<section id="loop">
<p class="ch">第9章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#2A2447" stroke="#161129" stroke-width="5"/><path d="M32,50 A18,18 0 1 1 50,68" fill="none" stroke="#EDAF23" stroke-width="6" stroke-linecap="round"/><path d="M50,60 L50,72 L62,72" fill="none" stroke="#EDAF23" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/></svg>毎フレームの仕事</h2>
<p class="lede">ゲームは「状態機械」です。いまがタイトルか、レディか、プレイ中か、クリア演出か——状態ごとに、その1フレームでやることが切り替わります。</p>
<pre class="mermaid">
flowchart TD
J["ジュース更新(火花・スコア・破片)<br/>状態に関係なく毎フレーム"] --> SW{"いまの状態は?"}
SW -->|タイトル| T["敵を眺めさせる・カメラを流す"]
SW -->|レディ| R["カウントダウン"]
SW -->|クリア/やられ/勝利| A["各種アニメーション"]
SW -->|プレイ中| P["フル・シミュレーション"]
P --> RN["描画:カメラ→背景→全スプライト配置"]
RN --> C["CRTポスト処理へ受け渡し"]
T --> RN
R --> RN
A --> RN
</pre>
<h3>「プレイ中」1フレームの中身</h3>
<p>プレイ中はこの順で処理が流れます。順番そのものがバグを防ぐ設計です(例:先に動く床を動かしてから、プレイヤーを床に乗せる)。</p>
<pre class="code"><span class="cm">// game.ts プレイ中の更新順(要約)</span>
タイマー更新(無敵・スター・ワープ・残り時間)
動く床
発射クールダウン・火の玉ショット
横入力(加速・摩擦)
ジャンプ(<span class="kw">コヨーテ+バッファ</span>)
重力・終端速度
<span class="kw">moveAndCollide</span>(当たり判定)
動く床に乗る
落下死・溶岩死の判定
土管ワープ判定
敵の更新と接触
ボスと弾
パワーアップの物理
火の玉の更新
コイン回収
ゴール旗の判定</pre>
<div class="callout"><b>🏁 スコアのしくみ</b><p>コイン200点、ブロック破壊50点。敵を連続で踏むと 100→200→400→800→…→8000 とスコアが伸び、8体目で 1UP。コインを100枚集めても 1UP。クリア時は残り時間 × 50 点、城クリアはさらに 5000 点ボーナス——このあたりの数字も、往年の名作へのオマージュになっています。</p></div>
</section>
<section id="enemies">
<p class="ch">第10章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><path d="M18,74 Q18,40 50,40 Q82,40 82,74 Z" fill="#46A64B" stroke="#2B7A31" stroke-width="5"/><circle cx="38" cy="60" r="6" fill="#fff" stroke="#2B7A31" stroke-width="2"/><circle cx="62" cy="60" r="6" fill="#fff" stroke="#2B7A31" stroke-width="2"/></svg>敵とパワーアップ</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/entities.ts" target="_blank" rel="noopener"><code class="inline">entities.ts</code></a>(型の定義)と <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a>(振る舞い)。敵もアイテムも、生成・破棄を繰り返さず<b>あらかじめ確保した“枠”を使い回す</b>(プーリング)ので、動作が安定します。</p>
<div class="tablescroll">
<table>
<tr><th style="width:26%">敵</th><th>倒しかた・性質</th></tr>
<tr><td>スライム(g)</td><td>基本の敵。上から踏むと倒せる</td></tr>
<tr><td>カタツムリ(n)</td><td>踏むと甲羅に。甲羅を蹴ると滑って他の敵を巻き込む。しばらくは自分に当たらない猶予つき</td></tr>
<tr><td>飛ぶ敵(f)</td><td>横に流れながら上下に波打つ(振幅・周期が定数で調整)</td></tr>
<tr><td>ピラニア</td><td>土管から一定周期で顔を出す(3.4秒サイクル)</td></tr>
<tr><td>ボス(城)</td><td>大きなクモ。HP3・無敵点滅・弾を最大4発。踏むかファイアで削る</td></tr>
</table>
</div>
<h3>パワーアップ</h3>
<div class="grid3">
<div class="term"><b>🍄 キノコ</b><p>体が大きくなる(当たり判定も背が高くなる)。ダメージを受けると小さい状態に戻る。</p></div>
<div class="term"><b>⭐ スター</b><p>一定時間、無敵。虹色にパレットが循環し、残像トレイル(6枚・3フレーム間隔)が尾を引く。シェーダで実装。</p></div>
<div class="term"><b>🔥 ファイアフラワー</b><p>火の玉を撃てるように。跳ねながら飛び、敵を貫く。同時に出せるのは2発まで、発射間隔0.28秒。</p></div>
</div>
<p><code class="inline">?</code> ブロックとレンガは叩くと下から突き上がるアニメがつき、レンガは大きい状態のプレイヤーが叩くと砕けて回転する破片(デブリ)を撒きます。破片・火花・スコア表示はすべて固定数の“プール”から貸し出され、寿命が来たら <code class="inline">visible=false</code> にして枠へ返します。</p>
</section>
<section id="sprites">
<p class="ch">第11章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="10" width="88" height="80" rx="6" fill="#3E8FD0" stroke="#245D95" stroke-width="5"/><path d="M6,72 Q30,52 54,66 Q78,80 94,60 L94,90 L6,90 Z" fill="#46A64B" stroke="#2B7A31" stroke-width="3"/><circle cx="72" cy="30" r="10" fill="#FCE39A"/></svg>スプライトと背景</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/atlas.ts" target="_blank" rel="noopener"><code class="inline">atlas.ts</code></a>(絵札の読込)と <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/parallax.ts" target="_blank" rel="noopener"><code class="inline">parallax.ts</code></a>(奥行きのある背景)。ここだけは画像素材(CC0 の Kenney 製)を使います。</p>
<h3>絵札を1枚のシートから切り出す</h3>
<p>スプライトは Kenney の「Platformer Art Deluxe」を使います。この素材は絵がびっしり<b>すき間なく詰め込まれた</b>シート(TexturePacker 形式)なので、格子で機械的に切るのではなく、付属の XML を自分で読んで「この矩形が coinGold」「ここが alienBeige_walk1」と1枚ずつ位置を拾い出します。詰め込みの境目でにじまないよう、UV座標もていねいに調整しています。</p>
<h3>4層のパララックス背景</h3>
<figure class="fig">
<svg viewBox="0 0 640 240" width="640" style="max-width:100%;height:auto;display:block;margin:0 auto" role="img" aria-label="4層パララックス背景の速度差" font-family="'Zen Kaku Gothic New',sans-serif" font-size="12">
<rect x="40" y="20" width="560" height="40" fill="#E9F2FB" stroke="#D3C9B2" stroke-width="1"/>
<text x="52" y="45" fill="#245D95" font-size="12">空(グラデーション)</text>
<text x="588" y="45" fill="#675F72" font-size="12" text-anchor="end">×0(静止)</text>
<rect x="40" y="66" width="560" height="40" fill="#F3F8FD" stroke="#D3C9B2" stroke-width="1"/>
<ellipse cx="140" cy="86" rx="26" ry="10" fill="#fff" stroke="#C9D9E8"/><ellipse cx="330" cy="88" rx="20" ry="8" fill="#fff" stroke="#C9D9E8"/>
<text x="52" y="90" fill="#245D95" font-size="12">雲</text>
<text x="588" y="90" fill="#675F72" font-size="12" text-anchor="end">×0.06</text>
<rect x="40" y="112" width="560" height="40" fill="#EAF5EB" stroke="#D3C9B2" stroke-width="1"/>
<path d="M40,150 Q120,124 200,146 Q300,120 400,148 Q500,126 600,148 L600,152 L40,152 Z" fill="#BFE0C2"/>
<text x="52" y="136" fill="#2B7A31" font-size="12">遠い丘</text>
<text x="588" y="136" fill="#675F72" font-size="12" text-anchor="end">×0.16</text>
<rect x="40" y="158" width="560" height="52" fill="#E2F1E3" stroke="#D3C9B2" stroke-width="1"/>
<path d="M40,208 Q140,168 240,204 Q360,166 480,206 Q560,180 600,202 L600,210 L40,210 Z" fill="#7FC384"/>
<text x="52" y="182" fill="#2B7A31" font-size="12">近い丘</text>
<text x="588" y="182" fill="#675F72" font-size="12" text-anchor="end">×0.3(最速)</text>
</svg>
<figcaption>カメラの移動量に係数を掛けて各層をずらす。遠い層ほど係数が小さく、ゆっくり動くので奥行きが生まれる。丘は三角関数の重ね合わせ、雲は決定論的な乱数(mulberry32)で配置。</figcaption>
</figure>
<p>背景は<b>タイルを並べるのではなく</b>、新機能の <code class="inline">uvScroll</code>(テクスチャの表示位置をずらす機能)で「横に無限スクロールしても追加コストゼロ」を実現しています。丘の稜線は 2 つの正弦波を <code class="inline">0.65×a + 0.35×b</code> で混ぜたなめらかな波、雲は種 <code class="inline">0x51a7</code> の疑似乱数で毎回同じ位置に5つ配置し、端で途切れないよう巻き取りコピーを添えています。</p>
</section>
<section id="procedural">
<p class="ch">第12章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><path d="M50,12 C64,32 78,40 78,60 A28,28 0 1 1 22,60 C22,40 36,32 50,12 Z" fill="#E5602A" stroke="#AF3B12" stroke-width="5"/><path d="M50,44 C57,54 62,58 62,68 A12,12 0 1 1 38,68 C38,58 43,54 50,44 Z" fill="#FCE39A"/></svg>描かずに作るエフェクト</h2>
<p class="lede">このデモの美点は、多くの見た目を<b>画像に頼らずコードで生成</b>していること。時間 <code class="inline">t</code> と画面座標 <code class="inline">uv</code> だけを入力に、シェーダや手続きで“描いて”います。</p>
<div class="grid2">
<div class="term"><b>🌋 溶岩(lava)</b><p>テクスチャを一切使わず、断片シェーダだけで生成。正弦波を重ねた流れ、ぶくぶくした表面のシルエット、立ち上る火の粉、陽炎のゆらぎを、時間と座標から計算する。</p></div>
<div class="term"><b>🏮 ランタン(lantern)</b><p>洞窟の暗さは、画面全体を暗くする「乗算の黒い板」に、プレイヤーの周りだけ丸くくり抜いた光だまりを重ねて表現。壁の松明は加算合成のゆらぐ光。</p></div>
<div class="term"><b>🔥 炎と火の玉(fire)</b><p>ファイアフラワーの絵も、ふわっと光る火の玉のテクスチャも実行時に生成。火の玉は加算合成なので、重なると明るくなる“弾らしさ”が出る。</p></div>
<div class="term"><b>✨ 火花・破片・スコア(juice)</b><p>コイン取得や踏みつけで弾ける金・白の火花、砕けたレンガの回転破片、ふわっと上がるスコア数字。どれも小さなプールから貸し出す使い回し。</p></div>
</div>
<div class="callout pipe"><b>🚪 土管ワープの演出(portal)</b><p>エリア切り替えの瞬間は、中央の円だけを残して外側を黒く塗るシェーダ(アイリス・ワイプ)でスッと閉じ、次のエリアで開きます。円の半径を <code class="inline">fx.params.x</code> で動かすだけの、こちらも“描かない”演出です。</p></div>
</section>
<section id="crt">
<p class="ch">第13章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="10" y="16" width="80" height="60" rx="10" fill="#2A2447" stroke="#161129" stroke-width="5"/><g stroke="#7E5AC8" stroke-width="2" opacity="0.8"><line x1="16" y1="28" x2="84" y2="28"/><line x1="16" y1="38" x2="84" y2="38"/><line x1="16" y1="48" x2="84" y2="48"/><line x1="16" y1="58" x2="84" y2="58"/><line x1="16" y1="68" x2="84" y2="68"/></g><rect x="38" y="76" width="24" height="10" fill="#161129"/></svg>CRTブラウン管</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/crt.ts" target="_blank" rel="noopener"><code class="inline">crt.ts</code></a>。最後にもう一段、画面全体を昔のブラウン管テレビのように加工して、レトロな空気をまとわせます。</p>
<p>やり方は「2段階レンダリング」です。まずゲーム本編を<b>いったん画面ではなく“オフスクリーンの絵”</b>(レンダーテクスチャ)に描き、次にその絵を CRT シェーダ越しにもう一度スプライトとして貼り直します。エンジンに新しく入った「オフスクリーン描画のフック」を最初に使った機能でもあります。</p>
<div class="tablescroll">
<table>
<tr><th>効果</th><th>内容</th></tr>
<tr><td>樽型のゆがみ</td><td>画面をほんの少しふくらませる(角は画面内に残る控えめさ)</td></tr>
<tr><td>色ずれ</td><td>赤と青を左右にわずかにずらす(色収差)</td></tr>
<tr><td>走査線</td><td>横方向のうっすらした縞。ゆっくり流れる</td></tr>
<tr><td>アパーチャマスク</td><td>蛍光体のピッチを思わせる縦の細かい模様</td></tr>
<tr><td>ブルーム・信号帯・粒子・ちらつき</td><td>にじむ光、流れる暗い帯、砂嵐、電源ハムのような微妙な明滅</td></tr>
</table>
</div>
<p>あえて<b>ヴィネット(四隅を暗く落とす処理)は入れていない</b>のがこだわりで、「画面のすみずみまで明るく」を選んでいます。効果の強さは <code class="inline">fx.params.x</code> で 0(素通し)〜1(全開)まで調整可能。この CRT は Aka 氏の MIT ライセンス「CRTFilter」を WGSL へ忠実に移植したものです。</p>
</section>
<section id="audio">
<p class="ch">第14章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><circle cx="50" cy="50" r="42" fill="#3E8FD0" stroke="#245D95" stroke-width="5"/><path d="M40,36 L40,64" stroke="#fff" stroke-width="6" stroke-linecap="round"/><path d="M54,30 L54,70" stroke="#fff" stroke-width="6" stroke-linecap="round"/><path d="M66,40 L66,60" stroke="#fff" stroke-width="6" stroke-linecap="round"/></svg>音を合成する</h2>
<p class="lede">担当:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/audio.ts" target="_blank" rel="noopener"><code class="inline">audio.ts</code></a>。効果音もBGMも<b>音声ファイルは1つもなく</b>、すべて WebAudio のオシレータ(発振器)とエンベロープでその場で合成します。まさにチップチューンです。</p>
<p>マスター音量(0.22)の下に、BGM専用のサブミックス(0.8)をぶら下げ、音楽が効果音の下にちゃんと敷かれるようにしています。効果音は、ジャンプ(420→760Hz の矩形波スイープ)、コイン、踏みつけ、ブロックの突き上げ、パワーアップ、やられ、1UP、クリアのジングル、ワープ、火の玉、ブロック破壊……と、往年の作品を思わせるラインナップです。</p>
<h3>ズレないリズムの秘密:ルックアヘッド</h3>
<p>BGMは「8分音符のグリッド」に音を並べた2曲——<b>オーバーワールド</b>(132 BPM、ハ長調の I–vi–IV–V)と<b>ケイブ</b>(104 BPM、イ短調でもの悲しい)——がループします。それぞれ主旋律・ベース・和音の分散が重なります。ここで <code class="inline">setInterval</code> にそのまま頼るとリズムがブレるので、<b>約30msごとに目を覚まし、約120ms先までの音をあらかじめ予約する</b>「ルックアヘッド・スケジューラ」で、正確な時刻に鳴らします。ブラウザのタイマーのブレを、先読みで吸収する定番のテクニックです。</p>
</section>
<section id="themes">
<p class="ch">第15章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#EDAF23" stroke="#B07713" stroke-width="5"/><path d="M28,52 L44,68 L74,34" fill="none" stroke="#8A5A0E" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/></svg>設計テーマと歴史書</h2>
<p class="lede">約4,900行を貫くのは、この3つの考え方です。自分のプロジェクトにもそのまま持ち帰れます。</p>
<div class="grid3">
<div class="term"><b>🎮 手ざわり駆動</b><p>可変ジャンプ・コヨーテ・バッファのように、「正しさ」より「気持ちよさ」を優先した小さな甘さを、数個の数字(PHYS)で集中管理。触り心地はコードで作れる。</p></div>
<div class="term"><b>✨ 実行時生成</b><p>溶岩・光・炎・火花・CRT・音は、画像や音声ファイルを持たず、時間と座標から生成。リポジトリを重くせず、パラメータで無段階に調整できる。</p></div>
<div class="term"><b>🚪 エリア積み替え</b><p>巨大な1枚マップを避け、エリアごとにデータをまるごと入れ替え。プールしたスプライトを片づけて詰め直すので、切り替えが軽くて破綻しない。</p></div>
</div>
<div class="callout"><b>📝 improvements.md = このデモの「歴史書」</b><p>フォルダには <code class="inline">improvements.md</code> という開発日誌が同梱されています。「スターの無敵をどう“まばゆく”したか」「12枚のタイル巻き取りから手続き生成のパララックスへ」「土管ワープでエリアを分ける発想」「洞窟の明かりと溶岩」「初のオフスクリーン描画=CRT」——完成形だけでなく、試行錯誤の経緯ごと読めるのが、このサンプル最大の教材価値です。Minecraft デモのコード内コメントと同じ“歴史書”文化が、ここにも息づいています。</p></div>
</section>
<section id="files">
<p class="ch">第16章</p>
<h2><svg viewBox="0 0 100 100" width="26" aria-hidden="true"><rect x="6" y="6" width="88" height="88" rx="7" fill="#2A2447" stroke="#161129" stroke-width="5"/><g stroke="#EDAF23" stroke-width="5" stroke-linecap="round"><line x1="26" y1="32" x2="74" y2="32"/><line x1="26" y1="50" x2="74" y2="50"/><line x1="26" y1="68" x2="60" y2="68"/></g></svg>ファイル一覧(全18ファイル)</h2>
<p class="lede">コードリーディングのおともに。行数は master ブランチのソース実測値です。<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a> だけで全体の約4割を占めます。</p>
<div class="tablescroll">
<table>
<tr><th>ファイル</th><th style="width:14%">行数</th><th>やくわり</th></tr>
<tr class="gh"><td colspan="3" style="background:var(--night); color:#EDE7F6">エントリ/中枢</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer.ts" target="_blank" rel="noopener"><code class="inline">platformer.ts</code></a></td><td class="num">44</td><td>エンジンを起こして game に委譲。エラー画面つきの薄い入口</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/game.ts" target="_blank" rel="noopener"><code class="inline">game.ts</code></a></td><td class="num">2,211</td><td>状態機械と毎フレームの司令塔。ゲームのルールはほぼここに集約</td></tr>
<tr class="gh"><td colspan="3" style="background:var(--brick-bg)">データ層</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/level.ts" target="_blank" rel="noopener"><code class="inline">level.ts</code></a></td><td class="num">554</td><td>文字の地図を3エリア分ぶんパースしてタイル・敵・地形に変換</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/entities.ts" target="_blank" rel="noopener"><code class="inline">entities.ts</code></a></td><td class="num">178</td><td>敵・ブロック・弾・火花・スコアなど、全エンティティの型</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/frames.ts" target="_blank" rel="noopener"><code class="inline">frames.ts</code></a></td><td class="num">65</td><td>物理定数 PHYS・TILE・スプライトのフレーム名・タイミング補助</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/constants.ts" target="_blank" rel="noopener"><code class="inline">constants.ts</code></a></td><td class="num">148</td><td>寸法・敵の挙動・パーティクル数などの調整値とスター用シェーダ</td></tr>
<tr class="gh"><td colspan="3" style="background:var(--sky-bg)">システム層</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/physics.ts" target="_blank" rel="noopener"><code class="inline">physics.ts</code></a></td><td class="num">111</td><td>軸分離のAABB衝突(moveAndCollide / overlaps)。一方通行の足場も</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/input.ts" target="_blank" rel="noopener"><code class="inline">input.ts</code></a></td><td class="num">178</td><td>キーボード+タッチ。押した瞬間の検出とフレーム消費</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/audio.ts" target="_blank" rel="noopener"><code class="inline">audio.ts</code></a></td><td class="num">323</td><td>WebAudioで効果音とBGMを合成。ルックアヘッド・スケジューラ</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/hud.ts" target="_blank" rel="noopener"><code class="inline">hud.ts</code></a></td><td class="num">151</td><td>スコア・コイン・時間・残機・ボスHP。DOMで実装(UI機能がないため)</td></tr>
<tr class="gh"><td colspan="3" style="background:var(--pipe-bg)">描画・エフェクト層</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/atlas.ts" target="_blank" rel="noopener"><code class="inline">atlas.ts</code></a></td><td class="num">163</td><td>TexturePacker形式のスプライトシートをXMLごと読み込む</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/parallax.ts" target="_blank" rel="noopener"><code class="inline">parallax.ts</code></a></td><td class="num">244</td><td>空・雲・遠い丘・近い丘の4層背景。uvScrollで無限スクロール</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/crt.ts" target="_blank" rel="noopener"><code class="inline">crt.ts</code></a></td><td class="num">224</td><td>ブラウン管シェーダ。走査線・色ずれ・樽ゆがみ(ヴィネット無し)</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/lava.ts" target="_blank" rel="noopener"><code class="inline">lava.ts</code></a></td><td class="num">92</td><td>溶岩。時間とUVだけの手続きシェーダ</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/portal.ts" target="_blank" rel="noopener"><code class="inline">portal.ts</code></a></td><td class="num">80</td><td>土管ワープとアイリス・ワイプの遷移演出</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/fire.ts" target="_blank" rel="noopener"><code class="inline">fire.ts</code></a></td><td class="num">91</td><td>ファイアフラワーと火の玉のテクスチャを実行時生成</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/lantern.ts" target="_blank" rel="noopener"><code class="inline">lantern.ts</code></a></td><td class="num">58</td><td>洞窟の光だまりと松明の加算グロー</td></tr>
<tr><td><a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/juice.ts" target="_blank" rel="noopener"><code class="inline">juice.ts</code></a></td><td class="num">49</td><td>火花パーティクル用のきらめきテクスチャ生成</td></tr>
</table>
</div>
<p style="font-size:13px;color:var(--ink-soft)">※ このほかに開発日誌 <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer/improvements.md" target="_blank" rel="noopener"><code class="inline">improvements.md</code></a> が同梱されています(コードではありません/381行)。行数は master ブランチ(2026年時点)のソース実測値です。</p>
</section>
</main>
</div>
<footer>
<div class="inner">
<p>ソースコード:<a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/lab/lite/src/demos/platformer.ts" rel="noopener">BabylonJS/Babylon-Lite — lab/lite/src/demos/platformer.ts</a>(コンポーネント群は同階層の <a href="https://github.com/BabylonJS/Babylon-Lite/tree/master/lab/lite/src/demos/platformer" rel="noopener">platformer/</a> フォルダ)</p>
<p>スプライト素材:<a href="https://kenney.nl/assets/platformer-art-deluxe" rel="noopener">Kenney Platformer Art Deluxe</a>(CC0 / パブリックドメイン)。CRT 効果は Aka 氏の <a href="https://www.shadertoy.com/" rel="noopener">CRTFilter</a>(MIT License)を WGSL に移植したものです。本ページの図は mermaid.js とインラインSVGで描画しています。</p>
<p>解説対象のソースコード(Babylon-Lite)は <a href="https://github.com/BabylonJS/Babylon-Lite/blob/master/LICENSE" rel="noopener">Apache License 2.0</a> で公開されています。本ページで引用しているコード断片・数値の著作権はそれぞれの著作者に帰属します。図の描画に使用している <a href="https://mermaid.js.org/" rel="noopener">mermaid.js</a>(MIT License)と Web フォント(SIL Open Font License)は CDN から参照しています。</p>
<p>Super Mario Bros. は任天堂株式会社の商標です。本ページおよび解説対象のデモは任天堂とは無関係の、独立した教育目的・オマージュのコンテンツです。</p>
</div>
</footer>
<script type="module">
try {
const { default: mermaid } = await import('https://cdn.jsdelivr.net/npm/mermaid@11.16.0/+esm');
mermaid.initialize({
startOnLoad: false,
theme: 'base',
securityLevel: 'loose',
fontFamily: "'Zen Kaku Gothic New', sans-serif",
flowchart: { htmlLabels: true, curve: 'linear', nodeSpacing: 34, rankSpacing: 42 },
themeVariables: {
fontSize: '13.5px',
primaryColor: '#E9F2FB',
primaryTextColor: '#2B2536',
primaryBorderColor: '#3E8FD0',
lineColor: '#675F72',
secondaryColor: '#FBF1D6',
tertiaryColor: '#E7F4E8',
clusterBkg: '#FAF7EE',
clusterBorder: '#D3C9B2',
edgeLabelBackground: '#F5F1E6',
titleColor: '#2B2536'
}
});
// Web フォント(Zen Kaku Gothic New 等)の読み込みを待ってから測定・描画する。
// 待たないとフォールバックフォントの幅でノードが確定し、本フォント差し替え後に
// 日本語テキストがノード枠からはみ出して切れてしまう。
if (document.fonts && document.fonts.ready) {
try { await document.fonts.ready; } catch (_) {}
}
await mermaid.run({ querySelector: 'pre.mermaid' });
// mermaid は SVG に width="100%" と style="max-width:○○px" を直接書き込む。
// このままだと狭い画面で図が丸ごと縮小され、13.5px の文字が3〜4pxまで潰れて読めない。
// viewBox の原寸を width/height に戻し、「縮めるか横スクロールさせるか」は CSS に委ねる。
// (flowchart の useMaxWidth:false は mermaid 11 では効かないため、ここで打ち消す)
for (const svg of document.querySelectorAll('pre.mermaid > svg')) {
const vb = svg.viewBox.baseVal;
if (!vb || !vb.width) continue;
svg.style.removeProperty('max-width');
svg.setAttribute('width', Math.ceil(vb.width));
svg.setAttribute('height', Math.ceil(vb.height));
}
} catch (e) {
console.error('mermaid の読み込みに失敗しました(オフラインの可能性):', e);
}
</script>
</body>
</html>