moonpxi-nimo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|