lotu 0.1.7 → 0.1.9
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/VERSION +1 -1
- data/examples/hello_world/hello_world.rb +15 -1
- data/examples/mouse_pointer/mouse_pointer.rb +35 -13
- data/examples/steering_behaviors/steering.rb +30 -10
- data/lib/lotu/actor.rb +2 -4
- data/lib/lotu/behaviors/collidable.rb +2 -2
- data/lib/lotu/behaviors/system_user.rb +10 -0
- data/lib/lotu/misc/string.rb +14 -0
- data/lib/lotu/systems/collision_system.rb +41 -0
- data/lib/lotu/systems/fps_system.rb +32 -0
- data/lib/lotu/systems/stalker_system.rb +30 -0
- data/lib/lotu/systems/{steering.rb → steering_system.rb} +63 -64
- data/lib/lotu/text_box.rb +10 -12
- data/lib/lotu/window.rb +136 -19
- data/lib/lotu.rb +3 -3
- data/lotu.gemspec +8 -6
- metadata +9 -7
- data/lib/lotu/behaviors/resourceful.rb +0 -61
- data/lib/lotu/misc/fps.rb +0 -28
- data/lib/lotu/systems/collision.rb +0 -46
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.9
|
@@ -7,13 +7,16 @@ class MovingRuby < Lotu::Actor
|
|
7
7
|
|
8
8
|
def initialize(opts={})
|
9
9
|
super
|
10
|
+
# Use the image which filename is CptnRuby Gem.png
|
10
11
|
set_image 'CptnRuby Gem.png'
|
12
|
+
# Map keys to some methods
|
11
13
|
set_keys(KbRight => :move_right,
|
12
14
|
KbLeft => :move_left,
|
13
15
|
KbUp => :move_up,
|
14
16
|
KbDown => :move_down)
|
15
17
|
end
|
16
18
|
|
19
|
+
# Let's define some basic movement methods
|
17
20
|
def move_right
|
18
21
|
@x += 1
|
19
22
|
end
|
@@ -35,14 +38,25 @@ end
|
|
35
38
|
class Example < Lotu::Window
|
36
39
|
|
37
40
|
def initialize
|
41
|
+
# This will call the hooks:
|
42
|
+
# load_resources, setup_systems and setup_actors
|
43
|
+
# declared in the parent class
|
38
44
|
super
|
45
|
+
# When the Escape key is pressed, call the close method on class Example
|
39
46
|
set_keys(KbEscape => :close)
|
47
|
+
end
|
40
48
|
|
49
|
+
def load_resources
|
50
|
+
# From this file,
|
41
51
|
with_path_from_file(__FILE__) do
|
42
|
-
|
52
|
+
# go back one dir and search inside media/
|
53
|
+
load_images '../media/'
|
43
54
|
end
|
55
|
+
end
|
44
56
|
|
57
|
+
def setup_actors
|
45
58
|
@ruby = MovingRuby.new(:x => width/2, :y => height/2)
|
59
|
+
# Create a TextBox so we can display a message on screen
|
46
60
|
@info = Lotu::TextBox.new
|
47
61
|
@info.text("Move around with arrow keys")
|
48
62
|
end
|
@@ -16,13 +16,27 @@ end
|
|
16
16
|
|
17
17
|
class Example < Lotu::Window
|
18
18
|
def initialize
|
19
|
+
# This will call the hooks:
|
20
|
+
# load_resources, setup_systems and setup_actors
|
21
|
+
# declared in the parent class
|
19
22
|
super
|
20
|
-
|
23
|
+
# Custom setup methods for this class
|
24
|
+
setup_input
|
25
|
+
setup_events
|
26
|
+
end
|
21
27
|
|
28
|
+
def load_resources
|
22
29
|
with_path_from_file(__FILE__) do
|
23
30
|
load_images '../media'
|
24
31
|
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_systems
|
35
|
+
use(Lotu::FpsSystem)
|
36
|
+
use(Lotu::StalkerSystem, :stalk => [Lotu::Actor, Lotu::InputController, Object])
|
37
|
+
end
|
25
38
|
|
39
|
+
def setup_actors
|
26
40
|
@ruby = WarpingRuby.new(:x => width/2, :y => height/2)
|
27
41
|
@cursor1 = Lotu::Cursor.new(:image => 'crosshair.png',
|
28
42
|
:keys => {MsLeft => [:click, false]},
|
@@ -36,25 +50,33 @@ class Example < Lotu::Window
|
|
36
50
|
KbLeft => :left,
|
37
51
|
KbRight => :right},
|
38
52
|
:color => 0xff99ff00)
|
53
|
+
@cursor2.x = width*3/4
|
54
|
+
@cursor2.y = height/2
|
55
|
+
|
56
|
+
# Create a TextBox with default option :size => 15
|
57
|
+
@info = Lotu::TextBox.new(:size => 15)
|
58
|
+
@info.watch(@systems[Lotu::FpsSystem])
|
59
|
+
@info.watch(@systems[Lotu::StalkerSystem])
|
60
|
+
# We can change the size for a specific line of text
|
61
|
+
@info.watch("@cursor1 data:", :size => 20)
|
62
|
+
# Color too
|
63
|
+
@info.watch(@cursor1, :color => 0xff0099ff)
|
64
|
+
@info.watch("@cursor2 data:", :size => 20)
|
65
|
+
@info.watch(@cursor2, :color => 0xff99ff00)
|
66
|
+
@info.text("Move @cursor1 with mouse and @cursor2 with arrow keys (click with space!)")
|
67
|
+
end
|
68
|
+
|
69
|
+
def setup_input
|
70
|
+
set_keys KbEscape => :close
|
71
|
+
end
|
39
72
|
|
73
|
+
def setup_events
|
40
74
|
@cursor1.on(:click) do |x,y|
|
41
75
|
@ruby.warp(x,y)
|
42
76
|
end
|
43
77
|
@cursor2.on(:click) do |x,y|
|
44
78
|
@ruby.warp(x,y)
|
45
79
|
end
|
46
|
-
|
47
|
-
@cursor2.x = width*3/4
|
48
|
-
@cursor2.y = height/2
|
49
|
-
|
50
|
-
@info = Lotu::TextBox.new
|
51
|
-
@info.watch(@fps_counter)
|
52
|
-
@info.watch("@cursor1 data:")
|
53
|
-
@info.watch(@cursor1, :color => 0xff0099ff, :font_size => 15)
|
54
|
-
@info.watch("@cursor2 data:")
|
55
|
-
@info.watch(@cursor2, :color => 0xff99ff00, :font_size => 15)
|
56
|
-
@info.text("")
|
57
|
-
@info.text("Move @cursor1 with mouse and @cursor2 with arrow keys (click with space!)", :font_size => 15)
|
58
80
|
end
|
59
81
|
|
60
82
|
end
|
@@ -7,7 +7,7 @@ class SteeringRuby < Lotu::Actor
|
|
7
7
|
def initialize(opts={})
|
8
8
|
super
|
9
9
|
set_image 'CptnRuby Gem.png'
|
10
|
-
|
10
|
+
use(Lotu::SteeringSystem, opts)
|
11
11
|
end
|
12
12
|
|
13
13
|
def warp(x, y)
|
@@ -17,14 +17,31 @@ end
|
|
17
17
|
|
18
18
|
class Example < Lotu::Window
|
19
19
|
def initialize
|
20
|
+
# This will call the hooks:
|
21
|
+
# load_resources, setup_systems and setup_actors
|
22
|
+
# declared in the parent class
|
20
23
|
super
|
21
|
-
|
22
|
-
|
24
|
+
# Custom setup methods for this class
|
25
|
+
setup_input
|
26
|
+
setup_events
|
27
|
+
end
|
23
28
|
|
29
|
+
def load_resources
|
24
30
|
with_path_from_file(__FILE__) do
|
25
31
|
load_images '../media'
|
26
32
|
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_input
|
36
|
+
set_keys(KbEscape => :close,
|
37
|
+
MsRight => :reset_ruby)
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_systems
|
41
|
+
use(Lotu::FpsSystem)
|
42
|
+
end
|
27
43
|
|
44
|
+
def setup_actors
|
28
45
|
@ruby = SteeringRuby.new(:mass => 0.3, :max_speed => 100, :max_turn_rate => 140)
|
29
46
|
@ruby.warp(width/2, height/2)
|
30
47
|
@ruby.activate(:evade)
|
@@ -34,21 +51,24 @@ class Example < Lotu::Window
|
|
34
51
|
|
35
52
|
@cursor = Lotu::Cursor.new(:image => 'crosshair.png',
|
36
53
|
:keys => {MsLeft => [:click, false]})
|
37
|
-
@cursor.on(:click) do |x,y|
|
38
|
-
@ruby.pursuer = @ruby2
|
39
|
-
@ruby2.evader = @ruby
|
40
|
-
end
|
41
54
|
|
42
|
-
@window_info = Lotu::TextBox.new(:
|
43
|
-
@window_info.watch(@
|
55
|
+
@window_info = Lotu::TextBox.new(:size => 15)
|
56
|
+
@window_info.watch(@systems[Lotu::FpsSystem])
|
44
57
|
@window_info.watch(@cursor, :color => 0xffff0000)
|
45
58
|
@window_info.text("Click to start the simulation")
|
46
59
|
@window_info.text("One will pursuit while the other evades, right click to center evader on screen")
|
47
60
|
|
48
|
-
@ruby_info = Lotu::TextBox.new(:attach_to => @ruby, :
|
61
|
+
@ruby_info = Lotu::TextBox.new(:attach_to => @ruby, :size => 14)
|
49
62
|
@ruby_info.watch(@ruby)
|
50
63
|
end
|
51
64
|
|
65
|
+
def setup_events
|
66
|
+
@cursor.on(:click) do |x,y|
|
67
|
+
@ruby.pursuer = @ruby2
|
68
|
+
@ruby2.evader = @ruby
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
52
72
|
def reset_ruby
|
53
73
|
@ruby.pos.x = width/2
|
54
74
|
@ruby.pos.y = height/2
|
data/lib/lotu/actor.rb
CHANGED
@@ -2,6 +2,8 @@ module Lotu
|
|
2
2
|
class Actor
|
3
3
|
attr_accessor :parent, :x, :y, :systems
|
4
4
|
|
5
|
+
include SystemUser
|
6
|
+
|
5
7
|
def initialize(opts={})
|
6
8
|
default_opts = {
|
7
9
|
:x => 0,
|
@@ -32,10 +34,6 @@ module Lotu
|
|
32
34
|
@parent.update_queue.delete(self)
|
33
35
|
end
|
34
36
|
|
35
|
-
def activate_system(klass, opts={})
|
36
|
-
@systems[klass] = klass.new(self, opts)
|
37
|
-
end
|
38
|
-
|
39
37
|
def update
|
40
38
|
@systems.each_pair do |klass, system|
|
41
39
|
system.update
|
@@ -11,7 +11,7 @@ module Lotu
|
|
11
11
|
|
12
12
|
def collides_as(tag)
|
13
13
|
@collision_tag = tag
|
14
|
-
@parent.systems[
|
14
|
+
@parent.systems[CollisionSystem].add_entity(self, tag)
|
15
15
|
end
|
16
16
|
|
17
17
|
def collides_with(other)
|
@@ -20,7 +20,7 @@ module Lotu
|
|
20
20
|
|
21
21
|
def die
|
22
22
|
super
|
23
|
-
@parent.systems[
|
23
|
+
@parent.systems[CollisionSystem].remove_entity(self, @collision_tag)
|
24
24
|
end
|
25
25
|
|
26
26
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# As saw in a russian comment by ZARATUSTR at:
|
2
|
+
# http://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/
|
3
|
+
class String
|
4
|
+
|
5
|
+
def red; colorize(self, "\e[1m\e[31m") end
|
6
|
+
def green; colorize(self, "\e[1m\e[32m") end
|
7
|
+
def dark_green; colorize(self, "\e[32m") end
|
8
|
+
def yellow; colorize(self, "\e[1m\e[33m") end
|
9
|
+
def blue; colorize(self, "\e[1m\e[34m") end
|
10
|
+
def dark_blue; colorize(self, "\e[34m") end
|
11
|
+
def pur; colorize(self, "\e[1m\e[35m") end
|
12
|
+
def colorize(text, color_code) "#{color_code}#{text}\e[0m" end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lotu
|
2
|
+
class CollisionSystem
|
3
|
+
|
4
|
+
def initialize(user, opts={})
|
5
|
+
user.extend UserMethods
|
6
|
+
@entities = Hash.new{ |h,k| h[k] = [] }
|
7
|
+
@actions = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_entity(obj, tag)
|
11
|
+
@entities[tag] << obj
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_entity(obj, tag)
|
15
|
+
@entities[tag].delete(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
def when_colliding(type1, type2, &blk)
|
19
|
+
@actions[[type1, type2]] = blk
|
20
|
+
end
|
21
|
+
|
22
|
+
def update
|
23
|
+
@actions.each do |tags, blk|
|
24
|
+
@entities[tags[0]].each do |ent1|
|
25
|
+
@entities[tags[1]].each do |ent2|
|
26
|
+
blk.call(ent1, ent2) if ent1.collides_with(ent2)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def draw;end
|
33
|
+
|
34
|
+
module UserMethods
|
35
|
+
def when_colliding(*args, &blk)
|
36
|
+
systems[CollisionSystem].when_colliding(*args, &blk)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Lotu
|
2
|
+
class FpsSystem
|
3
|
+
|
4
|
+
def initialize(user, opts={})
|
5
|
+
default_opts = {
|
6
|
+
:samples => 10
|
7
|
+
}
|
8
|
+
opts = default_opts.merge!(opts)
|
9
|
+
@accum = 0.0
|
10
|
+
@ticks = 0
|
11
|
+
@fps = 0.0
|
12
|
+
@samples = opts[:samples]
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
@ticks += 1
|
17
|
+
@accum += $window.dt
|
18
|
+
if @ticks >= @samples
|
19
|
+
@fps = @ticks/@accum
|
20
|
+
@ticks = 0
|
21
|
+
@accum = 0.0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"Samples per second: #{@samples} | FPS: #{format("%.2f",@fps)}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw;end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lotu
|
2
|
+
class StalkerSystem
|
3
|
+
|
4
|
+
def initialize(user, opts={})
|
5
|
+
default_opts = {
|
6
|
+
:stalk => [Actor]
|
7
|
+
}
|
8
|
+
opts = default_opts.merge!(opts)
|
9
|
+
@stalked = {}
|
10
|
+
opts[:stalk].each do |type|
|
11
|
+
@stalked[type] = 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
@stalked.each_key do |type|
|
17
|
+
@stalked[type] = ObjectSpace.each_object(type).count
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@stalked.map do |type, count|
|
23
|
+
"#{type}: #{count}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def draw;end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
module Lotu
|
3
|
-
class
|
4
|
-
attr_reader :force
|
3
|
+
class SteeringSystem
|
5
4
|
|
6
|
-
def initialize(
|
5
|
+
def initialize(user, opts={})
|
7
6
|
# Add new functionality to Actor
|
8
|
-
|
7
|
+
user.extend UserMethods
|
9
8
|
|
10
9
|
# Initialize attributes
|
11
10
|
default_opts = {
|
@@ -18,20 +17,20 @@ module Lotu
|
|
18
17
|
}
|
19
18
|
opts = default_opts.merge!(opts)
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
user.mass = opts[:mass]
|
21
|
+
user.max_speed = opts[:max_speed]
|
22
|
+
user.max_turn_rate = opts[:max_turn_rate]
|
23
|
+
user.max_force = opts[:max_force]
|
24
|
+
user.wander_radius = opts[:wander_radius]
|
25
|
+
user.wander_distance = opts[:wander_distance]
|
27
26
|
|
28
27
|
# More attributes
|
29
|
-
@
|
28
|
+
@user = user
|
30
29
|
@behaviors = {}
|
31
30
|
@force = Vector2d.new
|
32
31
|
@zero = Vector2d.new
|
33
|
-
|
34
|
-
|
32
|
+
user.pos.x = user.x
|
33
|
+
user.pos.y = user.y
|
35
34
|
end
|
36
35
|
|
37
36
|
def update
|
@@ -40,32 +39,32 @@ module Lotu
|
|
40
39
|
@force += send(behavior) if active
|
41
40
|
end
|
42
41
|
|
43
|
-
@
|
44
|
-
@
|
42
|
+
@user.accel = @force / @user.mass
|
43
|
+
@user.accel.truncate!(@user.max_force)
|
45
44
|
|
46
|
-
max_angle = @
|
47
|
-
new_velocity = @
|
48
|
-
angle_to_new_velocity = @
|
45
|
+
max_angle = @user.max_turn_rate * @user.dt
|
46
|
+
new_velocity = @user.vel + @user.accel * @user.dt
|
47
|
+
angle_to_new_velocity = @user.heading.angle_to(new_velocity)
|
49
48
|
|
50
49
|
if angle_to_new_velocity.abs > max_angle
|
51
|
-
sign = @
|
52
|
-
corrected_angle = @
|
53
|
-
@
|
54
|
-
@
|
50
|
+
sign = @user.heading.sign_to(new_velocity)
|
51
|
+
corrected_angle = @user.heading.angle + max_angle * sign
|
52
|
+
@user.vel.x = Gosu.offset_x(corrected_angle, new_velocity.length)
|
53
|
+
@user.vel.y = Gosu.offset_y(corrected_angle, new_velocity.length)
|
55
54
|
else
|
56
|
-
@
|
55
|
+
@user.vel = new_velocity
|
57
56
|
end
|
58
57
|
|
59
|
-
@
|
60
|
-
@
|
58
|
+
@user.vel.truncate!(@user.max_speed)
|
59
|
+
@user.pos += @user.vel * @user.dt
|
61
60
|
|
62
|
-
if @
|
63
|
-
@
|
61
|
+
if @user.vel.length > 0.0001
|
62
|
+
@user.heading = @user.vel.normalize
|
64
63
|
end
|
65
64
|
|
66
|
-
@
|
67
|
-
@
|
68
|
-
@
|
65
|
+
@user.x = @user.pos.x
|
66
|
+
@user.y = @user.pos.y
|
67
|
+
@user.angle = @user.heading.angle
|
69
68
|
end
|
70
69
|
|
71
70
|
def activate(behavior)
|
@@ -78,61 +77,61 @@ module Lotu
|
|
78
77
|
|
79
78
|
# The steering behaviors themselves
|
80
79
|
def seek
|
81
|
-
return @zero if @
|
82
|
-
desired_velocity = (@
|
83
|
-
return desired_velocity - @
|
80
|
+
return @zero if @user.target.nil?
|
81
|
+
desired_velocity = (@user.target - @user.pos).normalize * @user.max_speed
|
82
|
+
return desired_velocity - @user.vel
|
84
83
|
end
|
85
84
|
|
86
85
|
def flee
|
87
|
-
return @zero if @
|
88
|
-
desired_velocity = (@
|
89
|
-
return desired_velocity - @
|
86
|
+
return @zero if @user.target.nil?
|
87
|
+
desired_velocity = (@user.pos - @user.target).normalize * @user.max_speed
|
88
|
+
return desired_velocity - @user.vel
|
90
89
|
end
|
91
90
|
|
92
91
|
def arrive(deceleration = :normal)
|
93
|
-
return @zero if @
|
92
|
+
return @zero if @user.target.nil?
|
94
93
|
deceleration_values = {
|
95
94
|
:fast => 0.5,
|
96
95
|
:normal => 1,
|
97
96
|
:slow => 2
|
98
97
|
}
|
99
98
|
deceleration_tweaker = 1.0
|
100
|
-
to_target = @
|
99
|
+
to_target = @user.target - @user.pos
|
101
100
|
distance_to_target = to_target.length
|
102
101
|
|
103
102
|
if distance_to_target > 10
|
104
103
|
speed = distance_to_target / (deceleration_tweaker * deceleration_values[deceleration])
|
105
|
-
speed = [speed, @
|
104
|
+
speed = [speed, @user.max_speed].min
|
106
105
|
desired_velocity = to_target * speed / distance_to_target
|
107
|
-
return desired_velocity - @
|
106
|
+
return desired_velocity - @user.vel
|
108
107
|
else
|
109
|
-
@
|
110
|
-
@
|
108
|
+
@user.vel /= 1.15
|
109
|
+
@user.accel /= 1.15
|
111
110
|
end
|
112
111
|
return @zero
|
113
112
|
end
|
114
113
|
|
115
114
|
def pursuit
|
116
|
-
return @zero if @
|
117
|
-
to_evader = @
|
118
|
-
relative_heading = @
|
119
|
-
if to_evader.dot(@
|
120
|
-
@
|
115
|
+
return @zero if @user.evader.nil?
|
116
|
+
to_evader = @user.evader.pos - @user.pos
|
117
|
+
relative_heading = @user.heading.dot(@user.evader.heading)
|
118
|
+
if to_evader.dot(@user.heading) > 0 && relative_heading < -0.95
|
119
|
+
@user.target = @user.evader.pos
|
121
120
|
return seek
|
122
121
|
end
|
123
122
|
|
124
|
-
look_ahead_time = to_evader.length / (@
|
125
|
-
predicted_position = @
|
126
|
-
@
|
123
|
+
look_ahead_time = to_evader.length / (@user.max_speed + @user.evader.vel.length)
|
124
|
+
predicted_position = @user.evader.pos + @user.evader.vel * look_ahead_time
|
125
|
+
@user.target = predicted_position
|
127
126
|
return seek
|
128
127
|
end
|
129
128
|
|
130
129
|
def evade
|
131
|
-
return @zero if @
|
132
|
-
to_pursuer = @
|
133
|
-
look_ahead_time = to_pursuer.length / (@
|
134
|
-
predicted_position = @
|
135
|
-
@
|
130
|
+
return @zero if @user.pursuer.nil?
|
131
|
+
to_pursuer = @user.pursuer.pos - @user.pos
|
132
|
+
look_ahead_time = to_pursuer.length / (@user.max_speed + @user.pursuer.vel.length)
|
133
|
+
predicted_position = @user.pursuer.pos + @user.pursuer.vel * look_ahead_time
|
134
|
+
@user.target = @user.pursuer.pos
|
136
135
|
return flee
|
137
136
|
end
|
138
137
|
|
@@ -140,12 +139,12 @@ module Lotu
|
|
140
139
|
def wander
|
141
140
|
wander_jitter = 10
|
142
141
|
|
143
|
-
@
|
144
|
-
@
|
145
|
-
@
|
146
|
-
target_local = @
|
147
|
-
target_world = local_to_world(target_local, @
|
148
|
-
return target_world - @
|
142
|
+
@user.wander_target += Vector2d.new(Gosu.random(-1,1), Gosu.random(-1,1))
|
143
|
+
@user.wander_target.normalize!
|
144
|
+
@user.wander_target *= @user.wander_radius
|
145
|
+
target_local = @user.wander_target + Vector2d.new(0, @user.wander_distance)
|
146
|
+
target_world = local_to_world(target_local, @user.heading, @user.heading.perp, @user.pos)
|
147
|
+
return target_world - @user.pos
|
149
148
|
end
|
150
149
|
|
151
150
|
def local_to_world(local_target, heading, side, pos)
|
@@ -155,14 +154,14 @@ module Lotu
|
|
155
154
|
world_point = Vector2d.new(x, y) + pos
|
156
155
|
end
|
157
156
|
|
158
|
-
module
|
157
|
+
module UserMethods
|
159
158
|
|
160
159
|
def self.extended(instance)
|
161
160
|
instance.steering_setup
|
162
161
|
end
|
163
162
|
|
164
163
|
def steering_setup
|
165
|
-
# Create accessors for the
|
164
|
+
# Create accessors for the user
|
166
165
|
class << self
|
167
166
|
attr_accessor :mass, :pos, :heading, :vel, :accel,
|
168
167
|
:max_speed, :max_turn_rate, :max_force,
|
@@ -181,7 +180,7 @@ module Lotu
|
|
181
180
|
end
|
182
181
|
|
183
182
|
def activate(behavior)
|
184
|
-
@systems[
|
183
|
+
@systems[SteeringSystem].activate(behavior)
|
185
184
|
end
|
186
185
|
|
187
186
|
def distance_to_target
|
data/lib/lotu/text_box.rb
CHANGED
@@ -4,15 +4,14 @@ module Lotu
|
|
4
4
|
|
5
5
|
def initialize(opts={})
|
6
6
|
default_opts = {
|
7
|
-
:
|
7
|
+
:size => 20
|
8
8
|
}
|
9
9
|
opts = default_opts.merge!(opts)
|
10
10
|
super(opts)
|
11
11
|
#TODO puede especificar a quién watchear y sus opciones de
|
12
12
|
#dibujado en los parámetros
|
13
13
|
@watch_list = []
|
14
|
-
@
|
15
|
-
@font_size = opts[:font_size]
|
14
|
+
@size = opts[:size]
|
16
15
|
@attached_to = opts[:attach_to]
|
17
16
|
# Since we aren't setting an image for this, we need to specify
|
18
17
|
# this actor needs to be drawed
|
@@ -24,8 +23,7 @@ module Lotu
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def watch(subject, opts={})
|
27
|
-
@watch_list << subject
|
28
|
-
@subject_opts[subject] = opts
|
26
|
+
@watch_list << [subject, opts]
|
29
27
|
end
|
30
28
|
|
31
29
|
def attach_to(actor)
|
@@ -41,17 +39,17 @@ module Lotu
|
|
41
39
|
|
42
40
|
def draw
|
43
41
|
pos_y = 0
|
44
|
-
@watch_list.each do |watched|
|
45
|
-
|
46
|
-
my_color =
|
42
|
+
@watch_list.each do |watched, opts|
|
43
|
+
my_size = opts[:size] || @size
|
44
|
+
my_color = opts[:color] || @color
|
47
45
|
my_text = watched.to_s
|
48
46
|
if my_text.is_a?(String)
|
49
|
-
$window.fonts[
|
50
|
-
pos_y +=
|
47
|
+
$window.fonts[my_size].draw(my_text, @x, @y + pos_y, @z, @factor_x, @factor_y, my_color)
|
48
|
+
pos_y += my_size
|
51
49
|
else
|
52
50
|
my_text.each do |line|
|
53
|
-
$window.fonts[
|
54
|
-
pos_y +=
|
51
|
+
$window.fonts[my_size].draw(line, @x, @y + pos_y, @z, @factor_x, @factor_y, my_color)
|
52
|
+
pos_y += my_size
|
55
53
|
end
|
56
54
|
end
|
57
55
|
end
|
data/lib/lotu/window.rb
CHANGED
@@ -1,52 +1,99 @@
|
|
1
1
|
module Lotu
|
2
2
|
class Window < Gosu::Window
|
3
|
-
# delta
|
3
|
+
# Accessors for time delta, systems and fonts
|
4
4
|
attr_reader :dt, :systems, :fonts
|
5
|
-
|
5
|
+
# Accessors for queues
|
6
|
+
attr_accessor :update_queue, :draw_queue, :input_listeners
|
7
|
+
|
8
|
+
include SystemUser
|
6
9
|
|
7
10
|
def initialize(params={})
|
8
11
|
super(800, 600, false)
|
9
12
|
|
10
13
|
# Handy global window variable
|
11
14
|
$window = self
|
15
|
+
@debug = true
|
16
|
+
setup_containers
|
12
17
|
|
13
|
-
#
|
14
|
-
@systems = {}
|
15
|
-
@update_queue = []
|
16
|
-
@draw_queue = []
|
17
|
-
@input_register = Hash.new{|hash,key| hash[key] = []}
|
18
|
-
|
19
|
-
@fps_counter = FpsCounter.new
|
18
|
+
# For timer initialization
|
20
19
|
@last_time = Gosu::milliseconds
|
20
|
+
# Memoize fonts by size
|
21
21
|
@fonts = Hash.new{|h,k| h[k] = Gosu::Font.new(self, Gosu::default_font_name, k)}
|
22
22
|
|
23
23
|
# Add extra functionality
|
24
24
|
extend Controllable
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
# Call hook methods
|
27
|
+
load_resources
|
28
|
+
setup_systems
|
29
|
+
setup_actors
|
27
30
|
end
|
28
31
|
|
32
|
+
|
33
|
+
# Hook methods, these are meant to be replaced by subclasses
|
34
|
+
def load_resources;end
|
35
|
+
def setup_systems;end
|
36
|
+
def setup_actors;end
|
37
|
+
|
38
|
+
# Setup various containers
|
39
|
+
def setup_containers
|
40
|
+
# For systems
|
41
|
+
@systems = {}
|
42
|
+
|
43
|
+
# For queues
|
44
|
+
@update_queue = []
|
45
|
+
@draw_queue = []
|
46
|
+
@input_register = Hash.new{|hash,key| hash[key] = []}
|
47
|
+
|
48
|
+
# For resource management
|
49
|
+
@images = {}
|
50
|
+
@sounds = {}
|
51
|
+
@songs = {}
|
52
|
+
@animations = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Main update loop
|
29
56
|
def update
|
30
57
|
new_time = Gosu::milliseconds
|
31
58
|
@dt = (new_time - @last_time)/1000.0
|
32
59
|
@last_time = new_time
|
33
|
-
@fps_counter.update(@dt)
|
34
60
|
|
61
|
+
# Update each system
|
35
62
|
@systems.each_value do |system|
|
36
63
|
system.update
|
37
64
|
end
|
38
65
|
|
39
|
-
|
40
|
-
|
66
|
+
# Update each actor
|
67
|
+
@update_queue.each do |actor|
|
68
|
+
actor.update
|
41
69
|
end
|
42
70
|
end
|
43
71
|
|
72
|
+
# Main draw loop
|
44
73
|
def draw
|
45
|
-
|
46
|
-
|
74
|
+
# Systems may report interesting stuff
|
75
|
+
@systems.each_value do |system|
|
76
|
+
system.draw
|
77
|
+
end
|
78
|
+
|
79
|
+
# Draw each actor in queue
|
80
|
+
@draw_queue.each do |actor|
|
81
|
+
actor.draw
|
47
82
|
end
|
48
83
|
end
|
49
84
|
|
85
|
+
# For actor management
|
86
|
+
def manage_me(actor)
|
87
|
+
@draw_queue << actor
|
88
|
+
@update_queue << actor
|
89
|
+
end
|
90
|
+
|
91
|
+
def kill_me(actor)
|
92
|
+
@draw_queue.delete(actor)
|
93
|
+
@update_queue.delete(actor)
|
94
|
+
end
|
95
|
+
|
96
|
+
# These are for managing input
|
50
97
|
def button_down(id)
|
51
98
|
@input_register[id].each do |item|
|
52
99
|
item.button_down(id)
|
@@ -59,7 +106,6 @@ module Lotu
|
|
59
106
|
end
|
60
107
|
end
|
61
108
|
|
62
|
-
# Register controller
|
63
109
|
def register_for_input(controller)
|
64
110
|
controller.keys.each_key do |key|
|
65
111
|
@input_register[key] << controller
|
@@ -67,8 +113,79 @@ module Lotu
|
|
67
113
|
@update_queue << controller
|
68
114
|
end
|
69
115
|
|
70
|
-
|
71
|
-
|
116
|
+
# These are for managing resources
|
117
|
+
def image(name)
|
118
|
+
@images[name]
|
119
|
+
end
|
120
|
+
|
121
|
+
def sound(name)
|
122
|
+
@sounds[name]
|
123
|
+
end
|
124
|
+
|
125
|
+
def song(name)
|
126
|
+
@songs[name]
|
72
127
|
end
|
128
|
+
|
129
|
+
def animation(name)
|
130
|
+
@animations[name]
|
131
|
+
end
|
132
|
+
|
133
|
+
def load_images(path)
|
134
|
+
with_files(/\.png|\.jpg|\.bmp/, path) do |file_name, file_path|
|
135
|
+
@images[file_name] = Gosu::Image.new($window, file_path)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def load_sounds(path)
|
140
|
+
with_files(/\.ogg|\.mp3|\.wav/, path) do |file_name, file_path|
|
141
|
+
@sounds[file_name] = Gosu::Sample.new($window, file_path)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def load_songs(path)
|
146
|
+
with_files(/\.ogg|\.mp3|\.wav/, path) do |file_name, file_path|
|
147
|
+
@songs[file_name] = Gosu::Song.new($window, file_path)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def load_animations(path)
|
152
|
+
path = File.expand_path(File.join(@path, path))
|
153
|
+
puts "Loading from: #{path}"
|
154
|
+
|
155
|
+
count = 0
|
156
|
+
Dir.entries(path).grep(regexp).each do |entry|
|
157
|
+
begin
|
158
|
+
@animations[entry] = klass.new($window, File.join(path, entry))
|
159
|
+
count += 1
|
160
|
+
rescue Exception => e
|
161
|
+
puts e, File.join(path, entry)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
puts "#{count} #{klass} files loaded."
|
165
|
+
end
|
166
|
+
|
167
|
+
def with_path_from_file(path, &blk)
|
168
|
+
@path = File.expand_path(File.dirname path)
|
169
|
+
yield
|
170
|
+
end
|
171
|
+
|
172
|
+
def with_files(regexp, path)
|
173
|
+
path = File.expand_path(File.join(@path, path))
|
174
|
+
puts "\nLoading from: #{path}"
|
175
|
+
|
176
|
+
count = 0
|
177
|
+
Dir.entries(path).grep(regexp).each do |entry|
|
178
|
+
begin
|
179
|
+
yield(entry, File.join(path, entry))
|
180
|
+
count += 1
|
181
|
+
print '.'.green
|
182
|
+
rescue Exception => e
|
183
|
+
print '.'.red
|
184
|
+
puts e, File.join(path, entry) if @debug
|
185
|
+
end
|
186
|
+
end
|
187
|
+
puts "\n#{count} file(s) loaded."
|
188
|
+
end
|
189
|
+
|
73
190
|
end
|
74
191
|
end
|
data/lib/lotu.rb
CHANGED
@@ -2,7 +2,7 @@ LOTU_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'lotu'))
|
|
2
2
|
$LOAD_PATH.unshift(LOTU_ROOT)
|
3
3
|
|
4
4
|
require 'gosu'
|
5
|
-
%w{
|
6
|
-
%w{collidable controllable
|
7
|
-
%w{
|
5
|
+
%w{vector2d string}.each{|file| require "misc/#{file}"}
|
6
|
+
%w{system_user collidable controllable drawable controllable/input_controller eventful}.each{|file| require "behaviors/#{file}"}
|
7
|
+
%w{stalker_system fps_system collision_system steering_system}.each{|file| require "systems/#{file}"}
|
8
8
|
%w{window actor cursor text_box}.each{|file| require file}
|
data/lotu.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{lotu}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.9"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["lobo_tuerto"]
|
12
|
-
s.date = %q{2010-03-
|
12
|
+
s.date = %q{2010-03-25}
|
13
13
|
s.description = %q{lotu aims to bring an agile and simple game development framework to life. It provides useful abstractions so you can concentrate on developing your game.}
|
14
14
|
s.email = %q{dev@lobotuerto.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -52,12 +52,14 @@ Gem::Specification.new do |s|
|
|
52
52
|
"lib/lotu/behaviors/controllable/input_controller.rb",
|
53
53
|
"lib/lotu/behaviors/drawable.rb",
|
54
54
|
"lib/lotu/behaviors/eventful.rb",
|
55
|
-
"lib/lotu/behaviors/
|
55
|
+
"lib/lotu/behaviors/system_user.rb",
|
56
56
|
"lib/lotu/cursor.rb",
|
57
|
-
"lib/lotu/misc/
|
57
|
+
"lib/lotu/misc/string.rb",
|
58
58
|
"lib/lotu/misc/vector2d.rb",
|
59
|
-
"lib/lotu/systems/
|
60
|
-
"lib/lotu/systems/
|
59
|
+
"lib/lotu/systems/collision_system.rb",
|
60
|
+
"lib/lotu/systems/fps_system.rb",
|
61
|
+
"lib/lotu/systems/stalker_system.rb",
|
62
|
+
"lib/lotu/systems/steering_system.rb",
|
61
63
|
"lib/lotu/text_box.rb",
|
62
64
|
"lib/lotu/window.rb",
|
63
65
|
"lotu.gemspec",
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 9
|
9
|
+
version: 0.1.9
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- lobo_tuerto
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-03-
|
17
|
+
date: 2010-03-25 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -76,12 +76,14 @@ files:
|
|
76
76
|
- lib/lotu/behaviors/controllable/input_controller.rb
|
77
77
|
- lib/lotu/behaviors/drawable.rb
|
78
78
|
- lib/lotu/behaviors/eventful.rb
|
79
|
-
- lib/lotu/behaviors/
|
79
|
+
- lib/lotu/behaviors/system_user.rb
|
80
80
|
- lib/lotu/cursor.rb
|
81
|
-
- lib/lotu/misc/
|
81
|
+
- lib/lotu/misc/string.rb
|
82
82
|
- lib/lotu/misc/vector2d.rb
|
83
|
-
- lib/lotu/systems/
|
84
|
-
- lib/lotu/systems/
|
83
|
+
- lib/lotu/systems/collision_system.rb
|
84
|
+
- lib/lotu/systems/fps_system.rb
|
85
|
+
- lib/lotu/systems/stalker_system.rb
|
86
|
+
- lib/lotu/systems/steering_system.rb
|
85
87
|
- lib/lotu/text_box.rb
|
86
88
|
- lib/lotu/window.rb
|
87
89
|
- lotu.gemspec
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# Add methods to load and access images, sounds & songs
|
2
|
-
module Lotu
|
3
|
-
module Resourceful
|
4
|
-
|
5
|
-
def self.extended(instance)
|
6
|
-
instance.init_behavior
|
7
|
-
end
|
8
|
-
|
9
|
-
def init_behavior
|
10
|
-
@images = {}
|
11
|
-
@sounds = {}
|
12
|
-
@songs = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
def image(name)
|
16
|
-
@images[name]
|
17
|
-
end
|
18
|
-
|
19
|
-
def sound(name)
|
20
|
-
@sounds[name]
|
21
|
-
end
|
22
|
-
|
23
|
-
def song(name)
|
24
|
-
@songs[name]
|
25
|
-
end
|
26
|
-
|
27
|
-
def load_images(path)
|
28
|
-
load_resources(@images, /\.png|\.jpg|\.bmp/, path, Gosu::Image)
|
29
|
-
end
|
30
|
-
|
31
|
-
def load_sounds(path)
|
32
|
-
load_resources(@sounds, /\.ogg|\.mp3|\.wav/, path, Gosu::Sample)
|
33
|
-
end
|
34
|
-
|
35
|
-
def load_songs(path)
|
36
|
-
load_resources(@songs, /\.ogg|\.mp3|\.wav/, path, Gosu::Song)
|
37
|
-
end
|
38
|
-
|
39
|
-
def with_path_from_file(path, &blk)
|
40
|
-
@path = File.expand_path(File.dirname path)
|
41
|
-
yield
|
42
|
-
end
|
43
|
-
|
44
|
-
def load_resources(container, regexp, path, klass)
|
45
|
-
path = File.expand_path(File.join(@path, path))
|
46
|
-
puts "Loading from: #{path}"
|
47
|
-
|
48
|
-
count = 0
|
49
|
-
Dir.entries(path).grep(regexp).each do |entry|
|
50
|
-
begin
|
51
|
-
container[entry] = klass.new($window, File.join(path, entry))
|
52
|
-
count += 1
|
53
|
-
rescue Exception => e
|
54
|
-
puts e, File.join(path, entry)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
puts "#{count} #{klass} files loaded."
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
data/lib/lotu/misc/fps.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
class FpsCounter
|
2
|
-
attr_reader :fps
|
3
|
-
|
4
|
-
def initialize(samples = 10)
|
5
|
-
@accum = 0.0
|
6
|
-
@ticks = 0
|
7
|
-
@fps = 0.0
|
8
|
-
@samples = samples
|
9
|
-
@objs = @actors = @input_controllers = 0
|
10
|
-
end
|
11
|
-
|
12
|
-
def update(dt)
|
13
|
-
@ticks += 1
|
14
|
-
@accum += dt
|
15
|
-
if @ticks >= @samples
|
16
|
-
@fps = @ticks/@accum
|
17
|
-
@ticks = 0
|
18
|
-
@accum = 0.0
|
19
|
-
@objs = ObjectSpace.each_object.count
|
20
|
-
@actors = ObjectSpace.each_object(Lotu::Actor).count
|
21
|
-
@inputs = ObjectSpace.each_object(Lotu::InputController).count
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_s
|
26
|
-
"@samples(#{@samples}) @fps(#{format("%.2f",@fps)}) @objs(#{@objs}) @actors(#{@actors}) @inputs(#{@inputs})"
|
27
|
-
end
|
28
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Lotu
|
2
|
-
module Systems
|
3
|
-
|
4
|
-
module Collision
|
5
|
-
def self.extended(instance)
|
6
|
-
instance.systems[:collision] = CollisionSystem.new
|
7
|
-
end
|
8
|
-
|
9
|
-
def when_colliding(*args, &blk)
|
10
|
-
systems[:collision].when_colliding(*args, &blk)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class CollisionSystem
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@entities = Hash.new{ |h,k| h[k] = [] }
|
18
|
-
@actions = {}
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_entity(obj, tag)
|
22
|
-
@entities[tag] << obj
|
23
|
-
end
|
24
|
-
|
25
|
-
def remove_entity(obj, tag)
|
26
|
-
@entities[tag].delete(obj)
|
27
|
-
end
|
28
|
-
|
29
|
-
def when_colliding(type1, type2, &blk)
|
30
|
-
@actions[[type1, type2]] = blk
|
31
|
-
end
|
32
|
-
|
33
|
-
def update
|
34
|
-
@actions.each do |tags, blk|
|
35
|
-
@entities[tags[0]].each do |ent1|
|
36
|
-
@entities[tags[1]].each do |ent2|
|
37
|
-
blk.call(ent1, ent2) if ent1.collides_with(ent2)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|