Make dsl design more ecs-like
This commit is contained in:
+78
-48
@@ -1,14 +1,15 @@
|
||||
# Sample ruby DSL to create a game window.
|
||||
# reference of what the engine should build up to.
|
||||
|
||||
width, height = 720, 480
|
||||
image = Image.new "assets/loading.png"
|
||||
|
||||
window = Window.new width, height, title: "My Game", loading_bg: image
|
||||
|
||||
window.map_key :forward, "w"
|
||||
window.map_key :backward, "s"
|
||||
window.map_key :left, "a"
|
||||
window.map_key :right, "d"
|
||||
window.map_key ?w, :forward
|
||||
window.map_key ?s, :backward
|
||||
window.map_key ?a, :left
|
||||
window.map_key ?d, :right
|
||||
|
||||
menu_scene = Scene.new
|
||||
|
||||
@@ -28,26 +29,29 @@ world = ElementWorld.new
|
||||
|
||||
# world.cam # camera object to control the view of the world, has position, zoom, rotation
|
||||
|
||||
# can define one of 5 types of components connectable to any entity
|
||||
# can define any components with name and groups it is part of, systems will apply behaviour based on their groups
|
||||
# there are 5 inbuilt groups that have systems written for them, but you can create more component groups and systems for whatever you want
|
||||
# these groups are unique: true, meaning only on of it can be added to an entity and can be accesed using entity.group_name
|
||||
|
||||
world.create_body, :sample_body do |entity|
|
||||
world.new_component :sample_body, [:body] do
|
||||
# these properties exist in the dsl, and are used by the physics engine, but you can also add any custom properties you want to the body and use them in your controller code or whatever
|
||||
shape = :rectangle
|
||||
size = {width: 50, height: 50}
|
||||
angle = 0
|
||||
mass = 1 # this can be set to 0 for static bodies that should not be affected by physics, these bodies will not move but can still collide with other bodies (velocity will be ignored for static bodies)
|
||||
velocity = {x: 1, y: 0}
|
||||
collision = true # whether this body should collide with other bodies, if false it will not collide with anything and will not be affected by physics, but can still be moved by setting the position directly or by using a controller, useful for things like ghosts or whatever
|
||||
collision = true
|
||||
# whether this body should collide with other bodies, if false it will not collide with anything and will not be affected by physics, but can still be moved by setting the position directly or by using a controller, useful for things like ghosts or whatever
|
||||
end
|
||||
|
||||
world.create_sprite, :sample_sprite do |entity|
|
||||
world.new_component :sample_sprite, [:sprite] do
|
||||
tiles = Tiles.from "assets/sprite.png", tile_width: 50, tile_height: 50
|
||||
animation1 = {tiles: 5..6, frame_rate: 10, loop: true}
|
||||
animations = {idle: animation1}
|
||||
current_animation = :idle
|
||||
end
|
||||
|
||||
world.create_light, :sample_light do |entity|
|
||||
world.new_component :sample_light, [:light] do
|
||||
color = {r: 255, g: 255, b: 255, a: 255}
|
||||
intensity = 1.0
|
||||
radius = 100
|
||||
@@ -57,10 +61,10 @@ world.create_light, :sample_light do |entity|
|
||||
# it could be achived using other methods / custom properties but this is just a convenient way to do it
|
||||
end
|
||||
|
||||
world.create_render, :sample_render do |entity|
|
||||
world.new_component :sample_render, [:render] do
|
||||
# for extra rendering options like speech bubbles, health bars, etc
|
||||
z_index = 0 # by default all are y-sorted, but can override with z_index for more control in here
|
||||
renderer do |entity|
|
||||
renderer = do
|
||||
draw_rect 0, 0, 50, 50, {r: 255, g: 0, b: 0, a: 255} # example of custom rendering code for this entity (0,0 is the position of the entity)
|
||||
draw_text "Hello World", default_font, 0, 0, {r: 255, g: 255, b: 255, a: 255}
|
||||
end
|
||||
@@ -68,10 +72,7 @@ world.create_render, :sample_render do |entity|
|
||||
offset = {x: 0, y: 0}
|
||||
end
|
||||
|
||||
world.create_controller, :sample_controller do |entity|
|
||||
# event subscriptions for this entity
|
||||
events = [:damage] # custom events that can be triggered on this entity, useful for things like taking damage, opening a chest, etc
|
||||
|
||||
world.new_component :sample_controller, [:controller] do
|
||||
# these are defined in the window and mapped to the actual keys, so you can use whatever keys you want and just map them to these actions in the window
|
||||
# teh user can also remap them in the settings of the game
|
||||
keyboard_inputs = [
|
||||
@@ -87,18 +88,18 @@ world.create_controller, :sample_controller do |entity|
|
||||
# when multiple entities match the mouse input, the one with the highest priority will receive the input, useful for when you have multiple entities on top of each other and you want to control which one should receive the mouse input
|
||||
mouse_priority = 100
|
||||
# doesnt work without a body component (as they have no size)
|
||||
|
||||
update do |delta_time|
|
||||
|
||||
update = do |delta_time|
|
||||
# example of custom controller code for this entity, delta_time is the time since the last update in ms, useful for making movement frame rate independent
|
||||
if key_down?(:forward)
|
||||
# move the entity forward, you can access the body component of the entity to move it with physics, or you can set the position directly for more arcade style movement, etc
|
||||
# if the entity has a body component, you can also apply forces or impulses to it for more realistic physics interactions
|
||||
# if the entity has a sprite component, you can also change the current animation based on the input or whatever
|
||||
entity.body&.velocity.y = -1
|
||||
entity.body.velocity.x = 0
|
||||
elsif key_down?(:backward)
|
||||
entity.body&.velocity.y = 1
|
||||
entity.body.velocity.y = 1
|
||||
else
|
||||
entity.body&.velocity.y = 0
|
||||
entity.body.velocity.y = 0
|
||||
end
|
||||
|
||||
if colliding_with?([:enemies]) # list of groups to check collisions with
|
||||
@@ -110,66 +111,74 @@ world.create_controller, :sample_controller do |entity|
|
||||
puts "Mouse position relative to entity: #{mouse_position}"
|
||||
end
|
||||
end
|
||||
|
||||
on_event :damage do |amount|
|
||||
# example of custom event handling code for this entity, amount is the damage amount passed when the event is triggered, useful for things like taking damage, opening a chest, etc
|
||||
puts "Entity took #{amount} damage!"
|
||||
entity.store :health, entity.fetch(:health, 100) - amount # example of using the entity's store to keep track of custom properties like health, etc
|
||||
# these store properties cane be used in any components or at entity creation
|
||||
end
|
||||
end
|
||||
|
||||
world.add_entity "Enemy1" do |entity| # Name is optional (as entities can be referenced by group or name)
|
||||
# this is a custom compoenent group and component
|
||||
|
||||
world.define_group type: :component, name: :health, unique: false
|
||||
|
||||
world.new_component :enemy_health, [:health] do
|
||||
health = 100
|
||||
|
||||
# event subscriptions work like this, you can trigger events on the entity itself, or on the world
|
||||
on_event = {
|
||||
:damage => do |amount|
|
||||
health -= amount
|
||||
puts "Enemy took #{amount} damage, remaining health: #{health}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
world.define_group type: :entity, name: :enemies
|
||||
|
||||
world.add_entity "Enemy1" do # Name is optional (as entities can be referenced by group or name)
|
||||
groups = [:enemies]
|
||||
|
||||
position = {x: 100, y: 100} # entities always have a position, even if they don't have a body component, this is useful for rendering and for entities that don't need physics but still need to be positioned in the world
|
||||
body = :sample_body
|
||||
sprite = :sample_sprite
|
||||
render = :sample_render
|
||||
controller = :sample_controller
|
||||
|
||||
entity.store :health, 100
|
||||
components = [:sample_body, :sample_sprite, :sample_light, :sample_render, :sample_controller, :enemy_health]
|
||||
end
|
||||
|
||||
world.add_entity "Trap Preview" do |entity|
|
||||
groups = [:trap_previews]
|
||||
|
||||
world.add_entity "Trap Preview" do
|
||||
position = {x: 0, y: 0}
|
||||
sprite = :trap_sprite
|
||||
components = [:sample_sprite]
|
||||
|
||||
disabled = true # this entity will not be updated or rendered normally
|
||||
end
|
||||
|
||||
# systems are called on the world every update frame
|
||||
# systems are called on the world every update frame, some systems are built into the engine for things like physics, rendering, etc, but you can also create your own custom systems for things like trap placement, enemy spawning, etc
|
||||
|
||||
world.add_system :physics # default basic physics + collisions system
|
||||
world.add_system :health_regen do |dt|
|
||||
# example of a custom system that regenerates health for all entities with a health component, dt is the time since the last update in ms, useful for making things frame rate independent
|
||||
world.entities_query(:health).each do |entity|
|
||||
health_component = entity.healths.first # get the first health compoenent of teh entity (uses entity.group_names to access the component group - s or es added appropriately)
|
||||
health_component.health += 0.1 * dt # regenerate 0.1 health per second
|
||||
end
|
||||
end
|
||||
|
||||
world.add_system :trap_placement do |dt| # example from teh game mist trying to be made with this engine
|
||||
# do trap placement logic when a trap is held
|
||||
if world.fetch(:holding_trap, false) # example of using the world's store to keep track of global state like whether the player is currently holding a trap or not
|
||||
if mouse_down?(:left)
|
||||
# place a trap at the mouse position, you can use the mouse position relative to the world for this
|
||||
world.add_entity "Trap" do |entity|
|
||||
world.add_entity "Trap" do
|
||||
groups = [:traps]
|
||||
|
||||
position = mouse_position_world
|
||||
body = :trap_body
|
||||
sprite = :trap_sprite
|
||||
render = :trap_render
|
||||
controller = :trap_controller
|
||||
|
||||
components = [:trap_body, :trap_sprite, :trap_render, :trap_controller]
|
||||
end
|
||||
|
||||
world.store :holding_trap, false # stop placing traps until the player picks up another one
|
||||
else
|
||||
# show a preview of the trap at the mouse position, you can use the mouse position relative to the world for this
|
||||
world.entity("Trap Preview").update do |entity|
|
||||
world.entity("Trap Preview").update do
|
||||
position = mouse_position_world
|
||||
disabled = false # enable the trap preview entity to show it at the mouse position
|
||||
end
|
||||
end
|
||||
else
|
||||
# hide the trap preview when not holding a trap
|
||||
world.entity("Trap Preview").update do |entity|
|
||||
world.entity("Trap Preview").update do
|
||||
disabled = true
|
||||
end
|
||||
end
|
||||
@@ -179,7 +188,28 @@ end
|
||||
# when a bunch of enities reuse the same components, you can define the components once and then just add them to the entities, which is more efficient than defining the components for each entity separately
|
||||
# and also for example all the entites that have a body component can be packed and calculated together in the physics engine, all the entities that have a sprite component can be packed and rendered together in the rendering engine, etc.
|
||||
|
||||
world.add_shader :fog do |shader|
|
||||
# shaders are per world
|
||||
vertex "assets/fog.vert"
|
||||
fragment "assets/fog.frag"
|
||||
|
||||
data_format = {
|
||||
name: :float,
|
||||
time: :float,
|
||||
resolution: :vec2
|
||||
# data required by teh shader is defined here once
|
||||
}
|
||||
|
||||
frame do |shader|
|
||||
# example of custom shader code for this shader, this is called every frame and is where you would update the shader data based on the current state of the world or whatever
|
||||
shader.set_data :time, Time.now.to_f
|
||||
shader.set_data :resolution, {x: width, y: height}
|
||||
end
|
||||
|
||||
z = 100 # shaders always appear above normal sprites but z is used to sort them relative to each other, so a shader with z 100 will appear above a shader with z 50, but both will appear above normal sprites
|
||||
end
|
||||
|
||||
window.add_scene :menu_scene => menu_scene
|
||||
window.add_scene :game_scene => game_scene
|
||||
|
||||
window.start :menu_scene # first scene to show when the game starts stops the loading bg
|
||||
window.start :menu_scene # first scene to show when the game starts, stops the loading bg
|
||||
|
||||
Reference in New Issue
Block a user