draco 0.2.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|