chingu 0.9rc3 → 0.9rc4

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.
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{chingu}
8
- s.version = "0.9rc3"
8
+ s.version = "0.9rc4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["ippa"]
12
- s.date = %q{2011-02-26}
12
+ s.date = %q{2011-03-07}
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 = [
@@ -151,6 +151,7 @@ Gem::Specification.new do |s|
151
151
  "lib/chingu/game_states/popup.rb",
152
152
  "lib/chingu/gosu_ext/image.rb",
153
153
  "lib/chingu/helpers/class_inheritable_accessor.rb",
154
+ "lib/chingu/helpers/fps_counter.rb",
154
155
  "lib/chingu/helpers/game_object.rb",
155
156
  "lib/chingu/helpers/game_state.rb",
156
157
  "lib/chingu/helpers/gfx.rb",
@@ -19,6 +19,7 @@ class Game < Chingu::Window
19
19
  push_game_state(FillGradient)
20
20
  push_game_state(FillGradientRect)
21
21
  push_game_state(FillGradientMultipleColors)
22
+ push_game_state(DrawCircle)
22
23
  push_game_state(Particles)
23
24
  end
24
25
 
@@ -27,6 +28,20 @@ class Game < Chingu::Window
27
28
  end
28
29
  end
29
30
 
31
+ class DrawCircle < Chingu::GameState
32
+ def draw
33
+ $window.caption = "circles and arcs (space to continue)"
34
+ draw_circle(0, 0, 300, Color::RED)
35
+ fill_circle($window.width, $window.height, 200, Color::RED)
36
+
37
+ colors = [Color::RED, Color::YELLOW, Color::GREEN, Color::BLUE]
38
+ 0.step(360, 90).each_cons(2).zip(colors).each do |(a1, a2), color|
39
+ fill_arc(400, 100, 100, a1, a2, color)
40
+ end
41
+
42
+ end
43
+ end
44
+
30
45
  class Fill < Chingu::GameState
31
46
  def draw
32
47
  $window.caption = "fill (space to continue)"
@@ -38,7 +38,7 @@ require_all "#{CHINGU_ROOT}/chingu/async_tasks"
38
38
  require_all "#{CHINGU_ROOT}/chingu"
39
39
 
40
40
  module Chingu
41
- VERSION = "0.9rc3"
41
+ VERSION = "0.9rc4"
42
42
 
43
43
  DEBUG_COLOR = Gosu::Color.new(0xFFFF0000)
44
44
  DEBUG_ZORDER = 9999
@@ -19,7 +19,7 @@
19
19
  #
20
20
  #++
21
21
 
22
- require_rel 'helpers/*'
22
+ require_rel 'helpers'
23
23
  module Chingu
24
24
  #
25
25
  # GameObject inherits from BasicGameObject to get traits and some class-methods like .all and .destroy
@@ -25,6 +25,7 @@ module Chingu
25
25
  #
26
26
  #
27
27
  class Console
28
+ include Chingu::Helpers::FPSCounter # Adds FPSCounter delegators
28
29
  include Chingu::Helpers::GameState # Easy access to the global game state-queue
29
30
  include Chingu::Helpers::GameObject # Adds game_objects_of_class etc ...
30
31
 
@@ -73,28 +74,6 @@ module Chingu
73
74
  game_state_manager.inside_state || game_state_manager.current_game_state || self
74
75
  end
75
76
 
76
- #
77
- # Frames per second, access with $window.fps or $window.framerate
78
- #
79
- def fps
80
- @fps_counter.fps
81
- end
82
- alias :framerate :fps
83
-
84
- #
85
- # Total amount of game iterations (ticks)
86
- #
87
- def ticks
88
- @fps_counter.ticks
89
- end
90
-
91
- #
92
- # Mathematical short name for "milliseconds since last tick"
93
- #
94
- def dt
95
- @milliseconds_since_last_tick
96
- end
97
-
98
77
  #
99
78
  # Chingus core-logic / loop. Gosu will call this each game-iteration.
100
79
  #
@@ -26,7 +26,19 @@ module Chingu
26
26
  # register_tick() must be called every game loop iteration
27
27
  #
28
28
  class FPSCounter
29
- attr_reader :fps, :milliseconds_since_last_tick, :ticks
29
+ attr_reader :milliseconds_since_last_tick
30
+ alias :dt :milliseconds_since_last_tick
31
+
32
+ #
33
+ # Frames per second, access with $window.fps or $window.framerate
34
+ #
35
+ attr_reader :fps
36
+ alias :framerate :fps
37
+
38
+ #
39
+ # Total amount of game iterations (ticks)
40
+ #
41
+ attr_reader :ticks
30
42
 
31
43
  def initialize
32
44
  @current_second = Gosu::milliseconds / 1000
@@ -19,7 +19,7 @@
19
19
  #
20
20
  #++
21
21
 
22
- require_rel 'helpers/*'
22
+ require_rel 'helpers'
23
23
  module Chingu
24
24
  #
25
25
  # GameObject inherits from BasicGameObject to get traits and some class-methods like .all and .destroy
@@ -19,6 +19,7 @@
19
19
  #
20
20
  #++
21
21
 
22
+ require 'forwardable'
22
23
 
23
24
  module Chingu
24
25
  #
@@ -26,18 +27,21 @@ module Chingu
26
27
  # An instance of GameObjectList is automaticly created as "game_objects" if using Chingu::Window
27
28
  #
28
29
  class GameObjectList
30
+ extend Forwardable
31
+
29
32
  attr_reader :visible_game_objects, :unpaused_game_objects
30
33
 
31
34
  def initialize(options = {})
32
35
  @game_objects = options[:game_objects] || []
33
36
  @visible_game_objects = []
34
37
  @unpaused_game_objects = []
35
-
36
- #@game_objects = {}
37
- #@visible_game_objects = {}
38
- #@unpaused_game_objects = {}
39
38
  end
40
39
 
40
+ def_delegator :@game_objects, :size
41
+ def_delegator :@game_objects, :empty?
42
+ def_delegator :@game_objects, :first
43
+ def_delegator :@game_objects, :last
44
+
41
45
  def to_s
42
46
  "#{@game_objects.size} game objects."
43
47
  end
@@ -69,10 +73,6 @@ module Chingu
69
73
  @game_objects.push(object)
70
74
  @visible_game_objects.push(object) if object.respond_to?(:visible) && object.visible
71
75
  @unpaused_game_objects.push(object) if object.respond_to?(:paused) && !object.paused
72
-
73
- #@game_objects[object] = true
74
- #@visible_game_objects[object] = true if object.respond_to?(:visible) && object.visible
75
- #@unpaused_game_objects[object] = true if object.respond_to?(:paused) && !object.paused
76
76
  end
77
77
 
78
78
  def remove_game_object(object)
@@ -85,14 +85,6 @@ module Chingu
85
85
  @game_objects.select { |object| object.destroy if yield(object) }
86
86
  end
87
87
 
88
- def size
89
- @game_objects.size
90
- end
91
-
92
- def empty?
93
- @game_objects.empty?
94
- end
95
-
96
88
  def update
97
89
  @unpaused_game_objects.each { |go| go.update_trait; go.update; }
98
90
  end
@@ -131,14 +123,6 @@ module Chingu
131
123
  @game_objects.map { |object| yield object }
132
124
  end
133
125
 
134
- def first
135
- @game_objects.first
136
- end
137
-
138
- def last
139
- @game_objects.last
140
- end
141
-
142
126
  #
143
127
  # Disable automatic calling of update() and update_trait() each game loop for all game objects
144
128
  #
@@ -236,8 +236,15 @@ module Chingu
236
236
  # Pops through all game states until matching a given game state
237
237
  #
238
238
  def pop_until_game_state(new_state)
239
- while (state = @game_states.pop)
240
- break if state == new_state
239
+ if new_state.is_a? Class
240
+ raise ArgumentError, "No state of given class is on the stack" unless @game_states.map {|s| s.class }.include? new_state
241
+
242
+ @game_states.pop until current_game_state.is_a? new_state
243
+
244
+ else
245
+ raise ArgumentError, "State is not on the stack" unless @game_states.include? new_state
246
+
247
+ @game_states.pop while current_game_state != new_state
241
248
  end
242
249
  end
243
250
 
@@ -71,18 +71,20 @@ module Chingu
71
71
  #
72
72
  class NetworkClient < Chingu::GameState
73
73
  attr_reader :latency, :socket, :packet_counter, :packet_buffer, :ip, :port
74
+ alias_method :address, :ip
74
75
 
75
76
  def initialize(options = {})
76
77
  super
77
78
  @timeout = options[:timeout] || 4
78
79
  @debug = options[:debug]
79
80
  @ip = options[:ip] || "0.0.0.0"
80
- @port = options[:port] || 7778
81
+ @port = options[:port] || NetworkServer::DEFAULT_PORT
82
+ @max_read_per_update = options[:max_read_per_update] || 50000
81
83
 
82
84
  @socket = nil
83
85
  @latency = 0
84
86
  @packet_counter = 0
85
- @packet_buffer = ""
87
+ @packet_buffer = NetworkServer::PacketBuffer.new
86
88
  end
87
89
 
88
90
  #
@@ -110,7 +112,6 @@ module Chingu
110
112
  begin
111
113
  status = Timeout::timeout(@timeout) do
112
114
  @socket = TCPSocket.new(@ip, @port)
113
- @socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
114
115
  on_connect
115
116
  end
116
117
  rescue Errno::ECONNREFUSED
@@ -154,14 +155,14 @@ module Chingu
154
155
  # Call this from your update() to read from socket.
155
156
  # handle_incoming_data will call on_data(raw_data) when stuff comes on on the socket.
156
157
  #
157
- def handle_incoming_data(amount = 1000)
158
+ def handle_incoming_data(amount = @max_read_per_update)
158
159
  return unless @socket
159
160
 
160
161
  if IO.select([@socket], nil, nil, 0.0)
161
162
  begin
162
163
  packet, sender = @socket.recvfrom(amount)
163
164
  on_data(packet)
164
- rescue Errno::ECONNABORTED
165
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET
165
166
  on_disconnect
166
167
  end
167
168
  end
@@ -171,17 +172,10 @@ module Chingu
171
172
  # on_data(data) will be called from handle_incoming_data() by default.
172
173
  #
173
174
  def on_data(data)
174
- begin
175
- msgs = data.split("--- ")
176
- if msgs.size > 1
177
- @packet_buffer << msgs[0...-1].join("--- ")
178
- YAML::load_documents(@packet_buffer) { |msg| on_msg(msg) if msg }
179
- @packet_buffer = msgs.last
180
- else
181
- @packet_buffer << msgs.join
182
- end
183
- rescue ArgumentError
184
- puts "Bad YAML recieved:\n#{data}"
175
+ @packet_buffer.buffer_data data
176
+
177
+ while packet = @packet_buffer.next_packet
178
+ on_msg(Marshal.load(packet))
185
179
  end
186
180
  end
187
181
 
@@ -190,16 +184,23 @@ module Chingu
190
184
  # Can be whatever ruby-structure that responds to #to_yaml
191
185
  #
192
186
  def send_msg(msg)
193
- # the "---" part is a little hack to make server understand the YAML is fully transmitted.
194
- data = msg.to_yaml + "--- \n"
195
- send_data(data)
187
+ send_data(Marshal.dump(msg))
196
188
  end
197
189
 
198
190
  #
199
191
  # Send whatever raw data to the server
200
192
  #
201
193
  def send_data(data)
202
- @socket.write(data)
194
+ begin
195
+ @socket.write([data.length].pack(NetworkServer::PACKET_HEADER_FORMAT))
196
+ @socket.write(data)
197
+ rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
198
+ on_disconnect
199
+ end
200
+ end
201
+
202
+ # Ensure that the buffer is cleared of data to write (call at the end of update or, at least after all sends).
203
+ def flush
203
204
  @socket.flush
204
205
  end
205
206
 
@@ -69,20 +69,62 @@ module Chingu
69
69
  # A good idea is to have a socket-ivar in your Player-model and a Player.find_by_socket(socket)
70
70
  #
71
71
  class NetworkServer < Chingu::GameState
72
- attr_reader :socket, :sockets, :packet_counter, :packet_counter, :ip, :port
72
+ PACKET_HEADER_LENGTH = 4
73
+ PACKET_HEADER_FORMAT = "N"
74
+ DEFAULT_PORT = 7778
75
+
76
+ class PacketBuffer
77
+ def initialize
78
+ @data = '' # Buffered data.
79
+ @length = nil # Length of the next packet. nil if header not read yet.
80
+ end
81
+
82
+ # Add data string to the buffer.
83
+ def buffer_data(data)
84
+ @data << data
85
+ end
86
+
87
+ # Call after adding data with #buffer_data until there are no more packets left.
88
+ def next_packet
89
+ # Read the header to find out the length of the next packet.
90
+ unless @length
91
+ if @data.length >= PACKET_HEADER_LENGTH
92
+ @length = @data[0...PACKET_HEADER_LENGTH].unpack(PACKET_HEADER_FORMAT)[0]
93
+ @data[0...PACKET_HEADER_LENGTH] = ''
94
+ end
95
+ end
96
+
97
+ # If there is enough data after the header for the full packet, return it.
98
+ if @length and @length <= @data.length
99
+ begin
100
+ packet = @data[0...@length]
101
+ @data[0...@length] = ''
102
+ @length = nil
103
+ return packet
104
+ rescue TypeError => ex
105
+ puts "Bad data received:\n#{@data.inspect}"
106
+ raise ex
107
+ end
108
+ else
109
+ return nil
110
+ end
111
+ end
112
+ end
113
+
114
+ attr_reader :socket, :sockets, :ip, :port
115
+
116
+ alias_method :address, :ip
73
117
 
74
118
  def initialize(options = {})
75
119
  super
76
120
 
77
121
  @ip = options[:ip] || "0.0.0.0"
78
- @port = options[:port] || 7778
122
+ @port = options[:port] || DEFAULT_PORT
79
123
  @debug = options[:debug]
80
124
  @socket = nil
81
125
  @sockets = []
82
- @buffered_output = YAML::Stream.new
83
126
  @max_read_per_update = options[:max_read_per_update] || 20000
84
-
85
- @packet_counter = 0
127
+
86
128
  @packet_buffers = Hash.new
87
129
  end
88
130
 
@@ -94,7 +136,6 @@ module Chingu
94
136
  @port = port if port
95
137
  begin
96
138
  @socket = TCPServer.new(@ip, @port)
97
- @socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
98
139
  on_start
99
140
  rescue
100
141
  on_start_error($!)
@@ -134,11 +175,9 @@ module Chingu
134
175
  if @socket && !@socket.closed?
135
176
  handle_incoming_connections
136
177
  handle_incoming_data
137
- super
138
- handle_outgoing_data
139
- else
140
- super
141
178
  end
179
+
180
+ super
142
181
  end
143
182
 
144
183
  #
@@ -157,10 +196,11 @@ module Chingu
157
196
 
158
197
  def handle_incoming_connections
159
198
  begin
160
- socket = @socket.accept_nonblock
161
- @sockets << socket
162
- on_connect(socket)
163
- @packet_buffers[socket] = ""
199
+ while socket = @socket.accept_nonblock
200
+ @sockets << socket
201
+ @packet_buffers[socket] = PacketBuffer.new
202
+ on_connect(socket)
203
+ end
164
204
  rescue IO::WaitReadable, Errno::EINTR
165
205
  end
166
206
  end
@@ -177,6 +217,7 @@ module Chingu
177
217
  on_data(socket, packet)
178
218
  rescue Errno::ECONNABORTED, Errno::ECONNRESET
179
219
  @packet_buffers[socket] = nil
220
+
180
221
  on_disconnect(socket)
181
222
  end
182
223
  end
@@ -187,28 +228,12 @@ module Chingu
187
228
  # on_data(data) will be called from handle_incoming_data() by default.
188
229
  #
189
230
  def on_data(socket, data)
190
- begin
191
- msgs = data.split("--- ")
192
- if msgs.size > 1
193
- @packet_buffers[socket] << msgs[0...-1].join("--- ")
194
- YAML::load_documents(@packet_buffers[socket]) { |msg| on_msg(socket, msg) if msg}
195
- @packet_buffers[socket] = msgs.last
196
- else
197
- @packet_buffers[socket] << msgs.join
198
- end
199
- end
200
- end
201
-
202
- #
203
- # Send all buffered outgoing data
204
- #
205
- def handle_outgoing_data
206
- # the "---" part is a little hack to make server understand the YAML is fully transmitted.
207
-
208
- data = @buffered_output.emit
209
- if data.size > 0
210
- @sockets.each { |socket| send_data(socket, data + "--- \n") }
211
- @buffered_output = YAML::Stream.new
231
+ buffer = @packet_buffers[socket]
232
+
233
+ buffer.buffer_data data
234
+
235
+ while packet = buffer.next_packet
236
+ on_msg(socket, Marshal.load(packet))
212
237
  end
213
238
  end
214
239
 
@@ -217,7 +242,8 @@ module Chingu
217
242
  # Output is buffered and dispatched once each server-loop
218
243
  #
219
244
  def broadcast_msg(msg)
220
- @buffered_output.add(msg)
245
+ data = Marshal.dump(msg)
246
+ @sockets.each {|s| send_data(s, data) }
221
247
  end
222
248
 
223
249
  #
@@ -225,8 +251,7 @@ module Chingu
225
251
  # 'msg' must responds to #to_yaml
226
252
  #
227
253
  def send_msg(socket, msg)
228
- # the "---" part is a little hack to make server understand the YAML is fully transmitted.
229
- send_data(socket, msg.to_yaml + "--- \n")
254
+ send_data(socket, Marshal.dump(msg))
230
255
  end
231
256
 
232
257
  #
@@ -234,8 +259,8 @@ module Chingu
234
259
  #
235
260
  def send_data(socket, data)
236
261
  begin
262
+ socket.write([data.length].pack(PACKET_HEADER_FORMAT))
237
263
  socket.write(data)
238
- socket.flush
239
264
  rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
240
265
  on_disconnect(socket)
241
266
  end
@@ -247,6 +272,11 @@ module Chingu
247
272
  def disconnect_client(socket)
248
273
  socket.close
249
274
  end
275
+
276
+ # Ensure that the buffer is cleared of data to write (call at the end of update or, at least after all sends).
277
+ def flush
278
+ @sockets.each {|s| s.flush }
279
+ end
250
280
 
251
281
  #
252
282
  # Stops server
@@ -0,0 +1,39 @@
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
+ require 'forwardable'
23
+
24
+ module Chingu
25
+ module Helpers
26
+
27
+ #
28
+ # Convenience-methods for classes that have an FPS counter
29
+ # Mixed into Chingu::Window and Chingu::Console
30
+ #
31
+ module FPSCounter
32
+ extend Forwardable
33
+ def_delegator :@fps_counter, :ticks
34
+ def_delegators :@fps_counter, :fps, :framerate # TODO: switch to Gosu::fps
35
+ def_delegators :@fps_counter, :dt, :milliseconds_since_last_tick
36
+ end
37
+
38
+ end
39
+ end
@@ -1,135 +1,121 @@
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
- module Helpers
24
-
25
- #
26
- # Convenience-methods for classes that hold game objects
27
- # Mixed into Chingu::Window and Chingu::GameState
28
- #
29
- module GameObject
30
-
31
- def add_game_object(object)
32
- @game_objects.add_game_object(object)
33
- end
34
-
35
- def remove_game_object(object)
36
- @game_objects.remove_game_object(object)
37
- end
38
-
39
- def game_objects
40
- @game_objects
41
- end
42
-
43
- def show_game_object(object)
44
- @game_objects.show_game_object(object)
45
- end
46
- def hide_game_object(object)
47
- @game_objects.hide_game_object(object)
48
- end
49
- def pause_game_object(object)
50
- @game_objects.pause_game_object(object)
51
- end
52
- def unpause_game_object(object)
53
- @game_objects.unpause_game_object(object)
54
- end
55
-
56
-
57
- #
58
- # Fetch game objects of a certain type/class
59
- #
60
- def game_objects_of_class(klass)
61
- @game_objects.select { |game_object| game_object.is_a? klass }
62
- end
63
-
64
- #
65
- # Creates game objects from a Chingu-spezed game objects file (created with game state 'Edit')
66
- #
67
- def load_game_objects(options = {})
68
- file = options[:file] || self.filename + ".yml"
69
- debug = options[:debug]
70
- except = Array(options[:except]) || []
71
-
72
- require 'yaml'
73
-
74
- puts "* Loading game objects from #{file}" if debug
75
- if File.exists?(file)
76
- objects = YAML.load_file(file)
77
- objects.each do |object|
78
- object.each_pair do |klassname, attributes|
79
- begin
80
- klass = Kernel::const_get(klassname)
81
- unless klass.class == "GameObject" && !except.include?(klass)
82
- puts "Creating #{klassname.to_s}: #{attributes.to_s}" if debug
83
- object = klass.create(attributes)
84
- object.options[:created_with_editor] = true if object.options
85
- end
86
- rescue
87
- puts "Couldn't create class '#{klassname}'"
88
- end
89
- end
90
- end
91
- end
92
- end
93
-
94
- #
95
- # Save given game_objects to a file. Hashoptions
96
- #
97
- # :file - a String, name of file to write to, default is current game_state class name.
98
- # :game_objects - an Array, game objects to save
99
- # :classes - an Array, save only game objects of theese classes
100
- #
101
- # NOTE: To save a color do: :color => game_object.color.argb
102
- #
103
- def save_game_objects(options = {})
104
- #
105
- # TODO: Move this to GameObjectList#save ?
106
- #
107
- file = options[:file] || "#{self.class.to_s.downcase}.yml"
108
- game_objects = options[:game_objects]
109
- classes = options[:classes]
110
- attributes = options[:attributes] || [:x, :y, :angle, :zorder, :factor_x, :factor_y, :alpha]
111
-
112
- require 'yaml'
113
- objects = []
114
- game_objects.each do |game_object|
115
- # Only save specified classes, if given.
116
- next if classes and !classes.empty? and !classes.include?(game_object.class)
117
-
118
- attr_hash = {}
119
- attributes.each do |attr|
120
- begin
121
- attr_hash[attr] = game_object.send(attr)
122
- rescue NoMethodError
123
- # silently ignore attributes that doesn't exist on the particular game object
124
- end
125
- end
126
- objects << {game_object.class.to_s => attr_hash}
127
- end
128
-
129
- File.open(file, 'w') { |out| YAML.dump(objects, out) }
130
- end
131
-
132
- end
133
-
134
- end
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
+ require 'forwardable'
23
+
24
+ module Chingu
25
+ module Helpers
26
+
27
+ #
28
+ # Convenience-methods for classes that hold game objects
29
+ # Mixed into Chingu::Window and Chingu::GameState
30
+ #
31
+ module GameObject
32
+ extend Forwardable
33
+
34
+ attr_reader :game_objects
35
+
36
+ def_delegator :@game_objects, :add_game_object
37
+ def_delegator :@game_objects, :remove_game_object
38
+ def_delegator :@game_objects, :show_game_object
39
+ def_delegator :@game_objects, :hide_game_object
40
+ def_delegator :@game_objects, :pause_game_object
41
+ def_delegator :@game_objects, :unpause_game_object
42
+
43
+ #
44
+ # Fetch game objects of a certain type/class
45
+ #
46
+ def game_objects_of_class(klass)
47
+ @game_objects.select { |game_object| game_object.is_a? klass }
48
+ end
49
+
50
+ #
51
+ # Creates game objects from a Chingu-spezed game objects file (created with game state 'Edit')
52
+ #
53
+ def load_game_objects(options = {})
54
+ file = options[:file] || self.filename + ".yml"
55
+ debug = options[:debug]
56
+ except = Array(options[:except]) || []
57
+
58
+ require 'yaml'
59
+
60
+ puts "* Loading game objects from #{file}" if debug
61
+ if File.exists?(file)
62
+ objects = YAML.load_file(file)
63
+ objects.each do |object|
64
+ object.each_pair do |klassname, attributes|
65
+ begin
66
+ klass = Kernel::const_get(klassname)
67
+ unless klass.class == "GameObject" && !except.include?(klass)
68
+ puts "Creating #{klassname.to_s}: #{attributes.to_s}" if debug
69
+ object = klass.create(attributes)
70
+ object.options[:created_with_editor] = true if object.options
71
+ end
72
+ rescue
73
+ puts "Couldn't create class '#{klassname}'"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ #
81
+ # Save given game_objects to a file. Hashoptions
82
+ #
83
+ # :file - a String, name of file to write to, default is current game_state class name.
84
+ # :game_objects - an Array, game objects to save
85
+ # :classes - an Array, save only game objects of theese classes
86
+ #
87
+ # NOTE: To save a color do: :color => game_object.color.argb
88
+ #
89
+ def save_game_objects(options = {})
90
+ #
91
+ # TODO: Move this to GameObjectList#save ?
92
+ #
93
+ file = options[:file] || "#{self.class.to_s.downcase}.yml"
94
+ game_objects = options[:game_objects]
95
+ classes = options[:classes]
96
+ attributes = options[:attributes] || [:x, :y, :angle, :zorder, :factor_x, :factor_y, :alpha]
97
+
98
+ require 'yaml'
99
+ objects = []
100
+ game_objects.each do |game_object|
101
+ # Only save specified classes, if given.
102
+ next if classes and !classes.empty? and !classes.include?(game_object.class)
103
+
104
+ attr_hash = {}
105
+ attributes.each do |attr|
106
+ begin
107
+ attr_hash[attr] = game_object.send(attr)
108
+ rescue NoMethodError
109
+ # silently ignore attributes that doesn't exist on the particular game object
110
+ end
111
+ end
112
+ objects << {game_object.class.to_s => attr_hash}
113
+ end
114
+
115
+ File.open(file, 'w') { |out| YAML.dump(objects, out) }
116
+ end
117
+
118
+ end
119
+
120
+ end
135
121
  end
@@ -19,6 +19,7 @@
19
19
  #
20
20
  #++
21
21
 
22
+ require 'forwardable'
22
23
 
23
24
  module Chingu
24
25
  module Helpers
@@ -29,33 +30,14 @@ module Chingu
29
30
  # It will make call new() on a class, and just push an object.
30
31
  #
31
32
  module GameState
32
- def game_states
33
- game_state_manager.game_states
34
- end
35
-
36
- def push_game_state(state, options = {})
37
- game_state_manager.push_game_state(state, options)
38
- end
39
-
40
- def pop_game_state(options = {})
41
- game_state_manager.pop_game_state(options)
42
- end
43
-
44
- def switch_game_state(state, options = {})
45
- game_state_manager.switch_game_state(state, options)
46
- end
47
-
48
- def transitional_game_state(state, options = {})
49
- game_state_manager.transitional_game_state(state, options)
50
- end
51
-
52
- def current_game_state
53
- game_state_manager.current_game_state
54
- end
55
-
56
- def clear_game_states
57
- game_state_manager.clear_game_states
58
- end
33
+ extend Forwardable
34
+ def_delegator :game_state_manager, :game_states
35
+ def_delegator :game_state_manager, :push_game_state
36
+ def_delegator :game_state_manager, :pop_game_state
37
+ def_delegator :game_state_manager, :switch_game_state
38
+ def_delegator :game_state_manager, :transitional_game_state
39
+ def_delegator :game_state_manager, :current_game_state
40
+ def_delegator :game_state_manager, :clear_game_states
59
41
  end
60
42
 
61
43
  end
@@ -27,7 +27,7 @@ module Chingu
27
27
  # All drawings depend on the global variable $window which should be an instance of Gosu::Window or Chingu::Window
28
28
  #
29
29
  module GFX
30
-
30
+
31
31
  #
32
32
  # Fills whole window with specified 'color' and 'zorder'
33
33
  #
@@ -48,52 +48,33 @@ module Chingu
48
48
  # :orientation - Either :vertical (top to bottom) or :horizontal (left to right)
49
49
  #
50
50
 
51
- def fill(options, zorder = 0)
51
+ def fill(material, zorder = 0, mode = :default)
52
52
  #
53
53
  # if only 1 color-argument is given, assume fullscreen simple color fill.
54
54
  #
55
- if options.is_a?(Gosu::Color)
56
- $window.draw_quad(0, 0, options,
57
- $window.width, 0, options,
58
- $window.width, $window.height, options,
59
- 0, $window.height, options, zorder, :default)
55
+ if material.is_a?(Gosu::Color)
56
+ rect = Rect.new([0, 0, $window.width, $window.height])
57
+ _fill_rect(rect, material, material, material, material, zorder, mode)
60
58
  else
61
- fill_gradient(options)
59
+ fill_gradient(material)
62
60
  end
63
61
  end
64
62
 
65
63
  #
66
64
  # Draws an unfilled rect in given color
67
65
  #
68
- def draw_rect(rect, color, zorder)
69
- $window.draw_line(rect.x, rect.y, color, rect.right, rect.y, color, zorder)
70
- $window.draw_line(rect.right, rect.y, color, rect.right, rect.bottom, color, zorder)
71
- $window.draw_line(rect.right, rect.bottom, color, rect.x, rect.bottom, color, zorder)
72
- $window.draw_line(rect.x, rect.bottom, color, rect.x, rect.y, color, zorder)
66
+ def draw_rect(rect, color, zorder = 0, mode = :default)
67
+ rect = Rect.new(rect) unless rect.is_a? Rect
68
+ _stroke_rect(rect, color, color, color, color, zorder, mode)
73
69
  end
74
70
 
75
71
 
76
- #
77
- # Draws an unfilled circle, thanks shawn24!
78
- #
79
- CIRCLE_STEP = 10
80
- def draw_circle(cx,cy,r,color)
81
- 0.step(360, CIRCLE_STEP) do |a1|
82
- a2 = a1 + CIRCLE_STEP
83
- $window.draw_line cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r), cy + Gosu.offset_y(a2, r), color, 9999
84
- end
85
- end
86
-
87
72
  #
