Implemented event system, Implemented initial bit of scripting system, Stubbed component system. Entities can now use the scripting system to place their 'decorations'

This commit is contained in:
2019-09-26 12:13:08 -05:00
parent e41a2b6524
commit b6d7a6ebdb
24 changed files with 293 additions and 80 deletions

View File

@@ -1,9 +1,10 @@
context Vehicle # Generic, Weapon
component(:vehicle) # Generic, Weapon
on.button_down(:interact) do |event|
if event.player.touching?(event.entity)
event.player.enter_vehicle
elsif event.player.driving?(event.entity) or event.player.passenger?(event.entity)
event.player.exit_vehicle
end
$window.console.stdin("#{event.entity.name} handled button_down(:interact)")
# if event.player.touching?(event.entity)
# event.player.enter_vehicle
# elsif event.player.driving?(event.entity) or event.player.passenger?(event.entity)
# event.player.exit_vehicle
# end
end

View File

@@ -1,8 +1,8 @@
context Building
component(:building)
on.create do |event|
map.add_entity("base", "purchase_terminal", Vector.new(0, 1.5, 0), Vector.new(0, 90, 0), data: {team: event.entity.team})
map.add_entity("base", "information_panel", Vector.new(2, 1.5, 0), Vector.new(0, 0, 0))
map.add_entity("base", "door", Vector.new(2, 0, 6), Vector.new(0, 0, 0))
map.add_entity("base", "door", Vector.new(2, 0, 6), Vector.new(0, 180, 0))
event.map.insert_entity("base", "purchase_terminal", event.entity.position + Vector.new(1.5, 1.5, 4.52), Vector.new(0, 200, 0), data: {team: nil})
event.map.insert_entity("base", "information_panel", event.entity.position + Vector.new(-3, 0, -1), Vector.new(0, 90, 0))
event.map.insert_entity("base", "door", event.entity.position + Vector.new(0, 0, -6), Vector.new(0, 0, 0))
event.map.insert_entity("base", "door", event.entity.position + Vector.new(0, 0, -6), Vector.new(0, 180, 0))
end

View File

@@ -1,2 +1,5 @@
name: "war_factory"
model: "war_factory.obj"
scripts: [
"war_factory"
]

View File

@@ -0,0 +1,8 @@
component(:building)
on.create do |event|
event.map.insert_entity("base", "purchase_terminal", event.entity.position + Vector.new(6, 1.5, 3), Vector.new(0, -90, 0), data: {team: nil})
event.map.insert_entity("base", "information_panel", event.entity.position + Vector.new(0.5, 0, 3), Vector.new(0, 90, 0))
event.map.insert_entity("base", "door", event.entity.position + Vector.new(3.3, 0, 6), Vector.new(0, 0, 0))
event.map.insert_entity("base", "door", event.entity.position + Vector.new(3.3, 0, 6), Vector.new(0, 180, 0))
end

Binary file not shown.

Binary file not shown.

View File

@@ -56,6 +56,17 @@ require_relative "lib/ui/menus/main_menu"
require_relative "lib/states/game_states/game"
require_relative "lib/states/game_states/loading_state"
require_relative "lib/scripting"
require_relative "lib/subscription"
require_relative "lib/publisher"
require_relative "lib/event"
require_relative "lib/event_handler"
require_relative "lib/event_handlers/input"
require_relative "lib/event_handlers/entity_lifecycle"
require_relative "lib/component"
require_relative "lib/components/building"
require_relative "lib/game_objects/entity"
require_relative "lib/game_objects/model_loader"
require_relative "lib/game_objects/light"

40
lib/component.rb Normal file
View File

@@ -0,0 +1,40 @@
class IMICFPS
class Component
COMPONENTS = {}
def self.get(name)
COMPONENTS.dig(name)
end
def self.inherited(subclass)
pp subclass
COMPONENTS["__pending"] ||= []
COMPONENTS["__pending"] << subclass
end
def self.initiate
COMPONENTS["__pending"].each do |klass|
component = klass.new
COMPONENTS[component.name] = component
end
COMPONENTS.delete("__pending")
end
def initialize
setup
end
def name
string = self.class.name.split("::").last
split = string.scan(/[A-Z][a-z]*/)
component_name = "#{split.map { |s| s.downcase }.join("_")}".to_sym
return component_name
end
def setup
end
end
end

View File

@@ -0,0 +1,6 @@
class IMICFPS
class Components
class Building < Component
end
end
end

10
lib/event.rb Normal file
View File

