现在的位置: 首页 > 综合 > 正文

ruby游戏框架:gosu与chingu(二)

2014年01月16日 ⁄ 综合 ⁄ 共 15509字 ⁄ 字号 评论关闭

Chingu - OpenGL accelerated 2D Game framework in Ruby

chingu核心方法

Chingu包括以下核心类/概念:
Chingu::Window
主窗口,用它就像用Gosu::Window一样。计算帧速率,关注状态,掌控chingu格式输入,自动update,draw
对象
BasicGameObject / GameObjects,全局可用变量$window,你也可以自己创建全局变量。如,self.factor=3,将使所有的fortcomming
GameObjects scale三次

Chingu::GameObject

所有的游戏对象用这个。游戏者,敌人,子弹,道具,战利品。它是可复用的并且不包含任何逻辑(这取决于你)。就是用某种方式把屏幕塞满。如果你 用GameObject.create()替代了new(),chingu将会把其放置在 “game_object”表中以便自动地updat/draw,GameObjects还有一个输入映射的优点:@player.input
= { :left => :move_left, :right => :move_right, :space => :fire}有一个不是 Chingu::Window  就是 Chingu::GameState 作为父节点
.
Chingu::BasicGameObject
一些人感觉GameObject有点臃肿,这里推荐 BasicGameObject (GameObject继承自BasicGameObject). BasicGameObject是空的框架(没有 x,y,image accessors 或 draw逻辑).它能用 Chingus
trait 系统扩展. GameObject 的 new() 和 create() 行为 来源于 BasicGameObject. BasicGameObject#parent 指向 $window 或一个游戏状态,这个是创建时自动完成的.
Chingu::GameStateManager

跟踪游戏状态.利用push_game_state 和 pop_game_state.实现了一个基于堆栈的系统

Chingu::GameState

一个“独立游戏循环”,可以被激活和停用控制游戏流,一个游戏状态很像gosu 的主window,你在一个游戏状态中定义update()和draw(),它有两个附加项是主窗口没有的,#setup(激活时调用)和#finalize(停用时调用)

如果运用游戏状态,draw/update/button_up/button_down的流程为:Chingu::Window
–> Chingu::GameStateManager –> Chingu::GameState. 如:游戏状态内菜单你可以调用 push_game_state(Level).当Level存在,将返回菜单。

Traits

Traits 是一个included类中的BasicGameObjects 扩展(也能叫插件)。目的是将一般行为封装到模块中以便于include到你的游戏类中,制作一个traits很简单,就是一个包含setup_trait(),
update_trait() and/or draw_trait()方法的普通模块即可。一般包含在命名空间hingu::Traits中,用于GameObject类。

其他的类与helper

Chingu::Text

让 Image#from_text用法更ruby化,更强大,它的核心组成是 another Chingu::GameObject + image genning with Image#from_text.

Chingu::Animation

装载,交互非常方便,Player#update写入 “@image = @animation.next” 语句已经能让你开始了解它了!

Chingu::Parallax

视差滚动类,增加不同阻尼层,移动摄像头生成快照,看example3.rb. 注意: 在创建视差时当用到trait视图时将产生很坏的效果,如果你的视差需要一个视图时, Parallax.new ,然后手动创建parallax.update/draw.

Chingu::HighScoreList

跟踪高分类,限制列表,自动算分,保存装载,看example13.rb 

Chingu::OnlineHighScoreList

在 gamercv.com/上保持异步同步高分的类

Various
Helpers

 $window 和游戏状态都有一些画图helper,很常用:

fill()          # Fills whole window with color 'color'.
fill_rect()     # Fills a given Rect 'rect' with Color 'color'
fill_gradient() # Fills window or a given rect with a gradient between two colors.
draw_circle()   # Draws a circle
draw_rect()     # Draws a rect

如果你的模块是基于 GameObject (或 BasicGameObject) ,你可以用以下方法:

Enemy.all                 # Returns an Array of all Enemy-instances
Enemy.size                # Returns the amount of Enemy-instances
Enemy.destroy_all         # Destroys all Enemy-instances
Enemy.destroy_if(&block)  # Destroy all objects for which &block returns true

基础与实例

Chingu::Window