88
73
  # Fills a given Rect 'rect' with Color 'color', drawing with zorder 'zorder'
89
74
  #
90
- def fill_rect(rect, color, zorder = 0)
91
- rect = Rect.new(rect) # Make sure it's a rect
92
- $window.draw_quad( rect.x, rect.y, color,
93
- rect.right, rect.y, color,
94
- rect.right, rect.bottom, color,
95
- rect.x, rect.bottom, color,
96
- zorder, :default)
75
+ def fill_rect(rect, color, zorder = 0, mode = :default)
76
+ rect = Rect.new(rect) unless rect.is_a? Rect
77
+ _fill_rect(rect, color, color, color, color, zorder, mode)
97
78
  end
98
79
 
99
80
  #
@@ -105,46 +86,147 @@ module Chingu
105
86
  # :orientation - Either :vertical (top to bottom) or :horizontal (left to right)
106
87
  #
107
88
  def fill_gradient(options)
108
- default_options = { :from => Gosu::Color::BLACK,
109
- :to => Gosu::Color::WHITE,
110
- :thickness => 10,
111
- :orientation => :vertical,
112
- :rect => Rect.new([0, 0, $window.width, $window.height]),
113
- :zorder => 0,
114
- :mode => :default
115
- }
116
- options = default_options.merge(options)
89
+ options = { :from => Gosu::Color::BLACK,
90
+ :to => Gosu::Color::WHITE,
91
+ :orientation => :vertical,
92
+ :rect => [0, 0, $window.width, $window.height],
93
+ :zorder => 0,
94
+ :mode => :default
95
+ }.merge!(options)
117
96
 
