A grab-bag of B+ utilities you'll likely find useful.
Handles all the boilerplate of a basic game loop for you. Refer to the doc-string @game_loop
, and the file Helpers/game_loop.jl, for detailed info on usage.
The loop handles all built-in B+ services for you: Input
, GUI
, and BasicGraphics
Within the loop you'll have access to the variable LOOP::GameLoop
, which has the following fields which you can read but should not set:
delta_seconds::Float32
: the amount of time elapsed since the last loop iteration.frame_idx::Int
: the number of elapsed frames so far, plus 1.last_frame_time_ns::UInt64
: the most recent timestamp, from the end of the last frame.context::Context
: the OpenGL/GLFW contextservice_input
: the Input service.service_basic_graphics
: the Basic Graphics service.service_gui
: the GUI service.
LOOP
also has some fields which you can set to configure the loop. You can set them both in the SETUP
phase and the LOOP
phase.
max_fps::Optional{Int} = 300
caps the game's framerate.max_frame_duration::Float32 = 0.1
caps thedelta_seconds
field in the case of very slow frames. This prevents significant jumps in the game after a hang.
A B+ Context service that provides lots of basic resources: e which defines a bunch of useful GL resources:
screen_triangle
: A 1-triangle mesh with 2D positions in NDC-space. When drawn, it will perfectly cover the entire screen, making it easy to spin up post-processing effects. The UV coordinates can be calculated from the XY positions (or from gl_FragCoord).screen_quad
: A 2-triangle mesh describing a square, with 2D coordinates in NDC space (-1 to +1) This can be used for post-processing effects, but it's less efficient thanscreen_triangle
for technical reasons.blit
: A simple shader to render a 2D texture (e.x. copy a Target to the screen). Refer tosimple_blit()
.empty_mesh
: A mesh with no vertex data, for dispatching entirely procedural geometry. UsesPrimitiveType.points
.
To draw a texture, call simple_blit(tex_or_view; params...)
. It has the following named parmeters:
quad_transform::fmat3x3
transforms the 2D quad.- Defaults to the identity transform.
color_transform::fmat4x4
transforms the sampled color into an output color.- Defaults to the identity transform.
output_curve::Float32
is an exponent that can be applied to all 4 output channels.disable_depth_test::Bool
is a flag for automtically disabling depth tests before drawing the quad, then reinstating the depth test state afterwards.- By default it's true.
manage_tex_view::Bool
is a flag for automatically callingview_activate()
andview_deactivate()
on the blitted texture/view if it's not already activated.- By default it's true.
A representation of a movable, turnable 3D camera.
Cam3D{F}
represents the camera state, usingF
as the floating-point type (e.x.Float32
).Cam3D_Settings{F}
represents the camera's config.Cam3D_Inputs{F}
represents the camera controls for a particular frame.
To update the camera, do (cam, settings) = cam_update(cam, settings, input, delta_seconds)
. Note that the settings may change too; this is the case if you support the speed_change
input.
cam_basis(cam)
andcam_rightward(cam)
calculate the camera's facing vectors (the former gets all 3 axes, the latter gets just the rightward axis).- Get the view matrix with
cam_view_mat(cam)
. - Get the projection matrix with
cam_projection_mat(cam)
.- The camera's projection settings are either a
PerspectiveProjection{F}
or anOrthographicProjection{F}
.
- The camera's projection settings are either a
A system for reloading files from disk anytime they are changed. To use this system, do the following:
- Define the type of data that is cached. We will refer to this type as
TCached
. For example, a texture cache could useBplusApp.GL.Texture
. - Define a function which loads, or re-loads, an asset from disk. We will refer to this as
reload_response::Base.Callable
.- The signature should be
(path::AbstractString[, old_data;:TCached]) -> new_data::TCached[, dependent_files]
.- If your loaded asset depends on other files beyond its own path, add a second return value which is an iterator of those files. Then a reload can be triggered by any of these files changing.
- This function is allowed to throw if the file can't be loaded for any reason.
- The signature should be
- Define a function which acknowledges an error from calling
reload_response
. We will refer to this aserror_response::Base.Callable
.- The signature should be
(path::AbstractString, exception, trace[, old_data::TCached]) -> [fallback_data::TCached]
. - If you want to return a fallback instance (like an "error texture"), you can return it here. Otherwise, return anything other than a
TCached
, such asnothing
. - You may also want to log an error here, e.x.
@error "Failed to load $path" ex=(exception, trace)
- The signature should be
- Create an instance of the cacher with the above parameters:
FileCacher{TCached}(reload_response=reload_response, error_response=error_response, other_args...)
.- Pass
relative_path = p
to change the relative path for files.- It defaults to
pwd()
, a.k.a. the location Julia is running from.
- It defaults to
- Pass
check_interval_ms = a:b
to change the randomized time interval for checking each file for changes.- The randomization prevents all cached files from being checked at the same time, which could cause a disk bottleneck.
- Default is
3000:5000
, i.e. 3-5 seconds.
- Pass
- Update the cacher with
check_disk_modifications!(cacher)::Bool
.- It returns true if any files have been reloaded.
- The naive way to call this is once every frame, but you can probably get away with doing it much less often since files are not reloaded very frequently.
- Get a file with
get_cached_data!(cacher, relative_or_absolute_path)::Optional{TCached}
.- If your
error_response
provides a fallback instance, then this function always returns aTCached
.
- If your