在Chingu中我们用 Chingu::Window.它基于Gosu::Window,但是添加了一些小方法,如键盘控制,对所有的游戏对象自动调用 update/draw , fps计算等等.

你可能对以下的gosu格式代码很熟悉:

ROOT_PATH = File.dirname(File.expand_path(__FILE__))
class Game < Gosu::Window
  def initialize
    @player = Player.new
  end

  def update
    if button_down? Button::KbLeft
      @player.left
    elsif button_down? Button::KbRight
      @player.right
    end

    @player.update      
  end

  def draw
    @player.draw
  end
end

class Player
  attr_accessor :x,:y,:image
  def initialize(options)
    @x = options[:x]
    @y = options[:y]
    @image = Image.new(File.join(ROOT_PATH, "media", "player.png"))
  end

  def move_left
    @x -= 1
  end

  def move_right
    @x += 1
  end

  def draw
    @image.draw(@x,@y,100)
  end
end

Game.new.show   # Start the Game update/draw loop!

Chingu 不改变 Gosu基本的工作流与概念,但是代码将更少:

#
# 我们用 Chingu::Window 代替 Gosu::Window
#
class Game < Chingu::Window
  def initialize
    super       # 覆写Window#initialize总包含这一句
    #
    # Player将自动被updated 和 drawn, 因为它是一个 Chingu::GameObject 对象
    # 一会儿你将需要拥有你自己的Chingu::Window#update 和 Chingu::Window#draw,现在先用 #super chingu能自己做好
    #
    @player = Player.create
    @player.input = {:left => :move_left, :right => :move_right}
  end    
end

