belts_engine 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 96110da4df4e213c5a54284cfee9210bc0426105e6c7b20f0891742189d49bae
4
+ data.tar.gz: 6aab3aa5dd2c03b7fceae17c6232b24c3f266b15cfe1d56874c107c7804f5b2a
5
+ SHA512:
6
+ metadata.gz: bbd1af8726a3962be1228108a636f2c90857f60f0eed61605b09fd2379b26be80f315c8bf21cf727f33d240c5068f7d9264d428c24674133449e878c92ac52d9
7
+ data.tar.gz: ad4c75ec48c4c360137e1ca462f47bf5952bf98b74e39c5367dd9e61cef2e5928e147a64ec1d7a4d08f5a414cc9a5908b50c568a7bda71d2866015a8ca411821
@@ -0,0 +1,21 @@
1
+ module BeltsEngine
2
+ class Application
3
+ include BeltsSupport::Configuration
4
+
5
+ config.plugins = []
6
+
7
+ def initialize
8
+ @game = ::Game.new
9
+
10
+ config.plugins.each do |plugin_class|
11
+ @game.use plugin_class
12
+ end
13
+
14
+ @game.start
15
+
16
+ while true
17
+ @game.update
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ Mat4 = Struct.new(:m) do
2
+ class << self
3
+ def identity = Matrix.identity(4)
4
+
5
+ def translation(x, y, z)
6
+ Matrix[
7
+ [1, 0, 0, x],
8
+ [0, 1, 0, y],
9
+ [0, 0, 1, z],
10
+ [0, 0, 0, 1]
11
+ ]
12
+ end
13
+
14
+ def rotation(x, y, z)
15
+ x = x * Math::PI / 180
16
+ y = y * Math::PI / 180
17
+ z = z * Math::PI / 180
18
+
19
+ rotation_x = Matrix[
20
+ [1, 0, 0, 0],
21
+ [0, Math.cos(x), -Math.sin(x), 0],
22
+ [0, Math.sin(x), Math.cos(x), 0],
23
+ [0, 0, 0, 1]
24
+ ]
25
+
26
+ rotation_y = Matrix[
27
+ [Math.cos(y), 0, Math.sin(y), 0],
28
+ [0, 1, 0, 0],
29
+ [-Math.sin(y), 0, Math.cos(y), 0],
30
+ [0, 0, 0, 1]
31
+ ]
32
+
33
+ rotation_z = Matrix[
34
+ [Math.cos(z), -Math.sin(z), 0, 0],
35
+ [Math.sin(z), Math.cos(z), 0, 0],
36
+ [0, 0, 1, 0],
37
+ [0, 0, 0, 1]
38
+ ]
39
+
40
+ rotation_x * rotation_y * rotation_z
41
+ end
42
+
43
+ def scale(x, y, z)
44
+ Matrix[
45
+ [x, 0, 0, 0],
46
+ [0, y, 0, 0],
47
+ [0, 0, z, 0],
48
+ [0, 0, 0, 1]
49
+ ]
50
+ end
51
+
52
+ def perspective(fov, aspect, near, far)
53
+ fov_rad = fov * Math::PI / 180
54
+
55
+ y_scale = 1 / Math.tan(fov_rad)
56
+ x_scale = y_scale / aspect
57
+ frustumLength = far - near
58
+
59
+ Matrix[
60
+ [x_scale, 0, 0, 0],
61
+ [0, y_scale, 0, 0],
62
+ [0, 0, -(far + near) / frustumLength, -1],
63
+ [0, 0, 2 * -(far * near) / frustumLength, 0]
64
+ ].transpose
65
+ end
66
+
67
+ def orthographic(left, right, bottom, top, near, far)
68
+ scale = scale(2.0 / (right - left), 2.0 / (top - bottom), 2.0 / (far - near))
69
+ centerer = translation(- (right + left) / (right - left), - (top + bottom) / (top - bottom), (far - near) / 2.0)
70
+ inverter = scale(1, 1, -1)
71
+
72
+ scale * centerer# * inverter
73
+ end
74
+
75
+ def look_at(eye, target, up)
76
+ z_axis = (eye - target).normalize
77
+ x_axis = up.cross(z_axis).normalize
78
+ y_axis = z_axis.cross(x_axis)
79
+
80
+ Matrix[
81
+ [x_axis[0], x_axis[1], x_axis[2], -x_axis.dot(eye)],
82
+ [y_axis[0], y_axis[1], y_axis[2], -y_axis.dot(eye)],
83
+ [z_axis[0], z_axis[1], z_axis[2], -z_axis.dot(eye)],
84
+ [0, 0, 0, 1]
85
+ ]
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ Transform = Struct.new(:position, :rotation, :scale) do
2
+ def initialize(position: Vec3.zero, rotation: Vec3.zero, scale: Vec3.one) = super(position, rotation, scale)
3
+
4
+ def to_matrix
5
+ Mat4.translation(*position) *
6
+ Mat4.rotation(*rotation) *
7
+ Mat4.scale(*scale)
8
+ end
9
+
10
+ def forward
11
+ matrix = to_matrix
12
+ Vec3[matrix[0, 2], matrix[1, 2], matrix[2, 2]]
13
+ end
14
+
15
+ def right
16
+ matrix = to_matrix
17
+ Vec3[matrix[0, 0], matrix[1, 0], matrix[2, 0]]
18
+ end
19
+
20
+ def up
21
+ matrix = to_matrix
22
+ Vec3[matrix[0, 1], matrix[1, 1], matrix[2, 1]]
23
+ end
24
+
25
+ def back = -forward
26
+ def left = -right
27
+ def down = -up
28
+ end
@@ -0,0 +1,29 @@
1
+ class Vec3 < Vector
2
+ class << self
3
+ def [](x = 0, y = 0, z = 0) = super(x, y, z)
4
+
5
+ def zero = Vec3[0, 0, 0]
6
+ def one = Vec3[1, 1, 1]
7
+ def up = Vec3[0, 1, 0]
8
+ def down = Vec3[0, -1, 0]
9
+ def left = Vec3[-1, 0, 0]
10
+ def right = Vec3[1, 0, 0]
11
+ def forward = Vec3[0, 0, 1]
12
+ def back = Vec3[0, 0, -1]
13
+ end
14
+
15
+ def x = self[0]
16
+ def x=(value)
17
+ self[0] = value
18
+ end
19
+
20
+ def y = self[1]
21
+ def y=(value)
22
+ self[1] = value
23
+ end
24
+
25
+ def z = self[2]
26
+ def z=(value)
27
+ self[2] = value
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ module BeltsEngine::Ecs
2
+ class Collection < Hash
3
+ attr_reader :key
4
+
5
+ def initialize(with: [], without: [])
6
+ @with = with.sort
7
+ @without = without.sort
8
+ @key = {with: with, without: without}
9
+ end
10
+
11
+ def add_entity(entity)
12
+ return if (@with - entity.keys).any?
13
+ return if (@without & entity.keys).any?
14
+
15
+ self[entity[:id]] = entity.slice(:id, *@with)
16
+ end
17
+
18
+ def remove_entity(entity_id)
19
+ self.delete(entity_id)
20
+ end
21
+
22
+ def each_with_components
23
+ each do |k, v|
24
+ yield **v
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module BeltsEngine::Ecs
2
+ class CollectionManager < Hash
3
+ def register(**filters)
4
+ collection = Collection.new(**filters)
5
+ self[collection.key] ||= collection
6
+ end
7
+
8
+ def get(key)
9
+ raise "Collection not registered: #{key}" unless self.key?(key)
10
+ self[key]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module BeltsEngine::Ecs
2
+ class Entity < Hash
3
+ def initialize(id)
4
+ self[:id] = id
5
+ end
6
+
7
+ def add_components()
8
+ self[name] = value
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ module BeltsEngine::Ecs
2
+ class EntityManager < Hash
3
+ def initialize(game)
4
+ @game = game
5
+ @next_id = 0
6
+ end
7
+
8
+ def instantiate(prefab_class, position = Vec3.zero, rotation = Vec3.zero, scale = Vec3.one)
9
+ id = @next_id
10
+ entity = self[id] = Entity.new(id)
11
+ @next_id += 1
12
+
13
+ components = Marshal.load(Marshal.dump(prefab_class.to_s.constantize.components)) # deep copy
14
+ components[:transform].position = position if position
15
+ components[:transform].rotation = rotation if rotation
16
+ add_components(id, components)
17
+
18
+ id
19
+ end
20
+
21
+ def add_components(id, components)
22
+ entity = self[id]
23
+ entity.merge!(**components)
24
+
25
+ @game.collections.values.each do |collection|
26
+ collection.add_entity(entity)
27
+ end
28
+ end
29
+
30
+ def remove_components(id, keys)
31
+ entity = self[id]
32
+
33
+ keys.each do |key|
34
+ entity.delete(key)
35
+ end
36
+
37
+ @game.collections.values.each do |collection|
38
+ collection.remove_entity(id)
39
+ collection.add_entity(entity)
40
+ end
41
+ end
42
+
43
+ def destroy(id)
44
+ @game.collections.values.each do |collection|
45
+ collection.remove_entity(id)
46
+ end
47
+
48
+ self.delete(id)
49
+ end
50
+
51
+ def destroy_all
52
+ keys.each do |id|
53
+ destroy(id)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ module BeltsEngine::Ecs
2
+ class SystemManager
3
+ def initialize(game)
4
+ @game = game
5
+ @systems = []
6
+
7
+ register_app_systems
8
+ end
9
+
10
+ def update
11
+ @systems.each(&:update)
12
+ end
13
+
14
+ def register_system(klass)
15
+ @systems << klass.new(@game) # TODO: Avoid duplicates
16
+
17
+ klass.collection_keys.each do |key|
18
+ @game.collections.register(**key)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def register_app_systems
25
+ klasses = Dir["app/systems/*.rb"].map do |file|
26
+ File.basename(file, ".rb").camelize.constantize
27
+ end
28
+
29
+ klasses.each do |klass|
30
+ register_system(klass)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module BeltsEngine
2
+ class Game
3
+ include BeltsSupport::Configuration
4
+ include BeltsEngine::ToolsManager
5
+
6
+ def initialize
7
+ register_tool(:time, Tools::Time.new)
8
+ register_tool(:input, Tools::Input.new)
9
+ register_tool(:window, Tools::Window.new)
10
+ register_tool(:collections, Ecs::CollectionManager.new)
11
+ register_tool(:entities, Ecs::EntityManager.new(self))
12
+ register_tool(:scenes, Tools::SceneManager.new(self))
13
+ register_tool(:systems, Ecs::SystemManager.new(self))
14
+ end
15
+
16
+ def use(extension)
17
+ extension.install(self)
18
+ end
19
+
20
+ def start
21
+ main_scene_class = config.main_scene.to_s.constantize
22
+ raise 'Main scene not specified' unless main_scene_class
23
+
24
+ scenes.load_scene(main_scene_class)
25
+ end
26
+
27
+ def update
28
+ time.update
29
+ systems.update
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ module BeltsEngine
2
+ class Prefab
3
+ module ComponentMixin
4
+ class << self
5
+ def included(base)
6
+ base.extend ClassMethods
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def components
12
+ @components ||= {}
13
+ end
14
+
15
+ def component(name, val)
16
+ set_component(name, val)
17
+ end
18
+
19
+ private
20
+
21
+ def set_component(name, data)
22
+ @components ||= {}
23
+ @components[name] = data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ module BeltsEngine
2
+ class Prefab
3
+ include Prefab::ComponentMixin
4
+
5
+ def self.inherited(subclass)
6
+ super
7
+ subclass.component(:transform, Transform.new)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ module BeltsEngine
2
+ class Scene
3
+ attr_reader :game
4
+
5
+ self.instance_eval do
6
+ extend Module.new {
7
+ def prefab(class_name, **options)
8
+ @@prefabs ||= []
9
+ @@prefabs << options.merge(class_name: class_name)
10
+ end
11
+ }
12
+ end
13
+
14
+ def initialize(game)
15
+ @game = game
16
+ init_entities
17
+ end
18
+
19
+ private
20
+
21
+ def init_entities
22
+ @@prefabs.each do |prefab|
23
+ @game.entities.instantiate(prefab[:class_name], prefab[:position], prefab[:rotation], prefab[:scale])
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ module BeltsEngine
2
+ class System
3
+ module CollectionMixin
4
+ class << self
5
+ def included(base)
6
+ base.extend ClassMethods
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def collection_keys
12
+ @collection_keys ||= []
13
+ end
14
+
15
+ def collection(name, **filters)
16
+ key = Ecs::Collection.new(**filters).key
17
+ register_collection_key(key)
18
+
19
+ define_method(name) do
20
+ @collections.get(key)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def register_collection_key(key)
27
+ @collection_keys ||= []
28
+ @collection_keys << key
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module BeltsEngine
2
+ class System
3
+ include System::CollectionMixin
4
+
5
+ def initialize(game)
6
+ @game = game
7
+ register_tool_shortcuts
8
+
9
+ start
10
+ end
11
+
12
+ def start
13
+ end
14
+
15
+ private
16
+
17
+ def register_tool_shortcuts
18
+ @game.tools.each do |key, value|
19
+ instance_variable_set("@#{key}", value)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module BeltsEngine
2
+ module Tools
3
+ class Input
4
+ module Keyboard
5
+ KEYS = [:w, :a, :s, :d].freeze
6
+
7
+ def key?(key) = @keyboard_state[key]
8
+ def key_down?(key) = @keyboard_state[key] && !@keyboard_previous_state[key]
9
+ def key_up?(key) = !@keyboard_state[key] && @keyboard_previous_state[key]
10
+
11
+ def update_keys(changes)
12
+ @keyboard_previous_state = @keyboard_state.dup
13
+ @keyboard_state.merge!(changes)
14
+ end
15
+
16
+ private
17
+
18
+ def reset_keyboard_state
19
+ @keyboard_state = KEYS.map { |key| [key, false] }.to_h
20
+ @keyboard_previous_state = @keyboard_state.dup
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module BeltsEngine
2
+ module Tools
3
+ class Input
4
+ module Mouse
5
+ BUTTONS = [:mouse_1, :mouse_2, :mouse_3].freeze
6
+
7
+ def button?(button) = @mouse_state[button]
8
+ def button_down?(button) = @mouse_state[button] && !@mouse_previous_state[button]
9
+ def button_up?(button) = !@mouse_state[button] && @mouse_previous_state[button]
10
+ def mouse(axis) = @mouse_state[axis]
11
+
12
+ def update_buttons(changes)
13
+ @mouse_previous_state = @mouse_state.dup
14
+ @mouse_state.merge!(changes)
15
+ end
16
+
17
+ def update_position(x, y)
18
+ @mouse_state[:x] = x
19
+ @mouse_state[:y] = y
20
+ end
21
+
22
+ private
23
+
24
+ def reset_mouse_state
25
+ @mouse_state = BUTTONS.map { |button| [button, false] }.to_h
26
+ @mouse_state[:x] = @mouse_state[:y] = 0
27
+ @mouse_previous_state = @mouse_state.dup
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module BeltsEngine
2
+ module Tools
3
+ class Input
4
+ include Keyboard
5
+ include Mouse
6
+
7
+ def initialize
8
+ reset_keyboard_state
9
+ # reset_mouse_state
10
+ #@current_keys[:mouse_x] = @current_keys[:mouse_y] = 0
11
+ end
12
+
13
+ def update(changes)
14
+ update_keys(changes)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module BeltsEngine::Tools
2
+ class SceneManager
3
+ attr_reader :current_scene
4
+
5
+ def initialize(game)
6
+ @game = game
7
+ @current_scene = nil
8
+ end
9
+
10
+ def unload_scene
11
+ @game.entities.destroy_all
12
+ end
13
+
14
+ def load_scene(scene_class)
15
+ unload_scene
16
+ @current_scene = scene_class.new(@game)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module BeltsEngine
2
+ module Tools
3
+ class Time
4
+ attr_reader :delta_time, :uptime
5
+
6
+ def initialize
7
+ @start_time = system_time
8
+ @last_time = @start_time
9
+ @uptime = 0
10
+ @delta_time = 0
11
+ end
12
+
13
+ def update
14
+ @delta_time = system_time - @last_time
15
+ @last_time = system_time
16
+ @uptime += @delta_time
17
+ end
18
+
19
+ private
20
+
21
+ def system_time
22
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module BeltsEngine
2
+ module Tools
3
+ class Window
4
+ attr_reader :ratio, :width, :height, :title
5
+
6
+ DEFAULT_WIDTH = 640
7
+ DEFAULT_HEIGHT = 480
8
+ DEFAULT_TITLE = 'Belts Demo'.freeze
9
+
10
+ def initialize
11
+ @title = DEFAULT_TITLE
12
+ resize(DEFAULT_WIDTH, DEFAULT_HEIGHT)
13
+ end
14
+
15
+ def resize(width, height)
16
+ @width = width
17
+ @height = height
18
+ @ratio = @width.to_f / @height.to_f
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module BeltsEngine
2
+ module ToolsManager
3
+ def tools
4
+ @_tools ||= {}
5
+ end
6
+
7
+ def register_tool(name, obj)
8
+ raise "Tool #{name} already in use" if tools.key?(name)
9
+
10
+ tools[name] = obj
11
+ end
12
+
13
+ def respond_to?(name, include_private = false)
14
+ super || tools.key?(name.to_sym)
15
+ end
16
+
17
+ private
18
+
19
+ def method_missing(name, *args, &blk)
20
+ raise "Tool #{name} not registered" unless tools.key?(name)
21
+
22
+ tools[name]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require 'belts_support'
2
+
3
+ # TODO: Load with zeitwerk
4
+ require_relative './belts_engine/components/vec3'
5
+ require_relative './belts_engine/components/mat4'
6
+ require_relative './belts_engine/components/transform'
7
+
8
+ loader = Zeitwerk::Loader.for_gem
9
+ loader.setup
10
+
11
+ module BeltsEngine
12
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: belts_engine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Guilherme Graca
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: belts_support
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
27
+ description:
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/belts_engine.rb
34
+ - lib/belts_engine/application.rb
35
+ - lib/belts_engine/components/mat4.rb
36
+ - lib/belts_engine/components/transform.rb
37
+ - lib/belts_engine/components/vec3.rb
38
+ - lib/belts_engine/ecs/collection.rb
39
+ - lib/belts_engine/ecs/collection_manager.rb
40
+ - lib/belts_engine/ecs/entity.rb
41
+ - lib/belts_engine/ecs/entity_manager.rb
42
+ - lib/belts_engine/ecs/system_manager.rb
43
+ - lib/belts_engine/game.rb
44
+ - lib/belts_engine/prefab.rb
45
+ - lib/belts_engine/prefab/component_mixin.rb
46
+ - lib/belts_engine/scene.rb
47
+ - lib/belts_engine/system.rb
48
+ - lib/belts_engine/system/collection_mixin.rb
49
+ - lib/belts_engine/tools/input.rb
50
+ - lib/belts_engine/tools/input/keyboard.rb
51
+ - lib/belts_engine/tools/input/mouse.rb
52
+ - lib/belts_engine/tools/scene_manager.rb
53
+ - lib/belts_engine/tools/time.rb
54
+ - lib/belts_engine/tools/window.rb
55
+ - lib/belts_engine/tools_manager.rb
56
+ homepage: https://github.com/ggraca/belts
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.1.2
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.3.7
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: The core functionality of the Belts game engine
79
+ test_files: []