### Run ECSx Setup Generator Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/Installation.md Execute this command after installing ECSx to run the setup generator, which creates necessary files and folders for your project. ```console $ mix ecsx.setup ``` -------------------------------- ### Create New Phoenix Project Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/Installation.md Use this command to create a new Elixir/Phoenix project. Assumes Phoenix is installed. ```console $ mix phx.new my_app ``` -------------------------------- ### Ship Manager Module Definition Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Defines the Ship.Manager module, inheriting from ECSx.Manager and outlining key functions for setup, startup, components, and systems. ```elixir defmodule Ship.Manager do ... use ECSx.Manager def setup do ... end def startup do ... end def components do ... end def systems do ... end end ``` -------------------------------- ### Update Component Read/Write API from get_one to get Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/upgrade_guide.md In ECSx versions 0.4 and later, `MyComponent.get_one/1` has been renamed to `MyComponent.get/1`. This change applies to how you retrieve single component instances. ```elixir MyComponent.get(entity) ``` -------------------------------- ### Initialize Components from Database in ECSx Manager Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/common_caveats.md Use `ECSx.Manager.setup/1` to load data from a database at application startup and create components. This avoids slow database queries within game systems. ```elixir defmodule MyApp.Manager do use ECSx.Manager alias MyApp.Components.Height alias MyApp.Components.XPosition alias MyApp.Components.YPosition alias MyApp.Trees alias MyApp.Trees.Tree setup do for %Tree{id: id, x: x, y: y, height: height} <- Trees.my_db_query() do XPosition.add(id, x) YPosition.add(id, y) Height.add(id, height) end end ... end ``` -------------------------------- ### Initialize Game State and Player in LiveView Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Configures the initial state of the game, including player entity, screen dimensions, and loading status. It also initiates the first load sequence for game elements. ```elixir defmodule ShipWeb.GameLive do ... alias Ship.Components.ImageFile ... def mount(_params, %{"player_token" => token} = _session, socket) do player = Ship.Players.get_player_by_session_token(token) socket = socket |> assign(player_entity: player.id) |> assign(keys: MapSet.new()) # These will configure the scale of our display compared to the game world |> assign(game_world_size: 100, screen_height: 30, screen_width: 50) |> assign_loading_state() if connected?(socket) do ECSx.ClientEvents.add(player.id, :spawn_ship) send(self(), :first_load) end {:ok, socket} end defp assign_loading_state(socket) do assign(socket, x_coord: nil, y_coord: nil, current_hp: nil, player_ship_image_file: nil, other_ships: [], x_offset: 0, y_offset: 0, loading: true ) end def handle_info(:first_load, socket) do :ok = wait_for_spawn(socket.assigns.player_entity) socket = socket |> assign_player_ship() |> assign_other_ships() |> assign_offsets() |> assign(loading: false) :timer.send_interval(50, :refresh) {:noreply, socket} end def handle_info(:refresh, socket) do socket = socket |> assign_player_ship() |> assign_other_ships() |> assign_offsets() {:noreply, socket} end defp wait_for_spawn(player_entity) do if PlayerSpawned.exists?(player_entity) do :ok else Process.sleep(10) wait_for_spawn(player_entity) end end defp assign_player_ship(socket) do x = XPosition.get(socket.assigns.player_entity) y = YPosition.get(socket.assigns.player_entity) hp = HullPoints.get(socket.assigns.player_entity) image = ImageFile.get(socket.assigns.player_entity) assign(socket, x_coord: x, y_coord: y, current_hp: hp, player_ship_image_file: image) end defp assign_other_ships(socket) do other_ships = Enum.reject(all_ships(), fn {entity, _, _, _} -> entity == socket.assigns.player_entity end) assign(socket, other_ships: other_ships) end defp all_ships do for {ship, _hp} <- HullPoints.get_all() do x = XPosition.get(ship) y = YPosition.get(ship) image = ImageFile.get(ship) {ship, x, y, image} end end defp assign_offsets(socket) do # Note: the socket must already have updated player coordinates before assigning offsets! %{screen_width: screen_width, screen_height: screen_height} = socket.assigns %{x_coord: x, y_coord: y, game_world_size: game_world_size} = socket.assigns x_offset = calculate_offset(x, screen_width, game_world_size) y_offset = calculate_offset(y, screen_height, game_world_size) assign(socket, x_offset: x_offset, y_offset: y_offset) end defp calculate_offset(coord, screen_size, game_world_size) do case coord - div(screen_size, 2) do offset when offset < 0 -> 0 offset when offset > game_world_size - screen_size -> game_world_size - screen_size offset -> offset end end def handle_event("keydown", %{"key" => key}, socket) do ... end ``` -------------------------------- ### Fetch Project Dependencies Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/Installation.md After adding ECSx to your dependencies, run this command from your project's root directory to fetch all dependencies. ```console $ mix deps.get ``` -------------------------------- ### Create New Elixir Project with Supervision Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/Installation.md Use this command to create a new Elixir project with a supervision tree. Useful for standalone Elixir applications. ```console $ mix new my_app --sup ``` -------------------------------- ### Add ImageFile Component on Ship Startup Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Adds the 'npc_ship.svg' ImageFile component to entities during the ship startup process. ```elixir defmodule Ship.Manager do ... def startup do for _ships <- 1..40 do ... Ship.Components.ImageFile.add(entity, "npc_ship.svg") end end ... end ``` -------------------------------- ### Update GameLive Mount and HandleInfo Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Replaces the existing mount and handle_info functions in ShipWeb.GameLive. It initializes player data, assigns loading state, and triggers the first load sequence after the player has spawned. ```elixir defmodule ShipWeb.GameLive do ... alias Ship.Components.PlayerSpawned ... def mount(_params, %{"player_token" => token} = _session, socket) do player = Ship.Players.get_player_by_session_token(token) socket = socket |> assign(player_entity: player.id) |> assign(keys: MapSet.new()) # This gets its own helper in case we need to return to this state again later |> assign_loading_state() if connected?(socket) do ECSx.ClientEvents.add(player.id, :spawn_ship) # The first load will now have additional responsibilities send(self(), :first_load) end {:ok, socket} end defp assign_loading_state(socket) do assign(socket, x_coord: nil, y_coord: nil, current_hp: nil, # This new assign will control whether the loading screen is shown loading: true ) end def handle_info(:first_load, socket) do # Don't start fetching components until after spawn is complete! :ok = wait_for_spawn(socket.assigns.player_entity) socket = socket |> assign_player_ship() |> assign(loading: false) # We want to keep up-to-date on this info :timer.send_interval(50, :refresh) {:noreply, socket} end def handle_info(:refresh, socket) do {:noreply, assign_player_ship(socket)} end defp wait_for_spawn(player_entity) do if PlayerSpawned.exists?(player_entity) do :ok else Process.sleep(10) wait_for_spawn(player_entity) end end # Our previous :load_player_info handler becomes a shared helper for the new handlers defp assign_player_ship(socket) do x = XPosition.get(socket.assigns.player_entity) y = YPosition.get(socket.assigns.player_entity) hp = HullPoints.get(socket.assigns.player_entity) assign(socket, x_coord: x, y_coord: y, current_hp: hp) end def handle_event("keydown", %{"key" => key}, socket) do ... ``` -------------------------------- ### Adding Components to an Entity Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/ecs_design.md Demonstrates how to add initial components to an entity. This is a basic way to assign attributes like weapons or shields. ```elixir Weapon.add(hero_entity, "Longsword") Shield.add(hero_entity, "Buckler") ``` -------------------------------- ### Generate System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Use this command to generate a new system module. This creates the basic structure for organizing game logic. ```bash $ mix ecsx.gen.system Driver ``` -------------------------------- ### Initializing Ship Components Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates forty unique entities and adds various ship components to each, including armor, damage, range, speed, position, and velocity. ```elixir def startup do for _ships <- 1..40 do # First generate a unique ID to represent the new entity entity = Ecto.UUID.generate() # Then use that ID to create the components which make up a ship Ship.Components.ArmorRating.add(entity, 0) Ship.Components.AttackDamage.add(entity, 5) Ship.Components.AttackRange.add(entity, 10) Ship.Components.AttackSpeed.add(entity, 1.05) Ship.Components.HullPoints.add(entity, 50) Ship.Components.SeekingTarget.add(entity) Ship.Components.XPosition.add(entity, Enum.random(1..100)) Ship.Components.YPosition.add(entity, Enum.random(1..100)) Ship.Components.XVelocity.add(entity, 0) Ship.Components.YVelocity.add(entity, 0) end end ``` -------------------------------- ### Render Game Entities with SVG in LiveView Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This snippet shows how to render the game world using SVG within a LiveView template. It includes setting up the viewBox for camera control, rendering a background rectangle, displaying a loading message, and conditionally rendering player and other ships as images. It also shows how to display player Hull Points. ```elixir defmodule ShipWeb.GameLive do ... def render(assigns) do ~H"""
<%= if @loading do %> Loading... <% else %> <%= for {_entity, x, y, image_file} <- @other_ships do %> <% end %> Hull Points: <%= @current_hp %> <% end %>
""" end end ``` -------------------------------- ### Generate ECS Components, Systems, and Tags Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Commands to generate the necessary components, systems, and tags for projectile functionality. ```bash $ mix ecsx.gen.component ProjectileTarget binary mix ecsx.gen.component ProjectileDamage integer mix ecsx.gen.system Projectile mix ecsx.gen.tag IsProjectile ``` -------------------------------- ### Generate Player Authentication Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Use the Phoenix Auth generator to set up player authentication and manage player data. This command expects players to register with an email and password and creates a unique ID for each player. ```bash $ mix phx.gen.auth Players Player players --binary-id $ mix deps.get $ mix ecto.migrate ``` -------------------------------- ### Game LiveView Module Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This module handles the game's live view, managing player authentication, spawning ships, and processing player input for movement. It uses ECSx.ClientEvents to communicate frontend actions to the backend. ```elixir defmodule ShipWeb.GameLive do use ShipWeb, :live_view alias Ship.Components.HullPoints alias Ship.Components.XPosition alias Ship.Components.YPosition def mount(_params, %{"player_token" => token} = _session, socket) do # This context function was generated by phx.gen.auth player = Ship.Players.get_player_by_session_token(token) socket = socket |> assign(player_entity: player.id) # Keeping a set of currently held keys will allow us to prevent duplicate keydown events |> assign(keys: MapSet.new()) # We don't know where the ship will spawn, yet |> assign(x_coord: nil, y_coord: nil, current_hp: nil) # We don't want these calls to be made on both the initial static page render and again after # the LiveView is connected, so we wrap them in `connected?/1` to prevent duplication if connected?(socket) do ECSx.ClientEvents.add(player.id, :spawn_ship) :timer.send_interval(50, :load_player_info) end {:ok, socket} end def handle_info(:load_player_info, socket) do # This will run every 50ms to keep the client assigns updated x = XPosition.get(socket.assigns.player_entity) y = YPosition.get(socket.assigns.player_entity) hp = HullPoints.get(socket.assigns.player_entity) {:noreply, assign(socket, x_coord: x, y_coord: y, current_hp: hp)} end def handle_event("keydown", %{"key" => key}, socket) do if MapSet.member?(socket.assigns.keys, key) do # Already holding this key - do nothing {:noreply, socket} else # We only want to add a client event if the key is defined by the `keydown/1` helper below maybe_add_client_event(socket.assigns.player_entity, key, &keydown/1) {:noreply, assign(socket, keys: MapSet.put(socket.assigns.keys, key))} end end def handle_event("keyup", %{"key" => key}, socket) do # We don't have to worry about duplicate keyup events # But once again, we will only add client events for keys that actually do something maybe_add_client_event(socket.assigns.player_entity, key, &keyup/1) {:noreply, assign(socket, keys: MapSet.delete(socket.assigns.keys, key))} end defp maybe_add_client_event(player_entity, key, fun) do case fun.(key) do :noop -> :ok event -> ECSx.ClientEvents.add(player_entity, event) end end defp keydown(key) when key in ~w(w W ArrowUp), do: {:move, :north} defp keydown(key) when key in ~w(a A ArrowLeft), do: {:move, :west} defp keydown(key) when key in ~w(s S ArrowDown), do: {:move, :south} defp keydown(key) when key in ~w(d D ArrowRight), do: {:move, :east} defp keydown(_key), do: :noop defp keyup(key) when key in ~w(w W ArrowUp), do: {:stop_move, :north} defp keyup(key) when key in ~w(a A ArrowLeft), do: {:stop_move, :west} defp keyup(key) when key in ~w(s S ArrowDown), do: {:stop_move, :south} defp keyup(key) when key in ~w(d D ArrowRight), do: {:stop_move, :east} defp keyup(_key), do: :noop def render(assigns) do ~H"""

