jemini 2009.10.27

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