jemini 2009.10.27

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 (127) hide show
  1. data/README.txt +9 -0
  2. data/bin/jemini +26 -0
  3. data/lib/ibxm.jar +0 -0
  4. data/lib/jinput.jar +0 -0
  5. data/lib/jogg-0.0.7.jar +0 -0
  6. data/lib/jorbis-0.0.15.jar +0 -0
  7. data/lib/jruby-complete.jar +0 -0
  8. data/lib/lwjgl.jar +0 -0
  9. data/lib/lwjgl_util_applet.jar +0 -0
  10. data/lib/native_files/OpenAL32.dll +0 -0
  11. data/lib/native_files/jinput-dx8.dll +0 -0
  12. data/lib/native_files/jinput-dx8_64.dll +0 -0
  13. data/lib/native_files/jinput-raw.dll +0 -0
  14. data/lib/native_files/jinput-raw_64.dll +0 -0
  15. data/lib/native_files/jinput-wintab.dll +0 -0
  16. data/lib/native_files/libjinput-linux.so +0 -0
  17. data/lib/native_files/libjinput-linux64.so +0 -0
  18. data/lib/native_files/libjinput-osx.jnilib +0 -0
  19. data/lib/native_files/liblwjgl.jnilib +0 -0
  20. data/lib/native_files/liblwjgl.so +0 -0
  21. data/lib/native_files/liblwjgl64.so +0 -0
  22. data/lib/native_files/libopenal.so +0 -0
  23. data/lib/native_files/lwjgl.dll +0 -0
  24. data/lib/native_files/openal.dylib +0 -0
  25. data/lib/natives-linux.jar +0 -0
  26. data/lib/natives-mac.jar +0 -0
  27. data/lib/natives-win32.jar +0 -0
  28. data/lib/phys2d.jar +0 -0
  29. data/lib/slick.jar +0 -0
  30. data/package/jar/jemini.jar +0 -0
  31. data/src/behavior.rb +248 -0
  32. data/src/behavior_event.rb +23 -0
  33. data/src/behaviors/animated_image.rb +88 -0
  34. data/src/behaviors/audible.rb +16 -0
  35. data/src/behaviors/axis_stateful.rb +35 -0
  36. data/src/behaviors/bounding_box_collidable.rb +27 -0
  37. data/src/behaviors/cardinal_movable.rb +121 -0
  38. data/src/behaviors/clickable.rb +19 -0
  39. data/src/behaviors/countable.rb +32 -0
  40. data/src/behaviors/debug_physical.rb +43 -0
  41. data/src/behaviors/debug_tangible.rb +31 -0
  42. data/src/behaviors/drawable.rb +7 -0
  43. data/src/behaviors/drawable_image.rb +111 -0
  44. data/src/behaviors/drawable_line.rb +32 -0
  45. data/src/behaviors/drawable_shape.rb +48 -0
  46. data/src/behaviors/fading_image_trail_emittable.rb +32 -0
  47. data/src/behaviors/game_object_emittable.rb +13 -0
  48. data/src/behaviors/grid_bound.rb +108 -0
  49. data/src/behaviors/handles_events.rb +33 -0
  50. data/src/behaviors/inertial.rb +14 -0
  51. data/src/behaviors/magnetic.rb +34 -0
  52. data/src/behaviors/metered.rb +3 -0
  53. data/src/behaviors/movable.rb +81 -0
  54. data/src/behaviors/particle_emitter.rb +27 -0
  55. data/src/behaviors/physical.rb +384 -0
  56. data/src/behaviors/physical_cardinal_movable.rb +111 -0
  57. data/src/behaviors/physical_image.rb +45 -0
  58. data/src/behaviors/pointer.rb +30 -0
  59. data/src/behaviors/pressable.rb +17 -0
  60. data/src/behaviors/regional.rb +76 -0
  61. data/src/behaviors/rotates_to_point.rb +19 -0
  62. data/src/behaviors/spatial.rb +43 -0
  63. data/src/behaviors/stateful.rb +33 -0
  64. data/src/behaviors/taggable.rb +28 -0
  65. data/src/behaviors/tangible.rb +59 -0
  66. data/src/behaviors/timeable.rb +88 -0
  67. data/src/behaviors/top_down_vehicle.rb +42 -0
  68. data/src/behaviors/triangle_trail_emittable.rb +46 -0
  69. data/src/behaviors/unique.rb +0 -0
  70. data/src/behaviors/updates.rb +8 -0
  71. data/src/behaviors/updates_at_consistant_rate.rb +28 -0
  72. data/src/behaviors/vectored_movement.rb +48 -0
  73. data/src/behaviors/world_collidable.rb +9 -0
  74. data/src/color.rb +70 -0
  75. data/src/events/grid_changed_event.rb +8 -0
  76. data/src/events/physical_message.rb +9 -0
  77. data/src/events/tangible_collision_event.rb +8 -0
  78. data/src/file_system.rb +17 -0
  79. data/src/game.rb +110 -0
  80. data/src/game_object.rb +176 -0
  81. data/src/game_objects/background.rb +10 -0
  82. data/src/game_objects/fading_image.rb +23 -0
  83. data/src/game_objects/tangible_object.rb +4 -0
  84. data/src/game_objects/text.rb +71 -0
  85. data/src/game_objects/triangle_trail.rb +85 -0
  86. data/src/game_state.rb +164 -0
  87. data/src/inflector.rb +68 -0
  88. data/src/input_helpers/joystick_dead_zone_filter.rb +9 -0
  89. data/src/jemini.rb +31 -0
  90. data/src/jemini_version.rb +4 -0
  91. data/src/listenable_mixin.rb +15 -0
  92. data/src/logger_mixin.rb +11 -0
  93. data/src/managers/basic_game_object_manager.rb +95 -0
  94. data/src/managers/basic_physics_manager.rb +95 -0
  95. data/src/managers/basic_render_manager.rb +49 -0
  96. data/src/managers/basic_tile_manager.rb +3 -0
  97. data/src/managers/basic_update_manager.rb +30 -0
  98. data/src/managers/diagnostic/diagnostic_input_manager.rb +0 -0
  99. data/src/managers/input_manager.rb +161 -0
  100. data/src/managers/input_support/input_binding.rb +77 -0
  101. data/src/managers/input_support/input_builder.rb +44 -0
  102. data/src/managers/input_support/input_listener.rb +74 -0
  103. data/src/managers/input_support/input_message.rb +5 -0
  104. data/src/managers/input_support/joystick_listener.rb +53 -0
  105. data/src/managers/input_support/key_listener.rb +27 -0
  106. data/src/managers/input_support/mouse_listener.rb +38 -0
  107. data/src/managers/input_support/slick_input_listener.rb +20 -0
  108. data/src/managers/input_support/slick_input_message.rb +11 -0
  109. data/src/managers/input_support/slick_input_translator.rb +15 -0
  110. data/src/managers/message_queue.rb +60 -0
  111. data/src/managers/network_manager.rb +41 -0
  112. data/src/managers/render_support/hardware_cursor.rb +13 -0
  113. data/src/managers/resource_manager.rb +167 -0
  114. data/src/managers/sound_manager.rb +47 -0
  115. data/src/managers/tag_manager.rb +47 -0
  116. data/src/managers/tangible_manager.rb +36 -0
  117. data/src/math.rb +23 -0
  118. data/src/org/rubyforge/rawr/Main.java +67 -0
  119. data/src/platform.rb +28 -0
  120. data/src/proc_enhancement.rb +5 -0
  121. data/src/project_generator.rb +138 -0
  122. data/src/resource.rb +31 -0
  123. data/src/spline.rb +13 -0
  124. data/src/states/input_diagnostic_state.rb +53 -0
  125. data/src/vector.rb +143 -0
  126. data/test/test_state.rb +3 -0
  127. metadata +188 -0
