From 2d70736753444b4bf32cabc3ffd238c2d60a9e5a Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 20 Nov 2019 09:08:54 -0600 Subject: [PATCH] Extracted movement and turret into 'components' that entity get add to its self when defined --- i-mic-rts.rb | 1 + lib/component.rb | 23 ++++++ lib/components/movement.rb | 31 ++++++++ lib/components/turret.rb | 37 +++++++++ lib/entities/units/construction_worker.rb | 2 + lib/entities/units/harvester.rb | 4 +- lib/entities/units/tank.rb | 6 +- lib/entity.rb | 91 +++++++++++------------ 8 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 lib/component.rb create mode 100644 lib/components/movement.rb create mode 100644 lib/components/turret.rb diff --git a/i-mic-rts.rb b/i-mic-rts.rb index f2e95f6..94afae1 100755 --- a/i-mic-rts.rb +++ b/i-mic-rts.rb @@ -29,6 +29,7 @@ require_relative "lib/states/menus/solo_lobby_menu" require_relative "lib/states/menus/multiplayer_lobby_menu" require_relative "lib/zorder" +require_relative "lib/component" require_relative "lib/entity" require_relative "lib/map" require_relative "lib/tiled_map" diff --git a/lib/component.rb b/lib/component.rb new file mode 100644 index 0000000..b788686 --- /dev/null +++ b/lib/component.rb @@ -0,0 +1,23 @@ +class IMICRTS + class Component + @@components = {} + + def self.get(name) + @@components.dig(name) + end + + def self.inherited(klass) + name = klass.to_s.split("::").last.downcase.to_sym + + if get(name) + raise "#{klass.inspect} is already defined!" + else + @@components[name] = klass + end + end + end +end + +Dir.glob("#{IMICRTS::GAME_ROOT_PATH}/lib/components/**/*.rb").each do |component| + require_relative component +end \ No newline at end of file diff --git a/lib/components/movement.rb b/lib/components/movement.rb new file mode 100644 index 0000000..b63f785 --- /dev/null +++ b/lib/components/movement.rb @@ -0,0 +1,31 @@ +class IMICRTS + class Movement < Component + attr_accessor :pathfinder + def initialize(parent:) + @parent = parent + end + + def rotate_towards(vector) + _angle = Gosu.angle(@parent.position.x, @parent.position.y, vector.x, vector.y) + a = (360.0 + (_angle - @parent.angle)) % 360.0 + + # FIXME: Fails if vector is directly behind entity + if @parent.angle.between?(_angle - 3, _angle + 3) + @parent.angle = _angle + elsif a < 180 + @parent.angle -= 1.0 + else + @parent.angle += 1.0 + end + + @parent.angle %= 360.0 + end + + def follow_path + if @pathfinder && node = @pathfinder.path_current_node + @pathfinder.path_next_node if @pathfinder.at_current_path_node?(@parent) + @parent.position -= (@parent.position.xy - node.tile.position.xy).normalized * @parent.speed + end + end + end +end \ No newline at end of file diff --git a/lib/components/turret.rb b/lib/components/turret.rb new file mode 100644 index 0000000..9f563ea --- /dev/null +++ b/lib/components/turret.rb @@ -0,0 +1,37 @@ +class IMICRTS + class Turret < Component + attr_accessor :angle, :center + def initialize(parent:) + @parent = parent + + @angle = 0 + @center = CyberarmEngine::Vector.new(0.5, 0.5) + end + + def body_image=(image) + @body_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) + end + + def shell_image=(image) + @shell_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) + end + + def overlay_image=(image) + @overlay_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) + end + + def render + @render = Gosu.render(32, 32, retro: true) do + @body_image.draw(0, 0, 0) if @body_image + @shell_image.draw_rot(0, 0, 0, 0, 0, 0, 1, 1, @parent.player.color) if @shell_image + @overlay_image.draw(0, 0, 0) if @overlay_image + end + end + + def draw + render unless @render + @angle += 0.1 + @render.draw_rot(@parent.position.x + @center.x, @parent.position.y + @center.y, @parent.position.z, @angle) + end + end +end \ No newline at end of file diff --git a/lib/entities/units/construction_worker.rb b/lib/entities/units/construction_worker.rb index 32ced2f..a6d042f 100644 --- a/lib/entities/units/construction_worker.rb +++ b/lib/entities/units/construction_worker.rb @@ -1,4 +1,6 @@ IMICRTS::Entity.define_entity(:construction_worker, :unit, 1000, "Constructs buildings") do |entity| + entity.has(:movement) + entity.radius = 14 entity.movement = :ground entity.max_health = 100.0 diff --git a/lib/entities/units/harvester.rb b/lib/entities/units/harvester.rb index 9250d10..7e8922a 100644 --- a/lib/entities/units/harvester.rb +++ b/lib/entities/units/harvester.rb @@ -1,4 +1,6 @@ IMICRTS::Entity.define_entity(:harvester, :unit, 1400, "Harvests ore") do |entity, director| + entity.has(:movement) + entity.radius = 10 entity.movement = :ground entity.max_health = 100.0 @@ -28,7 +30,7 @@ IMICRTS::Entity.define_entity(:harvester, :unit, 1400, "Harvests ore") do |entit entity.define_singleton_method(:seek_refinery) do end - entity.define_singleton_method(:rotate_towards) do |target| + entity.component(:movement).define_singleton_method(:rotate_towards) do |target| entity.angle = Gosu.angle(target.x, target.y, entity.position.x, entity.position.y) end end diff --git a/lib/entities/units/tank.rb b/lib/entities/units/tank.rb index 7bcecbb..2b85473 100644 --- a/lib/entities/units/tank.rb +++ b/lib/entities/units/tank.rb @@ -1,11 +1,15 @@ IMICRTS::Entity.define_entity(:tank, :unit, 800, "Attacks ground targets") do |entity| + entity.has(:movement) + entity.has(:turret) + entity.radius = 14 entity.movement = :ground entity.max_health = 100.0 entity.shell_image = "vehicles/tank/tank_shell.png" - entity.turret_shell_image = "vehicles/tank/tank_turret_shell.png" + entity.component(:turret).shell_image = "vehicles/tank/tank_turret_shell.png" + entity.component(:turret).center.y = 0.4 entity.on_tick do end diff --git a/lib/entity.rb b/lib/entity.rb index 31f7406..ac599b9 100644 --- a/lib/entity.rb +++ b/lib/entity.rb @@ -15,7 +15,7 @@ class IMICRTS end end - attr_reader :player, :id, :name, :type + attr_reader :player, :id, :name, :type, :speed attr_accessor :position, :angle, :radius, :target, :state, :movement, :health, :max_health, :turret @@ -31,6 +31,8 @@ class IMICRTS @target = nil @state = :idle + @components = {} + if entity = Entity.get(name) @name = entity.name @type = entity.type @@ -40,6 +42,8 @@ class IMICRTS raise "Failed to find entity #{name.inspect} definition" end + component(:turret).angle = @angle if component(:turret) + @goal_color = Gosu::Color.argb(175, 25, 200, 25) @target_color = Gosu::Color.argb(175, 200, 25, 25) @@ -52,6 +56,20 @@ class IMICRTS def deserialize end + def has(symbol) + component = Component.get(symbol) + + if component + @components[symbol] = component.new(parent: self) + else + raise "Unknown component: #{component.inspect}" + end + end + + def component(symbol) + @components.dig(symbol) + end + def body_image=(image) @body_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) end @@ -60,17 +78,13 @@ class IMICRTS @shell_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) end - def turret_body_image=(image) - @turret_body_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) - end - - def turret_shell_image=(image) - @turret_shell_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) + def overlay_image=(image) + @overlay_image = Gosu::Image.new("#{IMICRTS::ASSETS_PATH}/#{image}", retro: true) end def target=(entity) @target = entity - @pathfinder = @director.find_path(player: @player, entity: self, goal: @target) + component(:movement).pathfinder = @director.find_path(player: @player, entity: self, goal: @target) if component(:movement) end def hit?(x_or_vector, y = nil) @@ -85,24 +99,28 @@ class IMICRTS @position.distance(vector) < @radius + 1 end - def draw - @body_image.draw_rot(@position.x, @position.y, @position.z, @angle) if @body_image - @shell_image.draw_rot(@position.x, @position.y, @position.z, @angle, 0.5, 0.5, 1, 1, @player.color) - @overlay_image.draw_rot(@position.x, @position.y, @position.z, @angle, 0.5, 0.5, 1, 1) if @overlay_image + def render + @render = Gosu.render(32, 32, 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 + end + end - @turret_body_image.draw_rot(@position.x, @position.y, @position.z, @angle, 0.5, 0.5, 1, 1) if @turret_body_image - @turret_shell_image.draw_rot(@position.x, @position.y, @position.z, @angle, 0.5, 0.5, 1, 1, @player.color) if @turret_shell_image - @turret_overlay_image.draw_rot(@position.x, @position.y, @position.z, @angle, 0.5, 0.5, 1, 1) if @turret_overlay_image + def draw + render unless @render + @render.draw_rot(@position.x, @position.y, @position.z, @angle) + + component(:turret).draw if component(:turret) end def update - if @movement - # rotate_towards(@target) if @target - rotate_towards(@pathfinder.path_current_node.tile.position) if @pathfinder && @pathfinder.path_current_node - end + if component(:movement) + if component(:movement).pathfinder && component(:movement).pathfinder.path_current_node + component(:movement).rotate_towards(component(:movement).pathfinder.path_current_node.tile.position) + end - if @movement - follow_path + component(:movement).follow_path end end @@ -114,13 +132,6 @@ class IMICRTS @on_tick = block end - def follow_path - if @pathfinder && node = @pathfinder.path_current_node - @pathfinder.path_next_node if @pathfinder.at_current_path_node?(self) - @position -= (@position.xy - node.tile.position.xy).normalized * @speed - end - end - def selected_draw draw_radius draw_gizmos @@ -133,15 +144,15 @@ class IMICRTS def draw_gizmos Gosu.draw_rect(@position.x - @radius, @position.y - (@radius + 2), @radius * 2, 2, Gosu::Color::GREEN, ZOrder::ENTITY_GIZMOS) - if Setting.enabled?(:debug_pathfinding) && @pathfinder && @pathfinder.path_current_node + if Setting.enabled?(:debug_pathfinding) && component(:movement) && component(:movement).pathfinder && component(:movement).pathfinder.path_current_node Gosu.draw_line( @position.x, @position.y, Gosu::Color::RED, - @pathfinder.path_current_node.tile.position.x, @pathfinder.path_current_node.tile.position.y, Gosu::Color::RED, + component(:movement).pathfinder.path_current_node.tile.position.x, component(:movement).pathfinder.path_current_node.tile.position.y, Gosu::Color::RED, ZOrder::ENTITY_GIZMOS ) - node = @pathfinder.path_current_node - @pathfinder.path[@pathfinder.path_current_node_index..@pathfinder.path.size - 1].each do |next_node| + node = component(:movement).pathfinder.path_current_node + component(:movement).pathfinder.path[component(:movement).pathfinder.path_current_node_index..component(:movement).pathfinder.path.size - 1].each do |next_node| if node Gosu.draw_line( node.tile.position.x, node.tile.position.y, Gosu::Color::RED, @@ -160,22 +171,6 @@ class IMICRTS Gosu.draw_line(@position.x, @position.y, @goal_color, @target.x, @target.y, @goal_color, ZOrder::ENTITY_GIZMOS) if @target end end - - def rotate_towards(vector) - _angle = Gosu.angle(@position.x, @position.y, vector.x, vector.y) - a = (360.0 + (_angle - @angle)) % 360.0 - - # Fails if vector is directly behind entity - if @angle.between?(_angle - 3, _angle + 3) - @angle = _angle - elsif a < 180 - @angle -= 1.0 - else - @angle += 1.0 - end - - @angle %= 360.0 - end end end