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