ippa-chingu 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,41 +1,149 @@
1
1
  module Chingu
2
- module GameStateHelpers
2
+
3
+ module InputClient
4
+ def input=(input_map)
5
+ @input = input_map
6
+ @parent.add_input_client(self) if @parent
7
+ end
8
+
9
+ def input
10
+ @input
11
+ end
12
+ end
13
+
14
+ module InputDispatcher
15
+ def add_input_client(object)
16
+ @input_clients << object
17
+ end
18
+
19
+ def remove_input_client(object)
20
+ @input_clients.delete(object)
21
+ end
22
+
23
+ def dispatch_button_down(id, object)
24
+ return if object.nil? || object.input.nil?
25
+
26
+ object.input.each do |symbol, action|
27
+ if Input::SYMBOL_TO_CONSTANT[symbol] == id
28
+ dispatch_action(action, object)
29
+ end
30
+ end
31
+ end
32
+
33
+ def dispatch_button_up(id, object)
34
+ return if object.nil? || object.input.nil?
35
+
36
+ object.input.each do |symbol, action|
37
+ if symbol.to_s.include? "released_"
38
+ symbol = symbol.to_s.sub("released_", "").to_sym
39
+ if Input::SYMBOL_TO_CONSTANT[symbol] == id
40
+ dispatch_action(action, object)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ #
47
+ # Dispatches input-mapper for any given object
48
+ #
49
+ def dispatch_input_for(object, prefix = "holding_")
50
+ return if object.nil? || object.input.nil?
51
+
52
+ object.input.each do |symbol, action|
53
+ if symbol.to_s.include? prefix
54
+ symbol = symbol.to_s.sub(prefix, "").to_sym
55
+ if $window.button_down?(Input::SYMBOL_TO_CONSTANT[symbol])
56
+ dispatch_action(action, object)
57
+ end
58
+ end
59
+ end
60
+ end
3
61
 
4
62
  #
5
- # push_gamestate accepts either a class inherited from GameState or an object-instance from such a class.
63
+ # For a given object, dispatch "action".
64
+ # An action can be:
6
65
  #
7
- # push_gamestate(Intro) is the same as:
8
- # push_gamestate(Intro.new)
9
- #
10
- # The first line ends upp calling "new" to Intro before activating the newlycreated gamestate.
66
+ # * Symbol (:p, :space), translates into a method-call
67
+ # * Proc/Lambda, call() it
68
+ # * GameState-instance, push it on top of stack
69
+ # * GameState-inherited class, create a new instance, cache it and push it on top of stack
11
70
  #
12
- def push_gamestate(state)
13
- if state.is_a? Chingu::GameState
14
- $window.game_state_manager.push_state(state)
15
- elsif state.superclass == Chingu::GameState
16
- $window.game_state_manager.push_state(state.new)
71
+ def dispatch_action(action, object)
72
+ # puts "Dispatch Action: #{action} - Objects class: #{object.class.to_s}"
73
+ if action.is_a? Symbol
74
+ object.send(action)
75
+ elsif action.is_a? Proc
76
+ action.call
77
+ elsif action.is_a? Chingu::GameState
78
+ push_game_state(action)
79
+ elsif action.superclass == Chingu::GameState
80
+ push_game_state(action)
17
81
  end
18
82
  end
83
+ end
84
+
85
+ #
86
+ # push_game_state accepts either a class inherited from GameState or an object-instance from such a class.
87
+ #
88
+ # push_game_state(Intro):
89
+ # game state mananger will create a new Intro-object first time called and cache it.
90
+ #
91
+ # push_game_state(Intro.new):
92
+ # The first line ends up calling "new" to Intro before activating the newly created game state.
93
+ # Each time 'push_game_state(Intro.new)' is called a new Intro-object will be created.
94
+ # Usefull for stuff like: push_game_state(Level.new(:level_nr => 11))
95
+ #
96
+ module GameStateHelpers
97
+ def push_game_state(state, options = {})
98
+ $window.game_state_manager.push_state(state, options)
99
+ end
19
100
 
20
- def pop_gamestate
21
- $window.game_state_manager.pop_state
101
+ def pop_game_state(options = {})
102
+ $window.game_state_manager.pop_state(options)
22
103
  end
23
104
 
24
- def current_gamestate
25
- $window.game_state_manager.state
105
+ def current_game_state
106
+ $window.game_state_manager.current_state
26
107
  end
27
108
 
28
- def previous_gamestate
109
+ def previous_game_state
29
110
  $window.game_state_manager.previous_state
30
111
  end
112
+
113
+ def clear_game_states
114
+ $window.game_state_manager.clear_states
115
+ end
31
116
  end
32
117
 
118
+ #
119
+ # Various helper-methods to manipulate the screen
120
+ #
33
121
  module DrawHelpers