118
97
  rect = Rect.new(options[:rect])
119
98
  colors = options[:colors] || options.values_at(:from, :to)
99
+ zorder = options[:zorder]
100
+ mode = options[:mode]
120
101
 
121
102
  case options[:orientation]
122
103
  when :vertical
123
104
  rect.height /= colors.count - 1
124
105
  colors.each_cons(2) do |from, to|
125
- $window.draw_quad( rect.left, rect.top, from,
126
- rect.right, rect.top, from,
127
- rect.right, rect.bottom, to,
128
- rect.left, rect.bottom, to,
129
- options[:zorder], options[:mode]
130
- )
106
+ _fill_rect(rect, from, to, to, from, zorder, mode)
131
107
  rect.top += rect.height
132
108
  end
133
109
  when :horizontal
134
110
  rect.width /= colors.count - 1
135
111
  colors.each_cons(2) do |from, to|
136
- $window.draw_quad( rect.left, rect.top, from,
137
- rect.left, rect.bottom, from,
138
- rect.right, rect.bottom, to,
139
- rect.right, rect.top, to,
140
- options[:zorder], options[:mode]
141
- )
112
+ _fill_rect(rect, from, from, to, to, zorder, mode)
142
113
  rect.left += rect.width
143
114
  end
144
115
  else
