chingu 0.7.0 → 0.7.5

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 (58) hide show
  1. data/History.txt +1 -1
  2. data/README.rdoc +110 -49
  3. data/benchmarks/game_objects_benchmark.rb +50 -0
  4. data/chingu.gemspec +24 -12
  5. data/examples/example10_traits_retrofy.rb +6 -4
  6. data/examples/example11_animation.rb +14 -23
  7. data/examples/example12_trait_timer.rb +1 -1
  8. data/examples/example13_high_scores.rb +1 -1
  9. data/examples/example14_bounding_box_circle.rb +6 -10
  10. data/examples/example15_trait_timer2.rb +1 -1
  11. data/examples/example16_online_high_scores.rb +1 -1
  12. data/examples/example17_gosu_tutorial.rb +4 -4
  13. data/examples/example18_animation_trait.rb +98 -0
  14. data/examples/example19_edit_viewport.rb +223 -0
  15. data/examples/example19_game_objects.yml +1591 -0
  16. data/examples/example20_trait_inheritence_test.rb +58 -0
  17. data/examples/example3_parallax.rb +1 -1
  18. data/examples/example4_gamestates.rb +1 -1
  19. data/examples/example7_gfx_helpers.rb +14 -9
  20. data/examples/example8_traits.rb +4 -4
  21. data/examples/example9_collision_detection.rb +1 -1
  22. data/examples/game1.rb +33 -38
  23. data/examples/game_of_life.rb +291 -0
  24. data/examples/media/droid_11x15.bmp +0 -0
  25. data/examples/media/droid_11x15.gal +0 -0
  26. data/examples/media/heli.bmp +0 -0
  27. data/examples/media/heli.gal +0 -0
  28. data/examples/media/star_25x25_default.png +0 -0
  29. data/examples/media/star_25x25_explode.gal +0 -0
  30. data/examples/media/star_25x25_explode.png +0 -0
  31. data/examples/media/stone_wall.bmp +0 -0
  32. data/lib/chingu.rb +1 -1
  33. data/lib/chingu/animation.rb +78 -9
  34. data/lib/chingu/basic_game_object.rb +16 -8
  35. data/lib/chingu/game_object.rb +36 -7
  36. data/lib/chingu/game_object_list.rb +20 -3
  37. data/lib/chingu/game_state.rb +8 -7
  38. data/lib/chingu/game_states/edit.rb +177 -90
  39. data/lib/chingu/helpers/class_inheritable_accessor.rb +12 -5
  40. data/lib/chingu/helpers/game_object.rb +45 -4
  41. data/lib/chingu/helpers/gfx.rb +150 -172
  42. data/lib/chingu/helpers/input_client.rb +7 -0
  43. data/lib/chingu/inflector.rb +16 -2
  44. data/lib/chingu/traits/animation.rb +84 -0
  45. data/lib/chingu/traits/bounding_box.rb +16 -3
  46. data/lib/chingu/traits/bounding_circle.rb +18 -4
  47. data/lib/chingu/traits/collision_detection.rb +10 -1
  48. data/lib/chingu/traits/velocity.rb +26 -3
  49. data/lib/chingu/traits/viewport.rb +10 -9
  50. data/lib/chingu/viewport.rb +103 -22
  51. data/lib/chingu/window.rb +8 -2
  52. metadata +46 -16
  53. data/examples/example18_viewport.rb +0 -173
  54. data/examples/media/city1.csv +0 -2
  55. data/examples/media/plane.csv +0 -2
  56. data/examples/media/saucer.csv +0 -4
  57. data/examples/media/stickfigure.bmp +0 -0
  58. data/examples/media/stickfigure.png +0 -0
Binary file
Binary file
@@ -33,7 +33,7 @@ require_all "#{CHINGU_ROOT}/chingu/traits"
33
33
  require_all "#{CHINGU_ROOT}/chingu"
34
34
 
35
35
  module Chingu
36
- VERSION = "0.7.0"
36
+ VERSION = "0.7.5"
37
37
 
38
38
  DEBUG_COLOR = Gosu::Color.new(0xFFFF0000)
39
39
  DEBUG_ZORDER = 9999
@@ -37,20 +37,69 @@ module Chingu
37
37
  @step = options[:step] || 1
38
38
  @dt = 0
39
39
 
40
+ @sub_animations = {}
41
+ @frame_actions = []
42
+ @file = media_path(@file) if @file && !File.exist?(@file)
43
+
44
+ #
45
+ # Various ways of determening the framesize
46
+ #
40
47
  if options[:size] && options[:size].is_a?(Array)
