or2d 0.0.0

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