34
- def fill(color)
35
- $window.draw_quad(0, 0, color, $window.width, 0, color, $window.width, $window.width, color, 0, $window.height, color, 0, :default)
36
- end
122
+ #
123
+ # Fills whole window with color 'c'
124
+ #
125
+ def fill(c)
126
+ $window.draw_quad(0,0,c,$window.width,0,c,$window.width,$window.width,c,0,$window.height,c,0,:default)
127
+ end
128
+
129
+ #
130
+ # Fills a given Rect 'r' with color 'c'
131
+ #
132
+ def fill_rect(r, c)
133
+ $window.draw_quad(r.x,r.y,c, r.right,r.y,c, r.right,r.bottom,c, r.x,r.bottom,c,0,:default)
134
+ end
37
135
 
38
136
  def fade(options = {})
39
137
  end
40
138
  end
139
+
140
+ module GameObjectHelpers
141
+ #
142
+ # Fetch game objects of a certain type/class
143
+ #
144
+ def game_objects_of_class(klass)
145
+ @game_objects.select { |game_object| game_object.is_a? klass }
146
+ end
147
+ end
148
+
41
149
  end
data/lib/chingu/input.rb CHANGED
@@ -19,14 +19,14 @@ module Chingu
19
19
 
20
20
  KbBackspace => [:backspace],
21
21
  KbDelete => [:delete, :del],
22
- KbDown => [:down],
22
+ KbDown => [:down_arrow, :down],
23
23
  KbEnd => [:end],
24
24
  KbEnter => [:enter],
25
25
  KbEscape => [:escape, :esc],
26
26
 
27
27
  KbHome => [:home],
28
28
  KbInsert => [:insert, :ins],
29
- KbLeft => [:left],
29
+ KbLeft => [:left_arrow, :left],
30
30
  KbLeftAlt => [:left_alt, :lalt],
31
31
  KbLeftControl => [:left_control, :left_ctrl, :lctrl],
32
32
  KbLeftShift => [:left_shift, :lshift],
@@ -40,13 +40,13 @@ module Chingu
40
40
  KbPageUp => [:page_up],
41
41
  # KbPause => [:pause],
42
42
  KbReturn => [:return],
43
- KbRight => [:right],
43
+ KbRight => [:right_arrow, :right],
44
44
  KbRightAlt => [:right_alt, :ralt],
45
45
  KbRightControl => [:right_control, :right_ctrl, :rctrl],
46
46
  KbRightShift => [:right_shift, :rshift],
47
47
  KbSpace => [:" ", :space],
48
48
  KbTab => [:tabulator, :tab],
49
- KbUp => [:up],
49
+ KbUp => [:up_arrow, :up],
50
50
 
51
51
  MsLeft => [:left_mouse_button, :mouse_left],
52
52
  MsMiddle => [:middle_mouse_button, :mouse_middle],
@@ -29,7 +29,10 @@ module Chingu
29
29
  self.add_background(arg)
30
30
  end
31
31
 
32
- def update
32
+ #
33
+ # TODO: make use of time here!
34
+ #
35
+ def update(time)
33
36
  @backgrounds.each do |background|
34
37
  background.x = -@x / background.damping
35
38
  background.y = @y / background.damping
data/lib/chingu/text.rb CHANGED
@@ -13,7 +13,17 @@ module Chingu
13
13
  #
14
14
  class Text < Chingu::GameObject
15
15
  attr_accessor :text
16
- attr_reader :height, :font
16
+ attr_reader :height, :gosu_font
17
+
18
+ @@size = nil
19
+ @@font = nil
20
+ def self.font; @@font; end
21
+ def self.font=(value); @@font = value; end
22
+
23
+ def self.size; @@size; end
24
+ def self.size=(value); @@size = value; end
25
+ def self.height; @@size; end
26
+ def self.height=(value); @@size = value; end
17
27
 
18
28
  #
19
29
  # Takes the standard GameObject-hash-arguments but also:
@@ -24,21 +34,21 @@ module Chingu
24
34
  def initialize(options)
25
35
  super(options)
26
36
  @text = options[:text] || "-No text specified-"
27
- @font_name = options[:font_name] || options[:font] || "verdana"
28
- @height = options[:height] || options[:size] || 15
37
+ @font = options[:font] || @@font || "verdana"
38
+ @height = options[:height] || options[:size] || @@size || default_font_name()
29
39
 
30
- @font = Gosu::Font.new($window, @font_name, @height)
40
+ @gosu_font = Gosu::Font.new($window, @font, @height)
31
41
  end
32
42
 
33
43
  def draw
34
- @font.draw_rot(@text, @x.to_i, @y.to_i, @zorder, @angle, @factor_x, @factor_y, @color, @mode)
44
+ @gosu_font.draw_rot(@text, @x.to_i, @y.to_i, @zorder, @angle, @factor_x, @factor_y, @color, @mode)
35
45
  end