41
48
  @width = options[:size][0]
42
49
  @height = options[:size][1]
43
50
  elsif options[:size]
44
51
  @width = options[:size]
45
52
  @height = options[:size]
53
+ elsif @file =~ /_(\d+)x(\d+)/
54
+ # Auto-detect width/height from filename
55
+ # Tilefile foo_10x25.png would mean frame width 10px and height 25px
56
+ @width = $1.to_i
57
+ @height = $2.to_i
58
+ else
59
+ # Assume the shortest side is the width/height for each frame
60
+ @image = Gosu::Image.new($window, @file)
61
+ @width = @height = (@image.width < @image.height) ? @image.width : @image.height
46
62
  end
47
63
 
48
- @file = media_path(@file) unless File.exist?(@file)
49
-
50
- @frame_actions = []
51
64
  @frames = Gosu::Image.load_tiles($window, @file, @width, @height, true)
52
65
  end
53
66
 
67
+ #
68
+ # Remove transparent space from each frame so the actual sprite is touching the border of the image.
69
+ #
70
+ def trim
71
+ @frames.each do |frame|
72
+ # TODO!
73
+ end
74
+ end
75
+
76
+ #
77
+ # Put name on specific ranges of frames.
78
+ # Eg. name_frames(:default => 0..3, :explode => 3..8)
79
+ #
80
+ # Can then be accessed with @animation[:explode]
81
+ #
82
+ def frame_names=(names)
83
+ names.each do |key, value|
84
+ @sub_animations[key] = self.new_from_frames(value) if value.is_a? Range
85
+ #
86
+ # TODO: Add support for [1,4,5] array frame selection
87
+ #
88
+ # @frame_names[key] = self.new_from_frames(value) if value.is_a? Array
89
+ end
90
+ end
91
+
92
+ #
93
+ # Return frame names in { :name => range } -form
94
+ #
95
+ def frame_names
96
+ @frame_names
97
+ end
98
+
99
+ def animations
100
+ @sub_animations.keys
101
+ end
102
+
54
103
  #
55
104
  # Returns first frame (GOSU::Image) from animation
56
105
  #
@@ -64,18 +113,37 @@ module Chingu
64
113
  def last
65
114
  @frames.last
66
115
  end
116
+
117
+ #
118
+ # Returns true if the current frame is the last
119
+ #
120
+ def last_frame?
121
+ @previous_index == @index
122
+ end
67
123
 
68
124
  #
69
125
  # Fetch a frame or frames:
70
126
  #
71
- # @animation[0] # returns first frame
72
- # @animation[0..2] # returns a new Animation-instance with first, second and third frame
127
+ # @animation[0] # returns first frame
128
+ # @animation[0..2] # returns a new Animation-instance with first, second and third frame
129
+ # @animation[:explode] # returns a cached Animation-instance with frames earlier set with @animation.frame_names = { ... }
73
130
  #
74
131
  def [](index)
75
132
  return @frames[index] if index.is_a?(Fixnum)
76
133
  return self.new_from_frames(index) if index.is_a?(Range)
134
+ return @sub_animations[index] if index.is_a?(Symbol)
77
135
  end
78
-
136
+
137
+ #
138
+ # Manually initialize a frame-range with an Animation-instance
139
+ #
140
+ # @animation[:scan] = Animation.new(...)
141
+ #
142
+ def []=(name, animation)
143
+ @sub_animations[name] = animation
144
+ return @sub_animations[name]
145
+ end
146
+
79
147
  #
80
148
  # Get the current frame (a Gosu#Image)
81
149
  #
@@ -96,7 +164,7 @@ module Chingu
96
164
  # Returns a new animation with the frames from the original animation.
97
165
  # Specify which frames you want with "range", for example "0..3" for the 4 first frames.
98
166
  #
99
- def new_from_frames(range)
167
+ def new_from_frames(range)
100
168
  new_animation = self.dup
101
169
  new_animation.frames = []
102
170
  range.each do |nr|
@@ -104,12 +172,13 @@ module Chingu
104
172
  end
105
173
  return new_animation
106
174
  end
107
-
175
+
108
176
  #
109
177
  # Propelles the animation forward. Usually called in #update within the class which holds the animation.
110
178
  # Animation#next() will look at bounce and loop flags to always return a correct frame (a Gosu#Image)
