draco 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -2
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +5 -0
- data/README.md +71 -1
- data/draco.gemspec +1 -1
- data/lib/draco.rb +135 -13
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 363ccf9c3649a752219bca392bdc13b88defbd864a63afb5615969986e59c070
|
4
|
+
data.tar.gz: 81dcbc8225646cec6efc357ecaacfd89334e9a183caad035cf4bd2ece9a01470
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa089a7a6abe9b615d07e8def023fad13fedcbca605e78bc1acf181431b1659961811074a9238f79702b7e1e9ddd76782a173fd079ed5f7ae04a2ee83905c660
|
7
|
+
data.tar.gz: e6a70346719b8cf5e5269924158bcbad7ed4ea66440686c57931b7dc6232d467adf6abeac5740970e6d16a573d61a66af27c5626599ed115a7a87624d9ac9bdf
|
data/.github/workflows/ruby.yml
CHANGED
@@ -22,8 +22,7 @@ jobs:
|
|
22
22
|
- name: Set up Ruby
|
23
23
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
24
24
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
25
|
-
|
26
|
-
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
|
25
|
+
uses: ruby/setup-ruby@v1
|
27
26
|
with:
|
28
27
|
ruby-version: 2.6
|
29
28
|
- name: Install dependencies
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -6,12 +6,36 @@ An Entity Component System is an architectural framework that decouples game obj
|
|
6
6
|
build game objects through composition. This allows you to easily share small logic components between different game
|
7
7
|
objects.
|
8
8
|
|
9
|
+
## Sample Application
|
10
|
+
|
11
|
+
This repository includes a sample application in `samples/teeny-tiny`.
|
12
|
+
|
13
|
+
### Running the sample application
|
14
|
+
|
15
|
+
1. Download the [latest release](https://github.com/guitsaru/draco/archive/main.zip) of this repository.
|
16
|
+
2. Create a copy of DragonRuby GTK in a new folder.
|
17
|
+
3. Copy the teeny-tiny directory from draco into your new DragonRuby GTK folder. `cp -r draco/samples/teeny-tiny dragonruby/teeny-tiny`.
|
18
|
+
4. Run it using `./dragonruby teeny-tiny`.
|
19
|
+
|
9
20
|
## Installation
|
10
21
|
|
11
22
|
1. Create a `lib` directory inside your game's `app` directory.
|
12
23
|
2. Copy `lib/draco.rb` into your new `lib` directory.
|
13
24
|
3. In your `main.rb` file, require `app/lib/draco.rb`.
|
14
25
|
|
26
|
+
## Support
|
27
|
+
|
28
|
+
- Find a bug? [Open an issue](https://github.com/guitsaru/draco/issues).
|
29
|
+
- Need help? [Start a Discussion](https://github.com/guitsaru/draco/discussions) or [Join us in Discord: Channel #oss-draco](https://discord.gg/vPUNtwfm).
|
30
|
+
|
31
|
+
## Versioning
|
32
|
+
|
33
|
+
Draco uses [https://semver.org/].
|
34
|
+
|
35
|
+
* Major (X.y.z) - Incremented for any backwards incompatible public API changes.
|
36
|
+
* Minor (x.Y.z) - Incremented for new, backwards compatible, public API enhancements/fixes.
|
37
|
+
* Patch (x.y.Z) - Incremented for small, backwards compatible, bug fixes.
|
38
|
+
|
15
39
|
## Usage
|
16
40
|
|
17
41
|
### Components
|
@@ -23,7 +47,7 @@ These can be shared across many different types of game objects.
|
|
23
47
|
class Visible < Draco::Component; end
|
24
48
|
```
|
25
49
|
|
26
|
-
`Visible` is an example of a
|
50
|
+
`Visible` is an example of a tag component. An entity either has it, or it doesn't. We can also associate data with our
|
27
51
|
components.
|
28
52
|
|
29
53
|
```ruby
|
@@ -44,6 +68,16 @@ component.x
|
|
44
68
|
# => 110
|
45
69
|
```
|
46
70
|
|
71
|
+
#### Tag Components
|
72
|
+
|
73
|
+
The `Visible` class above is an example of a tag component. These are common enough that we don't necessarily want to
|
74
|
+
define a bunch of empty component classes. Draco provides a way to generate these classes at runtime.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Draco::Tag(:visible)
|
78
|
+
# => Visible
|
79
|
+
```
|
80
|
+
|
47
81
|
### Entities
|
48
82
|
|
49
83
|
Entities are independant game objects. They consist of a unique id and a list of components.
|
@@ -129,6 +163,40 @@ world.systems << RenderSpriteSystem
|
|
129
163
|
world.tick(args)
|
130
164
|
```
|
131
165
|
|
166
|
+
Just like with entities, we can define a subclassed template for our world.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class Overworld < Draco::World
|
170
|
+
entity Goblin
|
171
|
+
entity Player, position: { x: 50, y: 50 }, as: :player
|
172
|
+
systems RenderSpriteSystem, InputSystem
|
173
|
+
end
|
174
|
+
|
175
|
+
world = Overworld.new
|
176
|
+
```
|
177
|
+
|
178
|
+
### Named Entities
|
179
|
+
|
180
|
+
If there are entities that are frequently accessed in our systems, we can give these a name. In the above example, our
|
181
|
+
player entity has been given the name `player`. We can now access this directly from our world:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
world.player
|
185
|
+
```
|
186
|
+
|
187
|
+
### Fetching entities by id
|
188
|
+
|
189
|
+
In some cases you'll want to keep track of entities by their id, such as when you want to keep track of another entity in a component.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
entity = Player.new
|
193
|
+
entity.id
|
194
|
+
# => 12
|
195
|
+
|
196
|
+
world.entities[12] == entity
|
197
|
+
# => true
|
198
|
+
```
|
199
|
+
|
132
200
|
## Learn More
|
133
201
|
|
134
202
|
Here are some good resources to learn about Entity Component Systems
|
@@ -140,6 +208,8 @@ Here are some good resources to learn about Entity Component Systems
|
|
140
208
|
|
141
209
|
Draco is licensed under AGPL. You can purchase the right to use Draco under the [commercial license](https://github.com/guitsaru/draco/blob/master/COMM-LICENSE).
|
142
210
|
|
211
|
+
Each purchase comes with free upgrades for the current major version of Draco.
|
212
|
+
|
143
213
|
<a class="gumroad-button" href="https://guitsaru.itch.io/draco" target="_blank">Purchase Commercial License</a>
|
144
214
|
|
145
215
|
## Development
|
data/draco.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
20
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
21
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|samples)/}) }
|
23
23
|
end
|
24
24
|
spec.bindir = "exe"
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/lib/draco.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# An Entity Component System is an architectural pattern used in game development to decouple behavior from objects.
|
6
6
|
module Draco
|
7
7
|
# Public: The version of the library. Draco uses semver to version releases.
|
8
|
-
VERSION = "0.
|
8
|
+
VERSION = "0.5.0"
|
9
9
|
|
10
10
|
# Public: A general purpose game object that consists of a unique id and a collection of Components.
|
11
11
|
class Entity
|
@@ -39,8 +39,8 @@ module Draco
|
|
39
39
|
@default_components[component] = defaults
|
40
40
|
end
|
41
41
|
|
42
|
-
# Internal: Returns the default components for the class.
|
43
42
|
class << self
|
43
|
+
# Internal: Returns the default components for the class.
|
44
44
|
attr_reader :default_components
|
45
45
|
end
|
46
46
|
|
@@ -300,6 +300,23 @@ module Draco
|
|
300
300
|
end
|
301
301
|
end
|
302
302
|
|
303
|
+
# Internal: Empty module to enable Tag() method.
|
304
|
+
module Tag; end
|
305
|
+
|
306
|
+
# Public: Creates a new empty component at runtime. If the given Class already exists, it reuses the existing Class.
|
307
|
+
#
|
308
|
+
# name - The symbol or string name of the component. It can be either camelcase or underscored.
|
309
|
+
#
|
310
|
+
# Returns a Class with superclass of Draco::Component.
|
311
|
+
def self.Tag(name) # rubocop:disable Naming/MethodName
|
312
|
+
klass_name = camelize(name)
|
313
|
+
|
314
|
+
return Object.const_get(klass_name) if Object.const_defined?(klass_name)
|
315
|
+
|
316
|
+
klass = Class.new(Component)
|
317
|
+
Object.const_set(klass_name, klass)
|
318
|
+
end
|
319
|
+
|
303
320
|
# Public: Systems contain the logic of the game.
|
304
321
|
# The System runs on each tick and manipulates the Entities in the World.
|
305
322
|
class System
|
@@ -373,6 +390,62 @@ module Draco
|
|
373
390
|
|
374
391
|
# Public: The container for current Entities and Systems.
|
375
392
|
class World
|
393
|
+
@default_entities = []
|
394
|
+
@default_systems = []
|
395
|
+
|
396
|
+
# Internal: Resets the default components for each class that inherites Entity.
|
397
|
+
#
|
398
|
+
# sub - The class that is inheriting Entity.
|
399
|
+
#
|
400
|
+
# Returns nothing.
|
401
|
+
def self.inherited(sub)
|
402
|
+
super
|
403
|
+
sub.instance_variable_set(:@default_entities, [])
|
404
|
+
sub.instance_variable_set(:@default_systems, [])
|
405
|
+
end
|
406
|
+
|
407
|
+
# Public: Adds a default Entity to the World.
|
408
|
+
#
|
409
|
+
# entity - The class of the Entity to add by default.
|
410
|
+
# defaults - The Hash of default values for the Entity. (default: {})
|
411
|
+
#
|
412
|
+
# Examples
|
413
|
+
#
|
414
|
+
# entity(Player)
|
415
|
+
#
|
416
|
+
# entity(Player, position: { x: 0, y: 0 })
|
417
|
+
#
|
418
|
+
# Returns nothing.
|
419
|
+
def self.entity(entity, defaults = {})
|
420
|
+
name = defaults[:as]
|
421
|
+
@default_entities.push([entity, defaults])
|
422
|
+
|
423
|
+
attr_reader(name.to_sym) if name
|
424
|
+
end
|
425
|
+
|
426
|
+
# Public: Adds default Systems to the World.
|
427
|
+
#
|
428
|
+
# systems - The System or Array list of System classes to add to the World.
|
429
|
+
#
|
430
|
+
# Examples
|
431
|
+
#
|
432
|
+
# systems(RenderSprites)
|
433
|
+
#
|
434
|
+
# systems(RenderSprites, RenderLabels)
|
435
|
+
#
|
436
|
+
# Returns nothing.
|
437
|
+
def self.systems(*systems)
|
438
|
+
@default_systems += Array(systems).flatten
|
439
|
+
end
|
440
|
+
|
441
|
+
class << self
|
442
|
+
# Internal: Returns the default Entities for the class.
|
443
|
+
attr_reader :default_entities
|
444
|
+
|
445
|
+
# Internal: Returns the default Systems for the class.
|
446
|
+
attr_reader :default_systems
|
447
|
+
end
|
448
|
+
|
376
449
|
# Public: Returns the Array of Systems.
|
377
450
|
attr_reader :systems
|
378
451
|
|
@@ -384,8 +457,17 @@ module Draco
|
|
384
457
|
# entities - The Array of Entities for the World (default: []).
|
385
458
|
# systems - The Array of System Classes for the World (default: []).
|
386
459
|
def initialize(entities: [], systems: [])
|
387
|
-
|
388
|
-
|
460
|
+
default_entities = self.class.default_entities.map do |default|
|
461
|
+
klass, attributes = default
|
462
|
+
name = attributes[:as]
|
463
|
+
entity = klass.new(attributes)
|
464
|
+
instance_variable_set("@#{name}", entity) if name
|
465
|
+
|
466
|
+
entity
|
467
|
+
end
|
468
|
+
|
469
|
+
@entities = EntityStore.new(default_entities + entities)
|
470
|
+
@systems = self.class.default_systems + systems
|
389
471
|
end
|
390
472
|
|
391
473
|
# Public: Runs all of the Systems every tick.
|
@@ -440,22 +522,36 @@ module Draco
|
|
440
522
|
def initialize(*entities)
|
441
523
|
@entity_to_components = Hash.new { |hash, key| hash[key] = Set.new }
|
442
524
|
@component_to_entities = Hash.new { |hash, key| hash[key] = Set.new }
|
525
|
+
@entity_ids = {}
|
443
526
|
|
444
527
|
self << entities
|
445
528
|
end
|
446
529
|
|
447
|
-
# Internal: Gets all Entities that implement all of the given Components
|
530
|
+
# Internal: Gets all Entities that implement all of the given Components or that match the given entity ids.
|
448
531
|
#
|
449
|
-
#
|
532
|
+
# components_or_ids - The Component Classes to filter by
|
450
533
|
#
|
451
534
|
# Returns a Set list of Entities
|
452
|
-
def [](*
|
453
|
-
|
535
|
+
def [](*components_or_ids)
|
536
|
+
components_or_ids
|
454
537
|
.flatten
|
455
|
-
.map { |
|
538
|
+
.map { |component_or_id| select_entities(component_or_id) }
|
456
539
|
.reduce { |acc, i| i & acc }
|
457
540
|
end
|
458
541
|
|
542
|
+
# Internal: Gets entities by component or id.
|
543
|
+
#
|
544
|
+
# component_or_id - The Component Class or entity id to select.
|
545
|
+
#
|
546
|
+
# Returns an Array of Entities.
|
547
|
+
def select_entities(component_or_id)
|
548
|
+
if component_or_id.is_a?(Numeric)
|
549
|
+
Array(@entity_ids[component_or_id])
|
550
|
+
else
|
551
|
+
@component_to_entities[component_or_id]
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
459
555
|
# Internal: Adds Entities to the EntityStore
|
460
556
|
#
|
461
557
|
# entities - The Entity or Array list of Entities to add to the EntityStore.
|
@@ -474,6 +570,8 @@ module Draco
|
|
474
570
|
def add(entity)
|
475
571
|
entity.subscribe(self)
|
476
572
|
|
573
|
+
@entity_ids[entity.id] = entity
|
574
|
+
|
477
575
|
components = entity.components.map(&:class)
|
478
576
|
@entity_to_components[entity].merge(components)
|
479
577
|
|
@@ -490,9 +588,10 @@ module Draco
|
|
490
588
|
#
|
491
589
|
# Returns the EntityStore
|
492
590
|
def delete(entity)
|
493
|
-
|
591
|
+
@entity_ids.delete(entity.id)
|
592
|
+
components = Array(@entity_to_components.delete(entity))
|
494
593
|
|
495
|
-
components.
|
594
|
+
components.each do |component|
|
496
595
|
@component_to_entities[component].delete(entity)
|
497
596
|
end
|
498
597
|
end
|
@@ -642,13 +741,36 @@ module Draco
|
|
642
741
|
#
|
643
742
|
# Returns a String.
|
644
743
|
def self.underscore(string)
|
645
|
-
string.split("::").last.bytes.map.with_index do |byte, i|
|
744
|
+
string.to_s.split("::").last.bytes.map.with_index do |byte, i|
|
646
745
|
if byte > 64 && byte < 97
|
647
|
-
downcased = byte + 32
|
746
|
+
downcased = byte + 32
|
648
747
|
i.zero? ? downcased.chr : "_#{downcased.chr}"
|
649
748
|
else
|
650
749
|
byte.chr
|
651
750
|
end
|
652
751
|
end.join
|
653
752
|
end
|
753
|
+
|
754
|
+
# Internal: Converts an underscored string into a camel case string.
|
755
|
+
#
|
756
|
+
# Examples
|
757
|
+
#
|
758
|
+
# camlize("camel_case")
|
759
|
+
# # => "CamelCase"
|
760
|
+
#
|
761
|
+
# Returns a string.
|
762
|
+
def self.camelize(string) # rubocop:disable Metrics/MethodLength
|
763
|
+
modifier = -32
|
764
|
+
|
765
|
+
string.to_s.bytes.map do |byte|
|
766
|
+
if byte == 95
|
767
|
+
modifier = -32
|
768
|
+
nil
|
769
|
+
else
|
770
|
+
char = (byte + modifier).chr
|
771
|
+
modifier = 0
|
772
|
+
char
|
773
|
+
end
|
774
|
+
end.compact.join
|
775
|
+
end
|
654
776
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Pruitt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A library for Entities, Components, and Systems in games.
|
14
14
|
email:
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- ".gitignore"
|
23
23
|
- ".rspec"
|
24
24
|
- ".rubocop.yml"
|
25
|
+
- CHANGELOG.md
|
25
26
|
- CODE_OF_CONDUCT.md
|
26
27
|
- COMM-LICENSE
|
27
28
|
- Gemfile
|