#
# 如果我们用Chingu::GameObject创建了一个类我们会得到一个东西.
# 里面有image,x,y,zorder,angle,factor_x,factor_y,center_x,center_y,mode,alpha.
# 我们还得到了一个缺省方法#draw 画一个包含以上参数列表的img到屏幕
# 你可能意识到了这与gosu的 #draw_rot很像 - http://www.libgosu.org/rdoc/classes/Gosu/Image.html#M000023
# 在它的核心,这就是Chingu::GameObject的核心,一个队draw_rot的封装,里面有了更多的方法
# 如:我们常常使Chingu::GameObject 自动调用 draw/update 方法. 
# 你可以这样做把它停下来: @player = Player.new(:draw => false, :update => false)
#
class Player < Chingu::GameObject
  def initialize(options)
    super(options.merge(:image => Image["player.png"])
  end

  def move_left
    @x -= 1
  end

  def move_right
    @x += 1
  end    
end

Game.new.show   # 开始游戏的 update/draw 循环!

粗略计算 50行的代码变成了26行,而且更加强大了(你可以试试 @player.angle = 100 )

如果你用了gosu一段时间了 你可能对window来回传参有些厌倦了,chingu用了$window来解决这一问题,是的,全局变量是不好的,但在这里它是有意义的,它被用在变量空间下

Chingu::Window 一旦调用 show() 方法后,基本流程如下 (这被称作一个游戏迭代或一个游戏循环):

- Chingu::Window#draw() 被调用时
-- draw() 在所有属于Chingu::Window的游戏对象上被调用
-- draw() 在所有属于游戏状态的游戏对象上被调用

- Chingu::Window#update() 被调用时
-- 输入Chingu::Window能处理的
-- 输入所有属于Chingu::Window游戏对象能处理的
-- update()在所有属于Chingu::Window的游戏对象上被调用
-- 输入游戏状态能处理的
-- 输入所有属于游戏状态的游戏对象能处理的
-- update()在所有属于游戏状态的游戏对象上被调用

… 以上一直重复直到游戏存在

Chingu::GameObject

这是我们基本的游戏单元类,众多的游戏对象(游戏者,敌人,子弹等等)应该继承自Chingu::GameObject. 基本理念如下:

  • 封装很常见的,大多数游戏对象的基本需要
  • 命名风格与gosu保持一致,但是增加了方便的方法与短名,更加ruby化
  • 无游戏逻辑.

它基于 Image#draw_rot. 所以当创建新对象时所有的参数与draw_rot一致.例子如下:

#
# 你可以从这里看到所有参数 http://www.libgosu.org/rdoc/classes/Gosu/Image.html#M000023
#
@player = Player.new(:image => Image["player.png"], :x=>100, :y=>100, :zorder=>100, :angle=>45, :factor_x=>10, :factor_y=>10, :center_x=>0, :center_y=>0)

#
# 短点的写法
#
@player = Player.new(:image => "player.png", :x=>100, :y=>100, :zorder=>100, :angle=>45, :factor=>10, :center=>0)

#
# 人性化的缺省值:
# x/y = [middle of the screen]  super出来的)
# angle = 0                     (no angle by default)
# center_x/center_y = 0.5       (basically the center of the image will be drawn at x/y)
# factor_x/factor_y = 1         (no zoom by default)
# 
@player = Player.new

#
# 你可以关闭自动绘制更新
#
@player = Player.new(:draw => false, :update => false)

Input

一个重要的变化是更自然的输入控制.你可以 在 Chingu::Window , Chingu::GameState 和 Chingu::GameObject中定义 input -> actions.像这样:

#
# 当左箭头被按下 调用 @player.turn_left ... 等等.
#
@player.input = { :left => :turn_left, :right => :turn_right, :left => :halt_left, :right => :halt_right }

#
# 在gosu环境下应该是这样
#
def button_down(id)
  @player.turn_left		if id == Button::KbLeft
  @player.turn_right	if id == Button::KbRight
end

def button_up(id)
  @player.halt_left		if id == Button::KbLeft
  @player.halt_right	if id == Button::KbRight
end

另一个更复杂的例子:

#
#那么这里发生了什么?
#按P键,则创建一个在Pause类外的游戏状态,缓存并激活它
#按esc调用Play#close
#游戏重复时,长按left,调用Play#move_left
#游戏重复时,长按right,调用Play#move_right
#抬起空格键,调用Play#fire
#

class Play < Chingu::GameState
  def initialize
    self.input = { :p => Pause, 
                   :escape => :close, 
                   :holding_left => :move_left, 
                   :holding_right => :move_right, 
                   :released_space => :fire }
  end
end
class Pause < Chingu::GameState
  # pause logic here
end

在gosu中,以上代码应该包括 button_up(), button_down() 和在update()中的检查方法 button_down?() .

每一个symbol都能加前缀 “released_” 或 “holding_”,但是没有前缀是只按一次的.

因此, 为什么不用:up_space 或 :release_space替代 :released_space ? :up_space 听上去不像英语,
:release_space 听上去更像一个命令而不是一个事件.

又或者为什么不是 :hold_left 或 :down_left 替代 :holding_left ? :holding_left听上去好像一些事情已经发生一段时间了而不是一个单独的触发,就像它工作的那样

加上缺省的:space => :something 你应该能想象到 --- :something 被调用了一次.你按了 :space 一次, :something 立即执行了.

游戏状态
/ 游戏状态管理

Chingu 合成了一个基本的推拉游戏状态系统 (讨论在这里: www.gamedev.net/community/forums/topic.asp?topic_id=477320).

游戏状态是组织你的说明,菜单,等级的方法.

游戏状态并不复杂,在chingu中,一个游戏状态就是一个表示行为的类,很像缺省的Gosu::Window (或在我们的例子中的 Chingu::Window) 游戏循环.

# 一个简单的游戏状态例子
class Intro < Chingu::GameState

  def initialize(options)
    # 当类创建时像往常一样调用,在这里加载资源和模拟器
  end

  def update
    # 逻辑在这里
  end

  def draw
    # 屏幕操作
  end

  # 每次当我们进入一个游戏状态,调用该方法,重置游戏状态到"virgin state"
  def setup
    @player.angle = 0   # point player upwards
  end

  # 离开一个游戏状态是调用
  def finalize
    push_game_state(Menu)   # 切换到游戏状态“Menu”
  end

end

看起来很熟悉吗?你可以激活上面的游戏状态以两种方式

class Game < Chingu::Window
  def initialize
    #
    # 1)创建一个新的intro对象,激活它 (pushing到顶端).
    # 这个版本更有意义,如果你想传参,如:
    # push_game_state(Level.new(:level_nr => 10))
    #
    push_game_state(Intro.new)

    #
    # 2)把激活的对象放入游戏状态管理器中.
    # Intro#initialize()被调用,然后是 Intro#setup()
    #
    push_game_state(Intro)
  end
end

另一个例子:

class Game < Chingu::Window
  def initialize
    #
    # 我们开始push Menu 到游戏状态栈中,激活它并使其成为栈中唯一的状态
    #
    # :setup => :false 将在调用时跳过 setup()  (切换到新状态时)
    #
    push_game_state(Menu, :setup => false)

    #
    # 再push另一个游戏状态Play到栈中,现在我们有了两个状态,第一个是激活状态
    #
    # :finalize => false 在调用时将跳过 finalize() 
    # 这将push到栈中,在这个例子中是 Menu.finalize().
    #
    push_game_state(Play, :finalize => false)

    #
    # 下一步,我们从栈中移除Play状态,回到Menu状态,但同时
    # .. 跳过标准流程中的 Menu#setup     (the new game state)
    # .. 跳过标准流程中的 Play#finalize  (the current game state)
    #
    # :setup => false 当弹出一个Pause状态时很有用. (见 example4.rb)
    #
    pop_game_state(:setup => false, :finalize => :false)

    #
    # 用一个新状态替换当前状态.
    #
    # :setup 和 :finalize 选项都是可见的但是:
    # .. setup 和 finalize 对于Menu来说总是跳过的 (在Play和Credits状态下)
    # ..  finalize 选项仅对弹出的游戏状态有影响
    # .. setup选项仅对正在切换的游戏状态有影响
    #
    switch_game_state(Credits)
  end
end

在chingu中,一个游戏状态就是一个拥有以下实例方法的类:

  • initialize() - 当游戏状态被创建时调用.

  • setup() - called each time the game state becomes active.

  • button_down(id) - called when a button is down.

  • button_up(id) - called when a button is released.

  • update() - just as in your normal game loop, put your game logic here.

  • draw() - just as in your normal game loop, put your screen manipulation here.

  • finalize() - called when a game state de-activated (for example by pushing a new one on top with push_game_state)

Chingu::Window automatically creates a @game_state_manager and makes it accessible in our game loop. By default the game loop calls update() / draw() on the the current game state.

Chingu also has a couple of helpers-methods for handling the game states: In a main loop or in a game state:

  • push_game_state(state) - adds a new gamestate on top of the stack, which then becomes the active one

  • pop_game_state - removes active gamestate and activates the previous one

  • switch_game_state(state) - replaces current game state with a new one

  • current_game_state - returns the current game state

  • previous_game_state - returns the previous game state (useful for pausing and dialog boxes, see example4.rb)

  • pop_until_game_state(state) - pop game states until given state is found

  • clear_game_states - removes all game states from stack

To switch to a certain gamestate with a keypress use Chingus input handler:

class Intro < Chingu::GameState
  def setup
    self.input = { :space => lambda{push_gamestate(Menu.new)} }
  end
end

Or Chingus shortcut:

class Intro < Chingu::GameState
  def setup
    self.input = { :space => Menu }
  end
end

Chingus inputhandler will detect that Menu is a GameState-class, create a new instance and activate it with push_game_state().

GOTCHA: Currently you can’t switch to a new game state from Within GameState#initialize() or GameState#setup()

Premade
game states

Chingu comes with some pre-made game states. A simple but usefull one is GameStates::Pause. Once pushed it will draw the previous game state but not update it – effectively pausing it. Some others are:

GameStates::EnterName

A gamestate where a gamer can select letters from a A-Z list, contructing his alias. When he’s done he selects “GO!” and a developer-specified callback will be called with the name/alias as argument.

push_game_state GameStates::EnterName.new(:callback => method(:add))

def add(name)
  puts "User entered name #{name}"
end

Combine GameStates::EnterName with class OnlineHighScoreList, a free acount @ www.gamercv.com and you have a premade stack to provide
your 48h gamecompo entry with online high scores that adds an extra dimension to your game. See example16 for a full working example of this.

GameStates::Edit

The biggest and most usable is GameStates::Edit which enables fast ‘n easy level-building with game objects. Start example19 and press ’E’ to get a full example.

Edit commands / shortcuts:

F1: Help screen
1-5: create object 1..5 shown in toolbar at mousecursor
CTRL+A: select all objects (not in-code-created ones though)
CTRL+S: Save
E: Save and Quit
Q: Quit (without saving)
ESC: Deselect all objects
Right Mouse Button Click: Copy object bellow cursor for fast duplication
Arrow-keys (with selected objects): Move objects 1 pixel at the time
Arrow-keys (with no selected objects): Scroll within a viewport

Bellow keys operates on all currently selected game objects
-----------------------------------------------------------------------------------
DEL: delete selected objects
BACKSPACE: reset angle and scale to default values
Page Up: Increase zorder
Page Down: Decrease zorder

R: scale up
F: scale down
T: tilt left
G: tilt right
Y: inc zorder
H: dec zorder
U: less transparency
J: more transparency

Mouse Wheel (with no selected objects): Scroll viewport up/down
Mouse Wheel: Scale up/down
SHIFT + Mouse Wheel: Tilt left/right
CTRL + Mouse Wheel: Zorder up/down
ALT + Mouse Wheel: Transparency less/more

Move mouse cursor close to the window border to scroll a viewport if your game state has one.

If you’re editing game state BigBossLevel the editor will save to big_boss_level.yml by default. All the game objects in that file are then easily loaded with the load_game_objects command.

Both Edit.new and load_game_objects take parameters as

:file => "enemies.yml"    # Save edited game objects to file enemies.yml
:debug => true            # Will print various debugmsgs to console, usefull if something behaves oddly
:except => Player         # Don't edit or load objects based on class Player

WorkFlow

(This text is under development)

The
setup-method

如果一个setup()在一个Chingu::GameObject, Chingu::Window and Chingu::GameState的实例中可见,那么它将自动被调用。对于像设置颜色或加载动画等配置/初始化任务来说,这是一个完美的地方,(如果你不用animation-trait)。你也可以覆写initialize(),不过它被证明是不可靠的,比较下面两段代码:

# 容易弄混,忘记options或super
def initialize(options = {})
  super
  @color = Color::WHITE
end

# 更少的代码,更容易做对 在GameObject, Window 和 GameState中
# 随时调用 setup(),更加自由 
def setup
  @color = Color::WHITE
end

Traits

Traits (在其他框架中被称为行为)是对任何继承了BasicGameObject / GameObject 的类添加逻辑的一种方法. Chingus trait 接口就是一个普通的有三个方法的ruby模块 :

- setup_trait 
- update_trait 
- draw_trait

每个方法必须调用super以保持在 trait-chain中.

Inside a certian trait-module you can also have a module called ClassMethods, methods inside that module will be added, yes you guessed it, as class methods. If initialize_trait is defined inside ClassMethods it will be called class-evaluation time (basicly
on the trait :some_trait line).

一个简单的trait如下:

module Chingu
  module Trait
    module Inspect

      #
      # 在 ClassMethods命名空间中的方法是类方法
      #
      module ClassMethods
        def initialize_trait(options)
          # possible initialize stuff here
        end

        def inspect
          "There's {self.size} active instances of class {self.to_s}"
        end
      end

      #
      # 在 ClassMethods命名空间外的方法变成了普通实例方法
      #
      def inspect
        "Hello I'm an #{self.class.to_s}"
      end

      #
      # 当一个包含trait的对象从类中创建时,setup_trait被调用
      # 你很有可能想把所有的trait设置和选项解析在这里
      #
      def setup_trait(options)
        @long_inspect = true
      end

    end
  end
end

class Enemy < GameObject
  trait :inspect    # includes Chingu::Trait::Inspect and extends Chingu::Trait::Inspect::ClassMethods
end
10.times { Enemy.create }
Enemy.inspect           # => "There's 10 active instances of class Enemy"
Enemy.all.first.inspect # => "Hello I'm a Enemy"

 traits 用法例子 :velocity and :timer.我们也用GameObject#setup,它在GameObject#initialize后自动调用. 覆写initialize()显得很简洁.

class Ogre < Chingu::GameObject
  traits :velocity, :timer

  def setup
    @red = Gosu::Color.new(0xFFFF0000)
    @white = Gosu::Color.new(0xFFFFFFFF)

    #
    # velocity-trait提供一些基本的物理学参数
    # 每个游戏迭代有两个参数将影响 @x 和 @y 
    # 因此如果你的食人魔站在地上,要确保你取消了 @acceleration_y 的影响
    #
    self.velocity_x = 1       # 持续向右移动
    self.acceleration_y = 0.4 # 重力基本上是一个向下的加速度
  end

  def hit_by(object)
    #
    # timer-trait提供了两个方法during() and then() 
    # 当撞击时,变红300毫秒,然后回到正常
    #
    during(100) { self.color = @red; self.mode = :additive }.then { self.color = @white; self.mode = :default }
  end
end

一个有些对象的流程是这个样子滴:

-- 创建 a GameObject class X  ( with a "trait :bounding_box, :scale => 0.80" )
1) trait 合并到 X 中, 实例方法和类方法被添加
2) GameObject.initialize_trait(:scale => 0.80)   (initialize_trait 是一个类方法)
-- 创建一个 X的实例
1) GameObject#initialize(options)
2) GameObject#setup_trait(options)
3) GameObject#setup(options)
-- 每次游戏循环
3) GameObject#draw_trait
4) GameObject#draw
5) GameObject#update_trait
6) GameObject#update

