diff --git a/assets/smoke/smoke.png b/assets/smoke/smoke.png new file mode 100644 index 0000000..11fa589 Binary files /dev/null and b/assets/smoke/smoke.png differ diff --git a/assets/svg/smoke/smoke.svg b/assets/svg/smoke/smoke.svg new file mode 100644 index 0000000..f029790 --- /dev/null +++ b/assets/svg/smoke/smoke.svg @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/i-mic-rts.rb b/i-mic-rts.rb index 94afae1..361bc99 100755 --- a/i-mic-rts.rb +++ b/i-mic-rts.rb @@ -30,6 +30,7 @@ require_relative "lib/states/menus/multiplayer_lobby_menu" require_relative "lib/zorder" require_relative "lib/component" +require_relative "lib/particle_emitter" require_relative "lib/entity" require_relative "lib/map" require_relative "lib/tiled_map" diff --git a/lib/entities/buildings/construction_yard.rb b/lib/entities/buildings/construction_yard.rb new file mode 100644 index 0000000..6c4aa47 --- /dev/null +++ b/lib/entities/buildings/construction_yard.rb @@ -0,0 +1,34 @@ +IMICRTS::Entity.define_entity(:construction_yard, :building, 2_000, "Provides radar and builds construction workers") do |entity| + entity.radius = 40 + entity.max_health = 100.0 + + entity.body_image = "buildings/construction_yard/construction_yard.png" + entity.shell_image = "buildings/construction_yard/construction_yard_shell.png" + entity.overlay_image = "buildings/construction_yard/construction_yard_overlay.png" + + position = entity.position.clone + position.z = IMICRTS::ZOrder::OVERLAY + emitters = [] + + p1 = position.clone + p1.x -= 25 + p1.y -= 8 + p2 = p1.clone + p2.y += 25 + + p3 = position.clone + p3.x += 8 + p3.y -= 15 + p4 = p3.clone + p4.x += 21 + + emitters.push(p1, p2, p3, p4) + + + emitters.each do |pos| + entity.particle_emitters << IMICRTS::SmokeEmitter.new(position: pos) + end + + entity.on_tick do + end +end diff --git a/lib/entity.rb b/lib/entity.rb index eea6055..8451578 100644 --- a/lib/entity.rb +++ b/lib/entity.rb @@ -18,7 +18,7 @@ class IMICRTS attr_reader :player, :id, :name, :type, :speed attr_accessor :position, :angle, :radius, :target, :state, :movement, :health, :max_health, - :turret, :center + :turret, :center, :particle_emitters def initialize(name:, player:, id:, position:, angle:, director:) @player = player @id = id @@ -27,10 +27,13 @@ class IMICRTS @director = director @speed = 0.5 - @radius = 32 / 2 + @sight_radius = 5 # tiles + @range_radius = 3 # tiles + @radius = 32 / 2 # pixels @target = nil @state = :idle @center = CyberarmEngine::Vector.new(0.5, 0.5) + @particle_emitters = [] @components = {} @@ -101,7 +104,7 @@ class IMICRTS end def render - @render = Gosu.render(32, 32, retro: true) do + @render = Gosu.render(@shell_image.width, @shell_image.height, retro: true) do @body_image.draw(0, 0, 0) if @body_image @shell_image.draw(0, 0, 0, 1, 1, @player.color) @overlay_image.draw(0, 0, 0) if @overlay_image @@ -113,6 +116,7 @@ class IMICRTS @render.draw_rot(@position.x, @position.y, @position.z, @angle, @center.x, @center.y) component(:turret).draw if component(:turret) + @particle_emitters.each(&:draw) end def update @@ -123,6 +127,11 @@ class IMICRTS component(:movement).follow_path end + + @particle_emitters.each do |emitter| + @particle_emitters.delete(emitter) if emitter.die? + emitter.update + end end def tick(tick_id) diff --git a/lib/map.rb b/lib/map.rb index a4bf5b9..bc55c62 100644 --- a/lib/map.rb +++ b/lib/map.rb @@ -1,6 +1,6 @@ class IMICRTS class Map - attr_reader :tile_size, :tiles, :ores, :width, :height + attr_reader :tile_size, :tiles, :ores, :spawnpoints, :width, :height def initialize(map_file:) @tiled_map = TiledMap.new(map_file) @@ -9,6 +9,7 @@ class IMICRTS @tiles = {} @ores = {} + @spawnpoints = @tiled_map.spawnpoints.freeze @tiled_map.layers.each do |layer| layer.height.times do |y| diff --git a/lib/particle_emitter.rb b/lib/particle_emitter.rb new file mode 100644 index 0000000..17e0cc4 --- /dev/null +++ b/lib/particle_emitter.rb @@ -0,0 +1,93 @@ +class IMICRTS + class ParticleEmitter + def initialize(position:, direction: CyberarmEngine::Vector.up, time_to_live: 1000, particle_time_to_live: 500, speed: 10.0, max_particles: 128, frequency: 10.0, images: [], color: Gosu::Color::WHITE.dup, jitter: 10.0) + @position = position + @direction = direction + @time_to_live = time_to_live + @particle_time_to_live = particle_time_to_live + @speed = speed + @max_particles = max_particles + @frequency = frequency + @images = images + @color = color + @jitter = jitter + + @born_at = Gosu.milliseconds + @last_emitted_at = 0 + @particles = [] + @factor = CyberarmEngine::Vector.new(1.0 ,1.0) + + setup + end + + def setup + end + + def emit + dir = @direction.clone + dir.x = rand(-@jitter.to_f..@jitter.to_f) * 1 + dir.normalized + + @particles << Particle.new( + position: @position, direction: dir, + factor: @factor.clone, angle: rand(360.0), + speed: @speed, time_to_live: @particle_time_to_live, image: @images.sample, + color: @color.dup + ) + + @last_emitted_at = Gosu.milliseconds + end + + def draw + @particles.each(&:draw) + end + + def update + if @particles.count < @max_particles && Gosu.milliseconds >= @last_emitted_at + (1000.0 / @frequency) + emit + end + + @particles.each do |particle| + @particles.delete(particle) if particle.die? + particle.update + end + end + + def die? + Gosu.milliseconds >= @born_at + @time_to_live + end + end + + class Particle + attr_accessor :position, :direction, :factor, :angle, :speed, :time_to_live, :image, :color, :born_at + def initialize(position:, direction:, factor:, angle:, speed:, time_to_live:, image:, color:) + @position = position + @direction = direction + @factor = factor + @angle = angle + @speed = speed + @time_to_live = time_to_live + @image = image + @color = color + + @born_at = Gosu.milliseconds + end + + def draw + @image.draw_rot(*@position.to_a[0..2], @angle, 0.5, 0.5, @factor.x, @factor.y, @color) + end + + def update + @position -= (@direction * @speed) * $window.dt + end + + def die? + Gosu.milliseconds >= @born_at + @time_to_live + end + end +end + +Dir.glob("#{IMICRTS::GAME_ROOT_PATH}/lib/particle_emitters/**/*.rb").each do |emitter| + p emitter + require_relative emitter +end \ No newline at end of file diff --git a/lib/particle_emitters/smoke_emitter.rb b/lib/particle_emitters/smoke_emitter.rb new file mode 100644 index 0000000..7218ee7 --- /dev/null +++ b/lib/particle_emitters/smoke_emitter.rb @@ -0,0 +1,37 @@ +class IMICRTS + class SmokeEmitter < ParticleEmitter + def setup + @time_to_live = Float::INFINITY + @particle_time_to_live = 2_500 + @frequency = 10.0 + @color = Gosu::Color.rgba(255, 255, 255, 255.0 * 0.4) + @direction = CyberarmEngine::Vector.up + @direction.y = 4.5 + @jitter = 25.0 + + @images = Gosu::Image.load_tiles("#{IMICRTS::ASSETS_PATH}/smoke/smoke.png", 32, 32, retro: false) + end + + def emit + super + + @particles.last.factor = CyberarmEngine::Vector.new(0.1, 0.1) + end + + def update + super + + @particles.each do |particle| + life_cycle = (Gosu.milliseconds - particle.born_at.to_f) / particle.time_to_live + scale = (2.0 * life_cycle) + 0.25 + particle.factor.x = scale + particle.factor.y = scale + + particle.color.alpha = ((255.0 * (1.0 - life_cycle)) - 255.0 * 0.4).clamp(0.0, 255.0) + particle.angle += rand(0.1..0.5) + + particle.direction.x = Math.cos(Gosu.milliseconds / 1_000.0) * 0.162 + end + end + end +end \ No newline at end of file diff --git a/lib/states/game.rb b/lib/states/game.rb index a8daddd..b432538 100644 --- a/lib/states/game.rb +++ b/lib/states/game.rb @@ -65,7 +65,24 @@ class IMICRTS end end - 100.times { |i| [@c, @h, @t].sample.instance_variable_get("@block").call } + # 100.times { |i| [@c, @h, @t].sample.instance_variable_get("@block").call } + spawnpoint = @director.map.spawnpoints.last + @player.entities << Entity.new( + name: :construction_yard, + director: @director, + player: @player, + id: @player.next_entity_id, + position: CyberarmEngine::Vector.new(spawnpoint.x, spawnpoint.y, ZOrder::BUILDING), + angle: 0 + ) + @player.entities << Entity.new( + name: :construction_worker, + director: @director, + player: @player, + id: @player.next_entity_id, + position: CyberarmEngine::Vector.new(spawnpoint.x - 64, spawnpoint.y + 64, ZOrder::GROUND_VEHICLE), + angle: 0 + ) end def draw diff --git a/lib/tiled_map.rb b/lib/tiled_map.rb index 53ba852..affdf14 100644 --- a/lib/tiled_map.rb +++ b/lib/tiled_map.rb @@ -1,7 +1,7 @@ class IMICRTS class TiledMap attr_reader :width, :height, :tile_size - attr_reader :layers, :tilesets + attr_reader :layers, :tilesets, :spawnpoints def initialize(map_file) @xml = Nokogiri::XML(File.read("#{IMICRTS::ASSETS_PATH}/#{map_file}")) @@ -10,6 +10,7 @@ class IMICRTS @layers = [] @tilesets = [] + @spawnpoints = [] @tiles = [] @@ -33,6 +34,15 @@ class IMICRTS @xml.search("//layer").each do |layer| @layers << Layer.new(layer) end + + @xml.search("//objectgroup").each do |objectgroup| + if objectgroup.attr("name") == "spawns" + objectgroup.children.each do |object| + next unless object.attr("name") && object.attr("name").downcase.strip == "spawn" + @spawnpoints << SpawnPoint.new(object) + end + end + end end def get_tile(tile_id) @@ -121,5 +131,14 @@ class IMICRTS end end end + + + + class SpawnPoint + attr_reader :x, :y + def initialize(xml_object) + @x, @y = Integer(xml_object.attr("x")), Integer(xml_object.attr("y")) + end + end end end \ No newline at end of file