145
116
  raise ArgumentError, "bad gradient orientation: #{options[:orientation]}"
146
117
  end
118
+
119
+ end
120
+
121
+ #
122
+ # Draws an unfilled circle, thanks shawn24!
123
+ #
124
+ def draw_circle(cx, cy, r, color, zorder = 0, mode = :default)
125
+ draw_arc(cx, cy, r, 0, 360, color, zorder, mode)
126
+ end
127
+
128
+ #
129
+ # Draws an unfilled arc from a1 to a2
130
+ #
131
+ def draw_arc(cx, cy, r, from, to, color, zorder = 0, mode = :default)
132
+ from, to = to, from if from > to
133
+ $window.translate(cx, cy) do
134
+ $window.scale(r) do
135
+ detail = _circle_segments(r)
136
+ _walk_arc(from, to, detail) do |x1, y1, x2, y2|
137
+ $window.draw_line(x1, y1, color,
138
+ x2, y2, color,
139
+ zorder, mode)
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ #
146
+ # Draws a filled circle
147
+ #
148
+ def fill_circle(cx, cy, r, color, zorder = 0, mode = :default)
149
+ fill_arc(cx, cy, r, 0, 360, color, zorder, mode)
150
+ end
151
+
152
+ #
153
+ # Draws a filled arc from a1 to a2
154
+ #
155
+ def fill_arc(cx, cy, r, from, to, color, zorder = 0, mode = :default)
156
+ from, to = to, from if from > to
157
+ $window.translate(cx, cy) do
158
+ $window.scale(r) do
159
+ detail = _circle_segments(r)
160
+ _walk_arc(from, to, detail) do |x1, y1, x2, y2|
161
+ $window.draw_triangle(0, 0, color,
162
+ x1, y1, color,
163
+ x2, y2, color,
164
+ zorder, mode)
165
+ end
166
+ end
167
+ end
147
168
  end
