chingu 0.9rc3 → 0.9rc4

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