111
179
  #
112
- def next
180
+ def next(recursion = true)
181
+
113
182
  if (@dt += $window.milliseconds_since_last_tick) > @delay
114
183
  @dt = 0
115
184
  @previous_index = @index
@@ -20,10 +20,10 @@ module Chingu
20
20
  # Adds a trait or traits to a certain game class
21
21
  # Executes a standard ruby "include" the specified module
22
22
  #
23
- def self.has_trait(trait, options = {})
23
+ def self.trait(trait, options = {})
24
24
 
25
25
  if trait.is_a?(::Symbol) || trait.is_a?(::String)
26
- ## puts "has_trait #{trait}, #{options}"
26
+ ## puts "trait #{trait}, #{options}"
27
27
  begin
28
28
  # Convert user-given symbol (eg. :timer) to a Module (eg. Chingu::Traits::Timer)
29
29
  mod = Chingu::Traits.const_get(Chingu::Inflector.camelize(trait))
@@ -39,7 +39,7 @@ module Chingu
39
39
  extend mod2
40
40
 
41
41
  # If the newly included trait has a initialize_trait method in the ClassMethods-scope:
42
- # ... call it with the options provided with the has_trait-line.
42
+ # ... call it with the options provided with the trait-line.
43
43
  if mod2.method_defined?(:initialize_trait)
44
44
  initialize_trait(options)
45
45
  end
@@ -49,10 +49,17 @@ module Chingu
49
49
  end
50
50
  end
51
51
  end
52
+ class << self; alias :has_trait :trait; end
52
53
 
53
- def self.has_traits(*traits)
54
- Array(traits).each { |trait| has_trait trait }
54
+ def self.traits(*traits)
55
+ Array(traits).each { |trait_name| trait trait_name }
55
56
  end
57
+ class << self; alias :has_traits :traits; end
58
+
59
+ #def self.inherited(subclass)
60
+ # subclass.initialize_inherited_trait if subclass.method_defined?(:initialize_inherited_trait)
61
+ #end
62
+
56
63
 
57
64
  #
58
65
  # BasicGameObject initialize
@@ -78,7 +85,7 @@ module Chingu
78
85
 
79
86
  # This will call #setup_trait on the latest trait mixed in
80
87
  # which then will pass it on to the next setup_trait() with a super-call.
81
- setup_trait(options)
88
+ setup_trait(options)
82
89
  end
83
90
 
84
91
  #
@@ -90,8 +97,8 @@ module Chingu
90
97
  # Chingus "game_objects" which is available in all game states and the main window.
91
98
  #
92
99
  #def self.create(options = {})
93
- def self.create(*options)
94
- instance = self.new(*options)
100
+ def self.create(*options, &block)
101
+ instance = self.new(*options, &block)
95
102
 
96
103
  #
97
104
  # Add to parents list of game objects
@@ -157,6 +164,7 @@ module Chingu
157
164
  #
158
165
  def self.initialize_trait(options); end
159
166
  def setup_trait(options); end
167
+ def setup; end
160
168
  def update_trait; end
161
169
  def draw_trait; end
162
170
  def update; end
@@ -39,19 +39,27 @@ module Chingu
39
39
 
40
40
  def initialize(options = {})
41
41
  super
42
-
43
- # All encapsulated draw_rot arguments can be set with hash-options at creation time
42
+
43
+ #
44
+ # All encapsulated Gosu::Image.draw_rot arguments can be set with hash-options at creation time
45
+ #
44
46
  if options[:image].is_a?(Gosu::Image)
45
47
  @image = options[:image]
46
48
  elsif options[:image].is_a? String
47
- @image = Gosu::Image[options[:image]]
49
+ begin
50
+ # 1) Try loading the image the normal way
51
+ @image = Gosu::Image.new($window, options[:image])
52
+ rescue
53
+ # 2) Try looking up the picture using Chingus Image-cache
54
+ @image = Gosu::Image[options[:image]]
55
+ end
48
56
  end
49
57
 
50
58
  @x = options[:x] || 0
51
59
  @y = options[:y] || 0
52
60
  @angle = options[:angle] || 0
53
61
 
54
- self.factor = options[:factor] || options[:scale] || 1.0
62
+ self.factor = options[:factor] || options[:scale] || $window.factor || 1.0
55
63
  @factor_x = options[:factor_x] if options[:factor_x]
56
64
  @factor_y = options[:factor_y] if options[:factor_y]
57
65
 