169
+
170
+ private
171
+
172
+ def _fill_rect(rect, color_a, color_b, color_c, color_d, zorder, mode)
173
+ left, top = *rect.topleft
174
+ right, bottom = *rect.bottomright
175
+ $window.draw_quad(left, top, color_a,
176
+ left, bottom, color_b,
177
+ right, bottom, color_c,
178
+ right, top, color_d,
179
+ zorder, mode)
180
+ end
181
+
182
+ def _stroke_rect(rect, color_a, color_b, color_c, color_d, zorder, mode)
183
+ left, top = *rect.topleft
184
+ right, bottom = *rect.bottomright
185
+ $window.draw_line(left, top, color_a, left, bottom, color_b, zorder, mode)
186
+ $window.draw_line(left, bottom, color_b, right, bottom, color_c, zorder, mode)
187
+ $window.draw_line(right, bottom, color_c, right, top, color_d, zorder, mode)
188
+ $window.draw_line(right, top, color_d, left, top, color_a, zorder, mode)
189
+ end
190
+
191
+ #
192
+ # Calculates a reasonable number of segments for a circle of the given
193
+ # radius. Forgive the use of a magic number.
194
+ #
195
+ # Adapted from SiegeLord's "An Efficient Way to Draw Approximate Circles
196
+ # in OpenGL" <http://slabode.exofire.net/circle_draw.shtml>.
197
+ #
198
+ def _circle_segments(r)
199
+ 10 * Math.sqrt(r)
200
+ end
201
+
202
+ #
203
+ # Appriximates an arc as a series of line segments and passes each segment
204
+ # to the block. Makes clever use of a transformation matrix to avoid
205
+ # repeated calls to sin and cos.
206
+ #
207
+ # Adapted from SiegeLord's "An Efficient Way to Draw Approximate Circles
208
+ # in OpenGL" <http://slabode.exofire.net/circle_draw.shtml>.
209
+ #
210
+ def _walk_arc(from, to, detail, &block)
211
+ walk_segments = ((to - from).to_f * (detail - 1) / 360).floor
212
+
213
+ theta = 2 * Math::PI / detail
214
+ c = Math.cos(theta)
215
+ s = Math.sin(theta)
216
+
217
+ x1 = Gosu.offset_x(from, 1)
218
+ y1 = Gosu.offset_y(from, 1)
219
+
220
+ 0.upto(walk_segments) do
221
+ x2 = c * x1 - s * y1
222
+ y2 = s * x1 + c * y1
223
+
224
+ block[x1, y1, x2, y2]
225
+
226
+ x1, y1 = x2, y2
227
+ end
228
+ end
229
+
148
230
  end
