A multiplayer top-down arena shooter. Move, shoot, and survive. Built entirely on the asobi_match behaviour.
One Erlang module. ~270 lines. Full multiplayer arena.
Bounded play area with random spawn points. Players can't leave the arena.
Aim and shoot with cooldown. Projectiles travel at 8 units/tick with collision detection.
Four hits to eliminate. Kills and deaths tracked per player.
Timed rounds with automatic scoring. Last player standing or highest kills wins.
Each match runs as an isolated OTP process. The game module implements callbacks for the full lifecycle.
Set up arena state: empty player map, projectile list, match timer.
Spawn players at random positions with 100 HP. Clean up on disconnect.
Process movement (WASD) and shooting (aim + fire) from each client.
Server tick: move projectiles, detect collisions, remove out-of-bounds, check win condition.
This is the actual game logic. Asobi handles networking, matchmaking, and state sync.
-module(asobi_arena_game).
-behaviour(asobi_match).
-export([init/1, join/2, leave/2, handle_input/3, tick/1, get_state/2]).
-define(ARENA_W, 800).
-define(ARENA_H, 600).
-define(MAX_HP, 100).
-define(DAMAGE, 25).
-define(GAME_DURATION, 90000).
init(_Config) ->
{ok, #{players => #{}, projectiles => [],
next_proj_id => 1, started_at => undefined}}.
join(PlayerId, #{players := Players} = State) ->
Player = #{x => rand:uniform(700) + 50,
y => rand:uniform(500) + 50,
hp => ?MAX_HP, kills => 0, deaths => 0},
{ok, State#{players => Players#{PlayerId => Player}}}.
handle_input(PlayerId, Input, State) ->
%% Apply movement + shooting per client tick
Player1 = apply_movement(Input, get_player(PlayerId, State)),
{State1, Player2} = maybe_shoot(PlayerId, Input, Player1, State),
{ok, put_player(PlayerId, Player2, State1)}.
tick(#{players := Players, projectiles := Projs} = State) ->
Projs1 = move_projectiles(Projs),
{Projs2, Players1} = check_collisions(Projs1, Players),
State1 = State#{players => Players1,
projectiles => remove_oob(Projs2)},
case time_up(State1) orelse one_standing(Players1) of
true -> {finished, build_result(Players1), State1};
false -> {ok, State1}
end.Each client demo connects to the same Asobi Arena backend. Pick your engine and try it out.
Full arena shooter with sprites, projectile rendering, and HUD. Unity 2021.3+.
View on GitHubGodot 4.x client with scene-based player and projectile nodes.
View on GitHubFlutter 2D arena client with Flame engine. Cross-platform mobile and desktop.
View on GitHubMore engine demos coming soon. Join Discord to get notified.
Asobi Arena is just one example. Implement the asobi_match behaviour and build anything — racing, puzzle, RTS, battle royale.
Get the arena running locally in a few minutes. You need Erlang/OTP 27+, PostgreSQL, and one of the game engines above.
git clone https://github.com/widgrensit/asobi_arena
cd asobi_arena
docker compose up -d
rebar3 shell# Pick your engine:
git clone https://github.com/widgrensit/asobi-godot-demo
# or: asobi-unity-demo, asobi-defold-demo, asobi-flame-demoOpen the demo in your engine, hit play, and register a player. Open a second window to matchmake against yourself. WASD to move, mouse to aim, click to shoot.
The backend runs on port 8084 by default. Check each demo's README for engine-specific setup.