@@ -73,6 +81,12 @@ module Chingu
73
81
 
74
82
  @mode = options[:mode] || :default # :additive is also available.
75
83
  @zorder = options[:zorder] || 100
84
+
85
+ ### super ## This crashes
86
+ # Call setup, this class holds an empty setup() to be overriden
87
+ # setup() will be an easier method to override for init-stuff since you don't need to do super etc..
88
+ setup
89
+
76
90
  end
77
91
 
78
92
  # Quick way of setting both factor_x and factor_y
@@ -117,7 +131,16 @@ module Chingu
117
131
  def distance_to(object)
118
132
  distance(self.x, self.y, object.x, object.y)
119
133
  end
120
-
134
+
135
+ #
136
+ # Returns a filename-friendly string from the current class-name
137
+ #
138
+ # "FireBall" -> "fire_ball"
139
+ #
140
+ def filename
141
+ Chingu::Inflector.underscore(self.class.to_s)
142
+ end
143
+
121
144
  #
122
145
  # Our encapsulation of GOSU's image.draw_rot, uses the objects variables to draw it on screen if @visible is true
123
146
  #
@@ -126,11 +149,17 @@ module Chingu
126
149
  end
127
150
 
128
151
  #
129
- # Works as #draw() but takes offsets for all draw_rot()-arguments. Used among others by by viewport-trait
152
+ # Works as #draw() but takes offsets for all draw_rot()-arguments. Used among others by the viewport-trait.
130
153
  #
131
154
  def draw_relative(x=0, y=0, zorder=0, angle=0, center_x=0, center_y=0, factor_x=0, factor_y=0)
132
155
  @image.draw_rot(@x+x, @y+y, @zorder+zorder, @angle+angle, @center_x+center_x, @center_y+center_y, @factor_x+factor_x, @factor_y+factor_y, @color, @mode) if @visible
133
156
  end
134
-
157
+
158
+ #
159
+ # Works as #draw() but takes x/y arguments. Used among others by the edit-game state.
160
+ #
161
+ def draw_at(x, y)
162
+ @image.draw_rot(x, y, @zorder, @angle, @center_x, @center_y, @factor_x, @factor_y, @color, @mode) if @visible
163
+ end
135
164
  end
136
165
  end
@@ -29,6 +29,8 @@ module Chingu
29
29
 
30
30
  def initialize(options = {})
31
31
  @game_objects = options[:game_objects] || []
32
+ @add_game_objects = []
33
+ @remove_game_objects = []
32
34
  #@game_objects_by_class = Hash.new
33
35
  end
34
36
 
@@ -49,12 +51,17 @@ module Chingu
49
51
  alias :remove_all :destroy_all
50
52
 
51
53
  def add_game_object(object)
52
- @game_objects.push(object)
54
+ #@game_objects.push(object)
55
+ @add_game_objects.push(object)
56
+
57
+
53
58
  #(@game_objects_by_class[object.class] ||= []).push(object)
54
59
  end
55
60
 
56
61
  def remove_game_object(object)
57
- @game_objects.delete(object)
62
+ #@game_objects.delete(object)
63
+ @remove_game_objects.push(object)
64
+
58
65
  #@game_objects_by_class[object.class].delete(object)
59
66
  end
60
67
 
@@ -80,8 +87,18 @@ module Chingu
80
87
  object.draw_relative(x, y, zorder, angle, center_x, center_y, factor_x, factor_y)
81
88
  end
82
89
  end
83
-
90
+
91
+ def sync
92
+ @game_objects += @add_game_objects
93
+ @add_game_objects.clear
94
+
95
+ @game_objects -= @remove_game_objects
96
+ @remove_game_objects.clear
97
+ end
98
+
84
99
  def update
100
+ sync
101
+
85
102
  @game_objects.select{ |object| not object.paused }.each do |object|
86
103
  object.update_trait
87
104
  object.update
@@ -57,7 +57,7 @@ module Chingu
57
57
  include Chingu::Helpers::ClassInheritableAccessor # adds classmethod class_inheritable_accessor
58
58
 
59
59
  attr_reader :options
60
- attr_accessor :game_state_manager, :game_objects
60
+ attr_accessor :game_objects, :game_state_manager
61
61
 
62
62
  class_inheritable_accessor :trait_options
63
63
  @trait_options = Hash.new
@@ -67,10 +67,10 @@ module Chingu
67
67
  # Adds a trait or traits to a certain game class
