View Source Your first Exray application
Baby Steps - Running from IEX to get the vibe
Once you have ensured that you are setup (added the :exray
version to your mix dependencies), you're ready to rumble.
Begin by running iex -S mix
in the root of your project, then try the following:
$> iex -S mix
#> Generated exray app
#> Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
## We first import Exray.Core.Window so we can call it's functions.
iex> import Exray.Core.Window
#> Exray.Core.Window
## Then, we can call init_window/3, which takes a width / height / title.
iex> init_window(200, 200, "Hello World!")
#> [INFO] : Initializing raylib 5.0
#> [INFO] : Platform backend: DESKTOP (GLFW)
#> [INFO] : Supported raylib modules:
#> [INFO] : > rcore:..... loaded (mandatory)
#> [INFO] : > rlgl:...... loaded (mandatory)
#> [INFO] : > rshapes:... loaded (optional)
#> [INFO] : > rtextures:. loaded (optional)
#> [INFO] : > rtext:..... loaded (optional)
#> [INFO] : > rmodels:... loaded (optional)
#> [INFO] : > raudio:.... loaded (optional)
#> [INFO] : DISPLAY: Device initialized successfully
#> [INFO] : > Display size: 1920 x 1080
#> [INFO] : > Screen size: 200 x 200
#> [INFO] : > Render size: 200 x 200
#> [INFO] : > Viewport offsets: 0, 0
#> [INFO] : GLAD: OpenGL extensions loaded successfully
#> [INFO] : GL: Supported extensions count: 390
#> [INFO] : GL: OpenGL device information:
#> [INFO] : > Vendor: NVIDIA Corporation
#> [INFO] : > Renderer: NVIDIA GeForce GTX 1080/PCIe/SSE2
#> [INFO] : > Version: 3.3.0 NVIDIA 565.57.01
#> [INFO] : > GLSL: 3.30 NVIDIA via Cg compiler
#> [INFO] : GL: VAO extension detected, VAO functions loaded successfully
#> [INFO] : GL: NPOT textures extension detected, full NPOT textures supported
#> [INFO] : GL: DXT compressed textures supported
#> [INFO] : GL: ETC2/EAC compressed textures supported
#> [INFO] : PLATFORM: DESKTOP (GLFW): Initialized successfully
#> [INFO] : TEXTURE: [ID 1] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
#> [INFO] : TEXTURE: [ID 1] Default texture loaded successfully
#> [INFO] : SHADER: [ID 1] Vertex shader compiled successfully
#> [INFO] : SHADER: [ID 2] Fragment shader compiled successfully
#> [INFO] : SHADER: [ID 3] Program shader loaded successfully
#> [INFO] : SHADER: [ID 3] Default shader loaded successfully
#> [INFO] : RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
#> [INFO] : RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
#> [INFO] : RLGL: Default OpenGL state initialized successfully
#> [INFO] : TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
#> [INFO] : FONT: Default font loaded successfully (224 glyphs)
#> :ok
## Without a running loop, it makes update & draw calls difficult. So for now, we'll close it up.
iex> close_window()
#> Segmentation fault (core dumped)
Wait- What? Segmentation fault (core dumped)??
Don't worry, we didn't exactly close it 'clean'; we called close_window but we hadn't done anything with the window, so it was pretty much holding itself together in a state of ".. Please, update and draw me".
Segmentation faults are unfortunately something that will trigger the entire BEAM VM to crash and there just isn't a good way to debug them. Thankfully, with a bit of knowledge working with the NIFs I've found a pretty decent way to at least get the game running and callback the Raylib C without it deciding it doesn't feel like it today, causing a segfault.
Basic Game Loop
So we're going to create a new file (exs|ex) and open it up to flesh it out.
Stage 1; initial module setup & function structure
Start with defining the module:
defmodule SuperCoolGame do
end
Then adding in a few functions, which will make sense shortly:
defmodule SuperCoolGame do
def run() do
end
defp main_loop() do
end
defp update() do
end
defp draw() do
end
end
Now what we've just done is create 3 private functions; main_loop()
, update()
and draw()
as well as a public function named run()
which is where we're actually going to enter this module from.
Let's add a little more structure, so the functions can call correctly:
defmodule SuperCoolGame do
def run(width \\ 200, height \\ 200, title \\ "Hello World!") do
main_loop()
:ok # :ok, because we're :ok :)
end
defp main_loop() do
update()
draw()
main_loop()
end
defp update() do
end
defp draw() do
end
end
Stage 2; adding Raylib stuff
And finally, let's organize some logic so we can setup a window, poll input and exit safely:
defmodule SuperCoolGame do
# We import Exray.Core.Window, like we did in IEX.
import Exray.Core.Window
# But, we also import Exray.Core.Drawing, to have access to begin_draw() and end_draw() calls.
# This is to poll for window_should_close?() events, as without end_draw() no inputs will be polled at all.
import Exray.Core.Drawing
# We also need to import Exray.Core.Timing in order to set our FPS. This is VERY important.
import Exray.Core.Timing
def run(width \\ 200, height \\ 200, title \\ "Hello World!") do
init_window(width, height, title)
set_target_fps(60) # <-- SUPER important! Call this just after you init_window, or segfaults are gonna happen a lot.
main_loop()
# As another layer of safety, if for some reason our window *didn't* close, we can do it here in this check.
if window_should_close?() do
close_window()
end
:ok # :ok, because we're :ok :)
end
defp main_loop() do
# Stay out of update and draw unless our window is ready to update and draw.
unless window_is_ready?(), do: main_loop()
# If at any point we press Raylib's default "Quit Application" key, (ESCAPE), stop looping and exit.
unless window_should_close?() do
update()
draw()
main_loop()
end
end
defp update() do
# Nothing yet!
end
defp draw() do
# Before drawing, we'll clear the background with the Exray.Colors.black function result- Which is %Exray.Structs.Color{r: 0, g: 0, b: 0, a: 255}.
clear_background(Exray.Colors.black())
begin_drawing()
# Nothing yet!
end_drawing()
end
end
Granted, you may still segfault
, however you're doing the best you can to avoid it. As Exray develops, I'm hoping to add a lot more defenses against this illusive (and transient) problem, but for now it's kind of just something that happens. I've found it's safest when running from a .exs
file, but I'm not really sure why.
In any case, you can now run this module by restarting your IEX and calling SuperCoolGame.run()
.
$> iex -S mix
#> Generated exray app
#> Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
iex> SuperCoolGame.run()
#> [INFO] : Initializing raylib 5.0
#> [INFO] : Platform backend: DESKTOP (GLFW)
#> [INFO] : Supported raylib modules:
#> [INFO] : > rcore:..... loaded (mandatory)
#> [INFO] : > rlgl:...... loaded (mandatory)
#> [INFO] : > rshapes:... loaded (optional)
#> .......
At that point, a window should pop up with a clear black screen, 200x200 across with the title "Hello World!". You can close it by pressing ESCAPE.