chingu 0.7.6.8 → 0.7.6.9
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.
- data/.gitignore +1 -0
- data/chingu.gemspec +8 -2
- data/examples/example4_gamestates.rb +5 -5
- data/lib/chingu.rb +1 -1
- data/lib/chingu/game_object.rb +1 -1
- data/lib/chingu/game_object_list.rb +4 -0
- data/lib/chingu/game_states/edit.rb +8 -20
- data/lib/chingu/helpers/input_client.rb +200 -38
- data/lib/chingu/helpers/input_dispatcher.rb +32 -47
- data/lib/chingu/simple_menu.rb +119 -0
- data/spec/chingu/game_object_spec.rb +17 -19
- data/spec/chingu/helpers/input_client_spec.rb +180 -0
- data/spec/chingu/helpers/input_dispatcher_spec.rb +84 -0
- data/spec/chingu/helpers/options_setter_spec.rb +1 -1
- data/spec/chingu/images/rect_20x20.png +0 -0
- data/spec/spec_helper.rb +1 -1
- metadata +11 -5
data/chingu.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{chingu}
|
8
|
-
s.version = "0.7.6.
|
8
|
+
s.version = "0.7.6.9"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["ippa"]
|
12
|
-
s.date = %q{2010-08-
|
12
|
+
s.date = %q{2010-08-18}
|
13
13
|
s.description = %q{OpenGL accelerated 2D game framework for Ruby. Builds on Gosu (Ruby/C++) which provides all the core functionality. Chingu adds simple yet powerful game states, prettier input handling, deployment safe asset-handling, a basic re-usable game object and stackable game logic.}
|
14
14
|
s.email = %q{ippa@rubylicio.us}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -138,6 +138,7 @@ Gem::Specification.new do |s|
|
|
138
138
|
"lib/chingu/particle.rb",
|
139
139
|
"lib/chingu/rect.rb",
|
140
140
|
"lib/chingu/require_all.rb",
|
141
|
+
"lib/chingu/simple_menu.rb",
|
141
142
|
"lib/chingu/text.rb",
|
142
143
|
"lib/chingu/traits/animation.rb",
|
143
144
|
"lib/chingu/traits/bounding_box.rb",
|
@@ -153,7 +154,10 @@ Gem::Specification.new do |s|
|
|
153
154
|
"lib/chingu/window.rb",
|
154
155
|
"spec/chingu/fpscounter_spec.rb",
|
155
156
|
"spec/chingu/game_object_spec.rb",
|
157
|
+
"spec/chingu/helpers/input_client_spec.rb",
|
158
|
+
"spec/chingu/helpers/input_dispatcher_spec.rb",
|
156
159
|
"spec/chingu/helpers/options_setter_spec.rb",
|
160
|
+
"spec/chingu/images/rect_20x20.png",
|
157
161
|
"spec/chingu/inflector_spec.rb",
|
158
162
|
"spec/chingu/rect_20x20.png",
|
159
163
|
"spec/chingu/text_spec.rb",
|
@@ -169,6 +173,8 @@ Gem::Specification.new do |s|
|
|
169
173
|
s.test_files = [
|
170
174
|
"spec/chingu/fpscounter_spec.rb",
|
171
175
|
"spec/chingu/game_object_spec.rb",
|
176
|
+
"spec/chingu/helpers/input_client_spec.rb",
|
177
|
+
"spec/chingu/helpers/input_dispatcher_spec.rb",
|
172
178
|
"spec/chingu/helpers/options_setter_spec.rb",
|
173
179
|
"spec/chingu/inflector_spec.rb",
|
174
180
|
"spec/chingu/text_spec.rb",
|
@@ -116,7 +116,7 @@ class Level < Chingu::GameState
|
|
116
116
|
@player = Player.create
|
117
117
|
|
118
118
|
#
|
119
|
-
# The below code can mostly be replaced with the use of
|
119
|
+
# The below code can mostly be replaced with the use of methods "holding?", "holding_all?" or "holding_any?" in Level#update
|
120
120
|
# Using holding? in update could be good if you need fine grained controll over when input is dispatched.
|
121
121
|
#
|
122
122
|
@player.input = { :holding_left => :move_left,
|
@@ -138,10 +138,10 @@ class Level < Chingu::GameState
|
|
138
138
|
#
|
139
139
|
# Another way of checking input
|
140
140
|
#
|
141
|
-
# @player.move_left if
|
142
|
-
# @player.move_right if
|
143
|
-
# @player.move_up if
|
144
|
-
# @player.move_down if
|
141
|
+
# @player.move_left if holding_any?(:left, :a)
|
142
|
+
# @player.move_right if holding_any?(:right, :d)
|
143
|
+
# @player.move_up if holding_any?(:up, :w)
|
144
|
+
# @player.move_down if holding_any?(:down, :s)
|
145
145
|
# @player.fire if holding?(:space)
|
146
146
|
|
147
147
|
Bullet.destroy_if {|bullet| bullet.outside_window? }
|
data/lib/chingu.rb
CHANGED
data/lib/chingu/game_object.rb
CHANGED
@@ -100,7 +100,7 @@ module Chingu
|
|
100
100
|
# Makes it easy to clone a objects x,y,angle etc.
|
101
101
|
#
|
102
102
|
def attributes
|
103
|
-
[@x, @y, @angle, @center_x, @center_y, @factor_x, @factor_y, @color, @mode, @zorder]
|
103
|
+
[@x, @y, @angle, @center_x, @center_y, @factor_x, @factor_y, @color.dup, @mode, @zorder]
|
104
104
|
end
|
105
105
|
|
106
106
|
#
|
@@ -53,7 +53,7 @@ module Chingu
|
|
53
53
|
options = {:draw_grid => true, :snap_to_grid => true, :resize_to_grid => true}.merge(options)
|
54
54
|
|
55
55
|
@grid = options[:grid] || [8,8]
|
56
|
-
@grid_color = options[:grid_color] || Color.new(0xaa222222)
|
56
|
+
@grid_color = options[:grid_color] || Gosu::Color.new(0xaa222222)
|
57
57
|
@draw_grid = options[:draw_grid]
|
58
58
|
@snap_to_grid = options[:snap_to_grid] # todo
|
59
59
|
@resize_to_grid = options[:resize_to_grid] # todo
|
@@ -501,7 +501,7 @@ END_OF_STRING
|
|
501
501
|
end
|
502
502
|
|
503
503
|
def quit
|
504
|
-
pop_game_state
|
504
|
+
pop_game_state
|
505
505
|
end
|
506
506
|
def save
|
507
507
|
save_game_objects(:game_objects => editable_game_objects, :file => @file, :classes => @classes)
|
@@ -654,26 +654,14 @@ END_OF_STRING
|
|
654
654
|
def create_new_game_object_from(template)
|
655
655
|
game_object = template.class.create(:parent => previous_game_state)
|
656
656
|
game_object.update
|
657
|
-
|
658
|
-
|
657
|
+
|
658
|
+
# If we don't create it from the toolbar, we're cloning another object
|
659
|
+
# When cloning we wan't the cloned objects attributes
|
660
|
+
game_object.attributes = template.attributes unless template.options[:toolbar]
|
659
661
|
game_object.x = self.mouse_x
|
660
662
|
game_object.y = self.mouse_y
|
661
|
-
|
662
|
-
|
663
|
-
game_object.angle = template.angle
|
664
|
-
game_object.factor_x = template.factor_x
|
665
|
-
game_object.factor_y = template.factor_y
|
666
|
-
game_object.color = template.color.dup
|
667
|
-
game_object.zorder = template.zorder
|
668
|
-
game_object.update
|
669
|
-
else
|
670
|
-
# Resize the new game object to fit the grid perfectly!
|
671
|
-
wanted_width = game_object.image.width + @grid[0] - (game_object.image.width % @grid[0])
|
672
|
-
wanted_height = game_object.image.height + @grid[1] - (game_object.image.height % @grid[1])
|
673
|
-
game_object.factor_x = wanted_width.to_f / game_object.image.width.to_f
|
674
|
-
game_object.factor_y = wanted_height.to_f / game_object.image.height.to_f
|
675
|
-
end
|
676
|
-
|
663
|
+
game_object.options[:created_with_editor] = true
|
664
|
+
|
677
665
|
game_object.options[:mouse_x_offset] = (game_object.x - self.mouse_x) rescue 0
|
678
666
|
game_object.options[:mouse_y_offset] = (game_object.y - self.mouse_y) rescue 0
|
679
667
|
|
@@ -44,60 +44,222 @@ module Chingu
|
|
44
44
|
#
|
45
45
|
#
|
46
46
|
module InputClient
|
47
|
+
# The current input handlers. This may contain data which is different to the input set,
|
48
|
+
# but it will be equivalent in function [Hash].
|
49
|
+
def input
|
50
|
+
@input ||= Hash.new { |hash, key| hash[key] = [] }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Is a particular key being held down at this moment?
|
54
|
+
#
|
55
|
+
# === Parameters
|
56
|
+
# +key+:: Chingu key code [Symbol]
|
57
|
+
#
|
58
|
+
public
|
59
|
+
def holding?(key)
|
60
|
+
$window.button_down?(Chingu::Input::SYMBOL_TO_CONSTANT[key])
|
61
|
+
end
|
62
|
+
|
63
|
+
# Are any of a list of keys being held down?
|
64
|
+
#
|
65
|
+
# === Parameters
|
66
|
+
# +key+:: Chingu key code [Symbol]
|
47
67
|
#
|
48
|
-
#
|
68
|
+
# :call-seq:
|
69
|
+
# holding_any?(key [, key]*) { } -> true or false
|
70
|
+
#
|
71
|
+
public
|
72
|
+
def holding_any?(*keys)
|
73
|
+
keys.any? { |key| holding?(key) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Are all keys in a list being held down simultaneously?
|
77
|
+
#
|
78
|
+
# === Parameters
|
79
|
+
# +key+:: Chingu key code [Symbol]
|
80
|
+
#
|
81
|
+
# :call-seq:
|
82
|
+
# holding_all?(key [, key]*) { } -> true or false
|
83
|
+
#
|
84
|
+
public
|
85
|
+
def holding_all?(*keys)
|
86
|
+
keys.all? { |key| holding?(key) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Ensures that the key code exists and ensures it has a consistent name.
|
49
90
|
#
|
50
|
-
#
|
51
|
-
#
|
91
|
+
# === Parameters
|
92
|
+
# +key+:: Chingu key code [Symbol]
|
52
93
|
#
|
53
|
-
|
54
|
-
|
55
|
-
|
94
|
+
# Returns: Key code [Symbol]
|
95
|
+
#
|
96
|
+
protected
|
97
|
+
def validate_input_key(key)
|
98
|
+
raise ArgumentError, "Input must be a Symbol or #{MultiInput}, but received #{key} (#{key.class})" unless key.is_a? Symbol
|
99
|
+
|
100
|
+
# The base key is the key without a prefix, so we can find it in the lookups.
|
101
|
+
base_key = Chingu::Input::SYMBOL_TO_CONSTANT.has_key?(key) ? key : nil
|
102
|
+
prefix_used = ""
|
103
|
+
["holding_", "released_"].each do |prefix|
|
104
|
+
break if base_key
|
105
|
+
if key =~ /^#{prefix}(.*)$/
|
106
|
+
base_key = Chingu::Input::SYMBOL_TO_CONSTANT.has_key?($1.to_sym) ? $1.to_sym : nil
|
107
|
+
prefix_used = prefix
|
108
|
+
end
|
56
109
|
end
|
57
|
-
|
110
|
+
|
111
|
+
raise ArgumentError, "No such input as #{key.inspect}" unless base_key
|
112
|
+
|
113
|
+
# Standardise the symbol used.
|
114
|
+
:"#{prefix_used}#{Input::CONSTANT_TO_SYMBOL[Input::SYMBOL_TO_CONSTANT[base_key]].first}"
|
58
115
|
end
|
59
|
-
|
116
|
+
|
117
|
+
# Ensures that the key code exists and ensures it has a consistent name.
|
60
118
|
#
|
61
|
-
#
|
119
|
+
# === Parameters
|
120
|
+
# +key+:: Chingu key code [Symbol]
|
121
|
+
# +action+:: Action to perform [Method/Proc/String/Symbol/Class/Chingu::GameState]
|
62
122
|
#
|
63
|
-
|
64
|
-
|
123
|
+
# Returns: Key code, action [Array]
|
124
|
+
#
|
125
|
+
protected
|
126
|
+
def validate_input(key, action)
|
127
|
+
key = validate_input_key(key)
|
65
128
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@input[symbol] = action
|
83
|
-
end
|
84
|
-
elsif possible_array.is_a? Symbol
|
85
|
-
@input[possible_array] = action
|
129
|
+
message = case action
|
130
|
+
when Method, Proc, Chingu::GameState
|
131
|
+
nil
|
132
|
+
|
133
|
+
when String, Symbol
|
134
|
+
"#{self.class} does not have a #{action} method" unless self.respond_to? action
|
135
|
+
# Resolve to a method.
|
136
|
+
action = method action
|
137
|
+
|
138
|
+
nil
|
139
|
+
|
140
|
+
when Class
|
141
|
+
if action.ancestors.include? Chingu::GameState
|
142
|
+
nil
|
143
|
+
else
|
144
|
+
"Input action is a class (#{action.class}) not inheriting from Chingu::GameState"
|
86
145
|
end
|
146
|
+
|
147
|
+
else
|
148
|
+
"Input action must be a Method, Proc, String, Symbol, Chingu::GameState or a class inheriting from Chingu::GameState (Received a #{action.class})"
|
149
|
+
end
|
150
|
+
|
151
|
+
raise ArgumentError, "(For input #{key.inspect}) #{message}" if message
|
152
|
+
|
153
|
+
[key, action]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Add input handlers all at once. This is an alternative to on_input, which sets input handlers individually.
|
157
|
+
#
|
158
|
+
# === Parameters
|
159
|
+
# +input_list+:: [Hash or Array]
|
160
|
+
#
|
161
|
+
# === Examples
|
162
|
+
# As a hash (Will call move_left or move_right methods when keys pressed):
|
163
|
+
# add_inputs :left_arrow => :move_left,
|
164
|
+
# :right_arrow => :move_right
|
165
|
+
#
|
166
|
+
# As an array (Will call left_arrow or right_arrow methods when keys pressed):
|
167
|
+
# add_inputs :left_arrow, :right_arrow
|
168
|
+
|
169
|
+
# :call-seq:
|
170
|
+
# add_inputs(input [, input]*] -> self
|
171
|
+
# add_inputs(input => action [, input => action]*) -> self
|
172
|
+
#
|
173
|
+
public
|
174
|
+
def add_inputs(*input_list)
|
175
|
+
raise "Need to provide inputs as either parameters or hashed parameters" if input_list.empty?
|
176
|
+
input_list = input_list[0] if input_list[0].is_a? Hash
|
177
|
+
|
178
|
+
case input_list
|
179
|
+
when Array
|
180
|
+
#
|
181
|
+
# Un-nest input_map [:left, :right, :space]
|
182
|
+
# Into: { :left => :left, :right => :right, :space => :space }
|
183
|
+
#
|
184
|
+
input_list.each { |symbol| on_input(symbol) }
|
185
|
+
|
186
|
+
when Hash
|
187
|
+
#
|
188
|
+
# Un-nest input: { [:pad_left, :arrow_left, :a] => :move_left }
|
189
|
+
# Into: { :pad_left => :move_left, :arrow_left => :move_left, :a => :move_left }
|
190
|
+
#
|
191
|
+
input_list.each_pair do |possible_array, action|
|
192
|
+
case possible_array
|
193
|
+
when Array
|
194
|
+
possible_array.each { |symbol| on_input(symbol, action) }
|
195
|
+
when Symbol
|
196
|
+
on_input(possible_array, action)
|
197
|
+
end
|
87
198
|
end
|
88
199
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
200
|
+
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
# Backwards compatibility function. Use on_inputs or on_input instead.
|
205
|
+
def input=(input_list)
|
206
|
+
@input = nil
|
207
|
+
input_list.is_a?(Array) ? add_inputs(*input_list) : add_inputs(input_list)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Adds an event handler for a key or any of a list of keys. This is an alternative to input=, which sets all
|
211
|
+
# handlers at once.
|
212
|
+
#
|
213
|
+
# === Parameters
|
214
|
+
# +input+:: Chingu input code(s) [Symbol or Array of Symbols]
|
215
|
+
# +action+:: [Method/Proc/String/Symbol/Class/Chingu::GameState]
|
216
|
+
#
|
217
|
+
# Returns: self
|
218
|
+
#
|
219
|
+
# === Examples
|
220
|
+
# Using actions:
|
221
|
+
# on_input(:space) # Will call the space method when input detected.
|
222
|
+
# on_input(:space, :fire_laser)
|
223
|
+
# on_input([:left, :a], method(:go_left))
|
224
|
+
#
|
225
|
+
# Using blocks:
|
226
|
+
# on_input :space do
|
227
|
+
# # Fire the laser
|
228
|
+
# end
|
229
|
+
# on_input [:left, :a] do
|
230
|
+
# # Move the player left.
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
# :call-seq:
|
234
|
+
# on_input(input) -> self
|
235
|
+
# on_input(input) { } -> self
|
236
|
+
# on_input(input, action) -> self
|
237
|
+
#
|
238
|
+
public
|
239
|
+
def on_input(input, action = nil, &block)
|
240
|
+
raise ArgumentError, "#{self.class}#on_input takes either an input OR an input and action OR an input with a block" if action and block
|
241
|
+
|
242
|
+
inputs = input.is_a?(Array) ? input : [input] # Can be a single input or an array of them.
|
243
|
+
action = block if block
|
244
|
+
action = input unless action
|
245
|
+
|
246
|
+
@input ||= Hash.new { |hash, key| hash[key] = [] }
|
247
|
+
|
248
|
+
# Ensure that the new input array is reasonable.
|
249
|
+
inputs.each do |input|
|
250
|
+
standardised_symbol, action = validate_input(input, action)
|
251
|
+
@input[standardised_symbol] << action
|
252
|
+
end
|
253
|
+
|
254
|
+
if @parent
|
255
|
+
if @input.empty?
|
92
256
|
@parent.remove_input_client(self)
|
93
257
|
else
|
94
258
|
@parent.add_input_client(self)
|
95
259
|
end
|
96
260
|
end
|
97
|
-
|
98
|
-
|
99
|
-
def input
|
100
|
-
@input
|
261
|
+
|
262
|
+
self
|
101
263
|
end
|
102
264
|
end
|
103
265
|
end
|
@@ -21,89 +21,74 @@
|
|
21
21
|
|
22
22
|
module Chingu
|
23
23
|
module Helpers
|
24
|
-
|
25
24
|
#
|
26
25
|
# Methods for parsing and dispatching Chingus input-maps
|
27
26
|
# Mixed into Chingu::Window and Chingu::GameState
|
28
27
|
#
|
29
28
|
module InputDispatcher
|
30
29
|
attr_reader :input_clients
|
31
|
-
|
30
|
+
|
31
|
+
# Called by input_clients to add themselves from forwarding.
|
32
32
|
def add_input_client(object)
|
33
33
|
@input_clients << object unless @input_clients.include?(object)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
|
+
# Called by input_clients to remove themselves from forwarding.
|
36
37
|
def remove_input_client(object)
|
37
38
|
@input_clients.delete(object)
|
38
39
|
end
|
39
40
|
|
41
|
+
# Dispatch button press to one of your clients.
|
40
42
|
def dispatch_button_down(id, object)
|
41
|
-
|
42
|
-
|
43
|
-
object.input.each do |symbol, action|
|
44
|
-
if Input::SYMBOL_TO_CONSTANT[symbol] == id
|
45
|
-
dispatch_action(action, object)
|
46
|
-
end
|
43
|
+
if actions = object.input[Input::CONSTANT_TO_SYMBOL[id].first]
|
44
|
+
dispatch_actions(actions)
|
47
45
|
end
|
48
46
|
end
|
49
|
-
|
47
|
+
|
48
|
+
# Dispatch button release to one of your clients.
|
50
49
|
def dispatch_button_up(id, object)
|
51
|
-
|
52
|
-
|
53
|
-
object.input.each do |symbol, action|
|
54
|
-
if symbol.to_s.include? "released_"
|
55
|
-
symbol = symbol.to_s.sub("released_", "").to_sym
|
56
|
-
if Input::SYMBOL_TO_CONSTANT[symbol] == id
|
57
|
-
dispatch_action(action, object)
|
58
|
-
end
|
59
|
-
end
|
50
|
+
if actions = object.input[:"released_#{Input::CONSTANT_TO_SYMBOL[id].first}"]
|
51
|
+
dispatch_actions(actions)
|
60
52
|
end
|
61
53
|
end
|
62
|
-
|
54
|
+
|
63
55
|
#
|
64
56
|
# Dispatches input-mapper for any given object
|
65
57
|
#
|
66
58
|
def dispatch_input_for(object, prefix = "holding_")
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
symbol = symbol.to_s.sub(prefix, "").to_sym
|
72
|
-
if $window.button_down?(Input::SYMBOL_TO_CONSTANT[symbol])
|
73
|
-
dispatch_action(action, object)
|
74
|
-
end
|
59
|
+
pattern = /^#{prefix}/
|
60
|
+
object.input.each do |symbol, actions|
|
61
|
+
if symbol =~ pattern and $window.button_down?(Input::SYMBOL_TO_CONSTANT[$'.to_sym])
|
62
|
+
dispatch_actions(actions)
|
75
63
|
end
|
76
64
|
end
|
77
65
|
end
|
78
|
-
|
66
|
+
|
79
67
|
#
|
80
68
|
# For a given object, dispatch "action".
|
81
|
-
# An action can be:
|
69
|
+
# An action can be an array containing any of:
|
82
70
|
#
|
83
|
-
# * Symbol (:p, :space) or String, translates into a method-call
|
84
71
|
# * Proc/Lambda or Method, call() it
|
85
72
|
# * GameState-instance, push it on top of stack
|
86
73
|
# * GameState-inherited class, create a new instance, cache it and push it on top of stack
|
87
74
|
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
push_game_state(action)
|
75
|
+
protected
|
76
|
+
def dispatch_actions(actions)
|
77
|
+
# puts "Dispatch Action: #{action} - Objects class: #{object.class.to_s}"
|
78
|
+
actions.each do |action|
|
79
|
+
case action
|
80
|
+
when Proc, Method
|
81
|
+
action[]
|
82
|
+
when Chingu::GameState, Class
|
83
|
+
# Don't need to check if the Class is a GameState, since that is already checked.
|
84
|
+
push_game_state(action)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Unexpected action #{action}"
|
101
87
|
end
|
102
|
-
else
|
103
|
-
# TODO possibly raise an error? This ought to be handled when the input is specified in the first place.
|
104
88
|
end
|
89
|
+
# Other types will already have been resolved to one of these, so no need for checking.
|
105
90
|
end
|
106
91
|
end
|
107
|
-
|
92
|
+
|
108
93
|
end
|
109
94
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Chingu -- OpenGL accelerated 2D game framework for Ruby
|
4
|
+
# Copyright (C) 2009 ippa / ippa@rubylicio.us
|
5
|
+
#
|
6
|
+
# This library is free software; you can redistribute it and/or
|
7
|
+
# modify it under the terms of the GNU Lesser General Public
|
8
|
+
# License as published by the Free Software Foundation; either
|
9
|
+
# version 2.1 of the License, or (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This library is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14
|
+
# Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with this library; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
19
|
+
#
|
20
|
+
#++
|
21
|
+
|
22
|
+
module Chingu
|
23
|
+
#
|
24
|
+
#
|
25
|
+
#
|
26
|
+
class SimpleMenu < BasicGameObject
|
27
|
+
include Chingu::Helpers::InputClient
|
28
|
+
attr_accessor :menu_items
|
29
|
+
|
30
|
+
def initialize(options = {})
|
31
|
+
super
|
32
|
+
|
33
|
+
@menu_items = options.delete(:menu_items)# || {"Exit" => :exit}
|
34
|
+
|
35
|
+
@x = options.delete(:x) || $window.width/2
|
36
|
+
@y = options.delete(:x) || 100
|
37
|
+
@spacing = options.delete(:x) || 100
|
38
|
+
@items = []
|
39
|
+
|
40
|
+
y = @spacing
|
41
|
+
menu_items.each do |key, value|
|
42
|
+
|
43
|
+
item = if key.is_a? String
|
44
|
+
Text.new(key, options.dup)
|
45
|
+
elsif key.is_a? Image
|
46
|
+
GameObject.new(options.merge!(:image => key))
|
47
|
+
elsif key.is_a? GameObject
|
48
|
+
menu_item.options.merge!(options.dup)
|
49
|
+
menu_item
|
50
|
+
end
|
51
|
+
|
52
|
+
item.options[:on_select] = method(:on_select)
|
53
|
+
item.options[:on_deselect] = method(:on_deselect)
|
54
|
+
item.options[:action] = value
|
55
|
+
|
56
|
+
item.rotation_center = :center_center
|
57
|
+
item.x = @x
|
58
|
+
item.y = y
|
59
|
+
y += item.height + @spacing
|
60
|
+
@items << item
|
61
|
+
end
|
62
|
+
@selected = options[:selected] || 0
|
63
|
+
step(0)
|
64
|
+
|
65
|
+
self.input = {:up => lambda{step(-1)}, :down => lambda{step(1)}, [:return, :space, :right] => :select}
|
66
|
+
end
|
67
|
+
|
68
|
+
def step(value)
|
69
|
+
selected.options[:on_deselect].call(selected)
|
70
|
+
@selected += value
|
71
|
+
@selected = @items.count-1 if @selected < 0
|
72
|
+
@selected = 0 if @selected == @items.count
|
73
|
+
selected.options[:on_select].call(selected)
|
74
|
+
end
|
75
|
+
|
76
|
+
def select
|
77
|
+
dispatch_action(selected.options[:action], self.parent)
|
78
|
+
end
|
79
|
+
|
80
|
+
def selected
|
81
|
+
@items[@selected]
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_deselect(object)
|
85
|
+
object.color = Color::WHITE
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_select(object)
|
89
|
+
object.color = Color::RED
|
90
|
+
end
|
91
|
+
|
92
|
+
def draw
|
93
|
+
@items.each { |item| item.draw }
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
#
|
99
|
+
# TODO - DRY this up with input dispatcher somehow
|
100
|
+
#
|
101
|
+
def dispatch_action(action, object)
|
102
|
+
case action
|
103
|
+
when Symbol, String
|
104
|
+
object.send(action)
|
105
|
+
when Proc, Method
|
106
|
+
action[]
|
107
|
+
when Chingu::GameState
|
108
|
+
push_game_state(action)
|
109
|
+
when Class
|
110
|
+
if action.ancestors.include?(Chingu::GameState)
|
111
|
+
push_game_state(action)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
# TODO possibly raise an error? This ought to be handled when the input is specified in the first place.
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Chingu
|
4
4
|
|
5
5
|
describe GameObject do
|
6
|
-
|
6
|
+
|
7
7
|
before(:all) do
|
8
8
|
@game = Chingu::Window.new
|
9
9
|
end
|
@@ -59,30 +59,28 @@ module Chingu
|
|
59
59
|
end
|
60
60
|
|
61
61
|
describe "GameObject with an image" do
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
62
|
+
|
63
|
+
let(:game_object) { GameObject.new(:image => "rect_20x20.png") }
|
64
|
+
|
67
65
|
it "should have width,height & size" do
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
game_object.height.should == 20
|
67
|
+
game_object.width.should == 20
|
68
|
+
game_object.size.should == [20,20]
|
71
69
|
end
|
72
70
|
|
73
71
|
it "should adapt width,height & size to scaling" do
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
72
|
+
game_object.factor = 2
|
73
|
+
game_object.height.should == 40
|
74
|
+
game_object.width.should == 40
|
75
|
+
game_object.size.should == [40,40]
|
78
76
|
end
|
79
77
|
|
80
78
|
it "should adapt factor_x/factor_y to new size" do
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
game_object.size = [10,40] # half the width, double the height
|
80
|
+
game_object.width.should == 10
|
81
|
+
game_object.height.should == 40
|
82
|
+
game_object.factor_x.should == 0.5
|
83
|
+
game_object.factor_y.should == 2
|
86
84
|
end
|
87
85
|
|
88
86
|
end
|
@@ -93,4 +91,4 @@ module Chingu
|
|
93
91
|
|
94
92
|
end
|
95
93
|
|
96
|
-
end
|
94
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Chingu
|
4
|
+
describe Helpers::InputClient do
|
5
|
+
before :each do
|
6
|
+
$window = mock Gosu::Window
|
7
|
+
$window.stub!(:button_down?).and_return(false)
|
8
|
+
|
9
|
+
@subject = Object.new.extend described_class
|
10
|
+
@subject.stub!(:handler1).and_return(nil)
|
11
|
+
@handler1 = @subject.method :handler1
|
12
|
+
@subject.stub!(:handler2).and_return(nil)
|
13
|
+
@handler2 = @subject.method :handler2
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#holding?" do
|
17
|
+
it "should be true if that key is being held down" do
|
18
|
+
$window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(true)
|
19
|
+
@subject.holding?(:space).should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be false if that key is being held down" do
|
23
|
+
$window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(false)
|
24
|
+
@subject.holding?(:space).should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#holding_all?" do
|
29
|
+
it "should be true if all of those keys are being held down" do
|
30
|
+
$window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(true)
|
31
|
+
$window.should_receive(:button_down?).with(Gosu::KbA).and_return(true)
|
32
|
+
@subject.holding_all?(:space, :a).should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be false if all of those keys are not being held down" do
|
36
|
+
@subject.holding_all?(:space, :a).should be_false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be false if some of those keys are not being held down" do
|
40
|
+
$window.stub!(:button_down?).with(Gosu::KbSpace).and_return(true)
|
41
|
+
@subject.holding_all?(:space, :a).should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#holding_any?" do
|
46
|
+
it "should be true if any of those keys are being held down" do
|
47
|
+
$window.stub!(:button_down?).with(Gosu::KbA).and_return(true)
|
48
|
+
$window.stub!(:button_down?).with(Gosu::KbSpace).and_return(true)
|
49
|
+
@subject.holding_any?(:space, :a).should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be false if none of those keys are being held down" do
|
53
|
+
@subject.holding_any?(:space, :a).should be_false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#input" do
|
58
|
+
it "should initially be an empty hash" do
|
59
|
+
@subject.input.should == {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#input=" do
|
64
|
+
it "should set the input hash" do
|
65
|
+
@subject.input = { :a => GameStates::Pause, :b => GameState }
|
66
|
+
@subject.input.should == { :a => [GameStates::Pause], :b => [GameState] }
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should set the input array" do
|
70
|
+
@subject.stub!(:a)
|
71
|
+
@subject.stub!(:b)
|
72
|
+
@subject.input = [:a, :b]
|
73
|
+
@subject.input.should == { :a => [@subject.method(:a)], :b => [@subject.method(:b)] }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#add_inputs" do
|
78
|
+
it "should set the input hash" do
|
79
|
+
@subject.add_inputs :a => GameStates::Pause, :b => GameState
|
80
|
+
@subject.input.should == { :a => [GameStates::Pause], :b => [GameState] }
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should set the input array" do
|
84
|
+
@subject.stub!(:a)
|
85
|
+
@subject.stub!(:b)
|
86
|
+
@subject.add_inputs :a, :b
|
87
|
+
@subject.input.should == { :a => [@subject.method(:a)], :b => [@subject.method(:b)] }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Not bothering with all the options, since it is tested fully, though indirectly, in #on_input already.
|
91
|
+
# I suspect it might be better to put the logic in on_input rather than in input too. Mmm.
|
92
|
+
it "should do other stuff"
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#on_input" do
|
96
|
+
it "should add a handler that is given as a block" do
|
97
|
+
block = lambda { }
|
98
|
+
@subject.on_input :a, &block
|
99
|
+
@subject.input.should == { :a => [block] }
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should add a handler that is given as a method" do
|
103
|
+
@subject.on_input :a, @handler1
|
104
|
+
@subject.input.should == { :a => [@handler1] }
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should add a handler that is given as a proc" do
|
108
|
+
proc = lambda { puts "Hello" }
|
109
|
+
@subject.on_input :a, proc
|
110
|
+
@subject.input.should == { :a => [proc] }
|
111
|
+
end
|
112
|
+
|
113
|
+
[:handler1, "handler1"].each do |handler|
|
114
|
+
it "should add a handler that is given as a #{handler.class}" do
|
115
|
+
@subject.on_input :a, handler
|
116
|
+
@subject.input.should == { :a => [@handler1] }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should add multiple handlers for the same event" do
|
121
|
+
@subject.on_input :a, @handler1
|
122
|
+
@subject.on_input :a, @handler2
|
123
|
+
@subject.input.should == { :a => [@handler1, @handler2] }
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should automatically handle to a method if only the input is given" do
|
127
|
+
@subject.stub!(:a)
|
128
|
+
@subject.on_input :a
|
129
|
+
@subject.input.should == { :a => [ @subject.method(:a) ] }
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should add multiple handlers for the same event, even if given different key names" do
|
133
|
+
@subject.on_input :left, @handler1
|
134
|
+
@subject.on_input :left_arrow, @handler2
|
135
|
+
@subject.input.should == { :left_arrow => [@handler1, @handler2] }
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should add a handler that is given as a Chingu::GameState class" do
|
139
|
+
@subject.on_input :a, GameStates::Pause
|
140
|
+
@subject.input.should == { :a => [GameStates::Pause] }
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should add a handler that is given as a Chingu::GameState instance" do
|
144
|
+
state = GameState.new
|
145
|
+
@subject.on_input :a, state
|
146
|
+
@subject.input.should == { :a => [state] }
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should raise an error if given an unknown key" do
|
150
|
+
lambda { @subject.on_input :aardvark, @handler1 }.should raise_error ArgumentError
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should raise an error if given an incorrect action" do
|
154
|
+
lambda { @subject.on_input :a, 47 }.should raise_error ArgumentError
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should add a new handler if one already exists for that input" do
|
158
|
+
@subject.on_input :a, @handler1
|
159
|
+
@subject.on_input :b, @handler2
|
160
|
+
@subject.input.should == { :a => [@handler1], :b => [@handler2] }
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should consider all key synonyms the same" do
|
164
|
+
@subject.on_input :left, @handler1
|
165
|
+
@subject.on_input :left_arrow, @handler2
|
166
|
+
@subject.input.should == { :left_arrow => [@handler1, @handler2] }
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should split up and standardise key arrays" do
|
170
|
+
@subject.on_input([:space, :left], @handler1)
|
171
|
+
@subject.input.should == { :" " => [@handler1], :left_arrow => [@handler1] }
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should raise an error if both an action and a hander are given" do
|
175
|
+
block = lambda { p "hello world" }
|
176
|
+
lambda { @subject.on_input :a, "Hello", &block }.should raise_error ArgumentError
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Chingu::Helpers::InputDispatcher do
|
4
|
+
before :each do
|
5
|
+
@subject = Object.new.extend described_class
|
6
|
+
@client = Object.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should respond to methods" do
|
10
|
+
@subject.should respond_to :input_clients
|
11
|
+
@subject.should respond_to :add_input_client
|
12
|
+
@subject.should respond_to :remove_input_client
|
13
|
+
end
|
14
|
+
|
15
|
+
{"button_down" => :a, "button_up" => :released_a}.each_pair do |event, key|
|
16
|
+
describe "#dispatch_#{event}" do
|
17
|
+
it "should dispatch key event if key is handled" do
|
18
|
+
@client.should_receive(:handler).with(no_args)
|
19
|
+
@client.stub!(:input).with(no_args).and_return({ key => [@client.method(:handler)] })
|
20
|
+
@subject.send("dispatch_#{event}", Gosu::KbA, @client)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not dispatch key event if key is not handled" do
|
24
|
+
@client.stub!(:input).with(no_args).and_return({})
|
25
|
+
@subject.send("dispatch_#{event}", Gosu::KbA, @client)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#dispatch_input_for" do
|
31
|
+
before :each do
|
32
|
+
$window = mock Chingu::Window
|
33
|
+
$window.stub!(:button_down?).and_return(false)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should dispatch if a key is being held" do
|
37
|
+
@client.should_receive(:handler).with(no_args)
|
38
|
+
$window.stub!(:button_down?).with(Gosu::KbA).and_return(true)
|
39
|
+
@client.stub!(:input).with(no_args).and_return({:holding_a => [@client.method(:handler)]})
|
40
|
+
@subject.dispatch_input_for(@client)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should do nothing if a key is not held" do
|
44
|
+
@client.stub!(:input).with(no_args).and_return({:holding_a => [lambda { raise "Shouldn't handle input!"}]})
|
45
|
+
@subject.dispatch_input_for(@client)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
describe "#dispatch_actions" do
|
51
|
+
it "should call a method" do
|
52
|
+
@client.should_receive(:handler).with(no_args)
|
53
|
+
@subject.send(:dispatch_actions, [@client.method(:handler)])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should call a proc" do
|
57
|
+
@client.should_receive(:handler)
|
58
|
+
@subject.send(:dispatch_actions, [lambda { @client.handler }])
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should push a game-state instance" do
|
62
|
+
state = Chingu::GameState.new
|
63
|
+
@subject.should_receive(:push_game_state).with(state)
|
64
|
+
@subject.send(:dispatch_actions, [state])
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should push a game-state class" do
|
68
|
+
@subject.should_receive(:push_game_state).with(Chingu::GameState)
|
69
|
+
@subject.send(:dispatch_actions, [Chingu::GameState])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should call multiple actions if more have been set" do
|
73
|
+
other = Object.new
|
74
|
+
other.should_receive(:handler).with(no_args)
|
75
|
+
@client.should_receive(:handler).with(no_args)
|
76
|
+
@subject.send(:dispatch_actions, [@client.method(:handler), other.method(:handler)])
|
77
|
+
end
|
78
|
+
|
79
|
+
# Note, doesn't check if a passed class is incorrect. Life is too short.
|
80
|
+
it "should raise an error with unexpected data" do
|
81
|
+
lambda { @subject.send(:dispatch_actions, [12]) }.should raise_error ArgumentError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
Binary file
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chingu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 125
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 7
|
9
9
|
- 6
|
10
|
-
-
|
11
|
-
version: 0.7.6.
|
10
|
+
- 9
|
11
|
+
version: 0.7.6.9
|
12
12
|
platform: ruby
|
13
13
|
authors:
|
14
14
|
- ippa
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-08-
|
19
|
+
date: 2010-08-18 00:00:00 +02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
hash: -
|
46
|
+
hash: -138847361
|
47
47
|
segments:
|
48
48
|
- 2
|
49
49
|
- 0
|
@@ -212,6 +212,7 @@ files:
|
|
212
212
|
- lib/chingu/particle.rb
|
213
213
|
- lib/chingu/rect.rb
|
214
214
|
- lib/chingu/require_all.rb
|
215
|
+
- lib/chingu/simple_menu.rb
|
215
216
|
- lib/chingu/text.rb
|
216
217
|
- lib/chingu/traits/animation.rb
|
217
218
|
- lib/chingu/traits/bounding_box.rb
|
@@ -227,7 +228,10 @@ files:
|
|
227
228
|
- lib/chingu/window.rb
|
228
229
|
- spec/chingu/fpscounter_spec.rb
|
229
230
|
- spec/chingu/game_object_spec.rb
|
231
|
+
- spec/chingu/helpers/input_client_spec.rb
|
232
|
+
- spec/chingu/helpers/input_dispatcher_spec.rb
|
230
233
|
- spec/chingu/helpers/options_setter_spec.rb
|
234
|
+
- spec/chingu/images/rect_20x20.png
|
231
235
|
- spec/chingu/inflector_spec.rb
|
232
236
|
- spec/chingu/rect_20x20.png
|
233
237
|
- spec/chingu/text_spec.rb
|
@@ -270,6 +274,8 @@ summary: OpenGL accelerated 2D game framework for Ruby
|
|
270
274
|
test_files:
|
271
275
|
- spec/chingu/fpscounter_spec.rb
|
272
276
|
- spec/chingu/game_object_spec.rb
|
277
|
+
- spec/chingu/helpers/input_client_spec.rb
|
278
|
+
- spec/chingu/helpers/input_dispatcher_spec.rb
|
273
279
|
- spec/chingu/helpers/options_setter_spec.rb
|
274
280
|
- spec/chingu/inflector_spec.rb
|
275
281
|
- spec/chingu/text_spec.rb
|