/lib/chingu/game_state_manager.rb
Ruby | 283 lines | 102 code | 38 blank | 143 comment | 22 complexity | 7357efab0787444e85db2743aba89bc6 MD5 | raw file
Possible License(s): LGPL-2.1
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 23module Chingu 24 # 25 # GameStateManger is responsible for keeping track of game states with a simple pop/push stack. 26 # 27 # More about the concept of states in games: 28 # http://gamedevgeek.com/tutorials/managing-game-states-in-c/ 29 # http://www.gamedev.net/community/forums/topic.asp?topic_id=477320 30 # 31 # Chingu::Window automatically creates a @game_state_manager and makes it accessible in our game loop. 32 # By default the game loop calls update, draw, button_up(id) and button_down(id) on the active state. 33 # 34 # ==== Chingu Examples 35 # 36 # Enter a new game state, Level, don't call finalize() on the game state we're leaving. 37 # push_game_state(Level, :finalize => false) 38 # 39 # Return to the previous game state, don't call setup() on it when it becomes active. 40 # pop_game_state(:setup => false) 41 # 42 # If you want to use Chingus GameStateManager _without_ Chingu::Window, see example5.rb 43 # 44 class GameStateManager 45 attr_accessor :inside_state 46 47 def initialize 48 @inside_state = nil 49 @game_states = [] 50 @transitional_game_state = nil 51 @transitional_game_state_options = {} 52 end 53 54 # 55 # Gets the currently active gamestate (top of stack) 56 # 57 def current_game_state 58 @game_states.last 59 end 60 alias :current current_game_state 61 62 # 63 # Returns all gamestates with currenlty active game state on top. 64 # 65 def game_states 66 @game_states.reverse 67 end 68 69 # 70 # Sets a game state to be called between the old and the new game state 71 # whenever a game state is switched,pushed or popped. 72 # 73 # The transitional game state is responsible for switching to the "new game state". 74 # It should do so with ":transitional => false" not to create an infinite loop. 75 # 76 # The new game state is the first argument to the transitional game states initialize(). 77 # 78 # Example: 79 # transitional_game_state(FadeIn) 80 # push_game_state(Level2) 81 # 82 # would in practice become: 83 # 84 # push_game_state(FadeIn.new(Level2)) 85 # 86 # This would be the case for every game state change until the transitional game state is removed: 87 # transitional_game_state(nil) # or false 88 # 89 # Very useful for fading effect between scenes. 90 # 91 def transitional_game_state(game_state, options = {}) 92 @transitional_game_state = game_state 93 @transitional_game_state_options = options 94 end 95 96 # 97 # Switch to a given game state, _replacing_ the current active one. 98 # By default setup() is called on the game state we're switching _to_. 99 # .. and finalize() is called on the game state we're switching _from_. 100 # 101 def switch_game_state(state, options = {}) 102 options = {:setup => true, :finalize => true, :transitional => true}.merge!(options) 103 104 # Don't setup or finalize the underlying state, since it never becomes active. 105 pop_game_state(options.merge(:setup => false)) 106 push_game_state(state, options.merge(:finalize => false)) 107 end 108 alias :switch :switch_game_state 109 110 # 111 # Adds a state to the game state-stack and activates it. 112 # By default setup() is called on the new game state 113 # .. and finalize() is called on the game state we're leaving. 114 # 115 def push_game_state(state, options = {}) 116 options = {:setup => true, :finalize => true, :transitional => true}.merge(options) 117 118 new_state = game_state_instance(state) 119 120 if new_state 121 122 # So BasicGameObject#create connects object to new state in its setup() 123 self.inside_state = new_state 124 125 # Make sure the game state knows about the manager 126 # Is this doubled in GameState.initialize() ? 127 new_state.game_state_manager = self 128 129 # Call setup 130 new_state.setup if new_state.respond_to?(:setup) && options[:setup] 131 132 # Give the soon-to-be-disabled state a chance to clean up by calling finalize() on it. 133 current_game_state.finalize if current_game_state.respond_to?(:finalize) && options[:finalize] 134 135 if @transitional_game_state && options[:transitional] 136 # If we have a transitional, push that instead, with new_state as first argument 137 transitional_game_state = @transitional_game_state.new(new_state, @transitional_game_state_options) 138 transitional_game_state.game_state_manager = self 139 self.push_game_state(transitional_game_state, :transitional => false) 140 else 141 # Push new state on top of stack and therefore making it active 142 @game_states.push(new_state) 143 end 144 ## MOVED: self.inside_state = current_game_state 145 end 146 147 self.inside_state = nil # no longer 'inside' (as in within initialize() etc) a game state 148 end 149 alias :push :push_game_state 150 151 # 152 # Pops a state off the game state-stack, activating the previous one. 153 # By default setup() is called on the game state that becomes active. 154 # .. and finalize() is called on the game state we're leaving. 155 # 156 def pop_game_state(options = {}) 157 options = {:setup => true, :finalize => true, :transitional => true}.merge(options) 158 159 # 160 # Give the soon-to-be-disabled state a chance to clean up by calling finalize() on it. 161 # 162 current_game_state.finalize if current_game_state.respond_to?(:finalize) && options[:finalize] 163 164 # 165 # Activate the game state "bellow" current one with a simple Array.pop 166 # 167 @game_states.pop 168 169 # So BasicGameObject#create connects object to new state in its setup() 170 # Is this doubled in GameState.initialize() ? 171 self.inside_state = current_game_state 172 173 # Call setup on the new current state 174 current_game_state.setup if current_game_state.respond_to?(:setup) && options[:setup] 175 176 if @transitional_game_state && options[:transitional] 177 # If we have a transitional, push that instead, with new_state as first argument 178 transitional_game_state = @transitional_game_state.new(current_game_state, @transitional_game_state_options) 179 transitional_game_state.game_state_manager = self 180 self.push_game_state(transitional_game_state, :transitional => false) 181 end 182 183 ## MOVED: self.inside_state = current_game_state 184 self.inside_state = nil # no longer 'inside' (as in within initialize() etc) a game state 185 end 186 alias :pop :pop_game_state 187 188 # 189 # Returns the previous game state. Shortcut: "previous" 190 # 191 def previous_game_state 192 current_game_state.previous_game_state 193 end 194 alias :previous previous_game_state 195 196 # 197 # Remove all game states from stack. Shortcut: "clear" 198 # 199 def clear_game_states 200 @game_states.clear 201 self.inside_state = nil 202 end 203 alias :clear :clear_game_states 204 205 # 206 # Pops through all game states until matching a given game state (takes either a class or instance to match). 207 # 208 def pop_until_game_state(new_state, options = {}) 209 if new_state.is_a? Class 210 raise ArgumentError, "No state of given class is on the stack" unless @game_states.any? {|s| s.is_a? new_state } 211 212 pop_game_state(options) until current_game_state.is_a? new_state 213 214 else 215 raise ArgumentError, "State is not on the stack" unless @game_states.include? new_state 216 217 pop_game_state(options) until current_game_state == new_state 218 end 219 end 220 221 # 222 # This method should be called from button_down(id) inside your main loop. 223 # Enables the game state manager to call button_down(id) on active game state. 224 # 225 # If you're using Chingu::Window instead of Gosu::Window this will automaticly be called. 226 # 227 def button_down(id) 228 current_game_state.button_down(id) if current_game_state 229 end 230 231 # 232 # This method should be called from button_up(id) inside your main loop. 233 # Enables the game state manager to call button_up(id) on active game state. 234 # 235 # If you're using Chingu::Window instead of Gosu::Window this will automaticly be called. 236 # 237 def button_up(id) 238 current_game_state.button_up(id) if current_game_state 239 end 240 241 # 242 # This method should be called from update() inside your main loop. 243 # Enables the game state manager to call update() on active game state. 244 # 245 # If you're using Chingu::Window instead of Gosu::Window this will automaticly be called. 246 # 247 def update(options = {}) 248 puts current_game_state.to_s if options[:debug] 249 current_game_state.update_trait if current_game_state 250 current_game_state.update if current_game_state 251 end 252 253 # 254 # This method should be called from draw() inside your main loop. 255 # Enables the game state manager to call update() on active game state. 256 # 257 # If you're using Chingu::Window instead of Gosu::Window this will automaticly be called. 258 # 259 def draw 260 if current_game_state 261 current_game_state.draw_trait 262 current_game_state.draw 263 end 264 end 265 266 private 267 268 # 269 # Returns a GameState-instance from either a GameState class or GameState-object 270 # 271 def game_state_instance(state) 272 new_state = state 273 # 274 # If state is a GameState-instance, just queue it. 275 # If state is a GameState-class, instance it. 276 # 277 new_state = state.new if state.is_a? Class 278 279 return new_state # if new_state.kind_of? Chingu::GameState # useless check. 280 end 281 282 end 283end