Player ID: <%= @player_entity %>

Player Coords: <%= inspect({@x_coord, @y_coord}) %>

Hull Points: <%= @current_hp %>

""" end end ``` -------------------------------- ### Client Event Handler System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This system processes events sent from the LiveView client. It handles spawning ships with specific stats and processing player movement commands. ```elixir defmodule Ship.Systems.ClientEventHandler do ... @behaviour ECSx.System alias Ship.Components.ArmorRating alias Ship.Components.AttackDamage alias Ship.Components.AttackRange alias Ship.Components.AttackSpeed alias Ship.Components.HullPoints alias Ship.Components.SeekingTarget alias Ship.Components.XPosition alias Ship.Components.XVelocity alias Ship.Components.YPosition alias Ship.Components.YVelocity @impl ECSx.System def run do client_events = ECSx.ClientEvents.get_and_clear() Enum.each(client_events, &process_one/1) end defp process_one({player, :spawn_ship}) do # We'll give player ships better stats than the enemy ships # (otherwise the game would be very short!) ArmorRating.add(player, 2) AttackDamage.add(player, 6) AttackRange.add(player, 15) AttackSpeed.add(player, 1.2) HullPoints.add(player, 75) SeekingTarget.add(player) XPosition.add(player, Enum.random(1..100)) YPosition.add(player, Enum.random(1..100)) XVelocity.add(player, 0) YVelocity.add(player, 0) end # Note Y movement will use screen position (increasing Y goes south) defp process_one({player, {:move, :north}}), do: YVelocity.update(player, -1) defp process_one({player, {:move, :south}}), do: YVelocity.update(player, 1) defp process_one({player, {:move, :east}}), do: XVelocity.update(player, 1) defp process_one({player, {:move, :west}}), do: XVelocity.update(player, -1) defp process_one({player, {:stop_move, :north}}), do: YVelocity.update(player, 0) defp process_one({player, {:stop_move, :south}}), do: YVelocity.update(player, 0) defp process_one({player, {:stop_move, :east}}), do: XVelocity.update(player, 0) defp process_one({player, {:stop_move, :west}}), do: XVelocity.update(player, 0) end ``` -------------------------------- ### Add Player Ship ImageFile Component on Spawn Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Adds the 'player_ship.svg' ImageFile component to a player entity when it spawns. ```elixir defmodule Ship.Systems.ClientEventHandler do ... alias Ship.Components.ImageFile ... defp process_one({player, :spawn_ship}) do ... ImageFile.add(player, "player_ship.svg") PlayerSpawned.add(player) end ... end ``` -------------------------------- ### Generate Integer Component Type Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Use this command to generate a new integer component type for your game entities. This sets up the necessary module and interface for handling this component. ```bash $ mix ecsx.gen.component HullPoints integer ``` ```bash $ mix ecsx.gen.component ArmorRating integer ``` ```bash $ mix ecsx.gen.component AttackDamage integer ``` ```bash $ mix ecsx.gen.component AttackRange integer ``` ```bash $ mix ecsx.gen.component XPosition integer ``` ```bash $ mix ecsx.gen.component YPosition integer ``` ```bash $ mix ecsx.gen.component XVelocity integer ``` ```bash $ mix ecsx.gen.component YVelocity integer ``` -------------------------------- ### Targeting System Implementation Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md The Targeting system iterates through entities with SeekingTarget, finds the closest valid enemy within attack range, and replaces SeekingTarget with AttackTarget. ```elixir defmodule Ship.Systems.Targeting do ... @behaviour ECSx.System alias Ship.Components.AttackRange alias Ship.Components.AttackTarget alias Ship.Components.HullPoints alias Ship.Components.SeekingTarget alias Ship.SystemUtils @impl ECSx.System def run do entities = SeekingTarget.get_all() Enum.each(entities, &attempt_target/1) end defp attempt_target(self) do case look_for_target(self) do nil -> :noop {target, _hp} -> add_target(self, target) end end defp look_for_target(self) do # For now, we're assuming anything which has HullPoints can be attacked HullPoints.get_all() # ... except your own ship! |> Enum.reject(fn {possible_target, _hp} -> possible_target == self end) |> Enum.find(fn {possible_target, _hp} -> distance_between = SystemUtils.distance_between(possible_target, self) range = AttackRange.get(self) distance_between < range end) end defp add_target(self, target) do SeekingTarget.remove(self) AttackTarget.add(self, target) end end ``` -------------------------------- ### Configure Persistence Adapter Source: https://github.com/ecsx-framework/ecsx/blob/master/README.md Configure the persistence adapter for ECSx by setting the 'persistence_adapter' application variable in your config.exs. ```elixir config :ecsx, ... persistence_adapter: ... ``` -------------------------------- ### Generate AttackTarget and AttackCooldown Components Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generate components to store the target entity's ID and the cooldown expiration datetime. Adjust types (binary, datetime) as needed for your project. ```bash $ mix ecsx.gen.component AttackTarget binary $ mix ecsx.gen.component AttackCooldown datetime ``` -------------------------------- ### Render GameLive with Loading State Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Updates the render function in ShipWeb.GameLive to conditionally display a 'Loading...' message or the player's game information based on the `@loading` assign. This controls the visibility of the loading screen. ```elixir def render(assigns) do ~H"""
<%= if @loading do %>