68
68
  # Executes a standard ruby "include" the specified module
69
69
  #
70
- def self.has_trait(trait, options = {})
70
+ def self.trait(trait, options = {})
71
71
 
72
72
  if trait.is_a?(::Symbol) || trait.is_a?(::String)
73
- ## puts "has_trait #{trait}, #{options}"
73
+ ## puts "trait #{trait}, #{options}"
74
74
  begin
75
75
  # Convert user-given symbol (eg. :timer) to a Module (eg. Chingu::Traits::Timer)
76
76
  mod = Chingu::Traits.const_get(Chingu::Inflector.camelize(trait))
@@ -86,7 +86,7 @@ module Chingu
86
86
  extend mod2
87
87
 
88
88
  # If the newly included trait has a initialize_trait method in the ClassMethods-scope:
89
- # ... call it with the options provided with the has_trait-line.
89
+ # ... call it with the options provided with the trait-line.
90
90
  if mod2.method_defined?(:initialize_trait)
91
91
  initialize_trait(options)
92
92
  end
@@ -96,11 +96,12 @@ module Chingu
96
96
  end
97
97
  end
98
98
  end
99
+ class << self; alias :has_trait :trait; end
99
100
 
100
- def self.has_traits(*traits)
101
- Array(traits).each { |trait| has_trait trait }
101
+ def self.traits(*traits)
102
+ Array(traits).each { |trait| trait trait }
102
103
  end
103
-
104
+ class << self; alias :has_traits :traits; end
104
105
 
105
106
  def initialize(options = {})
106
107
  @options = options
@@ -23,52 +23,80 @@ module Chingu
23
23
  module GameStates
24
24
 
25
25
  #
26
- # Premade game state for chingu - A simple pause state.
27
- # Pause whenever with:
28
- # push_game_state(Chingu::GameStates::Pause)
26
+ # Premade game state for chingu - simple level editing.
27
+ # Start editing in a gamestate with:
28
+ # push_game_state(Chingu::GameStates::Edit)
29
29
  #
30
30
  # requires the global $window set to the instance of Gosu::Window (automaticly handled if you use Chingu::Window)
31
31
  #
32
32
  class Edit < Chingu::GameState
33
+
33
34
  def initialize(options = {})
34
35
  super
35
- @grid = options[:grid]
36
+ @grid = options[:grid] || [8,8]
36
37
  @classes = options[:classes] || []
38
+ @only = options[:only] || []
39
+ @debug = options[:debug]
40
+ @file = options[:file] || "#{previous_game_state.class.to_s.downcase}.yml"
41
+ @zorder = 10000
37
42
 
38
- @color = Gosu::Color.new(200,0,0,0)
39
- @red = Gosu::Color.new(0xFFFF0000)
40
- @white = Gosu::Color.new(0xFFFFFFFF)
43
+ @hud_color = Gosu::Color.new(150,100,100,100)
41
44
  @selected_game_object = nil
42
45
  self.input = { :left_mouse_button => :left_mouse_button,
43
46
  :released_left_mouse_button => :released_left_mouse_button,
47
+ :right_mouse_button => :right_mouse_button,
48
+ :released_right_mouse_button => :released_right_mouse_button,
44
49
  :delete => :destroy_selected_game_objects,
45
50
  :backspace => :destroy_selected_game_objects,
46
51
  :e => :save_and_quit,
47
52
  :s => :save,
48
- :esc => :quit,
53
+ :esc => :save_and_quit,
54
+ :holding_up_arrow => :scroll_up,
55
+ :holding_down_arrow => :scroll_down,
56
+ :holding_left_arrow => :scroll_left,
57
+ :holding_right_arrow => :scroll_right,
58
+ :page_up => :page_up,
59
+ :page_down => :page_down,
49
60
  :"1" => :create_object_1,
50
61
  :"2" => :create_object_2,
51
62
  :"3" => :create_object_3,
52
63
  :"4" => :create_object_4,
53
64
  :"5" => :create_object_5,
54
65
  }
