felecs 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.byebug_history +20 -0
- data/.gitignore +17 -0
- data/.inch.yml +11 -0
- data/.rspec +1 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.mdown +80 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +87 -0
- data/LICENSE +21 -0
- data/README.mdown +465 -0
- data/Rakefile +66 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docs/CNAME +1 -0
- data/docs/FelECS/ComponentManager.html +1239 -0
- data/docs/FelECS/Components.html +337 -0
- data/docs/FelECS/Entities.html +792 -0
- data/docs/FelECS/Order.html +251 -0
- data/docs/FelECS/Scenes.html +765 -0
- data/docs/FelECS/Stage.html +572 -0
- data/docs/FelECS/Systems.html +1505 -0
- data/docs/FelECS.html +335 -0
- data/docs/FelFlame/ComponentManager.html +1239 -0
- data/docs/FelFlame/Components.html +333 -0
- data/docs/FelFlame/Entities.html +792 -0
- data/docs/FelFlame/Helper/ComponentManager.html +1627 -0
- data/docs/FelFlame/Helper.html +142 -0
- data/docs/FelFlame/Order.html +251 -0
- data/docs/FelFlame/Scenes.html +765 -0
- data/docs/FelFlame/Stage.html +572 -0
- data/docs/FelFlame/Systems.html +1505 -0
- data/docs/FelFlame.html +319 -0
- data/docs/Felflame_.html +143 -0
- data/docs/_index.html +188 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +497 -0
- data/docs/file.README.html +560 -0
- data/docs/file.version.html +74 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +560 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +419 -0
- data/docs/top-level-namespace.html +137 -0
- data/felecs.gemspec +45 -0
- data/lib/felecs/component_manager.rb +279 -0
- data/lib/felecs/entity_manager.rb +160 -0
- data/lib/felecs/order.rb +24 -0
- data/lib/felecs/scene_manager.rb +69 -0
- data/lib/felecs/stage_manager.rb +47 -0
- data/lib/felecs/system_manager.rb +258 -0
- data/lib/felecs/version.rb +9 -0
- data/lib/felecs.rb +67 -0
- data/mrbgem/mrbgem.rake +4 -0
- data/mrbgem/mrblib/felecs.rb +913 -0
- metadata +229 -0
@@ -0,0 +1,913 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FelECS
|
4
|
+
class Entities
|
5
|
+
# Creating a new Entity
|
6
|
+
# @param components [Components] Can be any number of components, identical duplicates will be automatically purged however different components from the same component manager are allowed.
|
7
|
+
# @return [Entity]
|
8
|
+
def initialize(*components)
|
9
|
+
# Add each component
|
10
|
+
add(*components)
|
11
|
+
self.class._data.push self
|
12
|
+
end
|
13
|
+
|
14
|
+
# A hash that uses component manager constant names as keys, and where the values of those keys are arrays that contain the the components attached to this entity.
|
15
|
+
# @return [Hash<Component_Manager, Array<Integer>>]
|
16
|
+
def components
|
17
|
+
@components ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# A single component from a component manager. Use this if you expect the component to only belong to one entity and you want to access it. Access the component using either parameter notation or array notation. Array notation is conventional for better readablility.
|
21
|
+
# @example
|
22
|
+
# @entity.component[@component_manager] # array notation(the standard)
|
23
|
+
# @entity.component(@component_manager) # method notation
|
24
|
+
# @param manager [ComponentManager] If you pass nil you can then use array notation to access the same value.
|
25
|
+
# @return [Component]
|
26
|
+
def component(manager = nil)
|
27
|
+
if manager.nil?
|
28
|
+
FelECS::Entities.component_redirect.entity = self
|
29
|
+
FelECS::Entities.component_redirect
|
30
|
+
else
|
31
|
+
if components[manager].nil?
|
32
|
+
raise "This entity(#{self}) doesnt have any components of this type: #{manager}"
|
33
|
+
elsif components[manager].length > 1
|
34
|
+
Warning.warn("This entity has MANY of this component but you called the method that is intended for having a single of this component type.\nYou may have a bug in your logic.")
|
35
|
+
end
|
36
|
+
|
37
|
+
components[manager].first
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Removes this Entity from the list and purges all references to this Entity from other Components, as well as its data.
|
42
|
+
# @return [Boolean] +true+
|
43
|
+
def delete
|
44
|
+
components.each do |_component_manager, component_array|
|
45
|
+
component_array.reverse_each do |component|
|
46
|
+
component.entities.delete(self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
FelECS::Entities._data.delete self
|
50
|
+
@components = {}
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add any number components to the Entity.
|
55
|
+
# @param components_to_add [Component] Any number of components created from any component manager
|
56
|
+
# @return [Boolean] +true+
|
57
|
+
def add(*components_to_add)
|
58
|
+
components_to_add.each do |component|
|
59
|
+
if components[component.class].nil?
|
60
|
+
components[component.class] = [component]
|
61
|
+
component.entities.push self
|
62
|
+
check_systems component, :addition_triggers
|
63
|
+
elsif !components[component.class].include? component
|
64
|
+
components[component.class].push component
|
65
|
+
component.entities.push self
|
66
|
+
check_systems component, :addition_triggers
|
67
|
+
end
|
68
|
+
end
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# triggers every system associated with this component's trigger
|
73
|
+
# @return [Boolean] +true+
|
74
|
+
# @!visibility private
|
75
|
+
def check_systems(component, trigger_type)
|
76
|
+
component_calls = component.class.send(trigger_type)
|
77
|
+
component.send(trigger_type).each do |system|
|
78
|
+
component_calls |= [system]
|
79
|
+
end
|
80
|
+
component_calls.sort_by(&:priority).reverse.each(&:call)
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
# Remove a component from the Entity
|
85
|
+
# @param components_to_remove [Component] A component created from any component manager
|
86
|
+
# @return [Boolean] +true+
|
87
|
+
def remove(*components_to_remove)
|
88
|
+
components_to_remove.each do |component|
|
89
|
+
check_systems component, :removal_triggers if component.entities.include? self
|
90
|
+
component.entities.delete self
|
91
|
+
components[component.class].delete component
|
92
|
+
components.delete component.class if components[component.class].empty?
|
93
|
+
end
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Export all data into a JSON String which can then be saved into a file
|
98
|
+
# TODO: This function is not yet complete
|
99
|
+
# @return [String] A JSON formatted String
|
100
|
+
# def to_json() end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
# Makes component managers behave like arrays with additional
|
104
|
+
# methods for managing the array
|
105
|
+
# @!visibility private
|
106
|
+
def respond_to_missing?(method, *)
|
107
|
+
if _data.respond_to? method
|
108
|
+
true
|
109
|
+
else
|
110
|
+
super
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Makes component managers behave like arrays with additional
|
115
|
+
# methods for managing the array
|
116
|
+
# @!visibility private
|
117
|
+
def method_missing(method, *args, **kwargs, &block)
|
118
|
+
if _data.respond_to? method
|
119
|
+
_data.send(method, *args, **kwargs, &block)
|
120
|
+
else
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Fancy method redirection for when the `component` method is called
|
126
|
+
# in an Entity
|
127
|
+
# WARNING: This method will not correctly work with multithreading
|
128
|
+
# @!visibility private
|
129
|
+
def component_redirect
|
130
|
+
if @component_redirect
|
131
|
+
else
|
132
|
+
@component_redirect = Object.new
|
133
|
+
@component_redirect.instance_variable_set(:@entity, nil)
|
134
|
+
@component_redirect.define_singleton_method(:entity) do
|
135
|
+
instance_variable_get(:@entity)
|
136
|
+
end
|
137
|
+
@component_redirect.define_singleton_method(:entity=) do |value|
|
138
|
+
instance_variable_set(:@entity, value)
|
139
|
+
end
|
140
|
+
@component_redirect.define_singleton_method(:[]) do |component_manager|
|
141
|
+
entity.component(component_manager)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
@component_redirect
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Array<Entity>] Array of all Entities that exist
|
148
|
+
# @!visibility private
|
149
|
+
def _data
|
150
|
+
@data ||= []
|
151
|
+
end
|
152
|
+
|
153
|
+
# Creates a new entity using the data from a JSON string
|
154
|
+
# TODO: This function is not yet complete
|
155
|
+
# @param json_string [String] A string that was exported originally using the {FelECS::Entities#to_json to_json} function
|
156
|
+
# @param opts [Keywords] What values(its {FelECS::Entities#id ID} or the {FelECS::ComponentManager#id component IDs}) should be overwritten TODO: this might change
|
157
|
+
# def from_json(json_string, **opts) end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# frozen_string_literal: true
|
163
|
+
|
164
|
+
module FelECS
|
165
|
+
module Components
|
166
|
+
@component_map = []
|
167
|
+
class << self
|
168
|
+
# Creates a new {FelECS::ComponentManager component manager}.
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# # Here color is set to default to red
|
172
|
+
# # while max and current are nil until set.
|
173
|
+
# # When you make a new component using this component manager
|
174
|
+
# # these are the values and accessors it will have.
|
175
|
+
# FelECS::Component.new('Health', :max, :current, color: 'red')
|
176
|
+
#
|
177
|
+
# @param component_name [String] Name of your new component manager. Must be stylized in the format of constants in Ruby
|
178
|
+
# @param attrs [:Symbols] New components made with this manager will include these symbols as accessors, the values of these accessors will default to nil
|
179
|
+
# @param attrs_with_defaults [Keyword: DefaultValue] New components made with this manager will include these keywords as accessors, their defaults set to the values given to the keywords
|
180
|
+
# @return [ComponentManager]
|
181
|
+
def new(component_name, *attrs, **attrs_with_defaults)
|
182
|
+
if FelECS::Components.const_defined?(component_name)
|
183
|
+
raise(NameError.new, "Component Manager '#{component_name}' is already defined")
|
184
|
+
end
|
185
|
+
|
186
|
+
const_set(component_name, Class.new(FelECS::ComponentManager) {})
|
187
|
+
update_const_cache
|
188
|
+
|
189
|
+
attrs.each do |attr|
|
190
|
+
if FelECS::Components.const_get(component_name).method_defined?(attr.to_s) || FelECS::Components.const_get(component_name).method_defined?("#{attr}=")
|
191
|
+
raise NameError, "The attribute name \"#{attr}\" is already a method"
|
192
|
+
end
|
193
|
+
|
194
|
+
FelECS::Components.const_get(component_name).attr_accessor attr
|
195
|
+
end
|
196
|
+
attrs_with_defaults.each do |attr, _default|
|
197
|
+
attrs_with_defaults[attr] = _default.dup
|
198
|
+
FelECS::Components.const_get(component_name).attr_reader attr
|
199
|
+
FelECS::Components.const_get(component_name).define_method("#{attr}=") do |value|
|
200
|
+
unless value.equal? send(attr)
|
201
|
+
instance_variable_set("@#{attr}", value)
|
202
|
+
attr_changed_trigger_systems(attr)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
FelECS::Components.const_get(component_name).define_method(:set_defaults) do
|
207
|
+
attrs_with_defaults.each do |attr, default|
|
208
|
+
instance_variable_set("@#{attr}", default.dup)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
FelECS::Components.const_get(component_name)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Stores the components managers in {FelECS::Components}. This
|
215
|
+
# is needed because calling `FelECS::Components.constants`
|
216
|
+
# will not let you iterate over the value of the constants
|
217
|
+
# but will instead give you an array of symbols. This caches
|
218
|
+
# the convertion of those symbols to the actual value of the
|
219
|
+
# constants
|
220
|
+
# @!visibility private
|
221
|
+
def const_cache
|
222
|
+
@const_cache || update_const_cache
|
223
|
+
end
|
224
|
+
|
225
|
+
# Updates the array that stores the constants.
|
226
|
+
# Used internally by FelECS
|
227
|
+
# @!visibility private
|
228
|
+
def update_const_cache
|
229
|
+
@const_cache = constants.map do |constant|
|
230
|
+
const_get constant
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Forwards undefined methods to the array of constants
|
235
|
+
# if the array can handle the request. Otherwise tells
|
236
|
+
# the programmer their code errored
|
237
|
+
# @!visibility private
|
238
|
+
def respond_to_missing?(method, *)
|
239
|
+
if const_cache.respond_to? method
|
240
|
+
true
|
241
|
+
else
|
242
|
+
super
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Makes component module behave like arrays with additional
|
247
|
+
# methods for managing the array
|
248
|
+
# @!visibility private
|
249
|
+
def method_missing(method, *args, **kwargs, &block)
|
250
|
+
if const_cache.respond_to? method
|
251
|
+
const_cache.send(method, *args, **kwargs, &block)
|
252
|
+
else
|
253
|
+
super
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Component Managers are what is used to create individual components which can be attached to entities.
|
260
|
+
# When a Component is created from a Component Manager that has accessors given to it, you can set or get the values of those accessors using standard ruby message sending (e.g +@component.var = 5+), or by using the {#to_h} and {#update_attrs} methods instead.
|
261
|
+
class ComponentManager
|
262
|
+
# Allows overwriting the storage of triggers, such as for clearing.
|
263
|
+
# This method should generally only need to be used internally and
|
264
|
+
# not by a game developer.
|
265
|
+
# @!visibility private
|
266
|
+
attr_writer :addition_triggers, :removal_triggers, :attr_triggers
|
267
|
+
|
268
|
+
# Stores references to systems that should be triggered when a
|
269
|
+
# component from this manager is added.
|
270
|
+
# Do not edit this array as it is managed by FelECS automatically.
|
271
|
+
# @return [Array<System>]
|
272
|
+
def addition_triggers
|
273
|
+
@addition_triggers ||= []
|
274
|
+
end
|
275
|
+
|
276
|
+
# Stores references to systems that should be triggered when a
|
277
|
+
# component from this manager is removed.
|
278
|
+
# Do not edit this array as it is managed by FelECS automatically.
|
279
|
+
# @return [Array<System>]
|
280
|
+
def removal_triggers
|
281
|
+
@removal_triggers ||= []
|
282
|
+
end
|
283
|
+
|
284
|
+
# Stores references to systems that should be triggered when an
|
285
|
+
# attribute from this manager is changed.
|
286
|
+
# Do not edit this hash as it is managed by FelECS automatically.
|
287
|
+
# @return [Hash<Symbol, Array<System>>]
|
288
|
+
def attr_triggers
|
289
|
+
@attr_triggers ||= {}
|
290
|
+
end
|
291
|
+
|
292
|
+
# Creates a new component and sets the values of the attributes given to it. If an attritbute is not passed then it will remain as the default.
|
293
|
+
# @param attrs [Keyword: Value] You can pass any number of Keyword-Value pairs
|
294
|
+
# @return [Component]
|
295
|
+
def initialize(**attrs)
|
296
|
+
# Prepare the object
|
297
|
+
# (this is a function created with metaprogramming
|
298
|
+
# in FelECS::Components)
|
299
|
+
set_defaults
|
300
|
+
|
301
|
+
# Fill params
|
302
|
+
attrs.each do |key, value|
|
303
|
+
send "#{key}=", value
|
304
|
+
end
|
305
|
+
|
306
|
+
# Save Component
|
307
|
+
self.class.push self
|
308
|
+
end
|
309
|
+
|
310
|
+
class << self
|
311
|
+
# Makes component managers behave like arrays with additional
|
312
|
+
# methods for managing the array
|
313
|
+
# @!visibility private
|
314
|
+
def respond_to_missing?(method, *)
|
315
|
+
if _data.respond_to? method
|
316
|
+
true
|
317
|
+
else
|
318
|
+
super
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Makes component managers behave like arrays with additional
|
323
|
+
# methods for managing the array
|
324
|
+
# @!visibility private
|
325
|
+
def method_missing(method, *args, **kwargs, &block)
|
326
|
+
if _data.respond_to? method
|
327
|
+
_data.send(method, *args, **kwargs, &block)
|
328
|
+
else
|
329
|
+
super
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Allows overwriting the storage of triggers, such as for clearing.
|
334
|
+
# This method should generally only need to be used internally and
|
335
|
+
# not by a game developer.
|
336
|
+
# @!visibility private
|
337
|
+
attr_writer :addition_triggers, :removal_triggers, :attr_triggers
|
338
|
+
|
339
|
+
# Stores references to systems that should be triggered when this
|
340
|
+
# component is added to an enitity.
|
341
|
+
# Do not edit this array as it is managed by FelECS automatically.
|
342
|
+
# @return [Array<System>]
|
343
|
+
def addition_triggers
|
344
|
+
@addition_triggers ||= []
|
345
|
+
end
|
346
|
+
|
347
|
+
# Stores references to systems that should be triggered when this
|
348
|
+
# component is removed from an enitity.
|
349
|
+
# Do not edit this array as it is managed by FelECS automatically.
|
350
|
+
# @return [Array<System>]
|
351
|
+
def removal_triggers
|
352
|
+
@removal_triggers ||= []
|
353
|
+
end
|
354
|
+
|
355
|
+
# Stores references to systems that should be triggered when an
|
356
|
+
# attribute from this component changed.
|
357
|
+
# Do not edit this hash as it is managed by FelECS automatically.
|
358
|
+
# @return [Hash<Symbol, System>]
|
359
|
+
def attr_triggers
|
360
|
+
@attr_triggers ||= {}
|
361
|
+
end
|
362
|
+
|
363
|
+
# @return [Array<Component>] Array of all Components that belong to a given component manager
|
364
|
+
# @!visibility private
|
365
|
+
def _data
|
366
|
+
@data ||= []
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Entities that have this component
|
371
|
+
# @return [Array<Component>]
|
372
|
+
def entities
|
373
|
+
@entities ||= []
|
374
|
+
end
|
375
|
+
|
376
|
+
# A single entity. Use this if you expect the component to only belong to one entity and you want to access it.
|
377
|
+
# @return [Component]
|
378
|
+
def entity
|
379
|
+
if entities.empty?
|
380
|
+
Warning.warn("This component belongs to NO entities but you called the method that is intended for components belonging to a single entity.\nYou may have a bug in your logic.")
|
381
|
+
elsif entities.length > 1
|
382
|
+
Warning.warn("This component belongs to MANY entities but you called the method that is intended for components belonging to a single entity.\nYou may have a bug in your logic.")
|
383
|
+
end
|
384
|
+
entities.first
|
385
|
+
end
|
386
|
+
|
387
|
+
# Update attribute values using a hash or keywords.
|
388
|
+
# @return [Hash<Symbol, Value>] Hash of updated attributes
|
389
|
+
def update_attrs(**opts)
|
390
|
+
opts.each do |key, value|
|
391
|
+
send "#{key}=", value
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Execute systems that have been added to execute on variable change
|
396
|
+
# @return [Boolean] +true+
|
397
|
+
# @!visibility private
|
398
|
+
def attr_changed_trigger_systems(attr)
|
399
|
+
systems_to_execute = self.class.attr_triggers[attr]
|
400
|
+
systems_to_execute = [] if systems_to_execute.nil?
|
401
|
+
|
402
|
+
systems_to_execute |= attr_triggers[attr] unless attr_triggers[attr].nil?
|
403
|
+
|
404
|
+
systems_to_execute.sort_by(&:priority).reverse_each(&:call)
|
405
|
+
true
|
406
|
+
end
|
407
|
+
|
408
|
+
# Removes this component from the list and purges all references to this Component from other Entities, as well as its data.
|
409
|
+
# @return [Boolean] +true+.
|
410
|
+
def delete
|
411
|
+
addition_triggers.each do |system|
|
412
|
+
system.clear_triggers component_or_manager: self
|
413
|
+
end
|
414
|
+
entities.reverse_each do |entity|
|
415
|
+
entity.remove self
|
416
|
+
end
|
417
|
+
self.class._data.delete self
|
418
|
+
instance_variables.each do |var|
|
419
|
+
instance_variable_set(var, nil)
|
420
|
+
end
|
421
|
+
true
|
422
|
+
end
|
423
|
+
|
424
|
+
# @return [Hash<Symbol, Value>] A hash, where all the keys are attributes storing their respective values.
|
425
|
+
def to_h
|
426
|
+
return_hash = instance_variables.each_with_object({}) do |key, final|
|
427
|
+
final[key.to_s.delete_prefix('@').to_sym] = instance_variable_get(key)
|
428
|
+
end
|
429
|
+
return_hash.delete(:attr_triggers)
|
430
|
+
return_hash
|
431
|
+
end
|
432
|
+
|
433
|
+
# Export all data into a JSON String, which could then later be loaded or saved to a file
|
434
|
+
# TODO: This function is not yet complete
|
435
|
+
# @return [String] a JSON formatted String
|
436
|
+
# def to_json
|
437
|
+
# # should return a json or hash of all data in this component
|
438
|
+
# end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# frozen_string_literal: true
|
443
|
+
|
444
|
+
module FelECS
|
445
|
+
class Systems
|
446
|
+
# How early this System should be executed in a list of Systems
|
447
|
+
attr_accessor :priority
|
448
|
+
|
449
|
+
# The Constant name assigned to this System
|
450
|
+
|
451
|
+
# Allows overwriting the storage of triggers, such as for clearing.
|
452
|
+
# This method should generally only need to be used internally and
|
453
|
+
# not by a game developer.
|
454
|
+
# @!visibility private
|
455
|
+
attr_writer :addition_triggers, :removal_triggers, :attr_triggers
|
456
|
+
|
457
|
+
# Stores all the scenes this system is a part of.
|
458
|
+
attr_writer :scenes
|
459
|
+
|
460
|
+
def scenes
|
461
|
+
@scenes ||= []
|
462
|
+
end
|
463
|
+
|
464
|
+
def priority=(priority)
|
465
|
+
@priority = priority
|
466
|
+
scenes.each do |scene|
|
467
|
+
scene.systems = scene.systems.sort_by(&:priority)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Stores references to components or their managers that trigger
|
472
|
+
# this component when a component or component from that manager
|
473
|
+
# is added to an entity.
|
474
|
+
# Do not edit this hash as it is managed by FelECS automatically.
|
475
|
+
# @return [Array<Component>]
|
476
|
+
def addition_triggers
|
477
|
+
@addition_triggers ||= []
|
478
|
+
end
|
479
|
+
|
480
|
+
# Stores references to components or their managers that trigger
|
481
|
+
# this component when a component or component from that manager
|
482
|
+
# is removed from an entity.
|
483
|
+
# Do not edit this hash as it is managed by FelECS automatically.
|
484
|
+
# @return [Array<Component>]
|
485
|
+
def removal_triggers
|
486
|
+
@removal_triggers ||= []
|
487
|
+
end
|
488
|
+
|
489
|
+
# Stores references to systems that should be triggered when an
|
490
|
+
# attribute from this manager is changed
|
491
|
+
# Do not edit this hash as it is managed by FelECS automatically.
|
492
|
+
# @return [Hash<Symbol, Array<Symbol>>]
|
493
|
+
def attr_triggers
|
494
|
+
@attr_triggers ||= {}
|
495
|
+
end
|
496
|
+
|
497
|
+
class << self
|
498
|
+
# Stores the systems in {FelECS::Components}. This
|
499
|
+
# is needed because calling `FelECS::Components.constants`
|
500
|
+
# will not let you iterate over the value of the constants
|
501
|
+
# but will instead give you an array of symbols. This caches
|
502
|
+
# the convertion of those symbols to the actual value of the
|
503
|
+
# constants
|
504
|
+
def const_cache
|
505
|
+
@const_cache || update_const_cache
|
506
|
+
end
|
507
|
+
|
508
|
+
# Updates the array that stores the constants.
|
509
|
+
# Used internally by FelECS
|
510
|
+
# @!visibility private
|
511
|
+
def update_const_cache
|
512
|
+
@const_cache = constants.map do |constant|
|
513
|
+
const_get constant
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# Forwards undefined methods to the array of constants
|
518
|
+
# if the array can handle the request. Otherwise tells
|
519
|
+
# the programmer their code errored
|
520
|
+
# @!visibility private
|
521
|
+
def respond_to_missing?(method, *)
|
522
|
+
if const_cache.respond_to? method
|
523
|
+
true
|
524
|
+
else
|
525
|
+
super
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Makes system module behave like arrays with additional
|
530
|
+
# methods for managing the array
|
531
|
+
# @!visibility private
|
532
|
+
def method_missing(method, *args, **kwargs, &block)
|
533
|
+
if const_cache.respond_to? method
|
534
|
+
const_cache.send(method, *args, **kwargs, &block)
|
535
|
+
else
|
536
|
+
super
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
# Creates a new System which can be accessed as a constant under the namespace {FelECS::Systems}.
|
542
|
+
# The name given is what constant the system is assigned to
|
543
|
+
#
|
544
|
+
# @example
|
545
|
+
# FelECS::Systems.new('PassiveHeal', priority: -2) do
|
546
|
+
# FelECS::Components::Health.each do |component|
|
547
|
+
# component.hp += 5
|
548
|
+
# end
|
549
|
+
# end
|
550
|
+
# # Give it a low priority so other systems such as a
|
551
|
+
# # Poison system would kill the player first
|
552
|
+
#
|
553
|
+
# @param name [String] The name this system will use. Needs to to be in the Ruby Constant format.
|
554
|
+
# @param priority [Integer] Which priority order this system should be executed in relative to other systems. Higher means executed earlier.
|
555
|
+
# @param block [Proc] The code you wish to be executed when the system is triggered. Can be defined by using a +do end+ block or using +{ }+ braces.
|
556
|
+
def initialize(name, priority: 0, &block)
|
557
|
+
FelECS::Systems.const_set(name, self)
|
558
|
+
FelECS::Systems.update_const_cache
|
559
|
+
@priority = priority
|
560
|
+
@block = block
|
561
|
+
@scenes = []
|
562
|
+
end
|
563
|
+
|
564
|
+
# Manually execute the system a single time
|
565
|
+
def call
|
566
|
+
@block.call
|
567
|
+
end
|
568
|
+
|
569
|
+
# Redefine what code is executed by this System when it is called upon.
|
570
|
+
# @param block [Proc] The code you wish to be executed when the system is triggered. Can be defined by using a +do end+ block or using +{ }+ braces.
|
571
|
+
def redefine(&block)
|
572
|
+
@block = block
|
573
|
+
end
|
574
|
+
|
575
|
+
# Removes triggers from this system. This function is fairly flexible so it can accept a few different inputs
|
576
|
+
# For addition and removal triggers, you can optionally pass in a component, or a manager to clear specifically
|
577
|
+
# the relevant triggers for that one component or manager. If you do not pass a component or manager then it will
|
578
|
+
# clear triggers for all components and managers.
|
579
|
+
# For attr_triggers
|
580
|
+
# @example
|
581
|
+
# # To clear all triggers that execute this system when a component is added:
|
582
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers
|
583
|
+
# # Same as above but for when a component is removed instead
|
584
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :removal_triggers
|
585
|
+
# # Same as above but for when a component has a certain attribute changed
|
586
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers
|
587
|
+
#
|
588
|
+
# # Clear a trigger from a specific component
|
589
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent[0]
|
590
|
+
# # Clear a trigger from a specific component manager
|
591
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :addition_triggers, FelECS::Component::ExampleComponent
|
592
|
+
#
|
593
|
+
# # Clear the trigger that executes a system when the ':example_attr' is changes
|
594
|
+
# FelECS::Systems::ExampleSystem.clear_triggers :attr_triggers, :example_attr
|
595
|
+
# @param trigger_types [:Symbols] One or more of the following trigger types: +:addition_triggers+, +:removal_triggers+, or +:attr_triggers+. If attr_triggers is used then you may pass attributes you wish to be cleared as symbols in this parameter as well
|
596
|
+
# @param component_or_manager [Component or ComponentManager] The object to clear triggers from. Use Nil to clear triggers from all components associated with this system.
|
597
|
+
# @return [Boolean] +true+
|
598
|
+
def clear_triggers(*trigger_types, component_or_manager: nil)
|
599
|
+
trigger_types = %i[addition_triggers removal_triggers attr_triggers] if trigger_types.empty?
|
600
|
+
|
601
|
+
if trigger_types.include? :attr_triggers
|
602
|
+
if (trigger_types - %i[addition_triggers
|
603
|
+
removal_triggers
|
604
|
+
attr_triggers]).empty?
|
605
|
+
|
606
|
+
if component_or_manager.nil?
|
607
|
+
# remove all attrs
|
608
|
+
attr_triggers.each do |cmp_or_mgr, attrs|
|
609
|
+
attrs.each do |attr|
|
610
|
+
next if cmp_or_mgr.attr_triggers[attr].nil?
|
611
|
+
|
612
|
+
cmp_or_mgr.attr_triggers[attr].delete self
|
613
|
+
end
|
614
|
+
self.attr_triggers = {}
|
615
|
+
end
|
616
|
+
else
|
617
|
+
# remove attrs relevant to comp_or_man
|
618
|
+
unless attr_triggers[component_or_manager].nil?
|
619
|
+
attr_triggers[component_or_manager].each do |attr|
|
620
|
+
component_or_manager.attr_triggers[attr].delete self
|
621
|
+
end
|
622
|
+
attr_triggers[component_or_manager] = []
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
elsif component_or_manager.nil?
|
627
|
+
|
628
|
+
(trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
|
629
|
+
# remove attr
|
630
|
+
attr_triggers.each do |cmp_or_mgr, _attrs|
|
631
|
+
cmp_or_mgr.attr_triggers[attr].delete self
|
632
|
+
end
|
633
|
+
end
|
634
|
+
attr_triggers.delete(trigger_types - %i[addition_triggers
|
635
|
+
removal_triggers
|
636
|
+
attr_triggers])
|
637
|
+
else
|
638
|
+
# remove attr from component_or_manager
|
639
|
+
(trigger_types - %i[addition_triggers removal_triggers attr_triggers]).each do |attr|
|
640
|
+
next if component_or_manager.attr_triggers[attr].nil?
|
641
|
+
|
642
|
+
component_or_manager.attr_triggers[attr].delete self
|
643
|
+
end
|
644
|
+
attr_triggers[component_or_manager] -= trigger_types unless attr_triggers[component_or_manager].nil?
|
645
|
+
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
(trigger_types & %i[removal_triggers addition_triggers] - [:attr_triggers]).each do |trigger_type|
|
650
|
+
if component_or_manager.nil?
|
651
|
+
# remove all removal triggers
|
652
|
+
send(trigger_type).each do |trigger|
|
653
|
+
trigger.send(trigger_type).delete self
|
654
|
+
end
|
655
|
+
send("#{trigger_type}=", [])
|
656
|
+
else
|
657
|
+
# remove removal trigger relevant to comp/man
|
658
|
+
send(trigger_type).delete component_or_manager
|
659
|
+
component_or_manager.send(trigger_type).delete self
|
660
|
+
end
|
661
|
+
end
|
662
|
+
true
|
663
|
+
end
|
664
|
+
|
665
|
+
# Add a component or component manager so that it triggers this system when the component or a component from the component manager is added to an entity
|
666
|
+
# @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when added
|
667
|
+
# @return [Boolean] +true+
|
668
|
+
def trigger_when_added(component_or_manager)
|
669
|
+
self.addition_triggers |= [component_or_manager]
|
670
|
+
component_or_manager.addition_triggers |= [self]
|
671
|
+
true
|
672
|
+
end
|
673
|
+
|
674
|
+
# Add a component or component manager so that it triggers this system when the component or a component from the component manager is removed from an entity
|
675
|
+
# @param component_or_manager [Component or ComponentManager] The component or component manager to trigger this system when removed
|
676
|
+
# @return [Boolean] +true+
|
677
|
+
def trigger_when_removed(component_or_manager)
|
678
|
+
self.removal_triggers |= [component_or_manager]
|
679
|
+
component_or_manager.removal_triggers |= [self]
|
680
|
+
true
|
681
|
+
end
|
682
|
+
|
683
|
+
# Add a component or component manager so that it triggers this system when a component's attribute is changed.
|
684
|
+
# @return [Boolean] +true+
|
685
|
+
def trigger_when_is_changed(component_or_manager, attr)
|
686
|
+
if component_or_manager.attr_triggers[attr].nil?
|
687
|
+
component_or_manager.attr_triggers[attr] = [self]
|
688
|
+
else
|
689
|
+
component_or_manager.attr_triggers[attr] |= [self]
|
690
|
+
end
|
691
|
+
if attr_triggers[component_or_manager].nil?
|
692
|
+
attr_triggers[component_or_manager] = [attr]
|
693
|
+
else
|
694
|
+
attr_triggers[component_or_manager] |= [attr]
|
695
|
+
end
|
696
|
+
true
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
# frozen_string_literal: true
|
702
|
+
|
703
|
+
module FelECS
|
704
|
+
class Scenes
|
705
|
+
# Allows overwriting the storage of systems, such as for clearing.
|
706
|
+
# This method should generally only need to be used internally and
|
707
|
+
# not by a game developer/
|
708
|
+
# @!visibility private
|
709
|
+
attr_writer :systems
|
710
|
+
|
711
|
+
# How early this Scene should be executed in a list of Scenes
|
712
|
+
attr_accessor :priority
|
713
|
+
|
714
|
+
def priority=(priority)
|
715
|
+
@priority = priority
|
716
|
+
FelECS::Stage.scenes = FelECS::Stage.scenes.sort_by(&:priority)
|
717
|
+
priority
|
718
|
+
end
|
719
|
+
|
720
|
+
# Create a new Scene using the name given
|
721
|
+
# @param name [String] String format must follow requirements of a constant
|
722
|
+
def initialize(name, priority: 0)
|
723
|
+
self.priority = priority
|
724
|
+
FelECS::Scenes.const_set(name, self)
|
725
|
+
end
|
726
|
+
|
727
|
+
# The list of Systems this Scene contains
|
728
|
+
# @return [Array<System>]
|
729
|
+
def systems
|
730
|
+
@systems ||= []
|
731
|
+
end
|
732
|
+
|
733
|
+
# Execute all systems in this Scene, in the order of their priority
|
734
|
+
# @return [Boolean] +true+
|
735
|
+
def call
|
736
|
+
systems.each(&:call)
|
737
|
+
true
|
738
|
+
end
|
739
|
+
|
740
|
+
# Adds any number of Systems to this Scene
|
741
|
+
# @return [Boolean] +true+
|
742
|
+
def add(*systems_to_add)
|
743
|
+
self.systems |= systems_to_add
|
744
|
+
self.systems = systems.sort_by(&:priority)
|
745
|
+
systems_to_add.each do |system|
|
746
|
+
system.scenes |= [self]
|
747
|
+
end
|
748
|
+
true
|
749
|
+
end
|
750
|
+
|
751
|
+
# Removes any number of Systems from this Scene
|
752
|
+
# @return [Boolean] +true+
|
753
|
+
def remove(*systems_to_remove)
|
754
|
+
self.systems -= systems_to_remove
|
755
|
+
true
|
756
|
+
end
|
757
|
+
|
758
|
+
# Removes all Systems from this Scene
|
759
|
+
# @return [Boolean] +true+
|
760
|
+
def clear
|
761
|
+
systems.each do |system|
|
762
|
+
system.scenes.delete self
|
763
|
+
end
|
764
|
+
systems.clear
|
765
|
+
# FelECS::Stage.update_systems_list if FelECS::Stage.scenes.include? self
|
766
|
+
true
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
# frozen_string_literal: true
|
772
|
+
|
773
|
+
module FelECS
|
774
|
+
module Stage
|
775
|
+
class << self
|
776
|
+
# Allows clearing of scenes and systems.
|
777
|
+
# Used internally by FelECS and shouldn't need to be ever used by developers
|
778
|
+
# @!visibility private
|
779
|
+
attr_writer :scenes
|
780
|
+
|
781
|
+
# Add any number of Scenes to the Stage
|
782
|
+
# @return [Boolean] +true+
|
783
|
+
def add(*scenes_to_add)
|
784
|
+
self.scenes |= scenes_to_add
|
785
|
+
self.scenes = scenes.sort_by(&:priority)
|
786
|
+
true
|
787
|
+
end
|
788
|
+
|
789
|
+
# Remove any number of Scenes from the Stage
|
790
|
+
# @return [Boolean] +true+
|
791
|
+
def remove(*scenes_to_remove)
|
792
|
+
self.scenes -= scenes_to_remove
|
793
|
+
true
|
794
|
+
end
|
795
|
+
|
796
|
+
# Clears all Scenes that were added to the Stage
|
797
|
+
# @return [Boolean] +true+
|
798
|
+
def clear
|
799
|
+
self.scenes.clear
|
800
|
+
true
|
801
|
+
end
|
802
|
+
|
803
|
+
# Executes one frame of the game. This executes all the Scenes added to the Stage in order of their priority.
|
804
|
+
# @return [Boolean] +true+
|
805
|
+
def call
|
806
|
+
self.scenes.each(&:call)
|
807
|
+
true
|
808
|
+
end
|
809
|
+
|
810
|
+
# Contains all the Scenes added to the Stage
|
811
|
+
# @return [Array<Scene>]
|
812
|
+
def scenes
|
813
|
+
@scenes ||= []
|
814
|
+
end
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
# frozen_string_literal: true
|
820
|
+
|
821
|
+
module FelECS
|
822
|
+
module Order
|
823
|
+
# Sets the priority of all items passed into this method
|
824
|
+
# according to the order they were passed.
|
825
|
+
# If an array is one of the elements then it will give all
|
826
|
+
# of those elements in the array the same priority.
|
827
|
+
# @param sortables [(Systems and Array<Systems>) or (Scenes and Array<Scenes>)]
|
828
|
+
# @return [Boolean] +true+.
|
829
|
+
def self.sort(*sortables)
|
830
|
+
sortables.each_with_index do |sorted, index|
|
831
|
+
if sorted.respond_to? :priority
|
832
|
+
sorted.priority = index
|
833
|
+
else
|
834
|
+
sorted.each do |item|
|
835
|
+
item.priority = index
|
836
|
+
end
|
837
|
+
end
|
838
|
+
end
|
839
|
+
true
|
840
|
+
end
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
# frozen_string_literal: true
|
845
|
+
|
846
|
+
# :nocov:
|
847
|
+
# Keeps the version of the Gem
|
848
|
+
module FelECS
|
849
|
+
# The version of the Gem
|
850
|
+
VERSION = '5.0.0'
|
851
|
+
end
|
852
|
+
# :nocov:
|
853
|
+
|
854
|
+
# frozen_string_literal: true
|
855
|
+
|
856
|
+
|
857
|
+
|
858
|
+
# The FelECS namespace where all its functionality resides under.
|
859
|
+
module FelECS
|
860
|
+
class << self
|
861
|
+
# :nocov:
|
862
|
+
|
863
|
+
# An alias for {FelECS::Stage.call}. It executes a single frame in the game.
|
864
|
+
def call
|
865
|
+
FelECS::Stage.call
|
866
|
+
end
|
867
|
+
# :nocov:
|
868
|
+
end
|
869
|
+
|
870
|
+
# Creates and manages Entities. Entities are just collections of Components.
|
871
|
+
# You can use array methods directly on this class to access Entities.
|
872
|
+
class Entities; end
|
873
|
+
|
874
|
+
# Creates component managers and allows accessing them them under the {FelECS::Components} namespace as Constants.
|
875
|
+
# You can use array methods directly on this class to access Component Managers.
|
876
|
+
#
|
877
|
+
# To see how component managers are used please look at the {FelECS::ComponentManager} documentation.
|
878
|
+
module Components; end
|
879
|
+
|
880
|
+
# Creates and manages Systems. Systems are the logic of the game and do not contain any data within them. Any systems you create are accessable under the {FelECS::Systems} namespace as Constants.
|
881
|
+
# You can use array methods directly on this class to access Systems.
|
882
|
+
class Systems; end
|
883
|
+
|
884
|
+
# Creates and manages Scenes. Scenes are collections of Systems, and execute all the Systems when called upon. Any scenes you create are accessable under the {FelECS::Scenes} namespace as Constants.
|
885
|
+
class Scenes; end
|
886
|
+
|
887
|
+
# Stores Scenes you add to it which you want to execute on each frame. When called upon will execute all Systems in the Scenes in the Stage and will execute them according to their priority order.
|
888
|
+
module Stage; end
|
889
|
+
|
890
|
+
# Sets the priority of a list of Systems or Scenes for you in the order you pass them to this class.
|
891
|
+
module Order; end
|
892
|
+
end
|
893
|
+
|
894
|
+
# An alias for {FelECS}
|
895
|
+
FECS = FelECS
|
896
|
+
|
897
|
+
# An alias for {FelECS::Entities}
|
898
|
+
FECS::Ent = FelECS::Entities
|
899
|
+
|
900
|
+
# An alias for {FelECS::Components}
|
901
|
+
FECS::Cmp = FelECS::Components
|
902
|
+
|
903
|
+
# An alias for {FelECS::Systems}
|
904
|
+
FECS::Sys = FelECS::Systems
|
905
|
+
|
906
|
+
# An alias for {FelECS::Scenes}
|
907
|
+
FECS::Scn = FelECS::Scenes
|
908
|
+
|
909
|
+
# An alias for {FelECS::Stage}
|
910
|
+
FECS::Stg = FelECS::Stage
|
911
|
+
|
912
|
+
# An alias for {FelECS::
|
913
|
+
FECS::Odr = FelECS::Order
|