belts_engine 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []