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
@@ -0,0 +1,18 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
class ImageRepresentation < ObjectRepresentation
|
4
|
+
represent :image
|
5
|
+
|
6
|
+
def load(resources, params)
|
7
|
+
raise "Must provide :image param for image loading" unless params.has_key?(:image)
|
8
|
+
|
9
|
+
@image = resources.image(params[:image])
|
10
|
+
@image = @image[params[:index]] if params.has_key?(:index)
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw
|
14
|
+
@image.draw(@game_object.x, @game_object.y, 0)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Nimo::GameObject's view. It holds actions to be executed on every update or when a key is pressed.
|
4
|
+
#
|
5
|
+
class ObjectRepresentation
|
6
|
+
include InputListener, EventListener
|
7
|
+
|
8
|
+
attr_reader :game_object
|
9
|
+
attr_accessor :game_window
|
10
|
+
|
11
|
+
def initialize(game_window, game_object)
|
12
|
+
@game_window, @game_object = game_window, game_object
|
13
|
+
@always_actions = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Hook to load data (i.e. images and fonts) from the game window.
|
17
|
+
def load(resources, params)
|
18
|
+
raise "Load should be overriden by representation"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Register and action that always execute on a game update.
|
22
|
+
def always(&action)
|
23
|
+
@always_actions << action
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Register an action that will execute when the game object sends a notification. Overrides EventListener to
|
28
|
+
# add default registration to game_object.
|
29
|
+
#
|
30
|
+
def listen_to(event_type, &action)
|
31
|
+
super
|
32
|
+
game_object.register_listener(event_type, self)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Register an observer to be invoked every update, after all actions runned. This could be useful when a more complex behavior
|
36
|
+
# is required from the representation, and there is a need to inspect the game object to change some state.
|
37
|
+
#
|
38
|
+
def with_observer(&observer)
|
39
|
+
@observer = observer
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
@always_actions.each { |action| @game_object.instance_eval(&action) }
|
44
|
+
process_inputs(@game_window)
|
45
|
+
@observer.call(self, game_object) unless @observer.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Should be overriden by childs.
|
49
|
+
def draw
|
50
|
+
raise "Draw should be overriden by representation"
|
51
|
+
end
|
52
|
+
|
53
|
+
def act_upon
|
54
|
+
@game_object
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Register itself as a object representation with the shortcut name.
|
60
|
+
#
|
61
|
+
def self.represent(representation_name)
|
62
|
+
Screen.register_representation(representation_name, self)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
class QuadRepresentation < ObjectRepresentation
|
4
|
+
represent :quad
|
5
|
+
attr_accessor :color
|
6
|
+
|
7
|
+
def load(resources, params)
|
8
|
+
raise "Must provide :color param for quad loading" unless params.has_key?(:color)
|
9
|
+
|
10
|
+
@color = params[:color]
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw
|
14
|
+
@game_window.draw_quad(
|
15
|
+
@game_object.x, @game_object.y, @color,
|
16
|
+
@game_object.x + @game_object.width, @game_object.y, @color,
|
17
|
+
@game_object.x, @game_object.y + @game_object.height, @color,
|
18
|
+
@game_object.x + @game_object.width, @game_object.y + @game_object.height, @color)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
class SpriteRepresentation < ObjectRepresentation
|
4
|
+
represent :sprite
|
5
|
+
attr_accessor :flip
|
6
|
+
|
7
|
+
def initialize(game_window, game_object)
|
8
|
+
super(game_window, game_object)
|
9
|
+
@animations = {}
|
10
|
+
@current_animation = nil
|
11
|
+
@next_animation = nil
|
12
|
+
unflip
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(resources, params)
|
16
|
+
raise "Must provide :image param for sprite loading" unless params.has_key?(:image)
|
17
|
+
@sprite_tiles = resources.image(params[:image])
|
18
|
+
end
|
19
|
+
|
20
|
+
def flip
|
21
|
+
@drawing = lambda { |sprite| sprite.draw(@game_object.x + @game_object.width, @game_object.y, 0, -1) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def unflip
|
25
|
+
@drawing = lambda { |sprite| sprite.draw(@game_object.x, @game_object.y, 0) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def change_to(animation_name)
|
29
|
+
@next_animation = animation_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def draw
|
33
|
+
if @current_animation.name != @next_animation && @animations.has_key?(@next_animation)
|
34
|
+
@current_animation = @animations[@next_animation]
|
35
|
+
@current_animation.reset_animation
|
36
|
+
end
|
37
|
+
@drawing.call(@sprite_tiles[@current_animation.frame_index])
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds a new animation to the sprite. The first animation will be set as the current.
|
41
|
+
# A list of options can be specified to customise the behavior:
|
42
|
+
# - :timeout (default is 0.1): time to wait between frames
|
43
|
+
# - :loop (default is true): true if the animation should start from the beginning, after the last element,
|
44
|
+
# otherwise it will draw the last frame
|
45
|
+
def with_animation(name, frame_indexes, options = {})
|
46
|
+
@animations[name] = Animation.new(name, frame_indexes, options)
|
47
|
+
@current_animation = @animations[name] if @current_animation.nil?
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Animation
|
53
|
+
attr_reader :name
|
54
|
+
|
55
|
+
def initialize(name, frame_indexes, options)
|
56
|
+
@name = name
|
57
|
+
@frame_indexes = frame_indexes
|
58
|
+
@options = {:timeout => 0.1, :loop => true}.merge(options)
|
59
|
+
reset_animation
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_animation
|
63
|
+
@current_index = 0
|
64
|
+
@time_since_last_draw = Time.now
|
65
|
+
end
|
66
|
+
|
67
|
+
def frame_index
|
68
|
+
if (Time.now - @time_since_last_draw) > @options[:timeout]
|
69
|
+
@time_since_last_draw = Time.now
|
70
|
+
@current_index += 1
|
71
|
+
if @current_index == @frame_indexes.size
|
72
|
+
@current_index = @options[:loop] ? 0 : @frame_indexes.size - 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@frame_indexes[@current_index]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
class TextRepresentation < ObjectRepresentation
|
4
|
+
represent :text
|
5
|
+
attr_accessor :color
|
6
|
+
|
7
|
+
def load(resources, params)
|
8
|
+
validate(params)
|
9
|
+
|
10
|
+
@font = resources.font(params[:font])
|
11
|
+
@color = params[:color]
|
12
|
+
@text = params[:text] # TODO Or should it get it from the game_object?
|
13
|
+
end
|
14
|
+
|
15
|
+
def draw
|
16
|
+
@font.draw(@text, @game_object.x, @game_object.y, 0, 1, 1, @color)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate(params)
|
22
|
+
[:font, :color, :text].each { |param| raise "Must provide :#{param} param for font loading" unless params.has_key?(param) }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/nimo/screen.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Nimo
|
4
|
+
|
5
|
+
# Represent a game's screen, holding representations of domain objects for updates and draws.
|
6
|
+
# TODO: Add info on how to add 'quad', 'image' (whatever) methods
|
7
|
+
#
|
8
|
+
class Screen
|
9
|
+
include InputListener, EventListener
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
attr_reader :id
|
13
|
+
|
14
|
+
def_delegators :@game_window, :go_to, :open_menu, :close_menu
|
15
|
+
def_delegators :@resources, :images, :sounds, :fonts
|
16
|
+
|
17
|
+
def initialize(id, game_window, resources)
|
18
|
+
@id, @game_window, @resources = id, game_window, resources
|
19
|
+
|
20
|
+
@representations = []
|
21
|
+
@events = {}
|
22
|
+
@timers = []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Register a representation to be called as a method.
|
26
|
+
# Example:
|
27
|
+
# Screen.register_representation(:blah, Blah)
|
28
|
+
# a_screen.blah :for => obj, :with => {:attr => 42} do
|
29
|
+
# # Initialising representation
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
def self.register_representation(representation_name, representation_class)
|
33
|
+
define_method representation_name do |options, &blk|
|
34
|
+
representation = add(representation_class, options)
|
35
|
+
representation.instance_eval &blk if blk
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add a representation to the screen. The options params is used to specify a GameObject and params. It returns the
|
40
|
+
# constructed representation.
|
41
|
+
# Examples of usage:
|
42
|
+
# screen.add(SomeRepresentation, :for => object, :with => { :attr => "something"}) # will construct SomeRepresentation with the supplied object and :with params
|
43
|
+
# screen.add(SomeRepresentation, :with => { :attr => "something"}) # will construct SomeRepresentation with a vanilla Nimo::GameObject
|
44
|
+
#
|
45
|
+
def add(representation_class, options)
|
46
|
+
params = options.has_key?(:with) ? options[:with] : {}
|
47
|
+
game_object = options.has_key?(:for) ? options[:for] : Nimo::GameObject.new(params)
|
48
|
+
|
49
|
+
representation = representation_class.new(@game_window, game_object)
|
50
|
+
representation.load(@resources, params)
|
51
|
+
|
52
|
+
@representations << representation
|
53
|
+
representation
|
54
|
+
end
|
55
|
+
|
56
|
+
# Defines an <tt>action</tt> to be executed after some <tt>seconds</tt>.
|
57
|
+
#
|
58
|
+
def timer_for(seconds, &action)
|
59
|
+
@timers << Timer.new(Time.now.to_f, seconds, action)
|
60
|
+
end
|
61
|
+
|
62
|
+
# :section: Gosu::Window hooks
|
63
|
+
|
64
|
+
# Updates all representations.
|
65
|
+
#
|
66
|
+
def update
|
67
|
+
process_timers
|
68
|
+
process_inputs(@game_window)
|
69
|
+
@representations.each { |representation| representation.update }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Draws all representations.
|
73
|
+
#
|
74
|
+
def draw
|
75
|
+
@representations.each { |representation| representation.draw }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def process_timers
|
81
|
+
@timers.find_all { |timer| timer.time_is_up? }.each do |timer|
|
82
|
+
timer.run_action
|
83
|
+
@timers.delete(timer)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Timer
|
90
|
+
def initialize(start_time, delay_in_seconds, action)
|
91
|
+
@start_time, @delay_in_seconds, @action = start_time, delay_in_seconds, action
|
92
|
+
end
|
93
|
+
|
94
|
+
def time_is_up?
|
95
|
+
(Time.now.to_f - @start_time) > @delay_in_seconds
|
96
|
+
end
|
97
|
+
|
98
|
+
def run_action
|
99
|
+
@action.call
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Helper method to create and show a new GameWindow with a construction block.
|
4
|
+
#
|
5
|
+
# Example, from simplest.rb:
|
6
|
+
#
|
7
|
+
# Nimo::Game("Simplest", 640, 480) do
|
8
|
+
# screen :game do
|
9
|
+
# quad :with => { :width => 20, :height => 20, :color => Gosu::white } do
|
10
|
+
# # Code ommited.
|
11
|
+
# end
|
12
|
+
# when_key(Gosu::Button::KbEscape) { exit }
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
def self.Game(title, width, height, &blk)
|
17
|
+
window = Nimo::GameWindow.new(title, width, height)
|
18
|
+
GameBuilder.new(window).instance_eval(&blk) if block_given?
|
19
|
+
window.show
|
20
|
+
return window
|
21
|
+
end
|
22
|
+
|
23
|
+
class GameBuilder
|
24
|
+
def initialize(game_window)
|
25
|
+
@game_window = game_window
|
26
|
+
@resources = Nimo::Resources.new(@game_window)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Load images that can be referenced by a tag.
|
30
|
+
# Example of <tt>image_definitions</tt>:
|
31
|
+
# images :some_tag => { :filename => "path_to_image.png" } # Load path_to_image.png to be used by the tag :some_tag
|
32
|
+
# images :tile_tag => { :filename => "path_to_tile.png", :tile_dimension => [32, 50] } # Load path_to_tile.png as a tile of width 32 and height 50
|
33
|
+
#
|
34
|
+
def images(image_definitions)
|
35
|
+
@resources.load_images(image_definitions)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Load fonts that can be referenced by a tag.
|
39
|
+
# Example of <tt>font_definitions</tt>:
|
40
|
+
# fonts :some_tag => { :type => "font_type", :size => 20 } # Load font of type 'font_type'
|
41
|
+
#
|
42
|
+
def fonts(font_definitions)
|
43
|
+
@resources.load_fonts(font_definitions)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Load sounds that can be referenced by a tag.
|
47
|
+
# Example of <tt>sound_definitions</tt>:
|
48
|
+
# sounds :some_tag => { :filename => "some_file.wav", :size => 20 }
|
49
|
+
#
|
50
|
+
def sounds(sound_definitions)
|
51
|
+
@resources.load_sounds(sound_definitions)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Register a new screen with the <tt>name</tt>, using the supplied block as the Screen constructor.
|
55
|
+
#
|
56
|
+
def screen(screen_id, &blk)
|
57
|
+
screen = Nimo::Screen.new(screen_id, @game_window, @resources)
|
58
|
+
screen.instance_eval(&blk) if block_given?
|
59
|
+
@game_window.add_screen(screen_id, screen)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Nimo
|
2
|
+
class Intersection
|
3
|
+
|
4
|
+
attr_reader :x, :y, :width, :height
|
5
|
+
|
6
|
+
def initialize(x, y, width, height)
|
7
|
+
@x = x
|
8
|
+
@y = y
|
9
|
+
@width = width
|
10
|
+
@height = height
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.between(obj1, obj2)
|
14
|
+
x = [obj1.x, obj2.x].max
|
15
|
+
y = [obj1.y, obj2.y].max
|
16
|
+
width = [obj1.x + obj1.width, obj2.x + obj2.width].min - x
|
17
|
+
height = [obj1.y + obj1.height, obj2.y + obj2.height].min - y
|
18
|
+
return Intersection.new(x, y, width, height)
|
19
|
+
end
|
20
|
+
|
21
|
+
def collistion_side_for(obj)
|
22
|
+
if @width >= @height
|
23
|
+
return obj.center.y > (@y + (@height/2)) ? :top : :bottom
|
24
|
+
else
|
25
|
+
return obj.center.x > (@x + (@width/2)) ? :left : :right
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
class Object
|
4
|
+
|
5
|
+
# Creates a new object from a hash. The hash keys will became attribute names, and
|
6
|
+
# the respective values will be the attribute's value.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# obj = Object.from_hash({:a => 1, :b => 42})
|
10
|
+
# puts obj.a # 1
|
11
|
+
# puts obj.b # 42
|
12
|
+
#
|
13
|
+
def self.from_hash(hash)
|
14
|
+
OpenStruct.new(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Nimo
|
2
|
+
|
3
|
+
# Resource loader.
|
4
|
+
#
|
5
|
+
class Resources
|
6
|
+
|
7
|
+
attr_reader :images, :fonts, :sounds
|
8
|
+
|
9
|
+
def initialize(game_window)
|
10
|
+
@game_window = game_window
|
11
|
+
@images = {}
|
12
|
+
@fonts = {}
|
13
|
+
@sounds = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_images(image_definitions)
|
17
|
+
image_definitions.each { |tag, definition| @images[tag] ||= create_image_from(definition) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_fonts(font_definitions)
|
21
|
+
font_definitions.each { |tag, definition| @fonts[tag] ||= Gosu::Font.new(@game_window, definition[:type], definition[:size]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_sounds(sound_definitions)
|
25
|
+
sound_definitions.each { |tag, definition| @sounds[tag] ||= Gosu::Song.new(@game_window, definition[:filename]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(meth, *args, &blk)
|
29
|
+
super unless [:image, :sound, :font].include?(meth)
|
30
|
+
validate_and_return_resource(meth, args.shift)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def create_image_from(definition)
|
36
|
+
if definition.has_key?(:tile_dimension)
|
37
|
+
width = definition[:tile_dimension][0]
|
38
|
+
height = definition[:tile_dimension][1]
|
39
|
+
return Gosu::Image.load_tiles(@game_window, definition[:filename], width, height, false)
|
40
|
+
end
|
41
|
+
return Gosu::Image.new(@game_window, definition[:filename], 0)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_and_return_resource(resource_type, tag)
|
45
|
+
resource_list = self.send("#{resource_type.to_s}s")
|
46
|
+
raise "No #{resource_type.to_s} resource named '#{tag}'" unless resource_list.has_key?(tag)
|
47
|
+
resource_list[tag]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|