55
- end
56
-
57
- def setup
58
- name = if defined?(previous_game_state.filename)
59
- previous_game_state.filename
60
- else
61
- "#{previous_game_state.class.to_s.downcase}.yml"
66
+
67
+ x = 20
68
+ y = 60
69
+ @classes.each do |klass|
70
+ puts "Creating a #{klass}" if @debug
71
+
72
+ # We initialize x,y,zorder,rotation_center after creation
73
+ # so they're not overwritten by the class initialize/setup or simular
74
+ game_object = klass.create
75
+ game_object.x = x
76
+ game_object.y = y
77
+ game_object.zorder = @zorder
78
+
79
+ # Scale down big objects, don't scale objects under [32, 32]
80
+ if game_object.image
81
+ game_object.factor_x = 32 / game_object.image.width if game_object.image.width > 32
82
+ game_object.factor_y = 32 / game_object.image.height if game_object.image.height > 32
83
+ end
84
+ x += 40
62
85
  end
63
- @filename = File.join($window.root, name)
64
- @title = Text.create("File: #{@filename}", :x => 5, :y => 10)
86
+ end
87
+
88
+ def setup
89
+ @title = Text.create("File: #{@file}", :x => 5, :y => 2, :factor => 1, :size => 16, :zorder => @zorder)
65
90
  @title.text += " - Grid: #{@grid}" if @grid
66
- @title2 = Text.create("(1-10) Create object at mouse pos (DEL) Delete selected object (S) Save (E) Save and Quit (ESC) Quit without saving", :x => 5, :y => 30)
67
- @text = Text.create("", :x => 5, :y => 50)
91
+ @text = Text.create("", :x => 200, :y => 20, :factor => 1, :size => 16, :zorder => @zorder)
92
+ @status_text = Text.create("-", :x => 5, :y => 20, :factor => 1, :size => 16, :zorder => @zorder)
93
+ #@title2 = Text.create("(1-10) Create object at mouse pos (DEL) Delete selected object (S) Save (E) Save and Quit (ESC) Quit without saving", :x => 5, :y => 30, :factor => 1)
68
94
  end
69
95
 
70
96
  def create_object_nr(number)
71
- @classes[number].create(:x => $window.mouse_x, :y => $window.mouse_y, :parent => previous_game_state) if @classes[number]
97
+ c = @classes[number].create(:x => x, :y => y, :parent => previous_game_state) if @classes[number]
98
+ c.update
99
+ #@text.text = "Created a #{c.class} @ #{c.x} / #{c.y}"
72
100
  end
73
101
 
74
102
  def create_object_1; create_object_nr(0); end
@@ -77,48 +105,61 @@ module Chingu
77
105
  def create_object_4; create_object_nr(3); end
78
106
  def create_object_5; create_object_nr(4); end
79
107
 
80
-
81
-
82
108
  def draw
83
- # Draw prev game state onto screen (the level we're editing for example)
84
- previous_game_state.draw
109
+ # Draw prev game state onto screen (the level we're editing)
110
+ previous_game_state.draw
85
111
 
86
- #
87
- # Draw an edit HUD
88
- #
89
- $window.draw_quad( 0,0,@color,
90
- $window.width,0,@color,
91
- $window.width,100,@color,
92
- 0,100,@color,10)
93
- #
94
- # Draw debug Texts etc..
95
- #
96
112
  super
97
113
 
98
114
  #
99
- # Draw a red rectangle around all selected game objects
115
+ # Draw an edit HUD
100
116
  #
101
- selected_game_objects.each do |game_object|
102
- draw_rect(game_object.bounding_box.inflate(2,2), @red, 999)
103
- end
117
+ $window.draw_quad( 0,0,@hud_color,
118
+ $window.width,0,@hud_color,
119
+ $window.width,100,@hud_color,
120
+ 0,100,@hud_color, @zorder-1)
104
121
 
105
122
  #
106
- # draw a simple triagle-shaped cursor
123
+ # Draw red rectangles/circles around all selected game objects
107
124
  #
108
- $window.draw_triangle( $window.mouse_x, $window.mouse_y, @white,
109
- $window.mouse_x, $window.mouse_y + 10, @white,
110
- $window.mouse_x + 10, $window.mouse_y + 10, @white, 9999)
125
+ selected_game_objects.each { |game_object| game_object.draw_debug }
111
126
 
127
+ if @cursor_game_object
128
+ @cursor_game_object.draw_at($window.mouse_x, $window.mouse_y)
129
+ else
130
+ #
131
+ # draw a simple triagle-shaped cursor
132
+ #
133
+ $window.draw_triangle( $window.mouse_x, $window.mouse_y, Color::WHITE,
134
+ $window.mouse_x, $window.mouse_y + 10, Color::WHITE,
135
+ $window.mouse_x + 10, $window.mouse_y + 10, Color::WHITE, @zorder + 10)
136
+ end
112
137
  end