36
46
 
37
47
  #
38
48
  # Returns the width, in pixels, the given text would occupy if drawn.
39
49
  #
40
50
  def width
41
- @font.text_width(@text, @factor_x)
51
+ @gosu_font.text_width(@text, @factor_x)
42
52
  end
43
53
 
44
54
  end
data/lib/chingu/window.rb CHANGED
@@ -1,11 +1,22 @@
1
1
  module Chingu
2
2
  class Window < Gosu::Window
3
- include Chingu::GameStateHelpers # adds push_gamestate(), pop_gamestate() and previous_gamestate()
4
- include Chingu::DrawHelpers # adds fill() etc..
3
+ # adds push_game_state, pop_game_state, current_game_state and previous_game_state
4
+ include Chingu::GameStateHelpers
5
5
 
6
- attr_reader :root, :update_list, :draw_list, :tick, :game_state_manager
7
- attr_accessor :key_receivers, :input
8
- attr_reader :game_objects
6
+ # adds fill() etc...
7
+ include Chingu::DrawHelpers
8
+
9
+ # adds game_objects_of_class etc ...
10
+ include Chingu::GameObjectHelpers
11
+
12
+ # Input dispatch helpers
13
+ include Chingu::InputDispatcher
14
+
15
+ # input= and input
16
+ include Chingu::InputClient
17
+
18
+ attr_reader :root, :game_state_manager, :game_objects, :milliseconds_since_last_tick
19
+
9
20
 
10
21
  #
11
22
  # See http://www.libgosu.org/rdoc/classes/Gosu/Window.html
@@ -26,136 +37,125 @@ module Chingu
26
37
  Gosu::Image.autoload_dirs = [".", File.join(@root, "gfx"), File.join(@root, "media")]
27
38
  Gosu::Sample.autoload_dirs = [".", File.join(@root, "sound"), File.join(@root, "media")]
28
39
  Gosu::Tile.autoload_dirs = [".", File.join(@root, "gfx"), File.join(@root, "media")]
40
+ Gosu::Song.autoload_dirs = [".", File.join(@root, "sfx"), File.join(@root, "media")]
29
41
 
30
- @game_objects = []
31
- @input = nil
32
-
33
- @ticks = 0
34
- @last_tick = Gosu::milliseconds
42
+ @game_objects = Set.new
43
+ @input_clients = Set.new # Set is like a unique Array with Hash lookupspeed
35
44
 
36
45
  @fps_counter = FPSCounter.new
37
46
  @game_state_manager = GameStateManager.new
38
47
 
39
- @update_list = []
40
- @draw_list = []
41
-
42
48
  self.input = { :escape => close }
43
49
  end
44
50
 
45
- def add_game_object(game_object)
46
- @game_objects.push(game_object) unless @game_objects.include?(game_object)
51
+ def add_game_object(object)
52
+ @game_objects << object
47
53
  end
48
-
49
- def update_tick
50
- @tick = Gosu::milliseconds - @last_tick
51
- @last_tick = Gosu::milliseconds
52
- @tick
53
- end
54
-
54
+ def remove_game_object(object)
55
+ @input_clients.delete(object)
56
+ end
57
+
58
+ #
59
+ # Frames per second
60
+ #
55
61
  def fps
56
62
  @fps_counter.fps
57
63
  end
58
-
59
- #
60
- # By default button_up sends the keyevent to the GameStateManager
61
- # .. Which then is responsible to send it to the right GameState(s)
62
- #
63
- def button_up(id)
64
- @game_state_manager.button_up(id)
65
- end
66
-
64
+ alias :framerate :fps
67
65
 
68
66
  #
69
- # By default button_down sends the keyevent to the GameStateManager
70
- # .. Which then is responsible to send it to the right GameState(s)
67
+ # Total amount of game iterations (ticks)
71
68
  #
72
- def button_down(id)
73
- @game_state_manager.button_down(id)
74
- end
69
+ def ticks
70
+ @fps_counter.ticks
71
+ end
75
72
 
76
-
77
73
  #
78
- # Standard GOSU main class update
74
+ # Chingus core-logic / loop. Gosu will call this each game-iteration.
79
75
  #
80
76
  def update
81
- @fps_counter.register_tick
82
- update_tick
83
-
84
77
  #
85
- # Process inputs for:
86
- # - Our main game window (self)
87
- # - .. and all gameobjects connected to it
88
- # - the active gamestate
89
- # - ... and all GameObjects connected to it
78
+ # Register a tick with our rather standard tick/framerate counter.
79
+ # Returns the amount of milliseconds since last tick. This number is used in all update()-calls.
80
+ # Without this self.fps would return an incorrect value.
81
+ # If you override this in your Chingu::Window class, make sure to call super.
90
82
  #
