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.
Files changed (57) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +15 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/examples/bouncy.rb +101 -0
  8. data/examples/eventy.rb +19 -0
  9. data/examples/images/dg_classm32.png +0 -0
  10. data/examples/images/dg_dungeon32.png +0 -0
  11. data/examples/images/jeeklabs.png +0 -0
  12. data/examples/images/tcheco.png +0 -0
  13. data/examples/imagey.rb +82 -0
  14. data/examples/screeny.rb +80 -0
  15. data/examples/simplest.rb +23 -0
  16. data/examples/sounds/KDE-Sys-Log-Out.ogg +0 -0
  17. data/examples/sounds/k3b_error1.wav +0 -0
  18. data/examples/soundy.rb +40 -0
  19. data/examples/spritey.rb +90 -0
  20. data/lib/nimo.rb +35 -0
  21. data/lib/nimo/behavior/deflector.rb +39 -0
  22. data/lib/nimo/behavior/moveable.rb +33 -0
  23. data/lib/nimo/behavior/projectile.rb +23 -0
  24. data/lib/nimo/behavior/with_velocity.rb +28 -0
  25. data/lib/nimo/game_object.rb +58 -0
  26. data/lib/nimo/game_window.rb +59 -0
  27. data/lib/nimo/listeners/event_listener.rb +27 -0
  28. data/lib/nimo/listeners/input_listener.rb +70 -0
  29. data/lib/nimo/representations/image_representation.rb +18 -0
  30. data/lib/nimo/representations/object_representation.rb +67 -0
  31. data/lib/nimo/representations/quad_representation.rb +23 -0
  32. data/lib/nimo/representations/sprite_representation.rb +78 -0
  33. data/lib/nimo/representations/text_representation.rb +27 -0
  34. data/lib/nimo/screen.rb +103 -0
  35. data/lib/nimo/utils/game.rb +63 -0
  36. data/lib/nimo/utils/intersection.rb +30 -0
  37. data/lib/nimo/utils/object_extension.rb +17 -0
  38. data/lib/nimo/utils/resources.rb +51 -0
  39. data/nimo.gemspec +117 -0
  40. data/spec/nimo/behavior/deflector_spec.rb +51 -0
  41. data/spec/nimo/behavior/moveable_spec.rb +81 -0
  42. data/spec/nimo/behavior/projectile_spec.rb +34 -0
  43. data/spec/nimo/behavior/with_velocity_spec.rb +18 -0
  44. data/spec/nimo/game_object_spec.rb +169 -0
  45. data/spec/nimo/game_window_spec.rb +88 -0
  46. data/spec/nimo/listeners/event_listener_spec.rb +34 -0
  47. data/spec/nimo/listeners/input_listener_spec.rb +87 -0
  48. data/spec/nimo/representations/image_representation_spec.rb +41 -0
  49. data/spec/nimo/representations/object_representation_spec.rb +74 -0
  50. data/spec/nimo/representations/sprite_representation_spec.rb +73 -0
  51. data/spec/nimo/representations/text_representation_spec.rb +21 -0
  52. data/spec/nimo/screen_spec.rb +114 -0
  53. data/spec/nimo/utils/game_spec.rb +44 -0
  54. data/spec/nimo/utils/object_extension_spec.rb +21 -0
  55. data/spec/nimo/utils/resources_spec.rb +59 -0
  56. data/spec/spec_helper.rb +9 -0
  57. 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
@@ -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