@@ -0,0 +1,16 @@
1
+ #Makes an object emit sounds.
2
+ class Audible < Jemini::Behavior
3
+
4
+ #Load a sound file and assign a reference to it, which can later be passed to emit_sound.
5
+ def load_sound(reference, path)
6
+ game_state.manager(:sound).add_sound(reference, path)
7
+ end
8
+
9
+ #Plays a sound whose reference was assigned via load_sound.
10
+ #Pass in volume to play it back quieter or louder (1.0 is normal).
11
+ #Pass in pitch for a higher or lower tone (1.0 is normal).
12
+ def emit_sound(reference, volume = 1.0, pitch = 1.0)
13
+ game_state.manager(:sound).play_sound(reference, volume, pitch)
14
+ end
15
+
16
+ end
@@ -0,0 +1,35 @@
1
+ #Makes an object transition between several states in succession.
2
+ class AxisStateful < Jemini::Behavior
3
+ #Indicates that an object has transitioned from one state to another along an axis.
4
+ class AxialStateTransferEvent
5
+ attr_accessor :before_state, :after_state, :axis
6
+ def initialize(axis, before_state, after_state)
7
+ @axis = axis
8
+ @before_state = before_state
9
+ @after_state = after_state
10
+ end
11
+ end
12
+
13
+ attr_accessor :default_state_on_axis, :current_state_on_axis
14
+ wrap_with_callbacks :transfer_state_on_axis
15
+
16
+ def load
17
+ @state_transitions = {}
18
+ @game_object.enable_listeners_for :axis_state_transfer_accepted
19
+ @game_object.enable_listeners_for :axis_state_transfer_rejected
20
+ end
21
+
22
+ def set_state_transisions_on_axis(axis, transitions)
23
+ @state_transitisions[axis] = transitions
24
+ end
25
+
26
+ def transfer_state_on_axis(axis, state)
27
+ event = AxialStateTransferEvent.new(axis, @current_state[axis], state)
28
+ if @state_transitions[axis][@current_state[axis]].include? state
29
+ @current_state = state
30
+ notify :axis_state_transfer_accepted, event
31
+ else
32
+ notify :axis_state_transfer_rejected, event
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ require 'behavior_event'
2
+ include_class 'org.newdawn.slick.geom.Rectangle'
3
+
4
+ #Makes an object generate a BoundingBoxCollisionEvent if its bounding box intersects another's.
5
+ class BoundingBoxCollidable < Jemini::Behavior
6
+ depends_on :Spatial2d
7
+
8
+ def load
9
+ @game_object.enable_listeners_for :collided
10
+ end
11
+
12
+ def collision_check(collidable)
13
+ return if self == collidable || @game_object == collidable
14
+
15
+ notify :collided, BoundingBoxCollisionEvent.new(@game_object, collidable) if bounds.intersects(collidable.bounds)
16
+ end
17
+ end
18
+
19
+ #Indicates that one object has collided with another.
20
+ class BoundingBoxCollisionEvent < Jemini::BehaviorEvent
21
+ attr_accessor :colliding_object, :collided_object
22
+
23
+ def load(source, other)
24
+ @colliding_object = source
25
+ @collided_object = other
26
+ end
27
+ end
@@ -0,0 +1,121 @@
1
+ # A CardinalMovable can move north, east, south and west.
2
+ # Movement along certain axis can be constrained, for example, pong has horizonal (north/east)
3
+ # TODO: Allow disabling of diagonal easily
4
+ # TODO: Allow disabling of directions
5
+ # TODO: Allow speed limit per axis
6
+ # TODO: Allow speed limit per direction
7
+ class CardinalMovable < Jemini::Behavior
8
+ NORTH = :north
9
+ EAST = :east
10
+ SOUTH = :south
11
+ WEST = :west
12
+ NORTH_EAST = :north_east
13
+ SOUTH_EAST = :south_east
14
+ SOUTH_WEST = :south_west
15
+ NORTH_WEST = :north_west
16
+ DIAGONAL_DIRECTIONS = [NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST]
17
+ CARDINAL_DIRECTIONS = [NORTH, EAST, SOUTH, WEST] + DIAGONAL_DIRECTIONS
18
+ DIRECTION_TRANSLATION_IN_DEGREES = []
19
+
20
+ depends_on :Tangible
21
+ depends_on :HandlesEvents
22
+ attr_accessor :facing_direction
23
+ #TODO: Rename to cardinal facing direction
24
+ wrap_with_callbacks :facing_direction=, :set_facing_drection
25
+
26
+ def load
27
+ @facing_direction = NORTH
28
+ @allowed_directions = CARDINAL_DIRECTIONS.dup
29
+ # @cardinal_speed = 25
30
+ end
31
+
32
+ # direct input events here to keep "pushing".
33
+ # TODO: After the update, clear the directions.
34
+ def set_cardinal_movement_for_update(directions)
35
+
36
+ end
37
+
38
+ def facing_direction=(direction)
39
+ if @allowed_directions.include? direction
40
+ if @moving && orthogonal_directions?(@facing_direction, direction)
41
+ diagonal_direction = begin
42
+ "#{self.class}::#{@facing_direction.to_s.upcase}_#{direction.to_s.upcase}".constantize
43
+ rescue
44
+ "#{self.class}::#{direction.to_s.upcase}_#{@facing_direction.to_s.upcase}".constantize
45
+ end
46
+ @facing_direction = diagonal_direction
47
+ else
48
+ @facing_direction = direction
49
+ end
50
+ end
51
+ end
52
+ alias_method :set_facing_direction, :facing_direction=
53
+
54
+ #Set direction(s) the object is allowed to move in.
55
+ #Takes either a single directional constant, or an Array of them.
56
+ def constrain_direction(directions)
57
+ directions = [directions] unless directions.kind_of? Array
58
+ directions.each
59
+ directions.each { |direction| @allowed_directions.delete direction }
60
+ end
61
+
62
+ #Initiate movement in a given direction.
63
+ #Takes a message with a directional constant as its value.
64
+ def begin_cardinal_movement(message)
65
+ direction = message.value
66
+ return unless @allowed_directions.include? direction
67
+ set_facing_direction direction
68
+ @moving = true
69
+ @cardinal_velocity = direction_to_polar_vector(@facing_direction)
70
+ end
71
+
72
+ #Halt movement in a given direction.
73
+ #Takes a message with a directional constant as its value.
74
+ def end_cardinal_movement(message)
75
+ direction = message.value
76
+ @facing_direction = other_direction(@facing_direction, direction) if diagonal_direction? @facing_direction
77
+ if @facing_direction == direction
78
+ @cardinal_velocity = Vector.new(0,0)
79
+ @moving = false
80
+ else
81
+ @cardinal_velocity = direction_to_polar_vector(@facing_direction)
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def direction_to_polar_vector(direction)
88
+ angle = case direction
89
+ when NORTH
90
+ 0
91
+ when NORTH_EAST
92
+ 45
93
+ when EAST
94
+ 90
95
+ when SOUTH_EAST
96
+ 135
97
+ when SOUTH
98
+ 180
99
+ when SOUTH_WEST
100
+ 225
101
+ when WEST
102
+ 270
103
+ when NORTH_WEST
104
+ 315
105
+ end
106
+ Vector.from_polar_vector(@cardinal_speed, angle)
107
+ end
108
+
109
+ def diagonal_direction?(direction)
110
+ DIAGONAL_DIRECTIONS.include? direction
111
+ end
112
+
113
+ def other_direction(diagonal_direction, direction)
114
+ diagonal_direction.to_s.sub(direction.to_s, '').sub('_', '').to_sym
115
+ end
116
+
117
+ def orthogonal_directions?(direction_a, direction_b)
118
+ ([NORTH, SOUTH].include?(direction_a) && [EAST, WEST].include?(direction_b)) ||
119
+ ([NORTH, SOUTH].include?(direction_b) && [EAST, WEST].include?(direction_a))
120
+ end
121
+ end
@@ -0,0 +1,19 @@
1
+ #Makes an object clickable with the mouse.
2
+ class Clickable < Jemini::Behavior
3
+ depends_on :HandlesEvents
4
+ depends_on :Regional
5
+ wrap_with_callbacks :pressed, :released
6
+
7
+ def load
8
+ @game_object.handle_event :mouse_button1_pressed do |mouse_event|
9
+ pressed if @game_object.within_region? mouse_event.value.location
10
+ end
11
+
12
+ @game_object.handle_event :mouse_button1_released do |mouse_event|
13
+ released if @game_object.within_region? mouse_event.value.location
14
+ end
15
+ end
16
+
17
+ def pressed; end
18
+ def released; end
19
+ end
@@ -0,0 +1,32 @@
1
+ #Makes an object receive events when its numbers have changed.
2
+ class Countable < Jemini::Behavior
3
+ attr_accessor :count
4
+ wrap_with_callbacks :count=
5
+
6
+ def load
7
+ @count = 0
8
+ @game_object.enable_listeners_for :incremented
9
+ @game_object.enable_listeners_for :decremented
10
+ end
11
+
12
+ def count=(count)
13
+ comparison = @count <=> count
14
+ @count = count
15
+ case comparison
16
+ when -1
17
+ @game_object.notify :decremented
18
+ when 0
19
+ # do nothing
20
+ when 1
21
+ @game_object.notify :incremented
22
+ end
23
+ end
24
+
25
+ def decrement
26
+ self.count = count - 1
27
+ end
28
+
29
+ def increment
30
+ self.count = count + 1
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ #Makes an object draw its interactions with the physics engine on the screen.
2
+ class DebugPhysical < Jemini::Behavior
3
+
4
+ PhysVector = Java::net::phys2d::math::Vector2f
5
+ PhysCircle = Java::net.phys2d.raw.shapes.Circle
6
+ PhysPolygon = Java::net.phys2d.raw.shapes.Polygon
7
+ PhysLine = Java::net.phys2d.raw.shapes.Line
8
+ SlickVector = Java::org::newdawn::slick::geom::Vector2f
9
+ SlickPolygon = Java::org.newdawn.slick.geom.Polygon
10
+ SlickCircle = Java::org.newdawn.slick.geom.Circle
11
+ SlickLine = Java::org.newdawn.slick.geom.Line
12
+
13
+ include_class 'net.phys2d.raw.shapes.Box'
14
+
15
+ def load
16
+ game_state.manager(:render).on_after_render do |graphics|
17
+ draw(graphics)
18
+ end
19
+ end
20
+
21
+ def unload
22
+ # TODO: Remove listener for after render?
23
+ end
24
+
25
+ private
26
+ def draw(graphics)
27
+ #TODO: Support joints and composite bodies(?)
28
+ body = @game_object.instance_variable_get(:@__behaviors)[:Physical].instance_variable_get(:@body)
29
+ physics_shape = body.shape
30
+ graphics_shape = if physics_shape.kind_of? Box
31
+ SlickPolygon.new(physics_shape.get_points(body.position, body.rotation).map{|point| [point.x, point.y]}.flatten.to_java(:float))
32
+ elsif physics_shape.kind_of? PhysPolygon
33
+ SlickPolygon.new(physics_shape.get_vertices(body.position, body.rotation).map{|point| [point.x, point.y]}.flatten.to_java(:float))
34
+ elsif physics_shape.kind_of? PhysCircle
35
+ SlickCircle.new(body.position.x, body.position.y, physics_shape.radius)
36
+ # elsif physics_shape.kind_of? PhysLine
37
+ # SlickLine.new(body_position.x, )
38
+ else
39
+ raise "#{self.class} does not know how to draw the shape #{physics_shape.class}"
40
+ end
41
+ graphics.draw(graphics_shape)
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ #Makes an object draw its collisions on the screen.
2
+ class DebugTangible < Jemini::Behavior
3
+ #declared_methods :draw
4
+ PhysVector = Java::net::phys2d::math::Vector2f
5
+ SlickVector = Java::org::newdawn::slick::geom::Vector2f
6
+
7
+ def load
8
+ game_state.manager(:render).on_after_render do |graphics|
9
+ draw(graphics)
10
+ end
11
+ end
12
+
13
+ def unload
14
+ # Remove listener for after render
15
+ end
16
+
17
+ def draw(graphics)
18
+ #TODO: Support joints and composite bodies(?)
19
+ tangible_shape = @game_object.instance_variable_get(:@__behaviors)[:Tangible].instance_variable_get(:@tangible_shape)
20
+ graphics_shape = if tangible_shape.kind_of? TangibleBox
21
+ Java::org.newdawn.slick.geom.Polygon.new(tangible_shape.get_points(@game_object.top_left_position, 0).map{|point| [point.x, point.y]}.flatten.to_java(:float))
22
+ # elsif tangible_shape.kind_of?(Java::net.phys2d.raw.shapes.Polygon)
23
+ # Java::org.newdawn.slick.geom.Polygon.new(tangible_shape.get_vertices(body.position, body.rotation).map{|point| [point.x, point.y]}.flatten.to_java(:float))
24
+ # elsif tangible_shape.kind_of? Java::net.phys2d.raw.shapes.Circle
25
+ # Java::org.newdawn.slick.geom.Circle.new(body.position.x, body.position.y, tangible_shape.radius)
26
+ else
27
+ raise "#{self.class} does not know how to draw the shape #{tangible_shape.class}"
28
+ end
29
+ graphics.draw(graphics_shape)
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ #Makes an object draw itself to the screen.
2
+ class Drawable < Jemini::Behavior
3
+ depends_on_kind_of :Spatial
4
+ wrap_with_callbacks :draw
5
+
6
+ def draw(graphics); end
7
+ end
@@ -0,0 +1,111 @@
1
+ require 'behaviors/drawable'
2
+
3
+ #Makes an object draw itself as a bitmap image.
4
+ class DrawableImage < Drawable
5
+ include_class 'org.newdawn.slick.Image'
6
+ depends_on :Spatial
7
+ attr_accessor :image, :color, :texture_coords, :image_size
8
+ alias_method :set_image_size, :image_size=
9
+ wrap_with_callbacks :draw
10
+
11
+ def load
12
+ @color = Color.new(1.0, 1.0, 1.0, 1.0)
13
+ @texture_coords = [Vector.new(0.0, 0.0), Vector.new(1.0, 1.0)]
14
+ @rotation = 0.0
15
+ end
16
+
17
+ #Takes a reference to an image loaded via the resource manager, and sets the bitmap.
18
+ def image=(reference)
19
+ store_image(game_state.manager(:resource).get_image(reference))
20
+ end
21
+ alias_method :set_image, :image=
22
+
23
+ #Assign a Color to the image.
24
+ def color=(color)
25
+ @color = color
26
+ end
27
+ alias_method :set_color, :color=
28
+
29
+ #Increase or decrease horizontal and vertical scale for the image. 1.0 is identical to the current scale. If y_scale is omitted, x_scale is used for both axes.
30
+ #TODO: Take vectors for first args as well
31
+ def image_scaling(x_scale, y_scale = nil)
32
+ y_scale = x_scale if y_scale.nil?
33
+ store_image(@image.get_scaled_copy(x_scale.to_f * image_size.x, y_scale.to_f * image_size.y))
34
+ end
35
+
36
+ #Set horizontal and vertical scale for the image relative to the original. 1.0 is original scale. If y_scale is omitted, x_scale is used for both axes.
37
+ #TODO: Take vectors for first args as well
38
+ def scale_image_from_original(x_scale, y_scale = nil)
39
+ y_scale = x_scale if y_scale.nil?
40
+ @original_image = @image.copy if @original_image.nil?
41
+ store_image(@original_image.get_scaled_copy(x_scale.to_f * @original_image.width, y_scale.to_f * @original_image.height))
42
+ end
43
+
44
+ # WARNING: Using Slick's image for rotation can cause some odd quirks with it
45
+ # not quite rotating correctly (especially noticable around 180 degress).
46
+ # @rotation stands alone for this reason, instead of using Slick's rotation
47
+ def image_rotation
48
+ @rotation
49
+ end
50
+
51
+ def image_rotation=(rotation)
52
+ @rotation = rotation
53
+ end
54
+ alias_method :set_image_rotation, :image_rotation=
55
+
56
+ #Increment the image rotation.
57
+ def add_rotation(rotation)
58
+ @rotation += rotation
59
+ end
60
+
61
+ #Flip the texture horizontally.
62
+ def flip_horizontally
63
+ @texture_coords[1].x, @texture_coords[0].x = @texture_coords[0].x, @texture_coords[1].x
64
+ end
65
+
66
+ #Flip the texture vertically.
67
+ def flip_vertically
68
+ @texture_coords[1].y, @texture_coords[0].y = @texture_coords[0].y, @texture_coords[1].y
69
+ end
70
+
71
+ #Returns a Vector with the x/y coordinates of the image's top left corner.
72
+ def top_left_position
73
+ #Vector.new(center_position.x - image_size.x / 2.0, center_position.y - image_size.y / 2.0)
74
+ Vector.new(@game_object.x - image_size.x / 2.0, @game_object.y - image_size.y / 2.0)
75
+ end
76
+
77
+ #Takes either a single Vector or the x/y coordinates to move the image's top left corner to.
78
+ def move_by_top_left(move_x_or_vector, move_y = nil)
79
+ half_width = image_size.x / 2.0
80
+ half_height = image_size.y / 2.0
81
+ if move_y.nil?
82
+ @game_object.move(move_x_or_vector.x + half_width, move_x_or_vector.y + half_height)
83
+ else
84
+ @game_object.move(move_x_or_vector + half_width, move_y + half_height)
85
+ end
86
+ end
87
+
88
+ #Draw the image to the given graphic context.
89
+ def draw(graphics)
90
+ return if @image.nil? || @image_size.nil?
91
+ half_width = image_size.x / 2.0
92
+ half_height = image_size.y / 2.0
93
+ center_x = @game_object.x - half_width
94
+ center_y = @game_object.y - half_height
95
+ unless 0 == @rotation
96
+ graphics.rotate @game_object.x, @game_object.y, @rotation
97
+ end
98
+ @image.draw(center_x, center_y, @game_object.x + half_width, @game_object.y + half_height,
99
+ @texture_coords[0].x * image_size.x, @texture_coords[0].y * image_size.y, @texture_coords[1].x * image_size.x, @texture_coords[1].y * image_size.y,
100
+ @color.native_color)
101
+ graphics.reset_transform
102
+ end
103
+
104
+ private
105
+
106
+ def store_image(value)
107
+ @image = value
108
+ set_image_size(Vector.new(@image.width, @image.height))
109
+ @image
110
+ end
111
+ end