在chingu有一组缺省的traits:

Trait
“sprite”

This trait 作为GameObject的补充. 一个 GameObject 是一个  BasicGameObject +  sprite-trait. 增加的属性有 :x, :y, :angle, :factor_x, :factor_y, :center_x, :center_y, :zorder, :mode, :visible, :color. 可查看文档看 GameObject 如何工作的.

Trait
“timer”

增加时间功能到你的游戏对象上

during(300) { self.color = Color.new(0xFFFFFFFF) }  # 强制@color在300毫秒内变白 每次update时
after(400) { self.destroy }                         # 在400毫秒后销毁对象
between(1000,2000) { self.angle += 10 }             # 在1秒后开始,在一秒内修改角度
every(2000) { Sound["bleep.wav"].play }             # 每两秒播放

Trait
“velocity”

为游戏对象增加属性 velocity_x, velocity_y, acceleration_x, acceleration_y, max_velocity。他们修改你想要的x,y

Trait
“bounding_box”

增加属性 ‘bounding_box’,基于当前的图片大小,x,y,factor_x,factor_y,center_x,center_y返回一个rect类的实例,你也可以用trait-options缩放这个被计算的rect:

# 这将返回一个稍微比image小些的矩形
# 使游戏者认为他能更好的躲避子弹,而且还真的躲开了;)
trait :bounding_box, :scale => 0.80 

