moonpxi-nimo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/examples/bouncy.rb +101 -0
- data/examples/eventy.rb +19 -0
- data/examples/images/dg_classm32.png +0 -0
- data/examples/images/dg_dungeon32.png +0 -0
- data/examples/images/jeeklabs.png +0 -0
- data/examples/images/tcheco.png +0 -0
- data/examples/imagey.rb +82 -0
- data/examples/screeny.rb +80 -0
- data/examples/simplest.rb +23 -0
- data/examples/sounds/KDE-Sys-Log-Out.ogg +0 -0
- data/examples/sounds/k3b_error1.wav +0 -0
- data/examples/soundy.rb +40 -0
- data/examples/spritey.rb +90 -0
- data/lib/nimo.rb +35 -0
- data/lib/nimo/behavior/deflector.rb +39 -0
- data/lib/nimo/behavior/moveable.rb +33 -0
- data/lib/nimo/behavior/projectile.rb +23 -0
- data/lib/nimo/behavior/with_velocity.rb +28 -0
- data/lib/nimo/game_object.rb +58 -0
- data/lib/nimo/game_window.rb +59 -0
- data/lib/nimo/listeners/event_listener.rb +27 -0
- data/lib/nimo/listeners/input_listener.rb +70 -0
- data/lib/nimo/representations/image_representation.rb +18 -0
- data/lib/nimo/representations/object_representation.rb +67 -0
- data/lib/nimo/representations/quad_representation.rb +23 -0
- data/lib/nimo/representations/sprite_representation.rb +78 -0
- data/lib/nimo/representations/text_representation.rb +27 -0
- data/lib/nimo/screen.rb +103 -0
- data/lib/nimo/utils/game.rb +63 -0
- data/lib/nimo/utils/intersection.rb +30 -0
- data/lib/nimo/utils/object_extension.rb +17 -0
- data/lib/nimo/utils/resources.rb +51 -0
- data/nimo.gemspec +117 -0
- data/spec/nimo/behavior/deflector_spec.rb +51 -0
- data/spec/nimo/behavior/moveable_spec.rb +81 -0
- data/spec/nimo/behavior/projectile_spec.rb +34 -0
- data/spec/nimo/behavior/with_velocity_spec.rb +18 -0
- data/spec/nimo/game_object_spec.rb +169 -0
- data/spec/nimo/game_window_spec.rb +88 -0
- data/spec/nimo/listeners/event_listener_spec.rb +34 -0
- data/spec/nimo/listeners/input_listener_spec.rb +87 -0
- data/spec/nimo/representations/image_representation_spec.rb +41 -0
- data/spec/nimo/representations/object_representation_spec.rb +74 -0
- data/spec/nimo/representations/sprite_representation_spec.rb +73 -0
- data/spec/nimo/representations/text_representation_spec.rb +21 -0
- data/spec/nimo/screen_spec.rb +114 -0
- data/spec/nimo/utils/game_spec.rb +44 -0
- data/spec/nimo/utils/object_extension_spec.rb +21 -0
- data/spec/nimo/utils/resources_spec.rb +59 -0
- data/spec/spec_helper.rb +9 -0
- metadata +133 -0
data/examples/spritey.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#
|
2
|
+
# spritey.rb
|
3
|
+
#
|
4
|
+
# Demonstrate how to create sprites and animations.
|
5
|
+
#
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
require 'nimo'
|
9
|
+
include Gosu::Button
|
10
|
+
|
11
|
+
WINDOW_WIDTH = 512
|
12
|
+
WINDOW_HEIGHT = 480
|
13
|
+
WINDOW = {:x => 0, :y => 0, :width => WINDOW_WIDTH, :height => WINDOW_HEIGHT}
|
14
|
+
|
15
|
+
class Player < Nimo::GameObject
|
16
|
+
include Nimo::Behavior::WithVelocity
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super(:x => 0, :y => WINDOW_HEIGHT - 62, :width => 48, :height => 62, :speed => 5, :boundary => Object.from_hash(WINDOW))
|
20
|
+
end
|
21
|
+
|
22
|
+
def move_left
|
23
|
+
@velocity.x = -1
|
24
|
+
end
|
25
|
+
|
26
|
+
def move_right
|
27
|
+
@velocity.x = 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def move
|
31
|
+
super
|
32
|
+
if y < @boundary.height - @height
|
33
|
+
@velocity.y += 0.01
|
34
|
+
else
|
35
|
+
@velocity.y = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
@velocity.x = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def jump
|
42
|
+
# Cannot jump twice
|
43
|
+
if @y >= @boundary.height - @height
|
44
|
+
@velocity.y = -1
|
45
|
+
@y += @speed * @velocity.y
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
if __FILE__ == $PROGRAM_NAME
|
52
|
+
Nimo::Game("Spritey", WINDOW_WIDTH, WINDOW_HEIGHT) do
|
53
|
+
images :jeeklabs => { :filename => "examples/images/jeeklabs.png" },
|
54
|
+
:tcheco => { :filename => "examples/images/tcheco.png", :tile_dimension => [48, 62] }
|
55
|
+
|
56
|
+
screen :title do
|
57
|
+
quad :with => WINDOW.merge(:color => Gosu::white)
|
58
|
+
image :with => {:x => 116, :y => 190, :image => :jeeklabs}
|
59
|
+
|
60
|
+
any_key { go_to :game }
|
61
|
+
end
|
62
|
+
|
63
|
+
screen :game do
|
64
|
+
player_observer = Proc.new do |rep, obj|
|
65
|
+
rep.flip if obj.velocity.x < 0
|
66
|
+
rep.unflip if obj.velocity.x > 0
|
67
|
+
|
68
|
+
rep.change_to(:stopped) if obj.velocity.x == 0
|
69
|
+
rep.change_to(:walking) if obj.velocity.x != 0
|
70
|
+
rep.change_to(:jumping) if obj.velocity.y < 0
|
71
|
+
rep.change_to(:falling) if obj.velocity.y > 0
|
72
|
+
end
|
73
|
+
|
74
|
+
quad :with => WINDOW.merge(:color => Gosu::white)
|
75
|
+
sprite :for => Player.new, :with => {:image => :tcheco} do
|
76
|
+
always { move }
|
77
|
+
when_key(KbLeft) { move_left }
|
78
|
+
when_key(KbRight) { move_right }
|
79
|
+
when_key(KbUp) { jump }
|
80
|
+
with_animation(:stopped, [0])
|
81
|
+
with_animation(:walking, [1, 2, 3, 4])
|
82
|
+
with_animation(:jumping, [5, 6, 7], :loop => false)
|
83
|
+
with_animation(:falling, [8, 9], :loop => false)
|
84
|
+
with_observer(&player_observer)
|
85
|
+
end
|
86
|
+
|
87
|
+
when_key(KbEscape) { exit }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/nimo.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Libraries
|
2
|
+
begin
|
3
|
+
# In case you use Gosu via RubyGems.
|
4
|
+
require "rubygems"
|
5
|
+
rescue LoadError
|
6
|
+
# In case you don't.
|
7
|
+
end
|
8
|
+
|
9
|
+
require "gosu"
|
10
|
+
|
11
|
+
# NIMO_DIR = File.expand_path(File.dirname(__FILE__) + "/nimo")
|
12
|
+
NIMO_DIR = "nimo"
|
13
|
+
|
14
|
+
require NIMO_DIR + "/utils/object_extension"
|
15
|
+
require NIMO_DIR + "/utils/intersection"
|
16
|
+
require NIMO_DIR + "/utils/resources"
|
17
|
+
require NIMO_DIR + "/utils/game"
|
18
|
+
|
19
|
+
require NIMO_DIR + "/listeners/input_listener"
|
20
|
+
require NIMO_DIR + "/listeners/event_listener"
|
21
|
+
|
22
|
+
require NIMO_DIR + "/game_object"
|
23
|
+
require NIMO_DIR + "/game_window"
|
24
|
+
require NIMO_DIR + "/screen"
|
25
|
+
|
26
|
+
require NIMO_DIR + "/behavior/with_velocity"
|
27
|
+
require NIMO_DIR + "/behavior/deflector"
|
28
|
+
require NIMO_DIR + "/behavior/projectile"
|
29
|
+
require NIMO_DIR + "/behavior/moveable"
|
30
|
+
|
31
|
+
require NIMO_DIR + "/representations/object_representation"
|
32
|
+
require NIMO_DIR + "/representations/quad_representation"
|
33
|
+
require NIMO_DIR + "/representations/image_representation"
|
34
|
+
require NIMO_DIR + "/representations/text_representation"
|
35
|
+
require NIMO_DIR + "/representations/sprite_representation"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Nimo::Behavior
|
2
|
+
|
3
|
+
module Deflector
|
4
|
+
|
5
|
+
def deflect(projectile)
|
6
|
+
@collision_timeout ||= 0
|
7
|
+
@collision_timeout -= 1 if @collision_timeout > 0
|
8
|
+
return unless @collision_timeout.zero? && collide?(projectile)
|
9
|
+
|
10
|
+
case intersection(projectile).collistion_side_for(self)
|
11
|
+
when :top
|
12
|
+
projectile.velocity.y = -projectile.velocity.y.abs
|
13
|
+
when :bottom
|
14
|
+
projectile.velocity.y = projectile.velocity.y.abs
|
15
|
+
when :left
|
16
|
+
projectile.velocity.x = -projectile.velocity.x.abs
|
17
|
+
when :right
|
18
|
+
projectile.velocity.x = projectile.velocity.x.abs
|
19
|
+
end
|
20
|
+
|
21
|
+
projectile.velocity.adjust(deflection_modifier(projectile))
|
22
|
+
@deflect_action.call(projectile) if @deflect_action
|
23
|
+
@collision_timeout = 5
|
24
|
+
end
|
25
|
+
|
26
|
+
def when_deflect(&action)
|
27
|
+
@deflect_action = action
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def deflection_modifier(projectile)
|
34
|
+
0
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Nimo::Behavior
|
2
|
+
|
3
|
+
module Moveable
|
4
|
+
|
5
|
+
def initialize(*params)
|
6
|
+
@speed = 0
|
7
|
+
@boundary = nil
|
8
|
+
super(*params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def move_left
|
12
|
+
@x -= @speed
|
13
|
+
@x = @boundary.x if @boundary && @x < @boundary.x
|
14
|
+
end
|
15
|
+
|
16
|
+
def move_right
|
17
|
+
@x += @speed
|
18
|
+
@x = @boundary.width - @width if @boundary && (@x + @width) > @boundary.width
|
19
|
+
end
|
20
|
+
|
21
|
+
def move_up
|
22
|
+
@y -= @speed
|
23
|
+
@y = @boundary.y if @boundary && @y < @boundary.y
|
24
|
+
end
|
25
|
+
|
26
|
+
def move_down
|
27
|
+
@y += @speed
|
28
|
+
@y = @boundary.height - @height if @boundary && (@y + @height) > @boundary.height
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nimo::Behavior
|
2
|
+
|
3
|
+
module Projectile
|
4
|
+
include WithVelocity
|
5
|
+
|
6
|
+
def initialize(*params)
|
7
|
+
@deflectors = []
|
8
|
+
super(*params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def move
|
12
|
+
@deflectors.each { |deflector| deflector.deflect(self) }
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_deflectors(*new_deflectors)
|
17
|
+
@deflectors += new_deflectors
|
18
|
+
@deflectors.flatten!
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Nimo::Behavior
|
2
|
+
|
3
|
+
Velocity = Struct.new(:x, :y) do
|
4
|
+
def adjust(radian)
|
5
|
+
xl = Math::cos(radian)*self.x - Math::sin(radian)*self.y
|
6
|
+
yl = Math::sin(radian)*self.x + Math::cos(radian)*self.y
|
7
|
+
self.x = xl
|
8
|
+
self.y = yl
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module WithVelocity
|
13
|
+
attr_reader :velocity
|
14
|
+
|
15
|
+
def initialize(*params)
|
16
|
+
@speed = 0
|
17
|
+
@velocity = Velocity.new(0.0, 0.0)
|
18
|
+
super(*params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def move
|
22
|
+
@x += @speed * @velocity.x
|
23
|
+
@y += @speed * @velocity.y
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
#
|
4
|
+
# Base game domain object containing position, dimension and state information. Any object that represents
|
5
|
+
# a game entity should extend this class.
|
6
|
+
#
|
7
|
+
# The position (:x and :y) and dimension (:width and :height) are used for visual representations and any
|
8
|
+
# useful game behavior.
|
9
|
+
#
|
10
|
+
class GameObject
|
11
|
+
attr_accessor :x, :y, :width, :height
|
12
|
+
|
13
|
+
def initialize(config_options = {})
|
14
|
+
configure_with({:x => 0, :y => 0, :width => 0, :height => 0}.merge(config_options))
|
15
|
+
@listeners = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def at(x, y)
|
19
|
+
@x = x
|
20
|
+
@y = y
|
21
|
+
end
|
22
|
+
|
23
|
+
def dimension(width, height)
|
24
|
+
@width = width
|
25
|
+
@height = height
|
26
|
+
end
|
27
|
+
|
28
|
+
# config_options is a hash that can take the following keys: :x, :y, :width, :height.
|
29
|
+
# The key restriction is not being enforced.
|
30
|
+
def configure_with(config_options)
|
31
|
+
config_options.each { |attribute, value| instance_variable_set("@#{attribute}", value) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def collide?(obj)
|
35
|
+
!(obj.x > (@x + @width) || @x > (obj.x + obj.width) ||
|
36
|
+
obj.y > (@y + @height) || @y > (obj.y + obj.height))
|
37
|
+
end
|
38
|
+
|
39
|
+
def intersection(obj)
|
40
|
+
collide?(obj) ? Intersection.between(self, obj) : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def center
|
44
|
+
Object.from_hash(:x => @x + (@width/2), :y => @y + (@height/2))
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_listener(event_type, listener)
|
48
|
+
@listeners[event_type] ||= []
|
49
|
+
@listeners[event_type] << listener
|
50
|
+
end
|
51
|
+
|
52
|
+
def notify(event_type)
|
53
|
+
@listeners[event_type].each { |listener| listener.notify(event_type) } if @listeners.has_key? event_type
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Represents a game instance and provides access to the game screen and screen transition.
|
4
|
+
# It is an extension of Gosu::Window, thus implementing the update, draw and button_down hooks.
|
5
|
+
#
|
6
|
+
class GameWindow < Gosu::Window
|
7
|
+
|
8
|
+
attr_reader :current_screen
|
9
|
+
|
10
|
+
def initialize(name, width, height)
|
11
|
+
super(width, height, false)
|
12
|
+
self.caption = name
|
13
|
+
|
14
|
+
@screens = {}
|
15
|
+
@background_screens = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_screen(name, screen)
|
19
|
+
@screens[name] = screen
|
20
|
+
go_to(name) if @screens.size == 1
|
21
|
+
end
|
22
|
+
|
23
|
+
# Switch to a Screen registered with the <tt>screen_name</tt>, notifying listeners of the :on_enter event.
|
24
|
+
#
|
25
|
+
def go_to(screen_name)
|
26
|
+
raise "There is no screen named #{screen_name}" unless @screens.has_key? screen_name
|
27
|
+
@current_screen = @screens[screen_name]
|
28
|
+
@current_screen.notify(:on_enter)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Open a Screen registered with the <tt>screen_name</tt> as a menu. To dismiss the menu, use close_menu.
|
32
|
+
#
|
33
|
+
def open_menu(screen_name)
|
34
|
+
@background_screens << @current_screen
|
35
|
+
go_to(screen_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def close_menu
|
39
|
+
@current_screen = @background_screens.pop
|
40
|
+
end
|
41
|
+
|
42
|
+
# :section: Gosu::Window hooks
|
43
|
+
|
44
|
+
def update
|
45
|
+
@current_screen.update
|
46
|
+
end
|
47
|
+
|
48
|
+
def draw
|
49
|
+
@background_screens.each { |screen| screen.draw }
|
50
|
+
@current_screen.draw
|
51
|
+
end
|
52
|
+
|
53
|
+
def button_down(id)
|
54
|
+
@current_screen.button_down(id)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Listen for events and execute for actions on notification.
|
4
|
+
#
|
5
|
+
module EventListener
|
6
|
+
|
7
|
+
# Register an action to be executed when an event is notified.
|
8
|
+
#
|
9
|
+
def listen_to(event, &action)
|
10
|
+
events[event] = action
|
11
|
+
end
|
12
|
+
|
13
|
+
# Execute registered action for notified event.
|
14
|
+
#
|
15
|
+
def notify(event)
|
16
|
+
events[event].call if events.has_key?(event)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def events
|
22
|
+
@events ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Listen for inputs (key presses) and execute the corresponding actions on update.
|
4
|
+
#
|
5
|
+
module InputListener
|
6
|
+
|
7
|
+
# Register an action that will execute when the key is pressed.
|
8
|
+
# An options hash can be specified to customise the behavior. The options are:
|
9
|
+
# - <tt>:repeatable</tt> (defaults to true) - execute the action every update regardless if the key was already pressed in the previous update.
|
10
|
+
#
|
11
|
+
def when_key(key, options = {}, &action)
|
12
|
+
key_actions[key] = KeyAction.new(action, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Register an action to be executed everytime a key is pressed.
|
16
|
+
#
|
17
|
+
def any_key(&action)
|
18
|
+
@any_key_action = action
|
19
|
+
end
|
20
|
+
|
21
|
+
# Gosu hook invoked anytime a button is pressed
|
22
|
+
#
|
23
|
+
def button_down(id) #:nodoc:
|
24
|
+
act_upon.instance_eval(&@any_key_action) if @any_key_action
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_inputs(game_window)
|
28
|
+
key_actions.each do |key, key_action|
|
29
|
+
act_upon.instance_eval(&key_action.action) if key_action.should_execute?(game_window.button_down?(key))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Defines the object to be the context of the action. By default it is <tt>self</tt>.
|
36
|
+
#
|
37
|
+
def act_upon
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def key_actions
|
44
|
+
@key_actions ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
class KeyAction
|
49
|
+
attr_reader :action
|
50
|
+
|
51
|
+
def initialize(action, options)
|
52
|
+
@action = action
|
53
|
+
@options = {:repeatable => true}.merge(options)
|
54
|
+
@pressed_since = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def should_execute?(is_button_down)
|
58
|
+
should_execute = false
|
59
|
+
if is_button_down
|
60
|
+
should_execute = @pressed_since.nil? || @options[:repeatable]
|
61
|
+
@pressed_since = Time.now if @pressed_since.nil?
|
62
|
+
else
|
63
|
+
@pressed_since = nil
|
64
|
+
end
|
65
|
+
return should_execute
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|