bevy 1.0.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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +4279 -0
  3. data/Cargo.toml +36 -0
  4. data/README.md +226 -0
  5. data/crates/bevy/Cargo.toml +52 -0
  6. data/crates/bevy/src/app.rs +43 -0
  7. data/crates/bevy/src/component.rs +111 -0
  8. data/crates/bevy/src/entity.rs +30 -0
  9. data/crates/bevy/src/error.rs +32 -0
  10. data/crates/bevy/src/event.rs +190 -0
  11. data/crates/bevy/src/input_bridge.rs +300 -0
  12. data/crates/bevy/src/lib.rs +42 -0
  13. data/crates/bevy/src/mesh_renderer.rs +328 -0
  14. data/crates/bevy/src/query.rs +53 -0
  15. data/crates/bevy/src/render_app.rs +689 -0
  16. data/crates/bevy/src/resource.rs +28 -0
  17. data/crates/bevy/src/schedule.rs +186 -0
  18. data/crates/bevy/src/sprite_renderer.rs +355 -0
  19. data/crates/bevy/src/system.rs +44 -0
  20. data/crates/bevy/src/text_renderer.rs +258 -0
  21. data/crates/bevy/src/types/color.rs +114 -0
  22. data/crates/bevy/src/types/dynamic.rs +131 -0
  23. data/crates/bevy/src/types/math.rs +260 -0
  24. data/crates/bevy/src/types/mod.rs +9 -0
  25. data/crates/bevy/src/types/transform.rs +166 -0
  26. data/crates/bevy/src/world.rs +163 -0
  27. data/crates/bevy_ruby_render/Cargo.toml +22 -0
  28. data/crates/bevy_ruby_render/src/asset.rs +360 -0
  29. data/crates/bevy_ruby_render/src/audio.rs +511 -0
  30. data/crates/bevy_ruby_render/src/camera.rs +365 -0
  31. data/crates/bevy_ruby_render/src/gamepad.rs +398 -0
  32. data/crates/bevy_ruby_render/src/lib.rs +26 -0
  33. data/crates/bevy_ruby_render/src/material.rs +310 -0
  34. data/crates/bevy_ruby_render/src/mesh.rs +491 -0
  35. data/crates/bevy_ruby_render/src/sprite.rs +289 -0
  36. data/ext/bevy/Cargo.toml +20 -0
  37. data/ext/bevy/extconf.rb +6 -0
  38. data/ext/bevy/src/conversions.rs +137 -0
  39. data/ext/bevy/src/lib.rs +29 -0
  40. data/ext/bevy/src/ruby_app.rs +65 -0
  41. data/ext/bevy/src/ruby_color.rs +149 -0
  42. data/ext/bevy/src/ruby_component.rs +189 -0
  43. data/ext/bevy/src/ruby_entity.rs +33 -0
  44. data/ext/bevy/src/ruby_math.rs +384 -0
  45. data/ext/bevy/src/ruby_query.rs +64 -0
  46. data/ext/bevy/src/ruby_render_app.rs +779 -0
  47. data/ext/bevy/src/ruby_system.rs +122 -0
  48. data/ext/bevy/src/ruby_world.rs +107 -0
  49. data/lib/bevy/animation.rb +597 -0
  50. data/lib/bevy/app.rb +675 -0
  51. data/lib/bevy/asset.rb +613 -0
  52. data/lib/bevy/audio.rb +545 -0
  53. data/lib/bevy/audio_effects.rb +224 -0
  54. data/lib/bevy/camera.rb +412 -0
  55. data/lib/bevy/component.rb +91 -0
  56. data/lib/bevy/diagnostics.rb +227 -0
  57. data/lib/bevy/ecs_advanced.rb +296 -0
  58. data/lib/bevy/event.rb +199 -0
  59. data/lib/bevy/gizmos.rb +158 -0
  60. data/lib/bevy/gltf.rb +227 -0
  61. data/lib/bevy/hierarchy.rb +444 -0
  62. data/lib/bevy/input.rb +514 -0
  63. data/lib/bevy/lighting.rb +369 -0
  64. data/lib/bevy/material.rb +248 -0
  65. data/lib/bevy/mesh.rb +257 -0
  66. data/lib/bevy/navigation.rb +344 -0
  67. data/lib/bevy/networking.rb +335 -0
  68. data/lib/bevy/particle.rb +337 -0
  69. data/lib/bevy/physics.rb +396 -0
  70. data/lib/bevy/plugins/default_plugins.rb +34 -0
  71. data/lib/bevy/plugins/input_plugin.rb +49 -0
  72. data/lib/bevy/reflect.rb +361 -0
  73. data/lib/bevy/render_graph.rb +210 -0
  74. data/lib/bevy/resource.rb +185 -0
  75. data/lib/bevy/scene.rb +254 -0
  76. data/lib/bevy/shader.rb +319 -0
  77. data/lib/bevy/shape.rb +195 -0
  78. data/lib/bevy/skeletal.rb +248 -0
  79. data/lib/bevy/sprite.rb +152 -0
  80. data/lib/bevy/sprite_sheet.rb +444 -0
  81. data/lib/bevy/state.rb +277 -0
  82. data/lib/bevy/system.rb +206 -0
  83. data/lib/bevy/text.rb +99 -0
  84. data/lib/bevy/text_advanced.rb +455 -0
  85. data/lib/bevy/timer.rb +147 -0
  86. data/lib/bevy/transform.rb +158 -0
  87. data/lib/bevy/ui.rb +454 -0
  88. data/lib/bevy/ui_advanced.rb +568 -0
  89. data/lib/bevy/version.rb +5 -0
  90. data/lib/bevy/visibility.rb +250 -0
  91. data/lib/bevy/window.rb +302 -0
  92. data/lib/bevy.rb +390 -0
  93. metadata +150 -0
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bevy
4
+ class ResourceDSL
5
+ class << self
6
+ def attribute(name, type, default: nil)
7
+ @attributes ||= {}
8
+ @attributes[name] = { type: type, default: default }
9
+
10
+ define_method(name) { @data[name] }
11
+ define_method(:"#{name}=") { |value| @data[name] = value }
12
+ end
13
+
14
+ def attributes
15
+ @attributes ||= {}
16
+ end
17
+
18
+ def resource_name
19
+ name || 'AnonymousResource'
20
+ end
21
+
22
+ def inherited(subclass)
23
+ super
24
+ subclass.instance_variable_set(:@attributes, attributes.dup)
25
+ end
26
+ end
27
+
28
+ def initialize(**attrs)
29
+ @data = {}
30
+ self.class.attributes.each do |attr_name, config|
31
+ default = config[:default]
32
+ default_value = default.respond_to?(:call) ? default.call : default
33
+ @data[attr_name] = attrs.fetch(attr_name, default_value)
34
+ end
35
+ end
36
+
37
+ def to_h
38
+ @data.dup
39
+ end
40
+ end
41
+
42
+ class Resources
43
+ def initialize
44
+ @resources = {}
45
+ end
46
+
47
+ def insert(resource)
48
+ type_name = resource_type_name(resource)
49
+ @resources[type_name] = resource
50
+ end
51
+
52
+ def get(resource_class)
53
+ type_name = resource_class_name(resource_class)
54
+ @resources[type_name]
55
+ end
56
+
57
+ def get_or_insert(resource_class, &block)
58
+ type_name = resource_class_name(resource_class)
59
+ @resources[type_name] ||= block.call
60
+ end
61
+
62
+ def remove(resource_class)
63
+ type_name = resource_class_name(resource_class)
64
+ @resources.delete(type_name)
65
+ end
66
+
67
+ def contains?(resource_class)
68
+ type_name = resource_class_name(resource_class)
69
+ @resources.key?(type_name)
70
+ end
71
+
72
+ def clear
73
+ @resources.clear
74
+ end
75
+
76
+ private
77
+
78
+ def resource_type_name(resource)
79
+ case resource
80
+ when ResourceDSL
81
+ resource.class.resource_name
82
+ else
83
+ resource.class.name || 'AnonymousResource'
84
+ end
85
+ end
86
+
87
+ def resource_class_name(resource_class)
88
+ case resource_class
89
+ when Class
90
+ resource_class.respond_to?(:resource_name) ? resource_class.resource_name : resource_class.name
91
+ when String
92
+ resource_class
93
+ else
94
+ raise ArgumentError, 'Expected Class or String'
95
+ end
96
+ end
97
+ end
98
+
99
+ class Time
100
+ attr_reader :delta, :elapsed, :delta_seconds, :elapsed_seconds, :time_scale
101
+
102
+ def initialize
103
+ @start_time = ::Time.now
104
+ @last_update = @start_time
105
+ @delta = 0.0
106
+ @elapsed = 0.0
107
+ @delta_seconds = 0.0
108
+ @elapsed_seconds = 0.0
109
+ @paused = false
110
+ @time_scale = 1.0
111
+ end
112
+
113
+ def update
114
+ return if @paused
115
+
116
+ now = ::Time.now
117
+ raw_delta = now - @last_update
118
+ @delta_seconds = raw_delta * @time_scale
119
+ @delta = @delta_seconds
120
+ @elapsed_seconds = now - @start_time
121
+ @elapsed = @elapsed_seconds
122
+ @last_update = now
123
+ end
124
+
125
+ def pause
126
+ @paused = true
127
+ end
128
+
129
+ def unpause
130
+ @paused = false
131
+ @last_update = ::Time.now
132
+ end
133
+
134
+ def paused?
135
+ @paused
136
+ end
137
+
138
+ def time_scale=(scale)
139
+ @time_scale = scale.clamp(0.0, 10.0)
140
+ end
141
+
142
+ def reset
143
+ @start_time = ::Time.now
144
+ @last_update = @start_time
145
+ @delta = 0.0
146
+ @elapsed = 0.0
147
+ @delta_seconds = 0.0
148
+ @elapsed_seconds = 0.0
149
+ end
150
+ end
151
+
152
+ class FixedTime
153
+ attr_reader :delta, :overstep, :accumulated, :timestep
154
+
155
+ def initialize(timestep: 1.0 / 60.0)
156
+ @timestep = timestep
157
+ @delta = timestep
158
+ @accumulated = 0.0
159
+ @overstep = 0.0
160
+ end
161
+
162
+ def timestep=(value)
163
+ @timestep = value.clamp(0.001, 1.0)
164
+ @delta = @timestep
165
+ end
166
+
167
+ def accumulate(delta)
168
+ @accumulated += delta
169
+ end
170
+
171
+ def expend
172
+ if @accumulated >= @timestep
173
+ @accumulated -= @timestep
174
+ @overstep = @accumulated
175
+ true
176
+ else
177
+ false
178
+ end
179
+ end
180
+
181
+ def steps_remaining
182
+ (@accumulated / @timestep).floor
183
+ end
184
+ end
185
+ end
data/lib/bevy/scene.rb ADDED
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Bevy
6
+ class Scene
7
+ attr_reader :name, :entities
8
+
9
+ def initialize(name = 'Untitled')
10
+ @name = name
11
+ @entities = []
12
+ end
13
+
14
+ def add_entity(components)
15
+ @entities << components
16
+ self
17
+ end
18
+
19
+ def spawn_all(world)
20
+ spawned = []
21
+ @entities.each do |components|
22
+ entity = world.spawn_entity(*components)
23
+ spawned << entity
24
+ end
25
+ spawned
26
+ end
27
+
28
+ def clear
29
+ @entities.clear
30
+ self
31
+ end
32
+
33
+ def entity_count
34
+ @entities.size
35
+ end
36
+
37
+ def type_name
38
+ 'Scene'
39
+ end
40
+ end
41
+
42
+ class DynamicScene
43
+ attr_reader :name
44
+
45
+ def initialize(name = 'DynamicScene')
46
+ @name = name
47
+ @entity_data = []
48
+ end
49
+
50
+ def capture_world(world, &filter)
51
+ @entity_data.clear
52
+
53
+ world.all_entities.each do |entity|
54
+ next if block_given? && !filter.call(entity)
55
+
56
+ entity_components = {}
57
+ world.mesh_components[entity.id]&.each do |type_name, component|
58
+ entity_components[type_name] = serialize_component(component)
59
+ end
60
+
61
+ @entity_data << {
62
+ id: entity.id,
63
+ components: entity_components
64
+ }
65
+ end
66
+
67
+ self
68
+ end
69
+
70
+ def restore_to_world(world)
71
+ spawned = []
72
+ @entity_data.each do |data|
73
+ components = data[:components].map do |type_name, comp_data|
74
+ deserialize_component(type_name, comp_data)
75
+ end.compact
76
+
77
+ entity = world.spawn_entity(*components) if components.any?
78
+ spawned << entity if entity
79
+ end
80
+ spawned
81
+ end
82
+
83
+ def to_data
84
+ {
85
+ name: @name,
86
+ entities: @entity_data
87
+ }
88
+ end
89
+
90
+ def load_data(data)
91
+ @name = data[:name] || data['name'] || @name
92
+ @entity_data = data[:entities] || data['entities'] || []
93
+ self
94
+ end
95
+
96
+ def entity_count
97
+ @entity_data.size
98
+ end
99
+
100
+ def type_name
101
+ 'DynamicScene'
102
+ end
103
+
104
+ private
105
+
106
+ def serialize_component(component)
107
+ case component
108
+ when Mesh::Rectangle
109
+ { type: 'Rectangle', width: component.width, height: component.height, color: component.color.to_a }
110
+ when Mesh::Circle
111
+ { type: 'Circle', radius: component.radius, color: component.color.to_a }
112
+ when Mesh::RegularPolygon
113
+ { type: 'RegularPolygon', radius: component.radius, sides: component.sides, color: component.color.to_a }
114
+ else
115
+ { type: component.class.name, data: component.respond_to?(:to_h) ? component.to_h : {} }
116
+ end
117
+ end
118
+
119
+ def deserialize_component(type_name, data)
120
+ case data[:type] || data['type']
121
+ when 'Rectangle'
122
+ color = data[:color] || data['color']
123
+ Mesh::Rectangle.new(
124
+ width: data[:width] || data['width'],
125
+ height: data[:height] || data['height'],
126
+ color: color.is_a?(Array) ? Color.rgba(*color) : Color.white
127
+ )
128
+ when 'Circle'
129
+ color = data[:color] || data['color']
130
+ Mesh::Circle.new(
131
+ radius: data[:radius] || data['radius'],
132
+ color: color.is_a?(Array) ? Color.rgba(*color) : Color.white
133
+ )
134
+ when 'RegularPolygon'
135
+ color = data[:color] || data['color']
136
+ Mesh::RegularPolygon.new(
137
+ radius: data[:radius] || data['radius'],
138
+ sides: data[:sides] || data['sides'],
139
+ color: color.is_a?(Array) ? Color.rgba(*color) : Color.white
140
+ )
141
+ else
142
+ nil
143
+ end
144
+ end
145
+ end
146
+
147
+ class SceneBundle
148
+ attr_reader :scene, :transform
149
+
150
+ def initialize(scene:, transform: nil)
151
+ @scene = scene
152
+ @transform = transform || Transform.identity
153
+ end
154
+
155
+ def type_name
156
+ 'SceneBundle'
157
+ end
158
+ end
159
+
160
+ class SceneSpawner
161
+ def initialize
162
+ @pending_scenes = []
163
+ @spawned_scenes = {}
164
+ end
165
+
166
+ def spawn(scene, transform: nil)
167
+ @pending_scenes << { scene: scene, transform: transform }
168
+ self
169
+ end
170
+
171
+ def spawn_pending(world)
172
+ spawned = []
173
+ @pending_scenes.each do |pending|
174
+ scene = pending[:scene]
175
+ entities = scene.spawn_all(world)
176
+ spawned.concat(entities)
177
+ @spawned_scenes[scene.name] = entities
178
+ end
179
+ @pending_scenes.clear
180
+ spawned
181
+ end
182
+
183
+ def despawn_scene(name, world)
184
+ entities = @spawned_scenes.delete(name)
185
+ return [] unless entities
186
+
187
+ entities.each { |e| world.despawn(e) }
188
+ entities
189
+ end
190
+
191
+ def pending_count
192
+ @pending_scenes.size
193
+ end
194
+
195
+ def spawned_scenes
196
+ @spawned_scenes.keys
197
+ end
198
+
199
+ def type_name
200
+ 'SceneSpawner'
201
+ end
202
+ end
203
+
204
+ module SceneSaver
205
+ def self.save_to_json(scene, file_path)
206
+ data = if scene.is_a?(DynamicScene)
207
+ scene.to_data
208
+ else
209
+ { name: scene.name, entities: scene.entities.map { |e| serialize_entity(e) } }
210
+ end
211
+
212
+ File.write(file_path, JSON.pretty_generate(data))
213
+ true
214
+ rescue StandardError => e
215
+ warn "Failed to save scene: #{e.message}"
216
+ false
217
+ end
218
+
219
+ def self.load_from_json(file_path)
220
+ return nil unless File.exist?(file_path)
221
+
222
+ data = JSON.parse(File.read(file_path), symbolize_names: true)
223
+ scene = DynamicScene.new(data[:name] || 'Loaded')
224
+ scene.load_data(data)
225
+ scene
226
+ rescue StandardError => e
227
+ warn "Failed to load scene: #{e.message}"
228
+ nil
229
+ end
230
+
231
+ def self.serialize_entity(components)
232
+ components.map do |comp|
233
+ {
234
+ type: comp.class.name,
235
+ data: comp.respond_to?(:to_h) ? comp.to_h : {}
236
+ }
237
+ end
238
+ end
239
+ end
240
+
241
+ class SceneInstance
242
+ attr_reader :scene_name, :root_entity, :entities
243
+
244
+ def initialize(scene_name, root_entity, entities)
245
+ @scene_name = scene_name
246
+ @root_entity = root_entity
247
+ @entities = entities
248
+ end
249
+
250
+ def type_name
251
+ 'SceneInstance'
252
+ end
253
+ end
254
+ end