@@ -0,0 +1,10 @@
class IMICFPS
class EventHandler
class Event
attr_reader :entity, :context, :map, :player
def initialize(entity:, context: nil, map: $window.current_state, player: nil)
@entity, @context, @map, @player = entity, context, map, player
end
end
end
end

37
lib/event_handler.rb Normal file
View File

@@ -0,0 +1,37 @@
class IMICFPS
class EventHandler
@@handlers = {}
def self.inherited(subclass)
@@handlers["__pending"] ||= []
@@handlers["__pending"] << subclass
end
def self.initiate
@@handlers["__pending"].each do |handler|
instance = handler.new
instance.handles.each do |event|
@@handlers[event] = instance
end
end
@@handlers.delete("__pending")
end
def self.get(event)
@@handlers.dig(event)
end
def initialize
end
def handlers
raise NotImplementedError
end
def handle(subscriber, context, *args)
raise NotImplementedError
end
end
end

View File

@@ -0,0 +1,16 @@
class IMICFPS
class EventHandler
class EntityLifeCycle < EventHandler
def handles
[:create, :entity_move, :destroy]
end
def handle(subscriber, context, *args)
return unless subscriber.entity == args.first.first
event = EventHandler::Event.new(entity: subscriber.entity, context: context)
subscriber.trigger(event)
end
end
end
end

View File

@@ -0,0 +1,24 @@
class IMICFPS
class EventHandler
class Input < EventHandler
def handles
[:button_down, :button_up]
end
def handle(subscriber, context, *args)
action = subscriber.args.flatten.first
key = args.flatten.first
event = EventHandler::Event.new(entity: subscriber.entity, context: context, player: context)
if action.is_a?(Numeric) && action == key
subscriber.trigger(event)
else
if InputMapper.get(action) == key
subscriber.trigger(event)
end
end
end
end
end
end

View File

@@ -4,6 +4,7 @@ class IMICFPS
# A game object is any renderable thing
class Entity
include CommonMethods
include Scripting
attr_accessor :scale, :visible, :renderable, :backface_culling
attr_accessor :position, :orientation, :velocity
@@ -38,6 +39,8 @@ class IMICFPS
@delta_time = Gosu.milliseconds
@last_position = Vector.new(@position.x, @position.y, @position.z)
load_scripts
setup
if @bound_model
@@ -52,6 +55,20 @@ class IMICFPS
return self
end
def load_scripts
@manifest.scripts.each do |script|
instance_eval(script.source)
end
end
def method_missing(method, *args, &block)
unless component = Component.get(method)
raise NoMemoryError, "undefined method '#{method}' for #<#{self.class}:#{self.object_id}>"
else
return component
end
end
def collidable?
@collidable.include?(@collision)
end

View File

@@ -91,10 +91,10 @@ class IMICFPS
vs = a
vs = b if a == entity
broadphase = search(Ray.new(entity.position, Vector.new(0, -1, 0), entity.velocity.y.abs))
broadphase = search(Ray.new(entity.position, Vector.down, entity.velocity.y.abs))
broadphase.detect do |ent|
ray = Ray.new(entity.position - ent.position, Vector.new(0, -1, 0))
ray = Ray.new(entity.position - ent.position, Vector.down)
if ent.model.aabb_tree.search(ray).size > 0
on_ground = true
return true

View File

@@ -2,9 +2,15 @@ class IMICFPS
module EntityManager # Get included into GameState context
def add_entity(entity)
@collision_manager.add(entity)# Add every entity to collision manager
@publisher.publish(:create, self, entity)
@entities << entity
end
def insert_entity(package, name, position, orientation, data = {})
ent = Map::Entity.new(package, name, position, orientation, Vector.new(1,1,1))
add_entity(IMICFPS::Entity.new(map_entity: ent, manifest: Manifest.new(package: package, name: name)))
end
def find_entity(entity)
@entities.detect {|entity| entity == entity}
end
@@ -13,6 +19,7 @@ class IMICFPS
ent = @entities.detect {|entity| entity == entity}
if ent
@collision_manager.remove(entity)
@publisher.publish(:destroy, self, entity)
@entities.delete(ent)
end
end

View File

@@ -115,6 +115,8 @@ IMICFPS::InputMapper.set(:jump, Gosu::KbSpace)
IMICFPS::InputMapper.set(:sprint, [Gosu::KbLeftControl])
IMICFPS::InputMapper.set(:turn_180, Gosu::KbX)
IMICFPS::InputMapper.set(:interact, Gosu::KbE)
IMICFPS::InputMapper.set(:ascend, Gosu::KbSpace)
IMICFPS::InputMapper.set(:descend, Gosu::KbC)
IMICFPS::InputMapper.set(:toggle_first_person_view, Gosu::KbF)