113
138
 
114
139
  def update
140
+ # Sync all changes to previous game states game objects list
141
+ # This is needed since we don't call update on it.
142
+ previous_game_state.game_objects.sync
143
+
115
144
  super
116
145
 
146
+ #
147
+ # We got a selected game object
148
+ #
149
+ if @selected_game_object
150
+ @text.text = "#{@selected_game_object.class.to_s} @ #{@selected_game_object.x} / #{@selected_game_object.y} - zorder: #{@selected_game_object.zorder}"
151
+ end
152
+
153
+ #
154
+ # We got a selected game object and the left mouse button is held down
155
+ #
117
156
  if @left_mouse_button && @selected_game_object
118
- @selected_game_object.x = ($window.mouse_x + @mouse_x_offset) / $window.factor
119
- @selected_game_object.y = ($window.mouse_y + @mouse_y_offset) / $window.factor
120
- @selected_game_object.x -= @selected_game_object.x % @grid[0]
121
- @selected_game_object.y -= @selected_game_object.y % @grid[1]
157
+ selected_game_objects.each do |selected_game_object|
158
+ selected_game_object.x = self.x + selected_game_object.options[:mouse_x_offset]
159
+ selected_game_object.y = self.y + selected_game_object.options[:mouse_y_offset]
160
+ selected_game_object.x -= selected_game_object.x % @grid[0]
161
+ selected_game_object.y -= selected_game_object.y % @grid[1]
162
+ end
122
163
 
123
164
  # TODO: better cleaner sollution
124
165
  if @selected_game_object.respond_to?(:bounding_box)
@@ -126,6 +167,8 @@ module Chingu
126
167
  @selected_game_object.bounding_box.y = @selected_game_object.y
127
168
  end
128
169
  end
170
+
171
+ @status_text.text = "Mouseposition: #{x} / #{y}"
129
172
  end
130
173
 
131
174
  def selected_game_objects
@@ -136,69 +179,76 @@ module Chingu
136
179
  selected_game_objects.each(&:destroy)
137
180
  end
138
181
 
182
+ #
183
+ # CLICKED LEFT MOUSE BUTTON
184
+ #
139
185
  def left_mouse_button
140
186
  @left_mouse_button = true
141
- x = $window.mouse_x / $window.factor
142
- y = $window.mouse_y / $window.factor
143
-
144
- @text.text = "Click @ #{x} / #{y}"
145
187
 
146
- #
147
- # Deselect all objects
148
- #
149
- selected_game_objects.each do |game_object|
150
- game_object.options[:selected] = false
188
+ if @cursor_game_object && game_object_at(x, y)==nil && game_object_icon_at($window.mouse_x, $window.mouse_y) == nil
189
+ game_object = @cursor_game_object.class.create(:parent => previous_game_state)
190
+ game_object.update
191
+ game_object.options[:selected] = true
192
+ game_object.x = x
193
+ game_object.y = y
151
194
  end
152
195
 
153
- #
154
- # Get new object that was clicked at (if any)
155
- #
196
+ # Get editable game object that was clicked at (if any)
156
197
  @selected_game_object = game_object_at(x, y)
157
198
 
199
+ # Check if user clicked on anything in the icon-toolbar of available game objects
200
+ @cursor_game_object = game_object_icon_at($window.mouse_x, $window.mouse_y)
201
+
158
202
  if @selected_game_object
159
- @text.text = "#{@text.text} : #{@selected_game_object.class.to_s}"
160
- @selected_game_object.options[:selected] = true
203
+ #
204
+ # If clicking on a new object that's wasn't previosly selected
205
+ # --> deselect all objects unless holding left_ctrl
206
+ #
207
+ if @selected_game_object.options[:selected] == nil
208
+ selected_game_objects.each { |x| x.options[:selected] = nil } unless holding?(:left_ctrl)
209
+ end
161
210
 
162
- @mouse_x_offset = @selected_game_object.x - $window.mouse_x
163
- @mouse_y_offset = @selected_game_object.y - $window.mouse_y
211
+ @selected_game_object.options[:selected] = true
212
+ #
213
+ # Re-align all objects x/y offset in relevance to the cursor
214
+ #
215
+ selected_game_objects.each do |selected_game_object|
216
+ selected_game_object.options[:mouse_x_offset] = selected_game_object.x - self.x
217
+ selected_game_object.options[:mouse_y_offset] = selected_game_object.y - self.y
218
+ end
219
+ else
220
+ selected_game_objects.each { |x| x.options[:selected] = nil } unless holding?(:left_ctrl)
164
221
  end
