chingu 0.7.0 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
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