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.
- data/README.txt +9 -0
- data/bin/jemini +26 -0
- data/lib/ibxm.jar +0 -0
- data/lib/jinput.jar +0 -0
- data/lib/jogg-0.0.7.jar +0 -0
- data/lib/jorbis-0.0.15.jar +0 -0
- data/lib/jruby-complete.jar +0 -0
- data/lib/lwjgl.jar +0 -0
- data/lib/lwjgl_util_applet.jar +0 -0
- data/lib/native_files/OpenAL32.dll +0 -0
- data/lib/native_files/jinput-dx8.dll +0 -0
- data/lib/native_files/jinput-dx8_64.dll +0 -0
- data/lib/native_files/jinput-raw.dll +0 -0
- data/lib/native_files/jinput-raw_64.dll +0 -0
- data/lib/native_files/jinput-wintab.dll +0 -0
- data/lib/native_files/libjinput-linux.so +0 -0
- data/lib/native_files/libjinput-linux64.so +0 -0
- data/lib/native_files/libjinput-osx.jnilib +0 -0
- data/lib/native_files/liblwjgl.jnilib +0 -0
- data/lib/native_files/liblwjgl.so +0 -0
- data/lib/native_files/liblwjgl64.so +0 -0
- data/lib/native_files/libopenal.so +0 -0
- data/lib/native_files/lwjgl.dll +0 -0
- data/lib/native_files/openal.dylib +0 -0
- data/lib/natives-linux.jar +0 -0
- data/lib/natives-mac.jar +0 -0
- data/lib/natives-win32.jar +0 -0
- data/lib/phys2d.jar +0 -0
- data/lib/slick.jar +0 -0
- data/package/jar/jemini.jar +0 -0
- data/src/behavior.rb +248 -0
- data/src/behavior_event.rb +23 -0
- data/src/behaviors/animated_image.rb +88 -0
- data/src/behaviors/audible.rb +16 -0
- data/src/behaviors/axis_stateful.rb +35 -0
- data/src/behaviors/bounding_box_collidable.rb +27 -0
- data/src/behaviors/cardinal_movable.rb +121 -0
- data/src/behaviors/clickable.rb +19 -0
- data/src/behaviors/countable.rb +32 -0
- data/src/behaviors/debug_physical.rb +43 -0
- data/src/behaviors/debug_tangible.rb +31 -0
- data/src/behaviors/drawable.rb +7 -0
- data/src/behaviors/drawable_image.rb +111 -0
- data/src/behaviors/drawable_line.rb +32 -0
- data/src/behaviors/drawable_shape.rb +48 -0
- data/src/behaviors/fading_image_trail_emittable.rb +32 -0
- data/src/behaviors/game_object_emittable.rb +13 -0
- data/src/behaviors/grid_bound.rb +108 -0
- data/src/behaviors/handles_events.rb +33 -0
- data/src/behaviors/inertial.rb +14 -0
- data/src/behaviors/magnetic.rb +34 -0
- data/src/behaviors/metered.rb +3 -0
- data/src/behaviors/movable.rb +81 -0
- data/src/behaviors/particle_emitter.rb +27 -0
- data/src/behaviors/physical.rb +384 -0
- data/src/behaviors/physical_cardinal_movable.rb +111 -0
- data/src/behaviors/physical_image.rb +45 -0
- data/src/behaviors/pointer.rb +30 -0
- data/src/behaviors/pressable.rb +17 -0
- data/src/behaviors/regional.rb +76 -0
- data/src/behaviors/rotates_to_point.rb +19 -0
- data/src/behaviors/spatial.rb +43 -0
- data/src/behaviors/stateful.rb +33 -0
- data/src/behaviors/taggable.rb +28 -0
- data/src/behaviors/tangible.rb +59 -0
- data/src/behaviors/timeable.rb +88 -0
- data/src/behaviors/top_down_vehicle.rb +42 -0
- data/src/behaviors/triangle_trail_emittable.rb +46 -0
- data/src/behaviors/unique.rb +0 -0
- data/src/behaviors/updates.rb +8 -0
- data/src/behaviors/updates_at_consistant_rate.rb +28 -0
- data/src/behaviors/vectored_movement.rb +48 -0
- data/src/behaviors/world_collidable.rb +9 -0
- data/src/color.rb +70 -0
- data/src/events/grid_changed_event.rb +8 -0
- data/src/events/physical_message.rb +9 -0
- data/src/events/tangible_collision_event.rb +8 -0
- data/src/file_system.rb +17 -0
- data/src/game.rb +110 -0
- data/src/game_object.rb +176 -0
- data/src/game_objects/background.rb +10 -0
- data/src/game_objects/fading_image.rb +23 -0
- data/src/game_objects/tangible_object.rb +4 -0
- data/src/game_objects/text.rb +71 -0
- data/src/game_objects/triangle_trail.rb +85 -0
- data/src/game_state.rb +164 -0
- data/src/inflector.rb +68 -0
- data/src/input_helpers/joystick_dead_zone_filter.rb +9 -0
- data/src/jemini.rb +31 -0
- data/src/jemini_version.rb +4 -0
- data/src/listenable_mixin.rb +15 -0
- data/src/logger_mixin.rb +11 -0
- data/src/managers/basic_game_object_manager.rb +95 -0
- data/src/managers/basic_physics_manager.rb +95 -0
- data/src/managers/basic_render_manager.rb +49 -0
- data/src/managers/basic_tile_manager.rb +3 -0
- data/src/managers/basic_update_manager.rb +30 -0
- data/src/managers/diagnostic/diagnostic_input_manager.rb +0 -0
- data/src/managers/input_manager.rb +161 -0
- data/src/managers/input_support/input_binding.rb +77 -0
- data/src/managers/input_support/input_builder.rb +44 -0
- data/src/managers/input_support/input_listener.rb +74 -0
- data/src/managers/input_support/input_message.rb +5 -0
- data/src/managers/input_support/joystick_listener.rb +53 -0
- data/src/managers/input_support/key_listener.rb +27 -0
- data/src/managers/input_support/mouse_listener.rb +38 -0
- data/src/managers/input_support/slick_input_listener.rb +20 -0
- data/src/managers/input_support/slick_input_message.rb +11 -0
- data/src/managers/input_support/slick_input_translator.rb +15 -0
- data/src/managers/message_queue.rb +60 -0
- data/src/managers/network_manager.rb +41 -0
- data/src/managers/render_support/hardware_cursor.rb +13 -0
- data/src/managers/resource_manager.rb +167 -0
- data/src/managers/sound_manager.rb +47 -0
- data/src/managers/tag_manager.rb +47 -0
- data/src/managers/tangible_manager.rb +36 -0
- data/src/math.rb +23 -0
- data/src/org/rubyforge/rawr/Main.java +67 -0
- data/src/platform.rb +28 -0
- data/src/proc_enhancement.rb +5 -0
- data/src/project_generator.rb +138 -0
- data/src/resource.rb +31 -0
- data/src/spline.rb +13 -0
- data/src/states/input_diagnostic_state.rb +53 -0
- data/src/vector.rb +143 -0
- data/test/test_state.rb +3 -0
- metadata +188 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# draws a line on the screen
|
|
2
|
+
# TODO: Enable color
|
|
3
|
+
class DrawableLine < Jemini::Behavior
|
|
4
|
+
java_import 'org.newdawn.slick.geom.Line'
|
|
5
|
+
depends_on :Spatial
|
|
6
|
+
wrap_with_callbacks :draw
|
|
7
|
+
|
|
8
|
+
attr_reader :line_end_position
|
|
9
|
+
attr_accessor :color
|
|
10
|
+
|
|
11
|
+
def load
|
|
12
|
+
@line_end_position = Vector.new(0.0, 0.0)
|
|
13
|
+
@line = Line.new @game_object.position.to_slick_vector, @line_end_position.to_slick_vector
|
|
14
|
+
@game_object.on_after_position_changes { set_line }
|
|
15
|
+
#@color = Color.white
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def line_end_position=(end_point)
|
|
19
|
+
@line_end_position = end_point
|
|
20
|
+
set_line
|
|
21
|
+
@line_end_position
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def draw(graphics)
|
|
25
|
+
graphics.draw @line
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def set_line
|
|
30
|
+
@line.set @game_object.position.to_slick_vector, @line_end_position.to_slick_vector
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'behaviors/drawable'
|
|
2
|
+
|
|
3
|
+
#Makes an object draw itself on the screen as a polygon.
|
|
4
|
+
class DrawableShape < Jemini::Behavior
|
|
5
|
+
java_import 'org.newdawn.slick.geom.Vector2f'
|
|
6
|
+
java_import 'org.newdawn.slick.geom.Polygon'
|
|
7
|
+
java_import 'org.newdawn.slick.geom.Circle'
|
|
8
|
+
|
|
9
|
+
depends_on :Spatial
|
|
10
|
+
wrap_with_callbacks :draw
|
|
11
|
+
|
|
12
|
+
attr_accessor :color, :image
|
|
13
|
+
attr_reader :visual_shape
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
#Set the shape to draw.
|
|
17
|
+
#Accepts :Polygon or the name of a class in the DrawableShape namespace.
|
|
18
|
+
#TODO: There are no DrawableShape::* classes yet!
|
|
19
|
+
def set_visual_shape(shape, *params)
|
|
20
|
+
case shape
|
|
21
|
+
when :polygon, :Polygon
|
|
22
|
+
@visual_shape = "#{self.class}::#{shape}".constantize.new
|
|
23
|
+
params.each do |vector|
|
|
24
|
+
@visual_shape.add_point vector.x, vector.y
|
|
25
|
+
end
|
|
26
|
+
when :circle, :Circle
|
|
27
|
+
@visual_shape = DrawableShape::Circle.new(position.x, position.y, params)
|
|
28
|
+
else
|
|
29
|
+
@visual_shape = ("DrawableShape::"+ shape.to_s).constantize.new(params)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#Takes a reference to an image loaded via the resource manager, and sets the bitmap.
|
|
34
|
+
def image=(reference)
|
|
35
|
+
@image = game_state.manager(:resource).get_image(reference)
|
|
36
|
+
end
|
|
37
|
+
alias_method :set_image, :image=
|
|
38
|
+
|
|
39
|
+
def draw(graphics)
|
|
40
|
+
if @visual_shape.kind_of? Polygon
|
|
41
|
+
#TODO: Tweak these values!!!!
|
|
42
|
+
#@image.width.to_f / game_state.screen_width.to_f, @image.height.to_f / game_state.screen_height.to_f
|
|
43
|
+
graphics.texture @visual_shape, @image, 1.0 / @image.width.to_f, 1.0 / @image.height.to_f
|
|
44
|
+
else
|
|
45
|
+
raise "#{@visual_shape.class} is not supported"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#Makes an object leave a trail of images behind it that fade over time.
|
|
2
|
+
class FadingImageTrailEmittable < Jemini::Behavior
|
|
3
|
+
depends_on :Updates
|
|
4
|
+
|
|
5
|
+
def load
|
|
6
|
+
@fading_image_offset = Vector.new(0,0)
|
|
7
|
+
@move_threshold = 4
|
|
8
|
+
@move_count = 0
|
|
9
|
+
@seconds_to_fade_away = 2
|
|
10
|
+
@game_object.on_update do
|
|
11
|
+
@move_count += 1
|
|
12
|
+
if @move_count >= @move_threshold
|
|
13
|
+
fading_image = game_state.create :FadingImage, @image, Color.new(:white), @seconds_to_fade_away
|
|
14
|
+
#center_position = @game_object.center_position
|
|
15
|
+
fading_image.position = Vector.new(@fading_image_offset.x + @game_object.x, @fading_image_offset.y + @game_object.y)
|
|
16
|
+
@move_count = 0
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#Sets the Image to emit.
|
|
22
|
+
def emit_fading_image(image)
|
|
23
|
+
@image = image
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#Causes fading images to appear at some distance away from object.
|
|
27
|
+
#Takes a Vector with the x/y offset at which to emit the images.
|
|
28
|
+
def emit_fading_image_trail_from_offset(offset)
|
|
29
|
+
@fading_image_offset = offset
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#Makes an object create other objects.
|
|
2
|
+
class GameObjectEmittable < Jemini::Behavior
|
|
3
|
+
attr_accessor :emitting_game_object_name
|
|
4
|
+
alias_method :set_emitting_game_object_name, :emitting_game_object_name=
|
|
5
|
+
def load
|
|
6
|
+
@game_object.enable_listeners_for :emit_game_object
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def emit_game_object(message=nil)
|
|
10
|
+
game_object = game_state.create_game_object @emitting_game_object_name
|
|
11
|
+
@game_object.notify :emit_game_object, game_object
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'events/grid_changed_event'
|
|
2
|
+
|
|
3
|
+
class GridBound < Jemini::Behavior
|
|
4
|
+
DEFAULT_GRID_WIDTH = 32
|
|
5
|
+
DEFAULT_GRID_HEIGHT = 32
|
|
6
|
+
DEFAULT_GRID_SIZE = Vector.new(DEFAULT_GRID_WIDTH, DEFAULT_GRID_HEIGHT)
|
|
7
|
+
depends_on :Spatial
|
|
8
|
+
depends_on :Movable
|
|
9
|
+
|
|
10
|
+
listen_for :grid_changed
|
|
11
|
+
|
|
12
|
+
attr_accessor :grid_size, :grid_position
|
|
13
|
+
|
|
14
|
+
def load
|
|
15
|
+
@grid_size = DEFAULT_GRID_SIZE
|
|
16
|
+
@game_object.on_movement { notify_grid_changed }
|
|
17
|
+
@grid_position = detect_grid_position
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def grid_size=(vector)
|
|
21
|
+
@grid_size = Vector.new(vector.x.to_i, vector.y.to_i)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def grid_position
|
|
25
|
+
detect_grid_position
|
|
26
|
+
end
|
|
27
|
+
# position is a vector
|
|
28
|
+
# setting grid position calculates at the center of the grid
|
|
29
|
+
def grid_position=(grid_position)
|
|
30
|
+
@game_object.position = position_at(grid_position)
|
|
31
|
+
@grid_position = grid_position
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def adjacent_grid(direction)
|
|
35
|
+
adjacent_grid = @grid_position.dup
|
|
36
|
+
case direction
|
|
37
|
+
when :top, :north, :up
|
|
38
|
+
adjacent_grid.y += 1
|
|
39
|
+
when :north_east
|
|
40
|
+
adjacent_grid.x += 1
|
|
41
|
+
adjacent_grid.y += 1
|
|
42
|
+
when :right, :east
|
|
43
|
+
adjacent_grid.x += 1
|
|
44
|
+
when :south_east
|
|
45
|
+
adjacent_grid.x += 1
|
|
46
|
+
adjacent_grid.y -= 1
|
|
47
|
+
when :bottom, :south, :down
|
|
48
|
+
adjacent_grid.y -= 1
|
|
49
|
+
when :south_west
|
|
50
|
+
adjacent_grid.x -= 1
|
|
51
|
+
adjacent_grid.y -= 1
|
|
52
|
+
when :left, :west
|
|
53
|
+
adjacent_grid.x -= 1
|
|
54
|
+
when :north_west
|
|
55
|
+
adjacent_grid.x -= 1
|
|
56
|
+
adjacent_grid.y += 1
|
|
57
|
+
end
|
|
58
|
+
adjacent_grid
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def move_to_adjacent_grid(direction)
|
|
62
|
+
move_to_grid(adjacent_grid(direction))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def move_to_grid(grid)
|
|
66
|
+
@game_object.move_to position_at(grid)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def snap_to_grid
|
|
70
|
+
grids = adjacent_grids
|
|
71
|
+
grids << grid_position
|
|
72
|
+
nearest_grid = grids.min_by { |g| position_at(g).distance_from(@game_object.position) }
|
|
73
|
+
self.grid_position = nearest_grid
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def adjacent_grids
|
|
77
|
+
[:north, :north_east, :east, :south_east, :south, :south_west, :west, :north_west].map do |direction|
|
|
78
|
+
adjacent_grid direction
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
def position_at(grid_position)
|
|
84
|
+
top_left_of_grid = Vector.new(grid_position.x.to_i * grid_size.x.to_i, grid_position.y.to_i * grid_size.y.to_i)
|
|
85
|
+
center_offset = grid_size.half
|
|
86
|
+
top_left_of_grid + center_offset
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def grid_at(position)
|
|
90
|
+
x = position.x.to_i / grid_size.x.to_i
|
|
91
|
+
y = position.y.to_i / grid_size.y.to_i
|
|
92
|
+
Vector.new(x, y)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def notify_grid_changed
|
|
96
|
+
old_position = @grid_position
|
|
97
|
+
new_position = detect_grid_position
|
|
98
|
+
|
|
99
|
+
unless old_position == new_position
|
|
100
|
+
@grid_position = new_position
|
|
101
|
+
@game_object.notify(:grid_changed, GridChangedEvent.new(new_position, old_position))
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def detect_grid_position
|
|
106
|
+
grid_at @game_object.position
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#Enables an object to respond to events.
|
|
2
|
+
class HandlesEvents < Jemini::Behavior
|
|
3
|
+
|
|
4
|
+
def load
|
|
5
|
+
@handled_events = []
|
|
6
|
+
@event_types = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
#Add a handler for a given event type. Subsequent events of the given type will be passed to the given block or the given method name.
|
|
10
|
+
def handle_event(event_name, method_name=nil, &block)
|
|
11
|
+
if !method_name.nil?
|
|
12
|
+
game_state.manager(:message_queue).add_listener(event_name, self, @game_object.send(:method, method_name).to_proc)
|
|
13
|
+
elsif block_given?
|
|
14
|
+
game_state.manager(:message_queue).add_listener(event_name, self, &block)
|
|
15
|
+
else
|
|
16
|
+
raise "Either a method name or a block must be provided"
|
|
17
|
+
end
|
|
18
|
+
@handled_events << event_name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def handles_events_for(event_type)
|
|
22
|
+
@event_types << event_type
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handles_events_for?(event_type)
|
|
26
|
+
@event_types.include? event_type
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#Unregisters all message handlers for this object.
|
|
30
|
+
def unload
|
|
31
|
+
@handled_events.each {|e| game_state.manager(:message_queue).remove_listener(e, self)}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# deprecated
|
|
2
|
+
class Inertial < Jemini::Behavior
|
|
3
|
+
depends_on :UpdatesAtConsistantRate
|
|
4
|
+
|
|
5
|
+
#A 2-element array with x/y inertial values. 0 means no resistance to acceleration.
|
|
6
|
+
attr_accessor :inertia
|
|
7
|
+
|
|
8
|
+
def load
|
|
9
|
+
@inertia = [0,0]
|
|
10
|
+
@game_object.on_update do
|
|
11
|
+
move(@inertia[0] + x, @inertia[1] + y)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#Makes an object attract other Physical game objects towards it.
|
|
2
|
+
class Magnetic < Jemini::Behavior
|
|
3
|
+
depends_on :Physical
|
|
4
|
+
|
|
5
|
+
#The force to exert. 0 means no push.
|
|
6
|
+
attr_accessor :magnetism
|
|
7
|
+
alias_method :set_magnetism, :magnetism=
|
|
8
|
+
|
|
9
|
+
#There is no effect on targets that are beyond this distance.
|
|
10
|
+
attr_accessor :magnetism_max_radius
|
|
11
|
+
alias_method :set_magnetism_max_radius, :magnetism_max_radius=
|
|
12
|
+
|
|
13
|
+
#The distance where force progression is cut off. Distances closer than this will be treated as if it were the distance provided
|
|
14
|
+
attr_accessor :magnetism_min_radius
|
|
15
|
+
alias_method :set_magnetism_min_radius, :magnetism_min_radius=
|
|
16
|
+
|
|
17
|
+
def load
|
|
18
|
+
@magnetism = 1.0
|
|
19
|
+
@magnetism_max_radius = 1000.0
|
|
20
|
+
@magnetism_min_radius = 10.0
|
|
21
|
+
@game_object.on_update do |delta|
|
|
22
|
+
physicals = game_state.manager(:game_object).game_objects.select {|game_object| game_object.has_behavior? :Physical}
|
|
23
|
+
physicals.each do |physical|
|
|
24
|
+
next if @game_object == physical
|
|
25
|
+
distance = @game_object.body_position.distance_from physical
|
|
26
|
+
next if distance > @magnetism_max_radius
|
|
27
|
+
distance = @magnetism_min_radius if distance < @magnetism_min_radius
|
|
28
|
+
force = delta * @magnetism / (distance * Jemini::Math::SQUARE_ROOT_OF_TWO)
|
|
29
|
+
magnetism = Vector.from_polar_vector(force, @game_object.body_position.angle_from(physical.body_position))
|
|
30
|
+
physical.add_force magnetism
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Enables simple movement
|
|
2
|
+
# This movement is not coupled to any physics behavior
|
|
3
|
+
# TODO: Consider adding #move_with(spline) method
|
|
4
|
+
class Movable < Jemini::Behavior
|
|
5
|
+
DEFAULT_DESTINATION_ACCURACY = 0.1
|
|
6
|
+
|
|
7
|
+
depends_on :Spatial
|
|
8
|
+
depends_on :Updates
|
|
9
|
+
wrap_with_callbacks :move, :moveable_speed=
|
|
10
|
+
listen_for :movement, :movable_arrival
|
|
11
|
+
|
|
12
|
+
attr_accessor :movable_speed, :movable_destination_accuracy
|
|
13
|
+
alias_method :set_movable_speed, :movable_speed
|
|
14
|
+
|
|
15
|
+
def load
|
|
16
|
+
@movable_speed = 0.01
|
|
17
|
+
@movable_destination_accuracy = DEFAULT_DESTINATION_ACCURACY
|
|
18
|
+
@game_object.on_update {|delta| update_movement delta }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# move at a direction indefinitely
|
|
22
|
+
def move(angle_or_polar, speed=nil)
|
|
23
|
+
@destination = nil
|
|
24
|
+
if angle_or_polar.kind_of? Numeric
|
|
25
|
+
self.movable_speed = speed unless speed.nil? # force the callbacks
|
|
26
|
+
@movement = Vector.from_polar_vector(movable_speed, angle_or_polar)
|
|
27
|
+
else
|
|
28
|
+
angle = angle_or_polar.angle_from(@game_object.position)
|
|
29
|
+
@movement = Vector.from_polar_vector(movable_speed, angle)
|
|
30
|
+
self.movable_speed = @movement.magnitude
|
|
31
|
+
warn "Vector given in first arg means speed of #{speed} is ignored." if speed
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# move to a destination
|
|
36
|
+
def move_to(desired_location, speed=nil)
|
|
37
|
+
self.movable_speed = speed if speed
|
|
38
|
+
move desired_location.angle_from(@game_object.position), movable_speed
|
|
39
|
+
@destination = desired_location
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def movement_direction
|
|
43
|
+
return nil if @movement.nil?
|
|
44
|
+
@movement.angle_from(Vector.new(0.0, 0.0))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
def update_movement(delta)
|
|
49
|
+
return unless @movement
|
|
50
|
+
new_position = @game_object.position + Vector.new(@movement.x * delta, @movement.y * delta)
|
|
51
|
+
|
|
52
|
+
if @destination
|
|
53
|
+
move_to_destination new_position
|
|
54
|
+
else
|
|
55
|
+
move_normally_with new_position
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def move_to_destination(new_position)
|
|
60
|
+
new_position = @destination if overshot_destination?(new_position)
|
|
61
|
+
move_normally_with new_position # may seem like a possible no-op from above, but will kick off callbacks for Movable and Spatial
|
|
62
|
+
|
|
63
|
+
if @game_object.position.near?(@destination, @movable_destination_accuracy)
|
|
64
|
+
@destination = nil
|
|
65
|
+
@movement = nil
|
|
66
|
+
@game_object.notify :movable_arrival
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def move_normally_with(new_position)
|
|
71
|
+
@game_object.position = new_position
|
|
72
|
+
@game_object.notify :movement
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def overshot_destination?(new_position)
|
|
76
|
+
previous_distance = @game_object.position.distance_from(@destination).abs
|
|
77
|
+
new_distance = new_position.distance_from(@destination).abs
|
|
78
|
+
|
|
79
|
+
new_distance > previous_distance
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class ParticleEmitter < Jemini::Behavior
|
|
2
|
+
has_behavior :Updates
|
|
3
|
+
|
|
4
|
+
java_import 'org.newdawn.slick.particles.ConfigurableEmitter'
|
|
5
|
+
java_import 'org.newdawn.slick.particles.ParticleSystem'
|
|
6
|
+
|
|
7
|
+
def load
|
|
8
|
+
on_update {|delta| update_particle(delta)}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def load_particle_data(resource)
|
|
12
|
+
@system = ParticleSystem.new(resource)
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def draw(graphics)
|
|
17
|
+
# use ConfigurableEmitter's angularOffset public field to apply rotation
|
|
18
|
+
# this may be a poor system considering it may not respect the openGL graphics/transform stack
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def update_particle(delta)
|
|
24
|
+
return if @system.nil?
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
require 'behaviors/spatial'
|
|
2
|
+
require 'events/physical_message'
|
|
3
|
+
|
|
4
|
+
#Makes an object interact with the physics engine.
|
|
5
|
+
class Physical < Jemini::Behavior
|
|
6
|
+
|
|
7
|
+
INFINITE_MASS = Java::net::phys2d::raw::Body::INFINITE_MASS
|
|
8
|
+
|
|
9
|
+
include_class "net.phys2d.raw.Body"
|
|
10
|
+
include_class "net.phys2d.raw.shapes.Box"
|
|
11
|
+
include_class "net.phys2d.raw.shapes.Circle"
|
|
12
|
+
include_class "net.phys2d.raw.shapes.Line"
|
|
13
|
+
include_class "net.phys2d.raw.shapes.Polygon"
|
|
14
|
+
include_class "net.phys2d.raw.shapes.ConvexPolygon"
|
|
15
|
+
include_class "net.phys2d.math.Vector2f"
|
|
16
|
+
include_class 'net.phys2d.raw.AngleJoint'
|
|
17
|
+
include_class 'net.phys2d.raw.BasicJoint'
|
|
18
|
+
include_class 'net.phys2d.raw.SpringJoint'
|
|
19
|
+
include_class 'net.phys2d.raw.FixedAngleJoint'
|
|
20
|
+
include_class 'net.phys2d.raw.DistanceJoint'
|
|
21
|
+
|
|
22
|
+
attr_reader :mass, :name, :shape
|
|
23
|
+
depends_on :Spatial
|
|
24
|
+
depends_on :Updates
|
|
25
|
+
wrap_with_callbacks :mass=, :add_to_world, :body_position=, :set_body_position
|
|
26
|
+
|
|
27
|
+
def load
|
|
28
|
+
@mass = 1
|
|
29
|
+
@shape = Box.new(1,1)
|
|
30
|
+
setup_body
|
|
31
|
+
@body.restitution = 0.0
|
|
32
|
+
@body.user_data = @game_object
|
|
33
|
+
@angular_damping = 0.0
|
|
34
|
+
@game_object.enable_listeners_for :physical_collided
|
|
35
|
+
@game_object.on_after_position_changes { move_body @game_object.position }
|
|
36
|
+
# Manual angular damping. New Phys2D may support this? If not, file a bug.
|
|
37
|
+
@game_object.on_update do |delta|
|
|
38
|
+
next if @angular_damping.zero? || angular_velocity.zero?
|
|
39
|
+
decay = (delta * @angular_damping * angular_velocity) / (mass * shape.surface_factor)
|
|
40
|
+
set_angular_velocity(angular_velocity - decay) unless decay.nan?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_excluded_physical(physical_game_object)
|
|
45
|
+
@body.add_excluded_body physical_game_object.instance_variable_get(:@__behaviors)[:Physical].instance_variable_get(:@body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# prevent collision and interaction with all physicals now and in the future
|
|
49
|
+
def exclude_all_physicals
|
|
50
|
+
game_state.manager(:game_object).game_objects.select {|game_object| game_object.has_behavior? :Physical }.each {|physical| add_excluded_physical physical }
|
|
51
|
+
|
|
52
|
+
game_state.manager(:game_object).on_before_add_game_object do |game_object, event|
|
|
53
|
+
add_excluded_physical game_object if game_object.has_behavior? :Physical
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def physics_bitmask
|
|
58
|
+
@body.bitmask
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def physics_bitmask=(bitmask)
|
|
62
|
+
@body.bitmask = bitmask
|
|
63
|
+
end
|
|
64
|
+
alias_method :set_physics_bitmask, :physics_bitmask=
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def body_position
|
|
68
|
+
@body.position.to_vector
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#Takes a Vector with the x/y coordinates to move the object to.
|
|
72
|
+
def body_position=(vector)
|
|
73
|
+
# set_position doesn't take a vector, only x/y
|
|
74
|
+
@body.set_position(vector.x, vector.y)
|
|
75
|
+
end
|
|
76
|
+
alias_method :set_body_position, :body_position=
|
|
77
|
+
|
|
78
|
+
# See about Phys2D joints here: http://www.cokeandcode.com/phys2d/source/javadoc/net/phys2d/raw/Joint.html
|
|
79
|
+
# TODO: Make the joint a game object and/or behavior
|
|
80
|
+
def join_to_physical(physical_game_object, options={})
|
|
81
|
+
other_body = physical_game_object.instance_variable_get(:@__behaviors)[:Physical].instance_variable_get(:@body)
|
|
82
|
+
|
|
83
|
+
self_body_point = (options[:self_body_point] || Vector::ORIGIN).to_phys2d_vector
|
|
84
|
+
other_body_point = (options[:other_body_point] || Vector::ORIGIN).to_phys2d_vector
|
|
85
|
+
joint = case options[:joint]
|
|
86
|
+
when :distance
|
|
87
|
+
DistanceJoint.new(@body, other_body, self_body_point, other_body_point, options[:distance])
|
|
88
|
+
when :fixed_angle
|
|
89
|
+
FixedAngleJoint.new(@body, other_body, self_body_point, other_body_point, options[:angle])
|
|
90
|
+
when :angle
|
|
91
|
+
AngleJoint.new(@body, other_body, self_body_point, other_body_point, options[:self_body_angle], options[:other_body_angle])
|
|
92
|
+
when :basic
|
|
93
|
+
joint = BasicJoint.new(@body, other_body, options[:anchor].to_phys2d_vector)
|
|
94
|
+
joint.relaxation = options[:relaxation] if options[:relaxation]
|
|
95
|
+
joint
|
|
96
|
+
when :spring
|
|
97
|
+
joint = SpringJoint.new(@body, other_body, options[:self_anchor].to_phys2d_vector, options[:other_anchor].to_phys2d_vector)
|
|
98
|
+
joint
|
|
99
|
+
else
|
|
100
|
+
raise "Joint type #{options[:joint].inspect} not supported."
|
|
101
|
+
end
|
|
102
|
+
@world.add joint
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#The maximum speed the object is allowed to travel. Takes either a Vector with the x/y limits or the numeric value to assign to both x and y.
|
|
106
|
+
def speed_limit=(vector_or_shared_value)
|
|
107
|
+
if vector_or_shared_value.kind_of? Numeric
|
|
108
|
+
axis_limit_x = axis_limit_y = vector_or_shared_value / Jemini::Math::SQUARE_ROOT_OF_TWO
|
|
109
|
+
else
|
|
110
|
+
axis_limit_x = vector_or_shared_value.x
|
|
111
|
+
axis_limit_y = vector_or_shared_value.y
|
|
112
|
+
end
|
|
113
|
+
@body.set_max_velocity(axis_limit_x, axis_limit_y)
|
|
114
|
+
end
|
|
115
|
+
alias_method :set_speed_limit, :speed_limit=
|
|
116
|
+
|
|
117
|
+
def width
|
|
118
|
+
@body.shape.bounds.width
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def height
|
|
122
|
+
@body.shape.bounds.height
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def radius
|
|
126
|
+
@body.shape.radius
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def box_size
|
|
130
|
+
@body.shape.size
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#Set the absolute rotation.
|
|
134
|
+
def physical_rotation=(degrees)
|
|
135
|
+
@body.rotation = Jemini::Math.degrees_to_radians(degrees)
|
|
136
|
+
end
|
|
137
|
+
alias_method :set_physical_rotation, :physical_rotation=
|
|
138
|
+
|
|
139
|
+
#Set the rotation relative to the current rotation.
|
|
140
|
+
def rotate_physical(degrees)
|
|
141
|
+
@body.adjust_rotation Jemini::Math.degrees_to_radians(degrees)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def physical_rotation
|
|
145
|
+
Jemini::Math.radians_to_degrees(@body.rotation)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def physical_rotatable?
|
|
149
|
+
@body.rotatable?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
#Set whether the object is allowed to rotate.
|
|
153
|
+
def physical_rotatable=(rotatable)
|
|
154
|
+
@body.rotatable = rotatable
|
|
155
|
+
end
|
|
156
|
+
alias_method :set_physical_rotatable, :physical_rotatable=
|
|
157
|
+
|
|
158
|
+
#Push on the object.
|
|
159
|
+
#Takes either a Vector or x/y values representing the force to apply.
|
|
160
|
+
def add_force(x_or_vector, y = nil)
|
|
161
|
+
if y.nil?
|
|
162
|
+
@body.add_force(x_or_vector.to_phys2d_vector)
|
|
163
|
+
else
|
|
164
|
+
@body.add_force(Vector2f.new(x_or_vector, y))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#Set the force being applied to the object. Disregards all other forces.
|
|
169
|
+
def set_force(x, y)
|
|
170
|
+
@body.set_force(x, y)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def force
|
|
174
|
+
@body.force
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
#Adjust the object's velocity.
|
|
178
|
+
#Takes either a Vector or x/y values representing the adjustment to make.
|
|
179
|
+
def add_velocity(x_or_vector, y = nil)
|
|
180
|
+
if x_or_vector.kind_of? Vector
|
|
181
|
+
@body.adjust_velocity(x_or_vector.to_phys2d_vector)
|
|
182
|
+
else
|
|
183
|
+
@body.adjust_velocity(Java::net::phys2d::math::Vector2f.new(x_or_vector, y))
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def velocity
|
|
188
|
+
@body.velocity.to_vector
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def velocity=(vector)
|
|
192
|
+
@body.adjust_velocity(Java::net::phys2d::math::Vector2f.new(vector.x - @body.velocity.x, vector.y - @body.velocity.y))
|
|
193
|
+
end
|
|
194
|
+
alias_method :set_velocity, :velocity=
|
|
195
|
+
|
|
196
|
+
def angular_velocity
|
|
197
|
+
@body.angular_velocity
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def angular_velocity=(delta)
|
|
201
|
+
@body.adjust_angular_velocity(delta - angular_velocity)
|
|
202
|
+
end
|
|
203
|
+
alias_method :set_angular_velocity, :angular_velocity=
|
|
204
|
+
|
|
205
|
+
def apply_angular_velocity(velocity)
|
|
206
|
+
@body.adjust_angular_velocity velocity
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
#Immediately halt movement.
|
|
210
|
+
def come_to_rest
|
|
211
|
+
current_velocity = @body.velocity
|
|
212
|
+
add_velocity(-current_velocity.x, -current_velocity.y)
|
|
213
|
+
set_angular_velocity(0)
|
|
214
|
+
@body.is_resting = true
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
#Takes the following values:
|
|
218
|
+
#[:infinite] Object has infinite mass.
|
|
219
|
+
#[number] Value to set the mass to.
|
|
220
|
+
def mass=(mass)
|
|
221
|
+
@mass = mass
|
|
222
|
+
@mass = @mass.kind_of?(Symbol) ? INFINITE_MASS : @mass
|
|
223
|
+
@body.set(@shape, @mass)
|
|
224
|
+
# TODO: Consider moving to set_shape
|
|
225
|
+
# A body's position is lost when it moves, reset the position to where it was
|
|
226
|
+
@body.move @game_object.position.x, @game_object.position.y
|
|
227
|
+
end
|
|
228
|
+
alias_method :set_mass, :mass=
|
|
229
|
+
|
|
230
|
+
def restitution
|
|
231
|
+
@body.restitution
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def restitution=(restitution)
|
|
235
|
+
@body.restitution = restitution
|
|
236
|
+
end
|
|
237
|
+
alias_method :set_restitution, :restitution=
|
|
238
|
+
|
|
239
|
+
#Set the shape of the object as seen by the physics engine.
|
|
240
|
+
#call-seq:
|
|
241
|
+
#set_shape(:Box, width, height)
|
|
242
|
+
#set_shape(:Circle, radius)
|
|
243
|
+
#set_shape(:Polygon, *vectors)
|
|
244
|
+
#
|
|
245
|
+
def set_shape(shape, *params)
|
|
246
|
+
# Save off data that is destroyed when @body.set is called
|
|
247
|
+
saved_damping = damping
|
|
248
|
+
saved_angular_damping = angular_damping
|
|
249
|
+
saved_body_position = body_position
|
|
250
|
+
saved_friction = friction
|
|
251
|
+
saved_position = @game_object.position
|
|
252
|
+
saved_x, saved_y = @game_object.x, @game_object.y
|
|
253
|
+
if shape.respond_to?(:to_str) || shape.kind_of?(Symbol)
|
|
254
|
+
case shape.to_s
|
|
255
|
+
when 'Polygon', 'ConvexPolygon'
|
|
256
|
+
params = [params.map {|vector| vector.to_phys2d_vector }.to_java(Vector2f)]
|
|
257
|
+
end
|
|
258
|
+
@shape = ("Physical::" + shape.to_s).constantize.new(*params)
|
|
259
|
+
else
|
|
260
|
+
@shape = shape
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
@body.set(@shape, @mass)
|
|
264
|
+
set_body_position saved_body_position
|
|
265
|
+
@game_object.set_position saved_position
|
|
266
|
+
set_friction saved_friction
|
|
267
|
+
@game_object.position = Vector.new(saved_x, saved_y)
|
|
268
|
+
self.damping = saved_damping
|
|
269
|
+
self.angular_damping = saved_angular_damping
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def name=(name)
|
|
273
|
+
@name = name
|
|
274
|
+
setup_body
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
#Place this object under the control of the physics engine.
|
|
278
|
+
def add_to_world(world)
|
|
279
|
+
world.add @body
|
|
280
|
+
@world = world
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
#Remove this object from the control of the physics engine.
|
|
284
|
+
def remove_from_world(world)
|
|
285
|
+
world.remove @body
|
|
286
|
+
@world = nil
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
#Turn debug mode on or off for this object.
|
|
290
|
+
def physical_debug_mode=(flag)
|
|
291
|
+
if flag
|
|
292
|
+
@game_object.add_behavior :DebugPhysical
|
|
293
|
+
else
|
|
294
|
+
@game_object.remove_behavior :DebugPhysical
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
alias_method :set_physical_debug_mode, :physical_debug_mode=
|
|
298
|
+
|
|
299
|
+
def movable=(flag)
|
|
300
|
+
@body.moveable = flag
|
|
301
|
+
end
|
|
302
|
+
alias_method :set_movable, :movable=
|
|
303
|
+
|
|
304
|
+
def movable?
|
|
305
|
+
@body.moveable?
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
#The amount of air friction slowing the object's movement.
|
|
309
|
+
def damping
|
|
310
|
+
@body.damping
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def damping=(damping)
|
|
314
|
+
@body.damping = damping
|
|
315
|
+
end
|
|
316
|
+
alias_method :set_damping, :damping=
|
|
317
|
+
|
|
318
|
+
def angular_damping
|
|
319
|
+
@angular_damping
|
|
320
|
+
#@body.rot_damping
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def angular_damping=(damping)
|
|
324
|
+
@angular_damping = damping
|
|
325
|
+
#@body.rot_damping = damping
|
|
326
|
+
end
|
|
327
|
+
alias_method :set_angular_damping, :angular_damping=
|
|
328
|
+
|
|
329
|
+
#Set this object as immobile.
|
|
330
|
+
def set_static_body
|
|
331
|
+
come_to_rest
|
|
332
|
+
@body.moveable = false
|
|
333
|
+
@body.rotatable = false
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
#Set whether gravity affects this object.
|
|
337
|
+
def gravity_effected=(flag)
|
|
338
|
+
@body.gravity_effected = flag
|
|
339
|
+
end
|
|
340
|
+
alias_method :set_gravity_effected, :gravity_effected=
|
|
341
|
+
|
|
342
|
+
#The amount of friction slowing the object's movement.
|
|
343
|
+
def friction
|
|
344
|
+
@body.friction
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def friction=(friction)
|
|
348
|
+
@body.friction = friction
|
|
349
|
+
end
|
|
350
|
+
alias_method :set_friction, :friction=
|
|
351
|
+
|
|
352
|
+
# def get_colliding_game_objects(tangible_game_object)
|
|
353
|
+
# # TODO: Tangibles only?
|
|
354
|
+
# tangible_game_object
|
|
355
|
+
# end
|
|
356
|
+
|
|
357
|
+
#Get a list of CollisionEvents for objects currently colliding with this one.
|
|
358
|
+
def get_collision_events
|
|
359
|
+
return [] if @world.nil? # a worthwhile check in some cases. Maybe because the physical is removed but the game object is not?
|
|
360
|
+
@world.get_contacts(@body).map do |event|
|
|
361
|
+
body_method = event.body_a == @body ? :body_b : :body_a
|
|
362
|
+
PhysicsMessage.new(event, event.send(body_method).user_data)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
private
|
|
367
|
+
def setup_body
|
|
368
|
+
if @name
|
|
369
|
+
@body = Body.new(@name, @shape, @mass)
|
|
370
|
+
else
|
|
371
|
+
@body = Body.new(@shape, @mass)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
x = @body.position.x
|
|
375
|
+
y = @body.position.y
|
|
376
|
+
|
|
377
|
+
@body.set(@shape, @mass)
|
|
378
|
+
@body.move(x, y)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def move_body(vector)
|
|
382
|
+
@body.move(vector.x, vector.y)
|
|
383
|
+
end
|
|
384
|
+
end
|