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.
- checksums.yaml +7 -0
- data/Cargo.lock +4279 -0
- data/Cargo.toml +36 -0
- data/README.md +226 -0
- data/crates/bevy/Cargo.toml +52 -0
- data/crates/bevy/src/app.rs +43 -0
- data/crates/bevy/src/component.rs +111 -0
- data/crates/bevy/src/entity.rs +30 -0
- data/crates/bevy/src/error.rs +32 -0
- data/crates/bevy/src/event.rs +190 -0
- data/crates/bevy/src/input_bridge.rs +300 -0
- data/crates/bevy/src/lib.rs +42 -0
- data/crates/bevy/src/mesh_renderer.rs +328 -0
- data/crates/bevy/src/query.rs +53 -0
- data/crates/bevy/src/render_app.rs +689 -0
- data/crates/bevy/src/resource.rs +28 -0
- data/crates/bevy/src/schedule.rs +186 -0
- data/crates/bevy/src/sprite_renderer.rs +355 -0
- data/crates/bevy/src/system.rs +44 -0
- data/crates/bevy/src/text_renderer.rs +258 -0
- data/crates/bevy/src/types/color.rs +114 -0
- data/crates/bevy/src/types/dynamic.rs +131 -0
- data/crates/bevy/src/types/math.rs +260 -0
- data/crates/bevy/src/types/mod.rs +9 -0
- data/crates/bevy/src/types/transform.rs +166 -0
- data/crates/bevy/src/world.rs +163 -0
- data/crates/bevy_ruby_render/Cargo.toml +22 -0
- data/crates/bevy_ruby_render/src/asset.rs +360 -0
- data/crates/bevy_ruby_render/src/audio.rs +511 -0
- data/crates/bevy_ruby_render/src/camera.rs +365 -0
- data/crates/bevy_ruby_render/src/gamepad.rs +398 -0
- data/crates/bevy_ruby_render/src/lib.rs +26 -0
- data/crates/bevy_ruby_render/src/material.rs +310 -0
- data/crates/bevy_ruby_render/src/mesh.rs +491 -0
- data/crates/bevy_ruby_render/src/sprite.rs +289 -0
- data/ext/bevy/Cargo.toml +20 -0
- data/ext/bevy/extconf.rb +6 -0
- data/ext/bevy/src/conversions.rs +137 -0
- data/ext/bevy/src/lib.rs +29 -0
- data/ext/bevy/src/ruby_app.rs +65 -0
- data/ext/bevy/src/ruby_color.rs +149 -0
- data/ext/bevy/src/ruby_component.rs +189 -0
- data/ext/bevy/src/ruby_entity.rs +33 -0
- data/ext/bevy/src/ruby_math.rs +384 -0
- data/ext/bevy/src/ruby_query.rs +64 -0
- data/ext/bevy/src/ruby_render_app.rs +779 -0
- data/ext/bevy/src/ruby_system.rs +122 -0
- data/ext/bevy/src/ruby_world.rs +107 -0
- data/lib/bevy/animation.rb +597 -0
- data/lib/bevy/app.rb +675 -0
- data/lib/bevy/asset.rb +613 -0
- data/lib/bevy/audio.rb +545 -0
- data/lib/bevy/audio_effects.rb +224 -0
- data/lib/bevy/camera.rb +412 -0
- data/lib/bevy/component.rb +91 -0
- data/lib/bevy/diagnostics.rb +227 -0
- data/lib/bevy/ecs_advanced.rb +296 -0
- data/lib/bevy/event.rb +199 -0
- data/lib/bevy/gizmos.rb +158 -0
- data/lib/bevy/gltf.rb +227 -0
- data/lib/bevy/hierarchy.rb +444 -0
- data/lib/bevy/input.rb +514 -0
- data/lib/bevy/lighting.rb +369 -0
- data/lib/bevy/material.rb +248 -0
- data/lib/bevy/mesh.rb +257 -0
- data/lib/bevy/navigation.rb +344 -0
- data/lib/bevy/networking.rb +335 -0
- data/lib/bevy/particle.rb +337 -0
- data/lib/bevy/physics.rb +396 -0
- data/lib/bevy/plugins/default_plugins.rb +34 -0
- data/lib/bevy/plugins/input_plugin.rb +49 -0
- data/lib/bevy/reflect.rb +361 -0
- data/lib/bevy/render_graph.rb +210 -0
- data/lib/bevy/resource.rb +185 -0
- data/lib/bevy/scene.rb +254 -0
- data/lib/bevy/shader.rb +319 -0
- data/lib/bevy/shape.rb +195 -0
- data/lib/bevy/skeletal.rb +248 -0
- data/lib/bevy/sprite.rb +152 -0
- data/lib/bevy/sprite_sheet.rb +444 -0
- data/lib/bevy/state.rb +277 -0
- data/lib/bevy/system.rb +206 -0
- data/lib/bevy/text.rb +99 -0
- data/lib/bevy/text_advanced.rb +455 -0
- data/lib/bevy/timer.rb +147 -0
- data/lib/bevy/transform.rb +158 -0
- data/lib/bevy/ui.rb +454 -0
- data/lib/bevy/ui_advanced.rb +568 -0
- data/lib/bevy/version.rb +5 -0
- data/lib/bevy/visibility.rb +250 -0
- data/lib/bevy/window.rb +302 -0
- data/lib/bevy.rb +390 -0
- 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
|