# Sample ruby DSL to create a game window. 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" menu_scene = Scene.new default_font = Font.new "assets/DejaVuSans.ttf", 12, italic: true start_button = ElementText.new "Start Game", font: default_font, position: {x: 100, y: 100} start_button.on_click do window.start :game_scene end menu_scene.add_element start_button game_scene = Scene.new 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 world.create_body, :sample_body do |entity| # 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 end world.create_sprite, :sample_sprite do |entity| 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| color = {r: 255, g: 255, b: 255, a: 255} intensity = 1.0 radius = 100 # useful if using inbuilt fog light shader only # mostly cuz i need it though most engines wouldnt have such a feature explicitly (it is dumb addition but i added it cuz i want to) # 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| # 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| 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 # offset from the position of the entity, useful for things like speech bubbles or health bars that should be above the entity, etc 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 # 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 = [ :forward, :backward, :left, :right ] # direct means only clicks on this entity will trigger the mouse inputs, useful for like chests or doors or whatever # global means mouse inputs will be triggered as long as the mouse is within the window, useful for like aiming or whatever # none means mouse inputs will not be triggered for this entity, useful for when the entity should not be interactable with the mouse # on direct and global mouse mode, you can also get the mouse position relative to the entity or the world mouse_input_mode = :direct mouse_position_mode = :relative mouse_inputs = [:left, :right, :hover] # 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| # 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 elsif key_down?(:backward) entity.body&.velocity.y = 1 else entity.body&.velocity.y = 0 end if colliding_with?([:enemies]) # list of groups to check collisions with puts "Colliding with an enemy!" end if mouse_down?(:left) # example of getting the mouse position relative to the entity or the world 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) 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 end world.add_entity "Trap Preview" do |entity| groups = [:trap_previews] position = {x: 0, y: 0} sprite = :trap_sprite disabled = true # this entity will not be updated or rendered normally end # systems are called on the world every update frame world.add_system :physics # default basic physics + collisions system 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| groups = [:traps] position = mouse_position_world body = :trap_body sprite = :trap_sprite render = :trap_render controller = :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| 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| disabled = true end end end # this is used by teh engine like so # 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. 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