149
231
 
150
232
  end
@@ -32,6 +32,7 @@ module Chingu
32
32
  # - Tracking of button_up/button_down etc to enable Chingus pretty inputhandling
33
33
  #
34
34
  class Window < Gosu::Window
35
+ include Chingu::Helpers::FPSCounter # Adds FPSCounter delegators
35
36
  include Chingu::Helpers::GFX # Adds fill(), fade() etc to each game state
36
37
  include Chingu::Helpers::GameState # Easy access to the global game state-queue
37
38
  include Chingu::Helpers::GameObject # Adds game_objects_of_class etc ...
@@ -96,28 +97,6 @@ module Chingu
96
97
  game_state_manager.inside_state || game_state_manager.current_game_state || self
97
98
  end
98
99
 
99
- #
100
- # Frames per second, access with $window.fps or $window.framerate
101
- #
102
- def fps
103
- @fps_counter.fps # TODO: switch to Gosu::fps
104
- end
105
- alias :framerate :fps
106
-
107
- #
108
- # Total amount of game iterations (ticks)
109
- #
110
- def ticks
111
- @fps_counter.ticks
112
- end
113
-
114
- #
115
- # Mathematical short name for "milliseconds since last tick"
116
- #
117
- def dt
118
- @milliseconds_since_last_tick
119
- end
120
-
121
100
  #