165
-
166
222
  end
167
223
 
168
- def released_left_mouse_button
169
- @left_mouse_button = false
170
- @selected_game_object = false
224
+ def right_mouse_button
225
+ @cursor_game_object = game_object_at(x, y)
226
+ selected_game_objects.each { |x| x.options[:selected] = nil }
227
+ end
228
+ def released_right_mouse_button
171
229
  end
172
230
 
231
+ #
232
+ # RELASED LEFT MOUSE BUTTON
233
+ #
234
+ def released_left_mouse_button
235
+ @left_mouse_button = false
236
+ end
237
+
238
+ def game_object_icon_at(x, y)
239
+ game_objects.select do |game_object|
240
+ game_object.respond_to?(:collision_at?) && game_object.collision_at?(x,y)
241
+ end.first
242
+ end
243
+
173
244
  def game_object_at(x, y)
174
245
  previous_game_state.game_objects.select do |game_object|
175
- game_object.respond_to?(:bounding_box) && game_object.bounding_box.collide_point?(x,y)
246
+ game_object.respond_to?(:collision_at?) && game_object.collision_at?(x,y)
176
247
  end.first
177
248
  end
178
249
 
179
-
180
250
  def save
181
- require 'yaml'
182
- objects = []
183
- previous_game_state.game_objects.each do |game_object|
184
- objects << {game_object.class.to_s =>
185
- {
186
- :x => game_object.x,
187
- :y => game_object.y,
188
- :angle => game_object.angle,
189
- :zorder => game_object.zorder,
190
- :factor_x => game_object.factor_x,
191
- :factor_y => game_object.factor_y,
192
- :center_x => game_object.center_x,
193
- :center_y => game_object.center_y,
194
- }
195
- }
196
- end
197
-
198
- #Marshal.dump(previous_game_state.game_objects, File.open(@filename, "w"))
199
- File.open(@filename, 'w') do |out|
200
- YAML.dump(objects, out)
201
- end
251
+ save_game_objects(:game_objects => previous_game_state.game_objects, :file => @file, :classes => @classes)
202
252
  end
203
253
 
204
254
  def save_and_quit
@@ -209,13 +259,50 @@ module Chingu
209
259
  def quit
210
260
  pop_game_state(:setup => false)
211
261
  end
262
+
263
+ def game_object_classes
264
+ ObjectSpace.enum_for(:each_object, class << GameObject; self; end).to_a.select do |game_class|
265
+ game_class.instance_methods
266
+ end
267
+ end
212
268
 
269
+ def page_up
270
+ selected_game_objects.each { |game_object| game_object.zorder += 1 }
271
+ #self.previous_game_state.viewport.y -= $window.height if defined?(self.previous_game_state.viewport)
272
+ end
273
+ def page_down
274
+ selected_game_objects.each { |game_object| game_object.zorder -= 1 }
275
+ #self.previous_game_state.viewport.y += $window.height if defined?(self.previous_game_state.viewport)
276
+ end
277
+ def scroll_up
278
+ self.previous_game_state.viewport.y -= 10 if defined?(self.previous_game_state.viewport)
279
+ end
280
+ def scroll_down
281
+ self.previous_game_state.viewport.y += 10 if defined?(self.previous_game_state.viewport)
282
+ end
283
+ def scroll_left
284
+ self.previous_game_state.viewport.x -= 10 if defined?(self.previous_game_state.viewport)
285
+ end
286
+ def scroll_right
287
+ self.previous_game_state.viewport.x += 10 if defined?(self.previous_game_state.viewport)
288
+ end
289
+ def x
290
+ x = $window.mouse_x
291
+ x += self.previous_game_state.viewport.x if defined?(self.previous_game_state.viewport)
292
+ end
293
+ def y
294
+ y = $window.mouse_y
295
+ y += self.previous_game_state.viewport.y if defined?(self.previous_game_state.viewport)
296
+ end
297
+
213
298
  #
214
299
  # If we're editing a game state with automaticly called special methods,
215
300
  # the following takes care of those.
216
301
  #
217
302
  def method_missing(symbol, *args)
218
- previous_game_state.__send__(symbol, *args)
303
+ if symbol != :button_down || symbol != :button_up
304
+ previous_game_state.__send__(symbol, *args)
305
+ end
219
306
  end
220
307
 
221
308
  end