diff options
author | Loïc Hoguin <[email protected]> | 2014-04-02 14:09:18 +0200 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2014-04-02 14:09:18 +0200 |
commit | 77d0e9d9ca8ed451a40f9b54f20365727ad76f5e (patch) | |
tree | 018650dcdf59e7976a1ae018754fd04d4a583ee0 /examples/bullet_engine/bullet_engine.erl | |
parent | 608acbb03f976b0fbf23877d8b4b6ad7529e1d53 (diff) | |
download | esdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.tar.gz esdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.tar.bz2 esdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.zip |
Add a bullet engine example
A function sdl_renderer:set_logical_size/3 has been added.
All the functions relative to the window, the renderer,
textures and events now run in a separate thread inside
the NIF. A few helper functions and macros have been
added in order to abstract this out. The code reads like
it is doing call or cast to the main thread. In the case
of call, the result is then sent back to the calling
process as a message (Erlang side catches it directly
before returning).
The functions relative to SDL init and surfaces have
not been threaded yet. It may still be needed from the
point of view of SDL or Erlang, but it seems to work
fine as it is so they were left alone for now.
The bullet example originally came from my submission to
Spawnfest 2011, and has been reactualized to work with
a modern Erlang, and SDL2.
Diffstat (limited to 'examples/bullet_engine/bullet_engine.erl')
-rw-r--r-- | examples/bullet_engine/bullet_engine.erl | 251 |
1 files changed, 251 insertions, 0 deletions
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. |