このプログラムはGitHub Pagesで公開しています。
放置しているのもどうかと思うので半年ぶりに書きます。
ウェブゲーム開発に入門
MDN web docs にある 「そのままのJavaScriptを使ったブロックくずしゲーム」
の教材でウェブゲーム開発に入門してみる。
PureScript+Halogen
この教材は pure JavaScript(そのままのJavaScript)とあるけど, そのままのJavaScript(UIフレームワークなし)は個人的にしんどいので変更します。
PureScript ってのは簡単にいうと
ビルドするとJavaScriptを得られる正格評価のHaskell。
開発環境
1
2
3
4
5
6
| Microsoft Windows [Version 10.0.22000.613]
(c) Microsoft Corporation. All rights reserved.
C:\Users\aki>wsl -l -v
NAME STATE VERSION
* Ubuntu-20.04 Running 2
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| $ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04 (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
|
このWindows 11 + WSL2 + Ubuntuに
https://github.com/purescript/documentation/blob/master/guides/Getting-Started.md
を済ませた開発環境で始める。
Halogen example
Halogen example の Basicを動かしてみる。
1
2
3
4
5
6
7
8
9
| aki: $ git clone https://github.com/purescript-halogen/purescript-halogen.git
Cloning into 'purescript-halogen'...
remote: Enumerating objects: 9892, done.
remote: Counting objects: 100% (385/385), done.
remote: Compressing objects: 100% (208/208), done.
remote: Total 9892 (delta 195), reused 325 (delta 167), pack-reused 9507
Receiving objects: 100% (9892/9892), 3.91 MiB | 5.70 MiB/s, done.
Resolving deltas: 100% (6005/6005), done.
aki:~$ cd purescript-halogen/examples/basic/
|
1
2
3
4
5
6
7
| aki:~/purescript-halogen/examples/basic$ npm install
aki:~/purescript-halogen/examples/basic$ npm run example-basic
省略...
[info] Build succeeded.
[info] Bundle succeeded and output file to examples/basic/dist/example.js
|
1
2
3
| aki:~/purescript-halogen/examples/basic$ cd dist/
aki:~/purescript-halogen/examples/basic/dist$ wslpath -w .
\\wsl.localhost\Ubuntu-20.04\home\aki\purescript-halogen\examples\basic\dist
|
Webブラウザで上のパスを開いて index.htmlを開く。ボタンがあってOnOffできることを確認出来たらOK。
自分の書くコードの置き場
1
2
3
4
5
6
7
8
9
10
11
12
| aki:~/purescript-halogen/examples/basic/dist$ cd
aki:~$ mkdir breakout
aki:~$ cd breakout
aki:~/breakout$ mkdir lesson01
aki:~/breakout$ cd lesson01/
aki:~/breakout/lesson01$ spago init
aki:~/breakout/lesson01$ spago run
省略...
[info] Build succeeded.
🍝
|
purescript-halogen/examples/basic/src/ にある Main.purs と Button.purs を src/ にコピーする。
1
2
3
4
| aki:~/breakout/lesson01$ cp ~/purescript-halogen/examples/basic/src/* src/
aki:~/breakout/lesson01$
aki:~/breakout/lesson01$ ls src
Button.purs Main.purs
|
vscodeを起動する
1
| aki:~/breakout/lesson01$ code .
|
赤線が出ている。そういえばVSCode拡張機能をいれていた。
これは halogen が無いというエラーだから, とりあえずターミナルから halogen を入れる。
1
2
| aki:~/breakout/lesson01$ spago install halogen
aki:~/breakout/lesson01$ spago build
|
Main.pursを保存すると赤線が消える。
1
2
3
| aki:~/breakout/lesson01$ spago bundle-app
[info] Build succeeded.
[info] Bundle succeeded and output file to index.js
|
これで index.js が出来た。
続いて purescript-halogen/examples/dists/index.html を ./ にコピーする。
1
2
3
4
5
| aki:~/breakout/lesson01$ ls
index.js output packages.dhall spago.dhall src test
aki:~/breakout/lesson01$ cp ~/purescript-halogen/examples/basic/dist/index.html ./
aki:~/breakout/lesson01$ ls
index.html index.js output packages.dhall spago.dhall src test
|
VSCode上で index.htmlを開いて
<script src="example.js"></script>
と書かれているところを
<script src="index.js"></script>
にして保存する。
Webブラウザでこの index.htmlを開く。
同じくボタンがあってOnOffできることを確認出来たらOK。
lesson01
準備が済んだので Canvasを作ってその上に描画する を始めます。
ゲームのHTML
チュートリアル通りならこのとおりだけど
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Gamedev Canvas Workshop</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #eee; display: block; margin: 0 auto; }
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script>
// JavaScriptのコードがここに入ります
</script>
</body>
</html>
|
中身はプログラムの方で用意するので, index.htmlはこのくらいにする。
1
2
3
4
5
6
7
8
9
10
11
12
13
| <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Gamedev Canvas Workshop</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
|
これをindex.htmlに保存する。
ブロックくずしゲームのUIコンポーネント
今あるButton.purs は不要なので削除して Breakout.purs を新規作成する。
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
| module Breakout (component) where
import Prelude
import CSS (background, block, display, margin, marginBottom, marginLeft, marginRight, marginTop, padding, px, rgb)
import CSS.Common (auto)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Aff.Class (class MonadAff)
import Graphics.Canvas (CanvasElement)
import Graphics.Canvas as Canvas
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.CSS (style)
import Halogen.HTML.Properties as HP
import Math as Math
canvasId :: String
canvasId = "myCanvas"
type State
= {}
data Action
= Initialize
component :: forall query input output m. MonadAff m => H.Component query input output m
component =
H.mkComponent
{ initialState
, render
, eval:
H.mkEval
$ H.defaultEval
{ handleAction = handleAction
, initialize = Just Initialize
}
}
initialState :: forall i. i -> State
initialState _ = {}
render :: forall m. State -> H.ComponentHTML Action () m
render _ =
HH.main
[ style do
margin (px 0.0) (px 0.0) (px 0.0) (px 0.0)
padding (px 0.0) (px 0.0) (px 0.0) (px 0.0)
]
[ HH.canvas
[ style do
background (rgb 238 238 238)
display block
marginTop (px 0.0)
marginRight auto
marginBottom (px 0.0)
marginLeft auto
, HP.id canvasId
, HP.width 480
, HP.height 320
]
]
handleAction :: forall output m. MonadAff m => Action -> H.HalogenM State Action () output m Unit
handleAction = case _ of
Initialize -> do
maybeCanvas <- H.liftEffect $ Canvas.getCanvasElementById canvasId
H.liftEffect
$ case maybeCanvas of
Nothing -> pure unit
Just canvas -> draw canvas
draw :: CanvasElement -> Effect Unit
draw canvas = do
ctx <- Canvas.getContext2D canvas
--
Canvas.beginPath ctx
Canvas.rect ctx
$ { x: 20.0
, y: 40.0
, width: 50.0
, height: 50.0
}
Canvas.setFillStyle ctx "#FF0000"
Canvas.fill ctx
Canvas.closePath ctx
--
Canvas.beginPath ctx
Canvas.arc ctx
$ { x: 240.0
, y: 160.0
, radius: 20.0
, start: 0.0
, end: Math.pi * 2.0
}
Canvas.setFillStyle ctx "green"
Canvas.fill ctx
Canvas.closePath ctx
--
Canvas.beginPath ctx
Canvas.rect ctx
$ { x: 160.0
, y: 10.0
, width: 100.0
, height: 40.0
}
Canvas.setStrokeStyle ctx "rgba(0, 0, 255, 0.5)"
Canvas.stroke ctx
Canvas.closePath ctx
|
Main.purs をこう書き換える。
1
2
3
4
5
6
7
8
9
10
11
12
13
| module Main where
import Prelude
import Effect (Effect)
import Breakout as Breakout
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
main :: Effect Unit
main =
HA.runHalogenAff do
body <- HA.awaitBody
runUI Breakout.component unit body
|
spagoの指示通り CSS Halogen-CSS Canvas Math Aff Maybe を入れる。
1
| aki:~/breakout/lesson01$ spago install css halogen-css canvas math aff maybe
|
Canvasの基本
チュートリアルで説明されている「Canvasの基本部分」はここ。
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
| draw :: CanvasElement -> Effect Unit
draw canvas = do
ctx <- Canvas.getContext2D canvas
--
Canvas.beginPath ctx
Canvas.rect ctx
$ { x: 20.0
, y: 40.0
, width: 50.0
, height: 50.0
}
Canvas.setFillStyle ctx "#FF0000"
Canvas.fill ctx
Canvas.closePath ctx
--
Canvas.beginPath ctx
Canvas.arc ctx
$ { x: 240.0
, y: 160.0
, radius: 20.0
, start: 0.0
, end: Math.pi * 2.0
}
Canvas.setFillStyle ctx "green"
Canvas.fill ctx
Canvas.closePath ctx
--
Canvas.beginPath ctx
Canvas.rect ctx
$ { x: 160.0
, y: 10.0
, width: 100.0
, height: 40.0
}
Canvas.setStrokeStyle ctx "rgba(0, 0, 255, 0.5)"
Canvas.stroke ctx
Canvas.closePath ctx
|
ブラウザーで確認すると
GitHubリポジトリ
コードはGitHub上にあります。
https://github.com/ak1211/breakout
つづく
よてい