View File

@@ -12,8 +12,6 @@ class IMICFPS
@file = manifest_file
parse(manifest_file)
pp @scripts
end
def parse(file)

39
lib/publisher.rb Normal file
View File

@@ -0,0 +1,39 @@
class IMICFPS
class Publisher
def self.subscribe(subscription)
raise "Expected IMICFPS::Subscription not #{subscription.class}" unless subscription.is_a?(IMICFPS::Subscription)
Publisher.instance.add_sub(subscription)
end
def self.unsubscribe(subscription)
end
def self.instance
@@instance
end
def initialize
@@instance = self
EventHandler.initiate
Component.initiate
@events = {}
end
def add_sub(subscription)
raise "Expected IMICFPS::Subscription not #{subscription.class}" unless subscription.is_a?(IMICFPS::Subscription)
@events[subscription.event] ||= []
@events[subscription.event] << subscription
end
def publish(event, context, *args)
if subscribers = @events.dig(event)
return unless event_handler = EventHandler.get(event)
subscribers.each do |subscriber|
event_handler.handle(subscriber, context, args)
end
end
end
end
end

11
lib/scripting.rb Normal file
View File

@@ -0,0 +1,11 @@
class IMICFPS
module Scripting
def on
Subscription.new(self)
end
def component(name)
Component.get(name)
end
end
end

View File

@@ -5,6 +5,8 @@ class IMICFPS
def setup
@collision_manager = CollisionManager.new(game_state: self)
@renderer = Renderer.new(game_state: self)
@publisher = Publisher.new
@map = @options[:map]
add_entity(Terrain.new(map_entity: @map.terrain, manifest: Manifest.new(package: @map.terrain.package, name: @map.terrain.name)))
@@ -85,6 +87,8 @@ class IMICFPS
@last_frame_time = Gosu.milliseconds
update_text
@publisher.publish(:tick, Gosu.milliseconds - @delta_time)
@collision_manager.update
@entities.each(&:update)
@@ -202,6 +206,7 @@ eos
@demo_changed = true
end
InputMapper.keydown(id)
@publisher.publish(:button_down, nil, id)
@entities.each do |entity|
entity.button_down(id) if defined?(entity.button_down)
@@ -218,6 +223,7 @@ eos
@demo_changed = true
end
InputMapper.keyup(id)
@publisher.publish(:button_up, nil, id)
@entities.each do |entity|
entity.button_up(id) if defined?(entity.button_up)

40
lib/subscription.rb Normal file
View File

@@ -0,0 +1,40 @@
class IMICFPS
class Subscription
attr_reader :entity, :event, :args, :block
def initialize(entity)
@entity = entity
@event = nil
@args = nil
@block = nil
end
def method_missing(event, *args, &block)
return unless subscribable_events.include?(event)
@event, @args, @block = event, args, block
Publisher.subscribe(self)
end
def trigger(event, *args)
if @block
@block.call(event, *args)
end
end
private def subscribable_events
[
:tick,
:create,
:destroy,
:button_down, :button_up,
:mouse_move,
:entity_move,
:interact,
:player_join, :player_leave, :player_die,
:pickup_item, :use_item, :drop_item,
:enter_vehicle, :exit_vehicle,
]
end
end
end

View File

@@ -3,6 +3,7 @@ class IMICFPS
attr_accessor :number_of_vertices, :needs_cursor
attr_reader :camera
attr_reader :console
def initialize(window_width = 1280, window_height = 800, fullscreen = false)
fps_target = (ARGV.first.to_i != 0) ? ARGV.first.to_i : 60
if ARGV.join.include?("--native")

View File

@@ -85,70 +85,6 @@
"scale": 1,
"scripts": []
},
{
"package":"base",
"name":"purchase_terminal",
"position": {
"x":37.8788,
"y":1.843869,
"z":-6.06061
},
"orientation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": 1,
"scripts": []
},
{
"package":"base",
"name":"information_panel",
"position": {
"x":37.8788,
"y":0.343869,
"z":-6.06061
},
"orientation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": 1,
"scripts": []
},
{
"package":"base",
"name":"door",
"position": {
"x":37.8788,
"y":0.343869,
"z":-12.06061
},
"orientation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": 1,
"scripts": []
},
{
"package":"base",
"name":"door",
"position": {
"x":37.8788,
"y":0.343869,
"z":-12.06061
},
"orientation": {
"x": 0,
"y": 180,
"z": 0
},
"scale": 1,
"scripts": []
},
{
"package":"base",
"name":"war_factory",