206 lines
7.8 KiB
Ruby
206 lines
7.8 KiB
Ruby
# Sample ruby DSL to create a game window.
|
|
# reference of what the engine should build up to.
|
|
|
|
width, height = 720, 480
|
|
|
|
image = Tiles.from("assets/loading.png").first
|
|
|
|
App.run width, height, title: "My Game", loading_bg: image # can be image or animation
|
|
|
|
App.map_key ?w, :forward
|
|
App.map_key ?s, :backward
|
|
App.map_key ?a, :left
|
|
App.map_key ?d, :right
|
|
|
|
App.map_key ?h, :heal
|
|
App.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}
|
|
|
|
game_scene = Scene.new
|
|
|
|
start_button.on_click do
|
|
App.switch_to game_scene
|
|
end
|
|
|
|
menu_scene << start_button
|
|
|
|
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|
|
|
# one of int or float or boolean which use c stuff and generally faster
|
|
# also allow for bulk operations to be done in hotloops.
|
|
# can use `any` which allows for any ruby value but uses runtime gc etc and so is worse to use performance wise for each entity.
|
|
field :health, :int, 100 # stored for each entity it is attached to.
|
|
field :max_health, :int, 100, global: true # stored only once per component
|
|
end
|
|
|
|
world.spawn do |e| # Name is optional
|
|
e.tags = [:enemy]
|
|
e.z = 0.5
|
|
|
|
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_layer = 1
|
|
end
|
|
e.add_component :sprite do |c|
|
|
tiles = Tiles.from "assets/sprite.png", tile_width: 50, tile_height: 50
|
|
animation1 = {tiles: tiles[0..3], frame_rate: 10, loop: true}
|
|
c.animations = {idle: animation1}
|
|
c.current_animation = :idle
|
|
## For tilemaps
|
|
# tiles is an array of of tiles in sheet, largest one's w and h will be used. or width: height: to override
|
|
c.tilemap = Tilemap.new tiles
|
|
c.default_tile = tiles.first # default tile to use for empty spaces in the tilemap
|
|
c.auto_tile = true # tiles must follow pattern
|
|
# c.tile[x: 0, y: 0] = tiles[0] # set tile at position x,y in tilemap to a specific tile from the sheet (if no auto_tile is used)
|
|
c.tile[x: 0, y: 0] = true # true/false otherwise
|
|
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.z = 0
|
|
e.tags = [:trap_preview]
|
|
e.active = false
|
|
e.add_component :sprite do |c|
|
|
tiles = Tiles.from "assets/trap.png", tile_width: 50, tile_height: 50
|
|
animation1 = {tiles: tiles[0..3], frame_rate: 10, loop: true}
|
|
c.animations = {idle: animation1}
|
|
c.current_animation = :idle
|
|
end
|
|
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
|
|
# mouse state can be read in systems always
|
|
|
|
query = Query.from(tag(:enemy) & component(:health, capture: true) & anti(name("Trap Preview")))
|
|
# 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] # get the first component in the query, in this case health
|
|
# , this will be a list of all health components for
|
|
# entities that match the query
|
|
healths[:health].add! 1 * dt
|
|
healths[:health].clamp! 0, :max_health
|
|
end
|
|
|
|
trigger = Trigger.from(key(:place))
|
|
|
|
world.system :trap_placement, trigger: trigger do |dt|
|
|
return unless world.globals.holding_trap?
|
|
|
|
world.spawn do |e|
|
|
e.tags = [:trap]
|
|
e.position = world.mouse_position_world
|
|
e.add_component :sprite do |c|
|
|
tiles = Tiles.from "assets/trap.png", tile_width: 50, tile_height: 50
|
|
tiles2 = Tiles.from "assets/trap_2.png", tile_width: 50, tile_height: 50
|
|
animation1 = {tiles: tiles[0..3] + tiles2[0..3], frame_rate: 10, loop: true}
|
|
c.animations = {idle: animation1}
|
|
c.current_animation = :idle
|
|
end
|
|
e.add_component :body do |c|
|
|
c.size = {width: 50, height: 50}
|
|
c.angle = 0
|
|
c.mass = 1
|
|
c.velocity = {x: 0, y: 0}
|
|
c.collision_layer = 0 # layer 0 doesnt collide.
|
|
end
|
|
end
|
|
end
|
|
|
|
name_query = Query.from(name("Trap Preview") & component(:sprite, capture: true))
|
|
|
|
# could be done as so or
|
|
world.system :trap_preview, for: name_query do |dt, matches| # example from the game mist trying to be made with this engine
|
|
# do trap placement logic when a trap is held
|
|
return unless world.globals.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 = matches[0] # get the first entity that matches the name query, in
|
|
# this case the trap preview entity, this will be nil if no entity matches
|
|
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", capture: true) & component(:sprite, capture: true) do |dt, matches|
|
|
return unless world.globals.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)
|
|
matches.second.each_with_index do |s, i|
|
|
matches.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
|
|
|
|
App.start menu_scene # first scene to show when the game starts, stops the loading bg
|