Loading...

<% else %>

Player ID: <%= @player_entity %>

Player Coords: <%= inspect({@x_coord, @y_coord}) %>

Hull Points: <%= @current_hp %>

<% end %>
""" end ``` -------------------------------- ### Elixir System for Ship Targeting and Attacking Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md This Elixir module implements an ECSx system for handling ship attacks. It iterates through entities with attack targets, checks range and cooldowns, and deals damage. Use this as a foundation for your game's combat mechanics. ```elixir defmodule Ship.Systems.Attacking do ... @behaviour ECSx.System alias Ship.Components.ArmorRating alias Ship.Components.AttackCooldown alias Ship.Components.AttackDamage alias Ship.Components.AttackRange alias Ship.Components.AttackSpeed alias Ship.Components.AttackTarget alias Ship.Components.HullPoints alias Ship.Components.SeekingTarget alias Ship.SystemUtils @impl ECSx.System def run do attack_targets = AttackTarget.get_all() Enum.each(attack_targets, &attack_if_ready/1) end defp attack_if_ready({self, target}) do cond do SystemUtils.distance_between(self, target) > AttackRange.get(self) -> # If the target ever leaves our attack range, we want to remove the AttackTarget # and begin searching for a new one. AttackTarget.remove(self) SeekingTarget.add(self) AttackCooldown.exists?(self) -> # We're still within range, but waiting on the cooldown :noop :otherwise -> deal_damage(self, target) add_cooldown(self) end end defp deal_damage(self, target) do attack_damage = AttackDamage.get(self) # Assuming one armor rating always equals one damage reduction_from_armor = ArmorRating.get(target) final_damage_amount = attack_damage - reduction_from_armor target_current_hp = HullPoints.get(target) target_new_hp = target_current_hp - final_damage_amount HullPoints.update(target, target_new_hp) end defp add_cooldown(self) do now = DateTime.utc_now() ms_between_attacks = calculate_cooldown_time(self) cooldown_until = DateTime.add(now, ms_between_attacks, :millisecond) AttackCooldown.add(self, cooldown_until) end # We're going to model AttackSpeed with a float representing attacks per second. # The goal here is to convert that into milliseconds per attack. defp calculate_cooldown_time(self) do attacks_per_second = AttackSpeed.get(self) seconds_per_attack = 1 / attacks_per_second ceil(seconds_per_attack * 1000) end end ``` -------------------------------- ### Generate SeekingTarget Tag Component Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Use this command to generate a Tag component for entities that are seeking a target. No additional data is needed for this component. ```bash $ mix ecsx.gen.tag SeekingTarget ``` -------------------------------- ### Generate Targeting System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates the Targeting system, which finds and assigns targets to entities possessing the SeekingTarget component. ```bash $ mix ecsx.gen.system Targeting ``` -------------------------------- ### Render Projectiles and Ships in LiveView Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This snippet shows how to iterate over lists of projectiles and other ships to render them as image elements within a LiveView template. It uses Elixir's `~H` sigil for HTML templating. ```elixir defmodule Ship.GameLive do def render(assigns) do ~H""" ... <%= for {_entity, x, y, image_file} <- @projectiles do %> <% end %> <%= for {_entity, x, y, image_file} <- @other_ships do %> ... """ end end ``` -------------------------------- ### Generate Float Component Type Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Use this command to generate a new float component type for your game entities. This sets up the necessary module and interface for handling this component. ```bash $ mix ecsx.gen.component AttackSpeed float ``` -------------------------------- ### Generate Attacking System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates the Attacking system, responsible for executing attacks on assigned targets if they are still in range and not on cooldown. ```bash $ mix ecsx.gen.system Attacking ``` -------------------------------- ### Define Game Route in Phoenix Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Add a new live route for the game interface within the router configuration. This route requires players to be authenticated before accessing the game. ```elixir scope "/", ShipWeb do pipe_through [:browser, :require_authenticated_player] live_session :require_authenticated_player, on_mount: [{ShipWeb.PlayerAuth, :ensure_authenticated}] do live "/game", GameLive ... end end ``` -------------------------------- ### Representing Complex Enchantments as Entities Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/ecs_design.md Demonstrates how to model complex or multiple enchantments as separate entities, linking them to the target item. This provides a flexible way to handle arbitrary enchantment details. ```elixir enchantment_entity = Ecto.UUID.generate() Description.add(enchantment_entity, "Firaga") EnchantTarget.add(enchantment_entity, sword_entity) ``` -------------------------------- ### Add ECSx Dependency Source: https://github.com/ecsx-framework/ecsx/blob/master/README.md Add the ECSx dependency to your project's mix.exs file to include the framework. ```elixir def deps do [ {:ecsx, "~> 0.5"} ] end ``` -------------------------------- ### Generate Destruction System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates a system to handle entity destruction. This system removes components and updates targeting when an entity's HP drops to zero or below. ```elixir defmodule Ship.Systems.Destruction do ... @behaviour ECSx.System alias Ship.Components.ArmorRating alias Ship.Components.AttackCooldown alias Ship.Components.AttackDamage alias Ship.Components.AttackRange alias Ship.Components.AttackSpeed alias Ship.Components.AttackTarget alias Ship.Components.DestroyedAt alias Ship.Components.HullPoints alias Ship.Components.SeekingTarget alias Ship.Components.XPosition alias Ship.Components.XVelocity alias Ship.Components.YPosition alias Ship.Components.YVelocity @impl ECSx.System def run do ships = HullPoints.get_all() Enum.each(ships, fn {entity, hp} -> if hp <= 0, do: destroy(entity) end) end defp destroy(ship) do ArmorRating.remove(ship) AttackCooldown.remove(ship) AttackDamage.remove(ship) AttackRange.remove(ship) AttackSpeed.remove(ship) AttackTarget.remove(ship) HullPoints.remove(ship) SeekingTarget.remove(ship) XPosition.remove(ship) XVelocity.remove(ship) YPosition.remove(ship) YVelocity.remove(ship) # when a ship is destroyed, other ships should stop targeting it untarget(ship) DestroyedAt.add(ship, DateTime.utc_now()) end defp untarget(target) do for ship <- AttackTarget.search(target) do AttackTarget.remove(ship) SeekingTarget.add(ship) end end end ``` -------------------------------- ### Add PlayerSpawned Tag in ClientEventHandler Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Integrates the PlayerSpawned tag by adding it to the player entity at the end of the :spawn_ship client event processing. This signals that the ship has successfully spawned. ```elixir defmodule Ship.Systems.ClientEventHandler do ... alias Ship.Components.PlayerSpawned ... defp process_one({player, :spawn_ship}) do ... PlayerSpawned.add(player) end ... end ``` -------------------------------- ### Creating Separate Entities for Associated Items Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/ecs_design.md Illustrates creating new entities to represent associated items like swords, linking them back to the owner entity. This approach supports multiple items and complex attributes. ```elixir sword_entity = Ecto.UUID.generate() Description.add(sword_entity, "Longsword") EquippedBy.add(sword_entity, hero_entity) ``` ```elixir another_sword_entity = Ecto.UUID.generate() Description.add(another_sword_entity, "Shortsword") EquippedBy.add(another_sword_entity, hero_entity) ``` -------------------------------- ### Update Attacking System to Spawn Projectiles Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Modifies the Attacking system to spawn a projectile entity instead of dealing damage directly. This involves replacing the `deal_damage/2` function with `spawn_projectile/2`. ```elixir defmodule Ship.Systems.Attacking do ... @behaviour ECSx.System alias Ship.Components.AttackCooldown alias Ship.Components.AttackDamage alias Ship.Components.AttackRange alias Ship.Components.AttackSpeed alias Ship.Components.AttackTarget alias Ship.Components.ImageFile alias Ship.Components.IsProjectile alias Ship.Components.ProjectileDamage alias Ship.Components.ProjectileTarget alias Ship.Components.SeekingTarget alias Ship.Components.XPosition alias Ship.Components.XVelocity alias Ship.Components.YPosition alias Ship.Components.YVelocity alias Ship.SystemUtils ... defp attack_if_ready({self, target}) do cond do ... :otherwise -> spawn_projectile(self, target) add_cooldown(self) end end defp spawn_projectile(self, target) do attack_damage = AttackDamage.get(self) x = XPosition.get(self) y = YPosition.get(self) # Armor reduction should wait until impact to be calculated cannonball_entity = Ecto.UUID.generate() IsProjectile.add(cannonball_entity) XPosition.add(cannonball_entity, x) YPosition.add(cannonball_entity, y) XVelocity.add(cannonball_entity, 0) YVelocity.add(cannonball_entity, 0) ImageFile.add(cannonball_entity, "cannonball.svg") ProjectileTarget.add(cannonball_entity, target) ProjectileDamage.add(cannonball_entity, attack_damage) end ... end ``` -------------------------------- ### Generate Cooldown Expiration System Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates a new system for managing cooldown expirations. This system is useful for time-based component removal. ```elixir defmodule Ship.Systems.CooldownExpiration do ... @behaviour ECSx.System alias Ship.Components.AttackCooldown @impl ECSx.System def run do now = DateTime.utc_now() cooldowns = AttackCooldown.get_all() Enum.each(cooldowns, &remove_when_expired(&1, now)) end defp remove_when_expired({entity, timestamp}, now) do case DateTime.compare(now, timestamp) do :lt -> :noop _ -> AttackCooldown.remove(entity) end end end ``` -------------------------------- ### Create a Basic SVG Circle Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This HTML code defines a basic SVG circle. The `width` and `height` attributes will be overridden by LiveView, but they influence the relative sizing of SVG elements like the circle's center and radius. ```html ``` -------------------------------- ### Implement Driver System for Position Update Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md This Elixir code implements the 'Driver' system, which updates the X and Y positions of entities based on their respective velocities. It iterates through all entities with velocity components and applies the positional changes. ```elixir defmodule Ship.Systems.Driver do ... @behaviour ECSx.System alias Ship.Components.XPosition alias Ship.Components.YPosition alias Ship.Components.XVelocity alias Ship.Components.YVelocity @impl ECSx.System def run do for {entity, x_velocity} <- XVelocity.get_all() do x_position = XPosition.get(entity) new_x_position = x_position + x_velocity XPosition.update(entity, new_x_position) end # Once the x-values are updated, do the same for the y-values for {entity, y_velocity} <- YVelocity.get_all() do y_position = YPosition.get(entity) new_y_position = y_position + y_velocity YPosition.update(entity, new_y_position) end end end ``` -------------------------------- ### Generate Destruction Component Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md Generates a new component to mark entities with a destruction timestamp. This is useful for tracking when an entity was destroyed. ```elixir mix ecsx.gen.component DestroyedAt datetime ``` -------------------------------- ### Add ECSx Dependency to Mix Project Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/Installation.md Add the ECSx framework to your project's dependencies by including it in the `deps` function in `mix.exs`. ```elixir defp deps do [ {:ecsx, "~> 0.5"} ] end ``` -------------------------------- ### Update Component Update API from add to update Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/upgrade_guide.md For unique components in ECSx versions 0.3.x to 0.4, use `MyComponent.update(entity, value)` instead of `MyComponent.add(entity, value)` for updating existing component values. If `add/2` was used for both creation and updates, separate these into distinct `add/2` for creation and `update/2` for updates. ```elixir MyComponent.update(entity, value) ``` -------------------------------- ### Update Destruction System for Projectile Targets Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Extends the `Destruction` system to include an `untarget` feature for `ProjectileTarget` components. This ensures projectiles are properly de-referenced when their target is destroyed. ```elixir defmodule Ship.Systems.Destruction do ... alias Ship.Components.ProjectileTarget ... defp untarget(target) do for ship <- AttackTarget.search(target) do AttackTarget.remove(ship) SeekingTarget.add(ship) end for projectile <- ProjectileTarget.search(target) do ProjectileTarget.remove(projectile) end end end ``` -------------------------------- ### Update Component Read/Write API for Optional Components Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/upgrade_guide.md In ECSx versions 0.3.x to 0.4, calls to `MyComponent.get_one(entity)` that might return `nil` should now explicitly include `nil` as the second argument: `MyComponent.get_one(entity, nil)`. ```elixir MyComponent.get_one(entity, nil) ``` -------------------------------- ### Adding Attributes to Associated Entities Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/ecs_design.md Shows how to add specific attributes, such as durability, to entities that represent associated items. This allows for detailed item management. ```elixir Durability.add(sword_entity, 150) Durability.add(another_sword_entity, 75) ``` -------------------------------- ### Assign Projectiles to LiveView State Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Adds a `projectiles` assign to the LiveView socket, populating it with data from all active projectiles. This is used for rendering projectiles on the frontend. ```elixir defmodule Ship.GameLive do ... alias Ship.Components.IsProjectile ... defp assign_loading_state(socket) do assign(socket, ... projectiles: [] ) end def handle_info(:first_load, socket) do ... socket = socket |> assign_player_ship() |> assign_other_ships() |> assign_projectiles() |> assign_offsets() |> assign(loading: false) ... end def handle_info(:refresh, socket) do socket = socket |> assign_player_ship() |> assign_other_ships() |> assign_projectiles() |> assign_offsets() ... end ... defp assign_projectiles(socket) do projectiles = for projectile <- IsProjectile.get_all() do x = XPosition.get(projectile) y = YPosition.get(projectile) image = ImageFile.get(projectile) {projectile, x, y, image} end assign(socket, projectiles: projectiles) end ... end ``` -------------------------------- ### Projectile System Logic Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Defines the ECSx system for managing projectile behavior, including seeking targets, moving, and handling collisions. It determines projectile actions based on distance to the target and applies damage upon impact. ```elixir defmodule Ship.Systems.Projectile do ... @behaviour ECSx.System alias Ship.Components.ArmorRating alias Ship.Components.HullPoints alias Ship.Components.ImageFile alias Ship.Components.IsProjectile alias Ship.Components.ProjectileDamage alias Ship.Components.ProjectileTarget alias Ship.Components.XPosition alias Ship.Components.XVelocity alias Ship.Components.YPosition alias Ship.Components.YVelocity @cannonball_speed 3 @impl ECSx.System def run do projectiles = IsProjectile.get_all() Enum.each(projectiles, fn projectile -> case ProjectileTarget.get(projectile, nil) do nil -> # The target has already been destroyed destroy_projectile(projectile) target -> continue_seeking_target(projectile, target) end end) end defp continue_seeking_target(projectile, target) do {dx, dy, distance} = get_distance_to_target(projectile, target) case distance do 0 -> collision(projectile, target) distance when distance / @cannonball_speed <= 1 -> move_directly_to_target(projectile, {dx, dy}) distance -> adjust_velocity_towards_target(projectile, {distance, dx, dy}) end end defp get_distance_to_target(projectile, target) do target_x = XPosition.get(target) target_y = YPosition.get(target) target_dx = XVelocity.get(target) target_dy = YVelocity.get(target) target_next_x = target_x + target_dx target_next_y = target_y + target_dy x = XPosition.get(projectile) y = YPosition.get(projectile) dx = target_next_x - x dy = target_next_y - y {dx, dy, ceil(:math.sqrt(dx ** 2 + dy ** 2))} end defp collision(projectile, target) do damage_target(projectile, target) destroy_projectile(projectile) end defp damage_target(projectile, target) do damage = ProjectileDamage.get(projectile) reduction_from_armor = ArmorRating.get(target) final_damage_amount = damage - reduction_from_armor target_current_hp = HullPoints.get(target) target_new_hp = target_current_hp - final_damage_amount HullPoints.update(target, target_new_hp) end defp destroy_projectile(projectile) do IsProjectile.remove(projectile) XPosition.remove(projectile) YPosition.remove(projectile) XVelocity.remove(projectile) YVelocity.remove(projectile) ImageFile.remove(projectile) ProjectileTarget.remove(projectile) ProjectileDamage.remove(projectile) end defp move_directly_to_target(projectile, {dx, dy}) do XVelocity.update(projectile, dx) YVelocity.update(projectile, dy) end defp adjust_velocity_towards_target(projectile, {distance, dx, dy}) do # We know what is needed, but we need to slow it down, so its travel # will take more than one tick. Otherwise the player will not see it! ticks_away = ceil(distance / @cannonball_speed) adjusted_dx = div(dx, ticks_away) adjusted_dy = div(dy, ticks_away) XVelocity.update(projectile, adjusted_dx) YVelocity.update(projectile, adjusted_dy) end end ``` -------------------------------- ### Add PlayerSpawned Tag Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md Generates a new ECSx tag named PlayerSpawned using the mix ecsx.gen.tag command. This tag will be used to mark when a player's ship has finished spawning. ```bash $ mix ecsx.gen.tag PlayerSpawned ``` -------------------------------- ### Limit Ship Movement within Map Boundaries Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/web_frontend_liveview.md This Elixir code limits the player ship's position to be between 0 and 99 on both the X and Y axes. It ensures the ship stays within the 100x100 world map boundaries. ```elixir defmodule Ship.Systems.Driver do ... def run do for {entity, x_velocity} <- XVelocity.get_all() do ... new_x_position = calculate_new_position(x_position, x_velocity) ... end for {entity, y_velocity} <- YVelocity.get_all() do ... new_y_position = calculate_new_position(y_position, y_velocity) ... end ... end # Do not let player ship move past the map limit defp calculate_new_position(current_position, velocity) do new_position = current_position + velocity new_position = Enum.min([new_position, 99]) Enum.max([new_position, 0]) end end ``` -------------------------------- ### Calculate Distance Between Entities Source: https://github.com/ecsx-framework/ecsx/blob/master/guides/tutorial/backend_basics.md A utility function to calculate the Euclidean distance between two entities using their X and Y position components. This is a shared mathematical helper for systems. ```elixir defmodule Ship.SystemUtils do @moduledoc """ Useful math functions used by multiple systems. """ alias Ship.Components.XPosition alias Ship.Components.YPosition def distance_between(entity_1, entity_2) do x_1 = XPosition.get(entity_1) x_2 = XPosition.get(entity_2) y_1 = YPosition.get(entity_1) y_2 = YPosition.get(entity_2) x = abs(x_1 - x_2) y = abs(y_1 - y_2) :math.sqrt(x ** 2 + y ** 2) end end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.