Files
project_misth/samples/main.rb
T
2026-05-01 13:04:06 +01:00

189 lines
7.0 KiB
Ruby

# 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 ?w, :forward
window.map_key ?s, :backward
window.map_key ?a, :left
window.map_key ?d, :right
window.map_key ?h, :heal
window.map_key ?t, :place
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 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 components and systems for whatever you want
# this is a custom component
world.component :health do |c|
field :health, int: 100
allow_redefine [:health]
end
world.spawn do |e| # Name is optional
e.tags = [:enemy]
e.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
e.add_component :body do |c|
c.shape = :rectangle
c.size = {width: 50, height: 50}
c.angle = 0
c.mass = 1
c.velocity = {x: 0, y: 0}
c.collision = true
end
e.add_component :sprite do |c|
c.tiles = Tiles.from "assets/sprite.png", tile_width: 50, tile_height: 50
animation1 = {tiles: 0..3, frame_rate: 10, loop: true}
c.animations = {idle: animation1}
c.current_animation = :idle
end
e.add_component :render do |c|
c.z_index = 0
c.renderer = proc 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
# offset from the position of the entity, useful for things like speech bubbles or health bars that should be above the entity, etc
c.offset = {x: 0, y: 0}
end
e.add_component :health do |c|
c.health = 100
end
end
world.spawn "Trap Preview" do |e|
e.position = {x: 0, y: 0}
e.tags = [:trap_preview]
e.active = false
e.add_component :sprite do |c|
c.tiles = Tiles.from "assets/trap.png", tile_width: 50, tile_height: 50
animation1 = {tiles: 0..3, frame_rate: 10, loop: true}
c.animations = {idle: animation1}
c.current_animation = :idle
end
# e.remove_component :sprite # compoenents can also be removed
end
# 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
# systems can be created on queries
# the query can be for compoenents or tags or both
# match is set to list of all capturing queries
# for components returns an object that responds to .each with every ceomponents values
# for entities it does similarly
query = tag(:enemy) & component(:health, true) & anti(name("Trap Preview", true))
# in the match name will return just the one entity
# tag and componet return list-like tables but they will be parallel and in order
# empty columns will be filled with nil (not possible if only & is used)
# the last part is to show how anti() works
# it doesnt actually do anything as tag(:enemy) and component(:health) will never match name("Trap Preview") but it shows how you can use anti() to negate a query
world.system :health_regen, for: query do |match, dt| # no trigger means always
# example of a simple health regeneration system that regenerates health for all entities with a health component
healths = match[0]
healths.each do |health|
health.health += 1 * dt # regenerate 1 health per second, dt is the delta time since the last update frame, so this will regenerate 1 health per second regardless of the frame rate
health.health = [health.health, 100].min # cap health at 100
end
end
trigger = [:place]
world.system :trap_placement, trigger: trigger do |dt|
return unless world.holding_trap?
world.spawn do |e|
e.tags = [:trap]
e.position = world.mouse_position_world
e.add_component :sprite do |c|
c.tiles = Tiles.from "assets/trap.png", tile_width: 50, tile_height: 50
animation1 = {tiles: 0..3, frame_rate: 10, loop: true}
c.animations = {idle: animation1}
c.current_animation = :idle
end
e.add_component :body do |c|
c.shape = :rectangle
c.size = {width: 50, height: 50}
c.angle = 0
c.mass = 1
c.velocity = {x: 0, y: 0}
c.collision = true
end
end
end
# could be done as so or
world.system :trap_preview, for: name("Trap Preview", true) do |match, dt| # example from teh game mist trying to be made with this engine
# do trap placement logic when a trap is held
return unless world.holding_trap?
# show a preview of the trap at the mouse position, you can use the mouse position relative to the world for this
e = match.first
e.position = world.mouse_position_world
e.active = true # enable the trap preview entity to show it at the mouse position
end
# OR, this one isnt the exact same but shows that .active works on both entites and components
world.system :trap_preview, for: name("Trap Preview", true) & component(:sprite, true) do |match, dt|
return unless world.holding_trap?
# if | was used between teh queries then the arrays would still be parallel but empty rows fit with nil
# for & between they will always match and so the arrays will always be the same length and so you can just use the first one to set the position of the entity and then use the second one to set the active of the sprite component, etc (without nil check)
match.second.each_with_index do |s, i|
match.first[i].position = world.mouse_position_world
s.active = true
end
end
## ------ ##
world.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