Quick-start to make an html5-canvas game with ocaml

With a rectangle starting element

You can compile OCaml code to Javascript using the rescript compiler version 8.4.2.
It implements an OCaml 4.06.1 syntax.

It's planned that rescript removes the ocaml syntax in future versions, but we will maybe still be able to use rescript version 8.4.2 in the futur.
Also maybe a fork will emerge when the ocaml syntax will be removed.

Let's start with a very simple example that just draws a rectangle in a canvas:

draw.ml
open Canvas

let () =
  let canvas = getElementById document "my_canvas" in
  let ctx = getContext canvas "2d" in

  fillStyle ctx "#DDD";
  fillRect ctx 180 100 240 200;
;;

'draw.ml' just draws a rectangle with ocaml.

canvas.res
type document  // type to represent an html document
type context

@val external document: document = "document"
@val external window: Dom.element = "window"

@send external getElementById: (document, string) => Dom.element = "getElementById"
@send external getContext: (Dom.element, string) => context = "getContext"

@send external fillRect: (context, int, int, int, int) => unit = "fillRect"
@set external fillStyle: (context, string) => unit = "fillStyle"

type key_event = {
  keyCode: int,
  key: string,
}

type interval_id

@send external addKeyEventListener: (Dom.element, string, key_event => unit, bool) => unit = "addEventListener"
@val external setInterval: (unit => unit, int) => interval_id = "setInterval"

'canvas.res' contains bindings to the javascript functions that we need.

Here are the commands to compile these 2 files:

bsc canvas.res
bsc -I . draw.ml > draw.js

The Javascript output is very readable, which is nice to compare with javascript tutorials:

Result: draw.js
// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

var canvas = document.getElementById("my_canvas");
var ctx = canvas.getContext("2d");

ctx.fillStyle = "#DDD";
ctx.fillRect(180, 100, 240, 200);

/* canvas Not a pure module */

You can include this javascript file in an .html file containing a canvas tag:

draw.html
<!DOCTYPE html>
<html>
<head>
<style rel="stylesheet" type="text/css">
body {
  background-color:#999;
}
canvas {
  background-color:#BBB;
  border:1px solid #666;
  margin:80px;
}
</style>
</head>
<body>

<canvas id="my_canvas" width="600" height="400">
</canvas>

<script>var exports = {};</script>
<script type="text/javascript" src="./draw.js"></script>

</body>
</html>

 

Here is a quick-start that you can use as a template:

start.ml
open Canvas

let canvas = getElementById document "my_canvas"
let ctx = getContext canvas "2d"


let display () =
  (* draw the background *)
  fillStyle ctx "#dde";
  fillRect ctx 0 0 360 220;

  (* draw a rectangle *)
  fillStyle ctx "#88f";
  fillRect ctx 80 60 60 40;
  ()
;;


let update () =
  ()
;;

type key_change = KeyDown | KeyUp

let keychange_event key_change ev =
  match key_change, ev.Canvas.keyCode with
  | KeyDown, 37 -> ()  (* left *)
  | KeyDown, 39 -> ()  (* right *)
  | KeyDown, 38 -> ()  (* up *)
  | KeyDown, 40 -> ()  (* down *)

  | KeyUp, 37 -> ()  (* left *)
  | KeyUp, 39 -> ()  (* right *)
  | KeyUp, 38 -> ()  (* up *)
  | KeyUp, 40 -> ()  (* down *)

  | _ -> ()


let () =
  (* init *)

  let animate () =
    update ();
    display ();
    ()
  in

  Canvas.addKeyEventListener Canvas.window "keydown" (keychange_event KeyDown) true;
  Canvas.addKeyEventListener Canvas.window "keyup"   (keychange_event KeyUp) true;

  let _ = Canvas.setInterval animate (1000/20) in   (* 20 frames per seconds *)
  ()
;;

We compile with:

bsc canvas.res
bsc -I . start.ml > start.js
sed -i -e 's!.*require.*!!g' start.js

Here I replace all the 'requires' from the beginning of 'start.js' by 'caml-platform.js' included in the html page.
This file groups in a single file the javascript implementations of the usual ocaml functions that are located in 'bs-platform/lib/js/' from the rescript 8.4.2 system.

We might use a basic Makefile:

start.js: start.ml canvas.cmt
	bsc -I . $< | sed -e 's!.*require.*!!g' | sed -e 's!export.*!!g' > $@
canvas.cmt: canvas.res
	bsc $<
clean:
	$(RM) *.cm[ijt] start.js

It's possible to use a javascript bundler like 'esbuild' but the result is then not very readable anymore, and it also packs everything into a closed function which make it difficult to have interactions with the outside elements.

start.html
<!DOCTYPE html>
<html>
<head>
<style rel="stylesheet" type="text/css">
body {
  background-color:#999;
}
canvas {
  background-color:#BBB;
  border:1px solid #666;
  margin:80px;
}
</style>
</head>
<body>

<canvas id="my_canvas" width="320" height="240">
</canvas>

<script type="text/javascript" src="./caml-platform.js"></script>
<script type="text/javascript" src="./start.js"></script>

</body>
</html>

See the result.

List of files:

© 2022, 2024 Florent Monnier
Content provided under cc-by-sa license.