
2026/03/29 5:39
CSSは終焉を迎える運命にあります。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
この記事は、CSSのみでレンダリングを行い、ロジックには最小限のJavaScriptしか使用しない完全にプレイ可能なDOOM風ゲームをウェブブラウザ上で動かす方法を紹介しています。壁・床・天井・スプライト・弾道などを表現するために数千もの
<div> 要素が生成され、各要素はカスタムプロパティとして生のDoom座標を保持し、CSS が hypot()(距離)や atan2()(角度)といった関数で幾何学を計算します。ワールドはプレイヤーの動きに逆行するように translate3d と rotateY で移動されますが、CSS にはカメラオブジェクトがないためです。
床は
rotateX(90deg) で回転し、clip-path(または新しい shape() 関数)を使って任意の多角形や穴に切り取られます。テクスチャタイルはセクター全体にわたって背景位置をワールド座標に合わせて (background-position: calc(var(--min-x)*-1px) …) 配置されます。ドア、リフト、その他の動的要素はカスタムプロパティ上で CSS トランジションによってアニメーションし、JavaScript が状態属性を更新します。スプライトは rotateY でカメラに向き、scaleX で鏡像化したビルボードです。スプライトのアニメーションは CSS の steps() キーフレームで行い、攻撃・死亡フレーム用のデータ状態は JavaScript が供給します。弾道は CSS アニメーションで移動し、衝突検出はまだ JavaScript で処理されます。
照明はセクターごとに
filter: brightness(var(--light)) を使って全体的に適用され、ちらつくライトは @property --light を通じてアニメーションします。プロジェクトではアンカー位置決め、@property、および「ハッキー」な CSS‑のみのカリング手法(オフスクリーン要素を隠すために負の遅延でアニメーションを一時停止)といった実験的機能が採用されています。
数千もの 3D 転送された要素によるパフォーマンスは課題となり、著者は JavaScript で手動フラスタムカリングを実装し、条件付き
if() のサポートが登場すれば将来的に純粋 CSS ソリューションへ移行する計画です。記事では Safari のビュー遷移による 3D フラット化、background‑image 再ラスター化の問題、コンポジタ不安定性などブラウザバグも文書化し、インラインスタイルやバグ報告といった回避策を紹介しています。
著者はより多くのロジックを純粋 CSS に移すことで JavaScript を完全に排除できる可能性があり、パフォーマンスをさらに向上させることを想定しています。成功すれば、このアプローチは軽量なブラウザベースゲームを刺激し、高度な CSS グラフィックス機能のサポートを促進し、重いエンジンを必要としない効率的なレンダリングが求められる開発者に利益をもたらすでしょう。
本文
CSS DOOM – ケーススタディ
1️⃣ なぜ純粋な CSS で DOOM を作るのか?
- 概念実証 – ブラウザが CSS のみで描画できる限界を押し広げる。
- パフォーマンス – 描画はブラウザの CSS エンジンだけで行い、JavaScript はゲームループのみ担当。
- 学習体験 – 近代 CSS(三角関数・
・クリップ‑パス・SVG フィルタ等)がどこまで機能するかを探る。@property
2️⃣ コア構成
| レイヤー | 責務 |
|---|---|
| データ | 元の WAD から抽出した頂点、ライン定義、サイド定義、セクター。 |
| ゲームループ(JS) | 物理・衝突判定・AI を計算し、カスタムプロパティ を更新。 |
| レンダラー(CSS) | 三角関数(、)、変形、アニメーション、クリッピング、ライティングを実装。 |
JavaScript → カスタムプロパティ → CSS。
3️⃣ 幾何学とレンダリング
.wall { --delta-x: calc(var(--end‑x) - var(--start‑x)); --delta-y: calc(var(--end‑y) - var(--start‑y)); width: calc(hypot(var(--delta‑x), var(--delta‑y)) * 1px); height: calc((var(--ceiling‑z) - var(--floor‑z)) * 1px); transform: translate3d( calc(var(--start‑x)*1px), calc(var(--ceiling‑z)*-1px), calc(var(--start‑y)*-1px) ) rotateY(atan2(var(--delta‑y), var(--delta‑x))); }
- 壁 –
/hypot
で長さと角度を算出。atan2 - 床 –
で水平に配置。rotateX(90deg) - セクター –
またはclip-path: polygon()
を使い、複雑な形状や穴を表現。path()
4️⃣ カメラ/ワールドの移動
#scene { translate: 0 0 var(--perspective); transform: rotateY(calc(var(--player‑angle) * -1rad)) translate3d( calc(var(--player‑x)*-1px), calc(var(--player‑z)*1px), calc(var(--player‑y)*1px) ); }
- プレイヤーの動きに逆行するように ワールド を移動させる → 典型的な「カメラトリック」。
は CSS の透視変形による歪みを補正。--perspective
5️⃣ テクスチャとライティング
.floor { background-repeat: repeat; background-size: 64px 64px; background-position: calc(var(--min‑x)*-1px) calc(var(--max‑y)*1px); } .wall, .floor, .sprite { filter: brightness(var(--light, 1)); }
- ワールド方向に合わせたテクスチャ座標でシームレスタイル。
と@property --light
を組み合わせて、セクターごとの照明とちらつきを実装。filter:brightness()
6️⃣ JS ループなしのアニメーション
| 要素 | CSS |
|---|---|
| ドア / リフト | |
| スプライト | , ; |
| 弾丸 | |
- JavaScript はカスタムプロパティや状態属性だけを設定。
7️⃣ 高度な CSS 機能の利用
| Feature | 用途 |
|---|---|
| 数値アニメーション可能にする(例:)。 |
+ | 穴付きの複雑なセクタ形状。 |
| SVG フィルタでシミリング・インビジブルスペクトル効果を実装。 |
| 武器や UI がステータスバーに固定されるように。 |
+ ヒント | 純 CSS でのカリング(可視性を計算値で制御)。 |
8️⃣ パフォーマンスとカリング
- ブラウザコンポジタは自動的に画面外要素をスキップしない → 手動カリングが必要。
- JavaScript は数フレームごとにカリング、実験的な CSS カリングは
アニメーションヒントで行う。cull-toggle - 深度ソートは主にブラウザ側で処理される;軽微な Z オフセットで共平面のちらつきを修正。
9️⃣ 制限とバグ
| 問題 | 回避策 |
|---|---|
Safari のビュートランジションが をフラット化 | 3D シーンではビュー・トランジションを回避。 |
CSS 変数で を再設定すると大規模な再レイアウトが発生 | テクスチャ画像はインラインスタイルで設定。 |
| Chrome のコンポジタが多くの 3D サーフェスで不安定 | マテリアルの複雑さを可能な限り削減。 |
🔚 結論
- 純粋な CSS だけでも完全なファーストパーソンシューティングゲーム(ゲームループ以外)を描画できる。
- 近代 CSS は、三角関数・アニメーション・クリッピング・フィルタといった強力な機能を提供し、かつ想像もされていないほどの表現力がある。
- このプロジェクトは、ロジック(JavaScript)とレンダリング(CSS)の責務をきれいに分離する方法を示している。
CSS で DOOM を走らせることは可能か?
はい – 十分なカスタムプロパティ、アニメーション、3D トランスフォームの賢明な活用で実現できます。