122
101
  # Chingus core-logic / loop. Gosu will call this each game-iteration.
123
102
  #
@@ -216,4 +195,4 @@ module Chingu
216
195
  $window = nil
217
196
  end
218
197
  end
219
- end
198
+ end
@@ -6,7 +6,8 @@ def data_set
6
6
  "a String" => ["Woof!"],
7
7
  "an Array" => [[1, 2, 3]],
8
8
  "a stream of packets" => [{ :foo => :bar }, "Woof!", [1, 2, 3]],
9
- "huge packet" => [[:frogspawn] * 1000]
9
+ "huge packet" => [[:frogspawn] * 1000],
10
+ "100 small packets" => 100.times.map { rand(100000) },
10
11
  }
11
12
  end
12
13
 
@@ -94,15 +95,13 @@ module Chingu
94
95
  it "should send/recv #{name}" do
95
96
  @server.update # Accept the clients, so know about their existence to broadcast.
96
97
 
97
- # Data should be cached.
98
- data.each {|packet| @server.broadcast_msg(packet) }
99
-
100
98
  data.each do |packet|
101
99
  @client.should_receive(:on_msg).with(packet)
102
100
  @client2.should_receive(:on_msg).with(packet)
103
101
  end
104
102
 
105
- @server.update # Push the cached messages.
103
+ data.each {|packet| @server.broadcast_msg(packet) }
104
+
106
105
  @client.update
