From a9307733e317f71f75bf844067029e226d29dddb Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 20 Nov 2019 12:51:24 -0600 Subject: [PATCH] TileMap parser can now load spawn locations, added construction yard building, added particle emitters, added smoke sprite and svg. --- assets/smoke/smoke.png | Bin 0 -> 2002 bytes assets/svg/smoke/smoke.svg | 259 ++++++++++++++++++++ i-mic-rts.rb | 1 + lib/entities/buildings/construction_yard.rb | 34 +++ lib/entity.rb | 15 +- lib/map.rb | 3 +- lib/particle_emitter.rb | 93 +++++++ lib/particle_emitters/smoke_emitter.rb | 37 +++ lib/states/game.rb | 19 +- lib/tiled_map.rb | 21 +- 10 files changed, 476 insertions(+), 6 deletions(-) create mode 100644 assets/smoke/smoke.png create mode 100644 assets/svg/smoke/smoke.svg create mode 100644 lib/entities/buildings/construction_yard.rb create mode 100644 lib/particle_emitter.rb create mode 100644 lib/particle_emitters/smoke_emitter.rb diff --git a/assets/smoke/smoke.png b/assets/smoke/smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..11fa5893259873414b464d9ce7ace030bf38b70a GIT binary patch literal 2002 zcmV;@2QB!CP)PrdX{G+g=n5k!l@Sl9c9gPtIfSz4l&v zt>xg{dx_CoTiTBNMr*@b`}^(nJ^tT@|92DbW%I)iKfH4F`qih#sfArNA@>-#L8ntgo+QOuaSekAHOV;K5(L+Z$2;Yr}~XC%&_~I{sN% zRmwY$C*ZxqwjIHH1fWzznx-5+a^zr9^iJHE+&FRU`FLgYJ@12%NL=Vh)0Cp{uZRu=>R(dUt_16~V{N%l#y63eE z7cRc}o&X+w^wF>QVE+)s$>)6tBuRvG4(|i&>+2Ln52YlX?Fe|JRE)b?H@-+ue903Lbdk?$Y4WBt5yKJT1EDuFbl0c4(B|kHfSBOzOjD)>e||sH~#*{);j_`{`j{ZI&$>zPn)L2 zm>Q`h1hCf9mgBaWjle)67A zJ$v!u#qL(0@|LwI2K}d&U*O2{oFqw5O4GItNfIv|)dp)V-upd>K%i|~+O`D*&N-x% zXsyZ846QYz(P$_lasTZ%um^DJ)TyId=}&ZB2Lg(spePEoR>VnybB;KUNzw%GJc~ui zVo_pDgAf8C1+&GR*>sK&f}-d#9*xO+Id>kqlU}b!aN*eP0D5;(*Y(|L9b;<4>iQbq zd;0x8)^;>agVvh1ZL!u8LO=+?qAbx$F&quavWzrMna*Z(T}SW%=Nz`PEEXkISrS56 z-4{S9mDY8QZ7o$<5hpQaSz=60-pdI-kfteRS>nAX_&^kCwANT_>GgUfNdh5Ytz}V` zY)>Y1w!_plUDu&>B=-gI-q%8Cgp!n1h4T)j6jfC%0j1gtOIenbrbGxqp67%Rn9t`- zr&D%wNNZ0~^f0xda~;Fs5Yw1=Ux2o4tM!dFym!oIbFA%1k^~_HaU9bx`V2?IotJCo zvpEQXF$N_SrZxluv+0aDNysxr9K|^2kxrtOCIk-d3ost%Zwn!aqIj8jaZD6N2qZev zBxypLrqp$fwU)e>>R?vg_qNeDEyF5`aLU-!DFW zJHTF2oIQJXu|1jG6heRySleN0L({a(rc*9oy24vm-s0xXnsSR-ylVush;c%yXEwh_5k|aV(L1S6~@yaW&9N!n9G3Irp1)L;C{g zIR1T}=Q!{1At0rsZCkpoBTW;EqM%aB_G{cw%AHuzxo11%n|4j|Iwzeh*4s1M__wplImeVxL*{^dQQ54Zxixh$+ zPKo1)x~iDZ=d_K*?+B0&p2irGI3|iCy!X_#Aq0;Uk~oUAci|_mzWQnlx0K!w{&pi=waeGrmkwPUAsnA)j01+lMJbrGh#3-!%Br=DJ9FmDXH_I$Oq1ke)9IASWI~o@z;d4G^?C>)c2w2iz5mnUyN-Tgb8~Yq z1n&y~;QaaX=}W(R>DjyPI&^y1(QZHR-s7BGs%KgK`M|~<51u=BZhGta_YJUXPdxF& zXRdEw|6!JZpurSF0Fg-M}xuO k?Ps2O=H>_O;{&yS0o(}Dw=kGnDgXcg07*qoM6N<$g2!mnga7~l literal 0 HcmV?d00001 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