diff options
Diffstat (limited to 'examples/bullet_engine')
-rw-r--r-- | examples/bullet_engine/bullet.png | bin | 0 -> 793 bytes | |||
-rw-r--r-- | examples/bullet_engine/bullet_engine.erl | 251 | ||||
-rwxr-xr-x | examples/bullet_engine/start.sh | 2 |
3 files changed, 253 insertions, 0 deletions
diff --git a/examples/bullet_engine/bullet.png b/examples/bullet_engine/bullet.png Binary files differnew file mode 100644 index 0000000..7ceec16 --- /dev/null +++ b/examples/bullet_engine/bullet.png diff --git a/examples/bullet_engine/bullet_engine.erl b/examples/bullet_engine/bullet_engine.erl new file mode 100644 index 0000000..0006bcd --- /dev/null +++ b/examples/bullet_engine/bullet_engine.erl @@ -0,0 +1,251 @@ +%% This is an example. Feel free to copy and reuse as you wish. + +-module(bullet_engine). +-export([run/0]). + +run() -> + spawn(fun init/0). + +init() -> + ok = sdl:start([video]), + ok = sdl:stop_on_exit(), + {ok, Window} = sdl_window:create("Hello SDL", 10, 10, 500, 600, []), + {ok, Renderer} = sdl_renderer:create(Window, -1, [accelerated, present_vsync]), + ok = sdl_renderer:set_logical_size(Renderer, 500 bsl 16, 600 bsl 16), + {ok, Texture} = sdl_texture:create_from_file(Renderer, "bullet.png"), + loop(#{window=>Window, renderer=>Renderer, texture=>Texture, + scene=>init_scene()}). + +loop(State=#{scene:=Scene}) -> + events_loop(), + Scene2 = update_scene(Scene, []), + State2 = State#{scene:=Scene2}, + render(State2), + loop(State2). + +events_loop() -> + case sdl_events:poll() of + false -> ok; + #{type:=quit} -> terminate(); + _ -> events_loop() + end. + +render(#{renderer:=Renderer, texture:=Texture, scene:=Scene}) -> + ok = sdl_renderer:clear(Renderer), + _ = [sdl_renderer:copy(Renderer, Texture, undefined, Bullet) + || Bullet = #{t:=Type} <- Scene, Type =/= invisible], + ok = sdl_renderer:present(Renderer). + +terminate() -> + init:stop(), + exit(normal). + +%% Demo scene. + +init_scene() -> + [new_invisible(#{x=>250 bsl 16, y=>300 bsl 16, w=>0, h=>0, actions=>[ + %% Part 1. + {var, w, 31}, + {var, x, 0}, + {loop, 30, [ + {loop, 18, [ + {fire, [ + {set, speed, 2 bsl 16}, + {set, dir, x} + ]}, + {var, x, '+=', 30} + ]}, + {wait, w}, + {var, w, '+=', -1} + ]}, + + %% Part 2. + {var, y, 31}, + {var, z, -3}, + {loop, 2, [ + {loop, 21, [ + {loop, 18, [ + {fire, [ + {set, speed, 2 bsl 16}, + {set, dir, x} + ]}, + {var, x, '+=', y}, + {wait, 1} + ]}, + {var, y, '+=', z} + ]}, + {var, z, '*=', -1} + ]}, + + %% Part 3. + {var, i, 45}, + {var, n, 4}, + {loop, 4, [ + {wait, 60}, + {fire, [ + {set, w, 64 bsl 16}, + {set, h, 64 bsl 16}, + {set, dir, i}, + {set, speed, 2 bsl 16}, + {loop, 10, [ + {fire, [ + {var, j, 180}, + {var, j, '+=', i}, + {set, dir, j}, + {set, speed, 10 bsl 16} + ]}, + {wait, 6} + ]}, + {set, speed, 0}, + {var, w, 60}, + {var, w, '*=', n}, + {wait, w}, + {var, d, 90}, + {var, n, '+=', -4}, + {var, n, '*=', -1}, + {var, d, '*=', n}, + {var, d, '+=', -225}, + {set, dir, d}, + {set, speed, 3 bsl 16}, + {loop, 120, [ + {var, d, '+=', 3}, + {set, dir, d}, + {wait, 1} + ]}, + {loop, 120, [ + {fire, []}, + {loop, 3, [ + {var, d, '+=', 3}, + {set, dir, d}, + {wait, 1} + ]} + ]}, + {loop, 120, [ + {fire, []}, + {var, d2, 180}, + {var, d2, '+=', d}, + {fire, [ + {set, dir, d2} + ]}, + {loop, 3, [ + {var, d, '+=', 3}, + {set, dir, d}, + {wait, 1} + ]} + ]}, + {loop, 120, [ + {fire, []}, + {var, d2, 180}, + {var, d2, '+=', d}, + {fire, [ + {set, dir, d2} + ]}, + {loop, 2, [ + {var, d, '+=', 3}, + {set, dir, d}, + {wait, 1} + ]} + ]}, + {loop, 120, [ + {fire, []}, + {var, d2, 180}, + {var, d2, '+=', d}, + {fire, [ + {set, dir, d2} + ]}, + {var, d, '+=', 3}, + {set, dir, d}, + {wait, 1} + ]}, + {wait, 240} + ]}, + {var, i, '+=', 90}, + {var, n, '+=', -1} + ]}, + + %% Wait for the scene to finish, then stop the VM. + {wait, 2640}, + init_stop + ]})]. + +update_scene([], Acc) -> + lists:flatten(Acc); +%% We avoid float arithmetic where possible. +%% For Pi we use the approximate fraction 103993/33102. +%% We may also want to do an integer cosine and sine. +update_scene([Bullet = #{x:=X, y:=Y, w:=W, h:=H, dir:=Dir, speed:=Speed, + wait:=Wait, actions:=Actions}|Tail], Acc) -> + A = (103993 * (Dir - 90)) / (33102 * 180), + X2 = X + round(Speed * math:cos(A)), + Y2 = Y + round(Speed * math:sin(A)), + if + Wait > 0 -> + update_scene(Tail, [Bullet#{x:=X2, y:=Y2, wait:=Wait - 1}|Acc]); + X2 > 500 bsl 16; X2 < -W; Y2 > 600 bsl 16; Y2 < -H -> + update_scene(Tail, Acc); + true -> + New = update_bullet(Bullet#{x:=X2, y:=Y2}, Actions, []), + update_scene(Tail, [New|Acc]) + end. + +%% Bullet engine. +%% +%% The scene is (500 bsl 16)x(600 bsl 16) rendered as 500x600. We avoid floats for +%% performance reasons so everything only goes up to 3 decimals, which is perfectly +%% fine anyway. +%% +%% Execution is done frame by frame for simplicity, relying on vsync. Another +%% advantage of doing this is that we can very easy record a replay based on user +%% input, although we don't have any in this small demo. + +new_invisible(Parent) -> + Parent#{t=>invisible, w=>0, h=>0, dir=>0, speed=>0, + wait=>0, vars=>#{}}. + +new_bullet(Parent=#{x:=X, y:=Y, w:=W, h:=H}, Actions) -> + Parent#{t=>bullet, x=>X + (W div 2) - (8 bsl 16), y=>Y + (H div 2) - (8 bsl 16), + w=>16 bsl 16, h=>16 bsl 16, wait=>0, actions=>Actions}. + +update_bullet(Bullet, [], Acc) -> + [Bullet#{actions:=[]}|Acc]; +%% Stop the VM. +update_bullet(Bullet, [init_stop|_], Acc) -> + init:stop(), + update_bullet(Bullet, [], Acc); +%% Manipulate variables. +update_bullet(Bullet=#{vars:=Vars}, [{var, V, Value}|Tail], Acc) -> + update_bullet(Bullet#{vars:=maps:put(V, Value, Vars)}, Tail, Acc); +update_bullet(Bullet=#{vars:=Vars}, [{var, V, '+=', W}|Tail], Acc) when is_atom(W) -> + update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) + maps:get(W, Vars), Vars)}, Tail, Acc); +update_bullet(Bullet=#{vars:=Vars}, [{var, V, '+=', W}|Tail], Acc) -> + update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) + W, Vars)}, Tail, Acc); +update_bullet(Bullet=#{vars:=Vars}, [{var, V, '*=', W}|Tail], Acc) when is_atom(W) -> + update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) * maps:get(W, Vars), Vars)}, Tail, Acc); +update_bullet(Bullet=#{vars:=Vars}, [{var, V, '*=', W}|Tail], Acc) -> + update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) * W, Vars)}, Tail, Acc); +%% Loop actions. +%% +%% We only unroll one iteration at a time to avoid wasting resources. +update_bullet(Bullet, [{loop, 1, Actions}|Tail], Acc) -> + update_bullet(Bullet, Actions ++ Tail, Acc); +update_bullet(Bullet, [{loop, N, Actions}|Tail], Acc) -> + update_bullet(Bullet, Actions ++ [{loop, N - 1, Actions}|Tail], Acc); +%% Wait a few frames. +update_bullet(Bullet=#{vars:=Vars}, [{wait, V}|Tail], Acc) when is_atom(V) -> + [Bullet#{wait:=maps:get(V, Vars), actions:=Tail}|Acc]; +update_bullet(Bullet, [{wait, N}|Tail], Acc) -> + [Bullet#{wait:=N, actions:=Tail}|Acc]; +%% Fire a new bullet. +update_bullet(Bullet, [{fire, Actions}|Tail], Acc) -> + update_bullet(Bullet, Tail, [new_bullet(Bullet, Actions)|Acc]); +%% Set bullet values directly. +update_bullet(Bullet=#{vars:=Vars}, [{set, Key, V}|Tail], Acc) when is_atom(V) -> + update_bullet(maps:put(Key, maps:get(V, Vars), Bullet), Tail, Acc); +update_bullet(Bullet=#{x:=X, y:=Y, w:=W, h:=H}, [{set, Key, Value}|Tail], Acc) -> + %% We need to reposition the bullet if the size changes, + %% are the bullet position is its top left corner. + case Key of + w -> update_bullet(maps:put(Key, Value, Bullet#{x:=X + ((W - Value) div 2)}), Tail, Acc); + h -> update_bullet(maps:put(Key, Value, Bullet#{y:=Y + ((H - Value) div 2)}), Tail, Acc); + _ -> update_bullet(maps:put(Key, Value, Bullet), Tail, Acc) + end. diff --git a/examples/bullet_engine/start.sh b/examples/bullet_engine/start.sh new file mode 100755 index 0000000..42f041c --- /dev/null +++ b/examples/bullet_engine/start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +erl +stbt db -pa ../../ebin -eval "bullet_engine:run()." |