107
106
  @client2.update
108
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chingu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9rc3
4
+ version: 0.9rc4
5
5
  prerelease: 3
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-02-26 00:00:00.000000000 +01:00
12
+ date: 2011-03-07 00:00:00.000000000 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: gosu
17
- requirement: &24030588 !ruby/object:Gem::Requirement
17
+ requirement: &25121820 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.7.27.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *24030588
25
+ version_requirements: *25121820
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &24030108 !ruby/object:Gem::Requirement
28
+ requirement: &25121472 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 2.1.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *24030108
36
+ version_requirements: *25121472
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: watchr
39
- requirement: &24029616 !ruby/object:Gem::Requirement
39
+ requirement: &25120920 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *24029616
47
+ version_requirements: *25120920
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: rcov
50
- requirement: &24029220 !ruby/object:Gem::Requirement
50
+ requirement: &25120464 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,7 +55,7 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *24029220
58
+ version_requirements: *25120464
59
59
  description: OpenGL accelerated 2D game framework for Ruby. Builds on Gosu (Ruby/C++)
60
60
  which provides all the core functionality. Chingu adds simple yet powerful game
61
61
  states, prettier input handling, deployment safe asset-handling, a basic re-usable
@@ -201,6 +201,7 @@ files:
201
201
  - lib/chingu/game_states/popup.rb
202
202
  - lib/chingu/gosu_ext/image.rb
203
203
  - lib/chingu/helpers/class_inheritable_accessor.rb
204
+ - lib/chingu/helpers/fps_counter.rb
204
205
  - lib/chingu/helpers/game_object.rb
205
206
  - lib/chingu/helpers/game_state.rb
206
207
  - lib/chingu/helpers/gfx.rb