91
- [self, @game_state_manager.state].each do |object|
92
- next if object.nil?
93
-
94
- dispatch_input_for(object)
95
-
96
- object.game_objects.each do |game_object|
97
- dispatch_input_for(game_object)
98
- end
99
- end
100
-
101
- @game_objects.each { |object| object.update }
102
- @game_state_manager.update
83
+ @milliseconds_since_last_tick = @fps_counter.register_tick
84
+
85
+ #
86
+ # Dispatch inputmap for main window
87
+ #
88
+ dispatch_input_for(self)
89
+
90
+ #
91
+ # Dispatch input for all input-clients handled by to main window (game objects with input created in main win)
92
+ #
93
+ @input_clients.each { |game_object| dispatch_input_for(game_object) }
94
+
95
+
96
+ #
97
+ # Call update(milliseconds_since_last_tick) on all game objects belonging to the main window.
98
+ #
99
+ update_game_objects
100
+
101
+ #
102
+ # Call update(milliseconds_since_last_tick) on all game objects belonging to the current game state.
103
+ #
104
+ update_game_state_manager
103
105
  end
104
106
 
107
+ #
108
+ # Chingus main screen manupulation method.
109
+ # If you override this in your Chingu::Window class, make sure to call super.
110
+ # Gosu will call this each game-iteration just after #update
111
+ #
105
112
  def draw
113
+ #
114
+ # Draw all game objects associated with the main window.
115
+ #
106
116
  @game_objects.each { |object| object.draw }
117
+
118
+ #
119
+ # Let the game state manager call draw on the active game state (if any)
120
+ #
107
121
  @game_state_manager.draw
108
122
  end
109
-
110
- private
111
123
 
112
124
  #
113
- # Dispatches a input for any given object
125
+ # Call update() on all game objects in main game window.
114
126
  #
115
- def dispatch_input_for(object)
116
- return if object.nil? || object.input.nil?
117
-
118
- object.input.each do |symbol, action|
119
- if button_down?(Input::SYMBOL_TO_CONSTANT[symbol])
120
- #puts "#{object.to_s} :#{symbol.to_s} => #{action.to_s}"
121
- #puts "[#{action.class.to_s} - #{action.class.superclass.to_s}]"
122
- if action.is_a? Symbol
123
- object.send(action)
124
- elsif action.is_a? Proc
125
- action.call
126
- elsif action.is_a? Chingu::GameState
127
- push_gamestate(action)
128
- elsif action.superclass == Chingu::GameState
129
- push_gamestate(action)
130
- end
131
- end
132
- end
127
+ def update_game_objects
128
+ @game_objects.each { |object| object.update(@milliseconds_since_last_tick) }
133
129
  end
134
130
 
135
- end
136
- end
137
-
138
-
131
+ #
132
+ # Call update() on our game_state_manger
133
+ # -> call update on active state
134
+ # -> call update on all game objects in that state
135
+ #
136
+ def update_game_state_manager
137
+ @game_state_manager.update(@milliseconds_since_last_tick)
138
+ end
139
139
 
140
140
 
141
- # def button_down(id)
142
- # key_recievers.each do |key_reciever|
143
- # key_reciever.keymap.each do |key, action|
144
- # key_reciever.send(:before_keymap_dispatch) if key_reciever.respond_to? (:before_keymap_dispatch)
145
- # if Keymap::constant_to_symbol[id] == key
146
- # puts "#{key.to_s} => #{action.to_s}"
147
- # key_reciever.send(action)
148
- # end
149
- # end
150
- # end
151
- # end
152
-
153
- #def button_up(id)
154
- # key_recievers.each do |key_reciever|
155
- # key_reciever.release_keymap.each do |key, action|
156
- # if Keymap::Keys[id] == key
157
- # key_reciever.send(action)
158
- # end
159
- # end
160
- # end
161
- #end
141
+ #
142
+ # By default button_up sends the keyevent to the GameStateManager
143
+ # .. Which then is responsible to send it to the right GameState(s)
144
+ #
145
+ def button_up(id)
146
+ dispatch_button_up(id, self)
147
+ @input_clients.each { |object| dispatch_button_up(id, object) }
148
+ @game_state_manager.button_up(id)
149
+ end
150
+
151
+ #
152
+ # By default button_down sends the keyevent to the GameStateManager
153
+ # .. Which then is responsible to send it to the right GameState(s)
154
+ #
155
+ def button_down(id)
156
+ dispatch_button_down(id, self)
157
+ @input_clients.each { |object| dispatch_button_down(id, object) }
158
+ @game_state_manager.button_down(id)
159
+ end
160
+ end
161
+ end