or2d 0.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.
@@ -0,0 +1,186 @@
1
+ module OR2D
2
+ # The Instance object represents a single running instance of OR2D. It maintains a list of all entities in the
3
+ # current instance, and a list of all scenes in the current instance. It also maintains a reference to the current
4
+ # scene. There should only be one instance of OR2D::Instance at any given time.
5
+ class Instance
6
+ include Singleton
7
+
8
+ # @!attribute [r] entities
9
+ # @return [Hash] a hash of all entities in the game instance
10
+ attr_reader :entities
11
+
12
+ # @!attribute [r] scene
13
+ # @return [OR2D::Scene] the current scene
14
+ attr_reader :scene
15
+
16
+ # @!attribute [r] window
17
+ # @return [Ruby2D::Window] the game window
18
+ attr_reader :window
19
+
20
+ # @!attribute [r] modifiers
21
+ # @return [Array] the currently held modifier keys
22
+ attr_reader :modifiers
23
+
24
+ # @!attribute [r] mouse
25
+ # @return [OR2D::Mouse] the mouse state
26
+ attr_reader :mouse
27
+
28
+ # Constructs a new OR2D instance.
29
+ def initialize
30
+ super()
31
+ setup
32
+ end
33
+
34
+ # Run the game instance.
35
+ def run
36
+ @window.render do
37
+ @scene.render unless @scene.nil? || @scene.rendered? || @scene.finished?
38
+ rescue StandardError => e
39
+ puts "An error occurred while rendering the scene: #{e.message}"
40
+ puts e.backtrace if OR2D.debug?
41
+ end
42
+
43
+ @window.update do
44
+ process_animations
45
+ process_scene
46
+ rescue StandardError => e
47
+ puts "An error occurred while updating the scene: #{e.message}"
48
+ puts e.backtrace if OR2D.debug?
49
+ end
50
+
51
+ @window.show
52
+ end
53
+
54
+ # Schedule an animation for an entity.
55
+ def animate(entity_id, &_)
56
+ @animations[entity_id] ||= Set.new
57
+ @animations[entity_id].add(OR2D::Animation.new(entity_id, &_))
58
+ end
59
+
60
+ # Adds a scene to the game instance.
61
+ # @param scene [OR2D::Scene] the scene to add
62
+ def add_scene(scene)
63
+ @scenes.add(scene)
64
+ end
65
+
66
+ # Adds an entity to the game instance.
67
+ # @param entity [OR2D::Entity] the entity to add
68
+ def add_entity(entity)
69
+ @entities[entity.id] = entity
70
+ end
71
+
72
+ # Removes an entity from the game instance.
73
+ # @param entity_id [String] the ID of the entity to remove
74
+ # @return [OR2D::Entity] the removed entity
75
+ def remove_entity(entity_id)
76
+ @entities.delete(entity_id)
77
+ end
78
+
79
+ # Is the current frame a key frame?
80
+ # @param frame [Integer] the key frame interval
81
+ # @return [Boolean] whether or not the current frame is a key frame
82
+ def key_frame?(frame = 60)
83
+ (@window.get(:frames) % frame).zero?
84
+ end
85
+
86
+ # Close the game instance.
87
+ def quit
88
+ @window.close
89
+ end
90
+
91
+ private
92
+
93
+ def setup
94
+ @window = Ruby2D::Window.new(title: ENV['OR2D_TITLE'] || 'OR2D Game',
95
+ width: ENV['OR2D_WIDTH']&.to_i || OR2D.lowest_scalable_width,
96
+ height: ENV['OR2D_HEIGHT']&.to_i || OR2D.lowest_scalable_height,
97
+ vsync: ENV['OR2D_VSYNC'] || true)
98
+
99
+ @projectiles = Set.new
100
+ @animations = {}
101
+ @entities = {}
102
+ @modifiers = { shift: false, control: false, alt: false, meta: false, backspace: false }
103
+ @mouse = { left: false, right: false, middle: false }
104
+ @scenes = Set.new
105
+
106
+ @window.on(:key_up) do |event|
107
+ case event.key
108
+ when /shift/ then @modifiers[:shift] = false
109
+ when /ctrl|control/ then @modifiers[:control] = false
110
+ when /alt/ then @modifiers[:alt] = false
111
+ when /meta/ then @modifiers[:meta] = false
112
+ when /backspace/ then @modifiers[:backspace] = false
113
+ end
114
+ end
115
+
116
+ @window.on(:key_held) do |event|
117
+ case event.key
118
+ when /shift/ then @modifiers[:shift] = true
119
+ when /ctrl|control/ then @modifiers[:control] = true
120
+ when /alt/ then @modifiers[:alt] = true
121
+ when /meta/ then @modifiers[:meta] = true
122
+ when /backspace/ then @modifiers[:backspace] = true
123
+ end
124
+ end
125
+
126
+ @window.on(:mouse_down) do |event|
127
+ case event.button
128
+ when :left then @mouse[:left] = true
129
+ when :right then @mouse[:right] = true
130
+ when :middle then @mouse[:middle] = true
131
+ end
132
+ end
133
+
134
+ @window.on(:mouse_up) do |event|
135
+ case event.button
136
+ when :left then @mouse[:left] = false
137
+ when :right then @mouse[:right] = false
138
+ when :middle then @mouse[:middle] = false
139
+ end
140
+ end
141
+
142
+ Signal.trap('INT') do
143
+ Thread.new do
144
+ puts 'Caught SIGINT, exiting...'
145
+ quit
146
+ end.join
147
+ end
148
+
149
+ Signal.trap('TERM') do
150
+ Thread.new do
151
+ puts 'Caught SIGTERM, exiting...'
152
+ quit
153
+ end.join
154
+ end
155
+ end
156
+
157
+ def process_animations
158
+ return if @animations.empty?
159
+
160
+ @animations.each do |id, bundle|
161
+ bundle.each(&:resume)
162
+ bundle.delete_if(&:complete?)
163
+ @animations.delete(id) if bundle.empty?
164
+ end
165
+ end
166
+
167
+ def process_scene
168
+ if @scene.nil? || @scene.finished?
169
+ if @scenes.empty?
170
+ @scene = OR2D::Scenes::PlaceholderScene.new
171
+ else
172
+ @scene = @scenes.first
173
+ @scenes.delete(@scene)
174
+ end
175
+
176
+ @scene.render
177
+ end
178
+
179
+ @scene.update
180
+ rescue StandardError => e
181
+ puts "An error occurred while processing the current scene: #{e.message}"
182
+ puts e.backtrace if OR2D.debug?
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,30 @@
1
+ module Ruby2D
2
+ class Music
3
+
4
+ attr :playing
5
+
6
+ def playing?
7
+ @playing
8
+ end
9
+
10
+ def play
11
+ @playing ||= true
12
+ ext_play
13
+ end
14
+
15
+ def pause
16
+ @playing = false
17
+ ext_pause
18
+ end
19
+
20
+ def resume
21
+ @playing = true
22
+ ext_resume
23
+ end
24
+
25
+ def stop
26
+ @playing = false
27
+ ext_stop
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module Ruby2D::Renderable
2
+ def to_entity
3
+ OR2D::Entity.new(:entity, { resource: self })
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module Ruby2D
2
+ class Sound
3
+
4
+ def play
5
+ @playing = true
6
+ ext_play
7
+ end
8
+
9
+ def playing?
10
+ @playing
11
+ end
12
+
13
+ def stop
14
+ @playing = false
15
+ ext_stop
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Ruby2D
2
+ class Sprite
3
+
4
+ attr_reader :animations, :defaults, :playing
5
+
6
+ # Explicitly set the frame of the Sprite.
7
+ # The animation hash is expected to be in either of the following formats:
8
+ # { animation_name: [frame_1, frame_2, ...] }
9
+ # { animation_name: frame_range }
10
+ # This function will grab the explicit frame index of the given animation and set it.
11
+ def set_explicit_frame(animation, frame = 0)
12
+ _set_explicit_frame(@animations[animation][frame])
13
+ end
14
+
15
+ def default_frame(animation, frame = 0)
16
+ @defaults[:animation] = animation
17
+ @defaults[:frame] = frame
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Ruby2D
2
+ class Window
3
+ attr_reader :objects
4
+
5
+ def rendered?(object)
6
+ @objects.include?(object)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,182 @@
1
+ # The Resource module contains helper methods for creating resources.
2
+ # @author Patrick W.
3
+ # @since 2023-04-20
4
+ module OR2D::Resource
5
+
6
+ # Construct a new resource of the given type with the passed options.
7
+ # @param type [Symbol] the type of resource to create
8
+ # @param options [Hash] the options to pass to the resource
9
+ def create(type, options)
10
+ case type
11
+ when :image then OR2D::Resource.create_image(options)
12
+ when :sprite then OR2D::Resource.create_sprite(options)
13
+ when :text then OR2D::Resource.create_text(options)
14
+ when :tileset then OR2D::Resource.create_tileset(options)
15
+ when :triangle then OR2D::Resource.create_triangle(options)
16
+ when :quad then OR2D::Resource.create_quad(options)
17
+ when :circle then OR2D::Resource.create_circle(options)
18
+ when :square then OR2D::Resource.create_square(options)
19
+ when :line then OR2D::Resource.create_line(options)
20
+ when :rectangle then OR2D::Resource.create_rectangle(options)
21
+ else raise "Unknown resource type: #{type}!"
22
+ end
23
+ end
24
+
25
+ # Create a Ruby2D::Circle object from the given options.
26
+ # @param options [Hash] the options to pass to the Circle
27
+ # @return [Ruby2D::Circle] the created Circle
28
+ def create_circle(options)
29
+ Ruby2D::Circle.new(x: options[:x] || 0,
30
+ y: options[:y] || 0,
31
+ z: options[:z] || 0,
32
+ radius: options[:radius] || 50,
33
+ sectors: options[:sectors] || 30,
34
+ color: options[:color] || 'yellow',
35
+ opacity: options[:opacity] || 1.0)
36
+ end
37
+
38
+ # Create a Ruby2D::Image object from the given options.
39
+ # @param options [Hash] the options to pass to the Image
40
+ # @return [Ruby2D::Image] the created Image
41
+ def create_image(options)
42
+ Ruby2D::Image.new(options[:path],
43
+ atlas: options[:atlas],
44
+ width: options[:width] * OR2D.scale || 4,
45
+ height: options[:height] * OR2D.scale || 4,
46
+ opacity: options[:opacity] || 1.0,
47
+ rotate: options[:rotate] || 0,
48
+ x: options[:x] || 0,
49
+ y: options[:y] || 0,
50
+ z: options[:z] || 0)
51
+ end
52
+
53
+ # Create a Ruby2D::Line object from the given options.
54
+ # @param options [Hash] the options to pass to the Line
55
+ # @return [Ruby2D::Line] the created Line
56
+ def create_line(options)
57
+ Ruby2D::Line.new(x1: options[:x1],
58
+ y1: options[:y1],
59
+ x2: options[:x2],
60
+ y2: options[:y2],
61
+ z: options[:z],
62
+ width: options[:width] * OR2D.scale || 2,
63
+ color: options[:color] || 'yellow',
64
+ opacity: options[:opacity] || 1.0)
65
+ end
66
+
67
+ # Create a Ruby2D::Rectangle object from the given options.
68
+ # @param options [Hash] the options to pass to the Rectangle
69
+ # @return [Ruby2D::Rectangle] the created Rectangle
70
+ def create_rectangle(options)
71
+ Ruby2D::Rectangle.new(x: options[:x] || 0,
72
+ y: options[:y] || 0,
73
+ z: options[:z] || 0,
74
+ width: options[:width] * OR2D.scale,
75
+ height: options[:height] * OR2D.scale,
76
+ color: options[:color] || 'yellow',
77
+ opacity: options[:opacity] || 1.0)
78
+ end
79
+
80
+ # Create a Ruby2D::Sprite object from the given options.
81
+ # @param options [Hash] the options to pass to the Sprite
82
+ # @return [Ruby2D::Sprite] the created Sprite
83
+ def create_sprite(options)
84
+ Ruby2D::Sprite.new(options[:path],
85
+ atlas: options[:atlas],
86
+ width: options[:width] ? options[:width] * OR2D.scale : nil,
87
+ height: options[:height] ? options[:height] * OR2D.scale : nil,
88
+ rotate: options[:rotate] || 0,
89
+ opacity: options[:opacity] || 1.0,
90
+ loop: options[:loop] || false,
91
+ time: options[:time] || 100,
92
+ animations: options[:animations] || {},
93
+ default: options[:default] || 0,
94
+ clip_height: options[:clip_height],
95
+ clip_width: options[:clip_width],
96
+ clip_x: options[:clip_x] || 0,
97
+ clip_y: options[:clip_y] || 0,
98
+ x: options[:x] || 0,
99
+ y: options[:y] || 0,
100
+ z: options[:z] || 0)
101
+ end
102
+
103
+ # Create a Ruby2D::Square object from the given options.
104
+ # @param options [Hash] the options to pass to the Square
105
+ # @return [Ruby2D::Square] the created Square
106
+ def create_square(options)
107
+ Ruby2D::Square.new(x: options[:x] || 0,
108
+ y: options[:y] || 0,
109
+ z: options[:z] || 0,
110
+ size: options[:size],
111
+ color: options[:color] || 'yellow',
112
+ opacity: options[:opacity] || 1.0)
113
+ end
114
+
115
+ # Create a Ruby2D::Text object from the given options.
116
+ # @param options [Hash] the options to pass to the Text
117
+ # @return [Ruby2D::Text] the created Text
118
+ def create_text(options)
119
+ Ruby2D::Text.new(options[:text],
120
+ x: options[:x] || 0,
121
+ y: options[:y] || 0,
122
+ z: options[:z] || 0,
123
+ size: (options[:size] || 16) * OR2D.scale,
124
+ style: options[:style],
125
+ font: options[:font] || Ruby2D::Font.default,
126
+ color: options[:color] || 'yellow',
127
+ rotate: options[:rotate] || 0,
128
+ opacity: options[:opacity] || 1.0)
129
+ end
130
+
131
+ # Create a Ruby2D::Tileset object from the given options.
132
+ # @param options [Hash] the options to pass to the Tileset
133
+ # @return [Ruby2D::Tileset] the created Tileset
134
+ def create_tileset(options)
135
+ Ruby2D::Tileset.new(options[:path],
136
+ tile_width: options[:tile_width] || 16,
137
+ tile_height: options[:tile_height] || 16,
138
+ width: options[:width],
139
+ height: options[:height],
140
+ z: options[:z] || 100,
141
+ padding: options[:padding] || 0,
142
+ spacing: options[:spacing] || 0,
143
+ scale: options[:scale] || 1,
144
+ show: options[:show] || false)
145
+ end
146
+
147
+ # Create a Ruby2D::Triangle object from the given options.
148
+ # @param options [Hash] the options to pass to the Triangle
149
+ # @return [Ruby2D::Triangle] the created Triangle
150
+ def create_triangle(options)
151
+ Ruby2D::Triangle.new(x1: options[:x1],
152
+ y1: options[:y1],
153
+ x2: options[:x2],
154
+ y2: options[:y2],
155
+ x3: options[:x3],
156
+ y3: options[:y3],
157
+ z: options[:z],
158
+ color: options[:color] || 'yellow',
159
+ opacity: options[:opacity] || 1.0)
160
+ end
161
+
162
+ # Create a Ruby2D::Quad object from the given options.
163
+ # @param options [Hash] the options to pass to the Quad
164
+ # @return [Ruby2D::Quad] the created Quad
165
+ def create_quad(options)
166
+ Ruby2D::Quad.new(x1: options[:x1],
167
+ y1: options[:y1],
168
+ x2: options[:x2],
169
+ y2: options[:y2],
170
+ x3: options[:x3],
171
+ y3: options[:y3],
172
+ x4: options[:x4],
173
+ y4: options[:y4],
174
+ z: options[:z],
175
+ color: options[:color] || 'yellow',
176
+ opacity: options[:opacity] || 1.0)
177
+ end
178
+
179
+ class << self
180
+ include OR2D::Resource
181
+ end
182
+ end
data/lib/or2d/scene.rb ADDED
@@ -0,0 +1,160 @@
1
+ module OR2D
2
+ # A Scene object represents a single scene in an OR2D instance. Each Scene maintains two internal lists to keep
3
+ # track of the visibility state of Entities in the scene. It provides 3 functions to control how the Scene is updated,
4
+ # rendered, and whether or not it should be redrawn. It is recommended to use inheritance to create new scenes.
5
+ # @example Inheritance
6
+ # class MyScene < OR2D::Scene
7
+ # def initialize
8
+ # super('my_scene')
9
+ # end
10
+ #
11
+ # private
12
+ #
13
+ # def setup_render
14
+ # super do
15
+ # # Render code here
16
+ # end
17
+ # end
18
+ #
19
+ # def setup_update
20
+ # super do
21
+ # # Update code here
22
+ # end
23
+ # end
24
+ #
25
+ # def setup_redraw
26
+ # super do
27
+ # # Redraw code here
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @author Patrick W.
33
+ # @since 2023-04-12
34
+ class Scene
35
+
36
+ # @!attribute [r] id
37
+ # @return [String] the ID of the scene
38
+ attr_reader :id
39
+
40
+ # Constructs a new Scene object.
41
+ # @param id [String] the ID of the scene
42
+ # @param procs [Hash] a hash of procs to be used for the scene
43
+ def initialize(id, procs = {})
44
+ @id = id
45
+ @descriptors = {}
46
+ @workers = {}
47
+ @visible = Set.new
48
+ @hidden = Set.new
49
+ @rendered = false
50
+ @finish = false
51
+
52
+ procs[:redraw?].nil? ? setup_redraw : setup_redraw(&procs[:redraw?])
53
+ procs[:update].nil? ? setup_update : setup_update(&procs[:update])
54
+ procs[:render].nil? ? setup_render : setup_render(&procs[:render])
55
+
56
+ self
57
+ end
58
+
59
+ # Updates the scene.
60
+ def update
61
+ @workers[:update].resume(self)
62
+ end
63
+
64
+ # Render the scene.
65
+ def render
66
+ @workers[:render].resume
67
+ end
68
+
69
+ # Should the scene be redrawn?
70
+ # @return [Boolean] true if the scene should be redrawn, false otherwise
71
+ def redraw?
72
+ @workers[:redraw?].resume
73
+ end
74
+
75
+ # Finishes the scene.
76
+ def finish
77
+ @descriptors.each_value { |descriptor| OR2D.game.window.off(descriptor) }
78
+ @finish = true
79
+ end
80
+
81
+ # Has the scene finished?
82
+ # @return [Boolean] true if the scene has finished, false otherwise
83
+ def finished?
84
+ @finish
85
+ end
86
+
87
+ # Has the scene been rendered?
88
+ def rendered?
89
+ @rendered
90
+ end
91
+
92
+ private
93
+
94
+ # Halts the running fiber, stopping the scene.
95
+ def halt
96
+ Fiber.yield
97
+ end
98
+
99
+ # Setup event descriptors.
100
+ def setup_descriptor(label, event, &block)
101
+ @descriptors[label] = OR2D.game.window.on(event, &block)
102
+ end
103
+
104
+ # Setup the Scene's render worker.
105
+ def setup_render(&_)
106
+ @workers[:render] = Fiber.new do
107
+ loop do
108
+ break if finished?
109
+
110
+ @visible.each { |entity_id| OR2D::Game.source.add(OR2D::Game.entities[entity_id]) }
111
+ @hidden.each { |entity_id| OR2D::Game.source.remove(OR2D::Game.entities[entity_id]) }
112
+
113
+ yield if block_given?
114
+ rescue StandardError => e
115
+ puts e.message
116
+ puts e.backtrace if OR2D.debug?
117
+ ensure
118
+ @rendered = true
119
+ Fiber.yield unless finished?
120
+ end
121
+ end
122
+ end
123
+
124
+ # Setup the Scene's update worker.
125
+ def setup_update(&_)
126
+ @workers[:update] = Fiber.new do |scene|
127
+ loop do
128
+ break if finished?
129
+
130
+ yield(scene) if block_given?
131
+ rescue StandardError => e
132
+ puts e.message
133
+ puts e.backtrace if OR2D.debug?
134
+ ensure
135
+ Fiber.yield unless finished?
136
+ end
137
+ end
138
+ end
139
+
140
+ # Setup the Scene's redraw? worker.
141
+ def setup_redraw(&_)
142
+ @workers[:redraw?] = Fiber.new do
143
+ loop do
144
+ break if finished?
145
+
146
+ if rendered? && block_given?
147
+ Fiber.yield(yield)
148
+ elsif rendered?
149
+ Fiber.yield(true)
150
+ else
151
+ Fiber.yield(false)
152
+ end
153
+ rescue StandardError => e
154
+ puts e.message
155
+ puts e.backtrace if OR2D.debug?
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,70 @@
1
+ module OR2D::Scenes
2
+ # The PlaceholderScene class is a placeholder scene for the OR2D gem. When no other scenes are enqueued or displayed, this scene will be displayed.
3
+ class PlaceholderScene < OR2D::Scene
4
+
5
+ # Construct a new PlaceholderScene object.
6
+ def initialize
7
+ super('PLACEHOLDER_SCENE')
8
+ @cursor_position = OR2D::Entity.new(:text, { color: 'white', x: 0, y: 0, size: 16, height: 16, width: 16 })
9
+
10
+ setup_events
11
+ setup_console
12
+ end
13
+
14
+ # Complete processing of the scene
15
+ def finish
16
+ @console.remove
17
+ @cursor_position.destroy
18
+ @descriptors.each_value { |descriptor| OR2D.game.window.off(descriptor) }
19
+ super
20
+ end
21
+
22
+ private
23
+
24
+ def setup_console
25
+ @console = OR2D::Console.new
26
+ end
27
+
28
+ def setup_render
29
+ super do
30
+ @cursor_position.show
31
+ end
32
+ end
33
+
34
+ def setup_update
35
+ super do
36
+ @cursor_position.x = OR2D.game.window.get(:mouse_x)
37
+ @cursor_position.y = OR2D.game.window.get(:mouse_y)
38
+ @console.update
39
+ end
40
+ end
41
+
42
+ def setup_events
43
+ setup_descriptor(:mouse_input, :mouse) do |event|
44
+ @cursor_position.resource.text = "[x: #{event.x}, y: #{event.y}]"
45
+ @cursor_position.width = @cursor_position.resource.text.length * (6 * OR2D.scale)
46
+ end
47
+
48
+ setup_descriptor(:debug_input, :key_up) do |event|
49
+ case event.key
50
+ when 'g' then @cursor_position.grow(factor: 0.2)
51
+ when 'z' then @cursor_position.shrink(factor: 0.2)
52
+ when 't' then @cursor_position.fade(speed: 0.01)
53
+ when 'r'
54
+ if OR2D.game.modifiers[:control]
55
+ OR2D.game.entities.each_value(&:rotate) && @cursor_position.rotate
56
+ elsif OR2D.game.modifiers[:meta]
57
+ @cursor_position.rotation(speed: 1.0, angle: 360, clockwise: false)
58
+ else
59
+ @cursor_position.rotation(speed: 1.0, angle: 360, clockwise: true)
60
+ end
61
+ when 'b'
62
+ if OR2D.game.modifiers[:control]
63
+ OR2D.game.entities.each_value(&:toggle_boundary) && @cursor_position.toggle_boundary
64
+ end
65
+ when 'escape' then finish
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end