# 使bounding box 比image大些
# :debug => true 显示了在屏幕上实际是红色的box
trait :bounding_box, :scale => 1.5, :debug => true

在你的对象内部还有cache_bounding_box()(缓存边界框).在这个边界框将会更快,但它将不再适应大小的变化。

Trait
“bounding_circle”

增加属性 ‘radius’(半径),基于当前的图片大小,factor_x and factor_y返回一个整数 你也可以利用trait-options缩放计算出来的radius:

# 返回一个比初始化计算值稍微大一点儿的半径值
trait :bounding_circle, :scale => 1.10

# :debug => true  用红色表示显示屏幕上的实际圆形
trait :bounding_circle, :debug => true

在你的对象中也有 cache_bounding_circle().这个 radius()速度更快但是不在有尺寸的变化

Trait
“animation”

根据类名称自动加载动画。当有许多简单的类都用于显示一个动画时,很有用。假设下面的代码包含一个类 火球。

#
# 如果 fire_ball_10x10.png/bmp 存在, 它将作为叠加动画被加载.

# 10x10表明每一片的宽高,因此chingu知道如何把它切割刀一个单独的帧中

# 作为一个动画实例,animations[:default] 缺省下是可见的.
#
# 如果超过一个动画效果存在, 它们将同时被加载,如:
# fire_ball_10x10_fly.png       # 将在animations[:fly]中可见
# fire_ball_10x10_explode.png   # 将在animations[:explode]中可见
#

# 下例为在每帧之间延迟200毫秒所有动画加载

#
trait :animation, :delay => 200

Trait
“effect”

增加属性 rotation_rate, fade_rate 和 scale_rate到游戏对象

抱歉!评论已关闭.