zyps 0.1.1
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/COPYING.LESSER.txt +165 -0
- data/COPYING.txt +674 -0
- data/README.txt +80 -0
- data/bin/zyps +41 -0
- data/bin/zyps_demo +525 -0
- data/lib/zyps.rb +358 -0
- data/lib/zyps/views/trails.rb +147 -0
- data/test/test_zyps.rb +310 -0
- metadata +61 -0
data/README.txt
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
== Synopsis
|
|
2
|
+
|
|
3
|
+
Zyps - A simulation/game with autonomous creatures.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
== Description
|
|
7
|
+
|
|
8
|
+
Zyps are small creatures with minds of their own. You can create dozens of Zyps, then decide how each will act. (Will it run away from the others? Follow them? Eat them for lunch?) You can combine rules and actions to create very complex behaviors.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
== Requirements
|
|
12
|
+
|
|
13
|
+
* Ruby: http://www.ruby-lang.org
|
|
14
|
+
* Ruby-GNOME2: http://ruby-gnome2.sourceforge.jp
|
|
15
|
+
* Rake (to build from source): http://rake.rubyforge.org
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
== Installation
|
|
19
|
+
|
|
20
|
+
Make sure you have administrative privileges, then type the following at a command line:
|
|
21
|
+
|
|
22
|
+
gem install zyps
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
== Usage
|
|
26
|
+
|
|
27
|
+
Ensure Ruby-GNOME2 is installed and working.
|
|
28
|
+
|
|
29
|
+
To see the demo, at a command line, type:
|
|
30
|
+
|
|
31
|
+
zyps_demo
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
== Development
|
|
35
|
+
|
|
36
|
+
Source code and documentation are available via the project site (http://jay.mcgavren.com/zyps).
|
|
37
|
+
|
|
38
|
+
Once downloaded, change into the project directory. For a list of targets, run:
|
|
39
|
+
|
|
40
|
+
rake -T
|
|
41
|
+
|
|
42
|
+
To build a gem:
|
|
43
|
+
|
|
44
|
+
rake
|
|
45
|
+
|
|
46
|
+
To see the demo:
|
|
47
|
+
|
|
48
|
+
rake demo
|
|
49
|
+
|
|
50
|
+
To create a "doc" subdirectory with API documentation:
|
|
51
|
+
|
|
52
|
+
rake rdoc
|
|
53
|
+
|
|
54
|
+
Also see "bin/zyps_demo" and the "test" subfolder in the project directory for sample code.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
== Thanks
|
|
58
|
+
|
|
59
|
+
The GUI is provided via Ruby-GNOME2. Thanks to its authors for their considerable effort in making their library easy to use.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
== Author
|
|
63
|
+
|
|
64
|
+
Copyright 2007 Jay McGavren, jay@mcgavren.com
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
== License
|
|
68
|
+
|
|
69
|
+
Zyps is free software; you can redistribute it and/or modify
|
|
70
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
71
|
+
the Free Software Foundation; either version 3 of the License, or
|
|
72
|
+
(at your option) any later version.
|
|
73
|
+
|
|
74
|
+
This program is distributed in the hope that it will be useful,
|
|
75
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
76
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
77
|
+
GNU General Public License for more details.
|
|
78
|
+
|
|
79
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
80
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/bin/zyps
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby
|
|
2
|
+
|
|
3
|
+
# Copyright 2007 Jay McGavren, jay@mcgavren.com.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of Zyps.
|
|
6
|
+
#
|
|
7
|
+
# Zyps is free software; you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
9
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
require 'zyps'
|
|
23
|
+
require 'zyps/views/trails'
|
|
24
|
+
rescue LoadError
|
|
25
|
+
require 'rubygems'
|
|
26
|
+
require 'zyps'
|
|
27
|
+
require 'zyps/views/trails'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
|
|
33
|
+
puts "This is just a placeholder for the final application."
|
|
34
|
+
puts "To see a demonstration of the library, please run 'zyps_demo'."
|
|
35
|
+
|
|
36
|
+
rescue => exception
|
|
37
|
+
|
|
38
|
+
#Print error to STDERR and exit with an abnormal status.
|
|
39
|
+
abort "Error: " + exception.message + exception.backtrace.join("\n")
|
|
40
|
+
|
|
41
|
+
end
|
data/bin/zyps_demo
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby
|
|
2
|
+
|
|
3
|
+
# Copyright 2007 Jay McGavren, jay@mcgavren.com.
|
|
4
|
+
#
|
|
5
|
+
# This file is part of Zyps.
|
|
6
|
+
#
|
|
7
|
+
# Zyps is free software; you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
9
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
require 'zyps'
|
|
23
|
+
require 'zyps/views/trails'
|
|
24
|
+
rescue LoadError
|
|
25
|
+
require 'rubygems'
|
|
26
|
+
require 'zyps'
|
|
27
|
+
require 'zyps/views/trails'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Demo
|
|
34
|
+
|
|
35
|
+
#The view width.
|
|
36
|
+
WIDTH = 400
|
|
37
|
+
#The view height.
|
|
38
|
+
HEIGHT = 300
|
|
39
|
+
#Number of frames to draw per demo.
|
|
40
|
+
FRAME_COUNT = 80
|
|
41
|
+
|
|
42
|
+
#Set up a window, a canvas, and an object environment, then run the given block.
|
|
43
|
+
def demo
|
|
44
|
+
|
|
45
|
+
#Create a window, and set GTK up to quit when it is closed.
|
|
46
|
+
window = Gtk::Window.new
|
|
47
|
+
window.signal_connect("delete_event") {false}
|
|
48
|
+
window.signal_connect("destroy") {Gtk.main_quit}
|
|
49
|
+
|
|
50
|
+
#Add view to window.
|
|
51
|
+
@view = TrailsView.new(WIDTH, HEIGHT)
|
|
52
|
+
window.add(@view.canvas)
|
|
53
|
+
window.show_all
|
|
54
|
+
|
|
55
|
+
#Create environment.
|
|
56
|
+
@environment = Environment.new
|
|
57
|
+
|
|
58
|
+
#Point view at environment.
|
|
59
|
+
@environment.add_observer(@view)
|
|
60
|
+
|
|
61
|
+
#Run the given block.
|
|
62
|
+
yield
|
|
63
|
+
|
|
64
|
+
#Activate the GUI.
|
|
65
|
+
Gtk.main
|
|
66
|
+
|
|
67
|
+
#A divider for explanation text between demos.
|
|
68
|
+
say("-" * 30)
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
#Animate an environment for a given number of frames.
|
|
74
|
+
def animate(frame_count)
|
|
75
|
+
begin
|
|
76
|
+
(1..frame_count).each do |i|
|
|
77
|
+
@environment.interact
|
|
78
|
+
#Delay 1/60th second to avoid screen flicker.
|
|
79
|
+
sleep 1.0 / 60.0
|
|
80
|
+
end
|
|
81
|
+
rescue Exception => exception
|
|
82
|
+
puts exception, exception.backtrace
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#Populate an environment with the given number of creatures.
|
|
88
|
+
def populate(environment, count = 50)
|
|
89
|
+
count.times do |i|
|
|
90
|
+
multiplier = i / count.to_f
|
|
91
|
+
environment.objects << Creature.new(
|
|
92
|
+
i,
|
|
93
|
+
Location.new(multiplier * @view.width, multiplier * @view.height),
|
|
94
|
+
Color.new(multiplier, 1 - multiplier, multiplier / 2 + 0.5),
|
|
95
|
+
Vector.new(100 * multiplier, multiplier * 360)
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#Explain what's going on to the user.
|
|
102
|
+
def say(phrase)
|
|
103
|
+
puts phrase
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
#Demonstrates drawing an environment and changing its size.
|
|
108
|
+
def test_render
|
|
109
|
+
|
|
110
|
+
thread = Thread.new do
|
|
111
|
+
|
|
112
|
+
say("The things that populate an environment are called GameObjects. Each object has:")
|
|
113
|
+
object = GameObject.new
|
|
114
|
+
say("...a name")
|
|
115
|
+
object.name = "Clancy"
|
|
116
|
+
say("...a Location with x and y coordiates")
|
|
117
|
+
object.location = Location.new(@view.width/2, @view.height/2)
|
|
118
|
+
say("...a Color with red, green and blue components ranging from 0 to 1")
|
|
119
|
+
object.color = Color.new(1, 0, 0)
|
|
120
|
+
say("...and a Vector giving its speed and an angle from 0 to 360.")
|
|
121
|
+
object.vector = Vector.new(10, 45)
|
|
122
|
+
|
|
123
|
+
say("Once your object is ready, add it to the environment.")
|
|
124
|
+
@environment.objects << object
|
|
125
|
+
|
|
126
|
+
say("Call a view's render(objects) method to draw the objects.")
|
|
127
|
+
say("Call an environment's interact() method to have the objects in it move around.")
|
|
128
|
+
say("Our demo's animate() method does both these things for us.")
|
|
129
|
+
animate(FRAME_COUNT)
|
|
130
|
+
|
|
131
|
+
say("Let's add a couple more objects with different colors and vectors.")
|
|
132
|
+
@environment.objects << GameObject.new(nil, Location.new(@view.width/2, @view.height/2), Color.new(0, 1, 0), Vector.new(20, 135))
|
|
133
|
+
@environment.objects << GameObject.new(nil, Location.new(@view.width/2, @view.height/2), Color.new(0, 0, 1), Vector.new(30, 225))
|
|
134
|
+
animate(FRAME_COUNT)
|
|
135
|
+
|
|
136
|
+
say("The viewing area can be resized at any time via its width and height attributes.")
|
|
137
|
+
@view.width += 100
|
|
138
|
+
@view.height += 100
|
|
139
|
+
animate(FRAME_COUNT)
|
|
140
|
+
|
|
141
|
+
say("TrailsView lets you set the thickness and length of the trails as well.")
|
|
142
|
+
@view.trail_width = 10
|
|
143
|
+
@view.trail_length = 10
|
|
144
|
+
animate(FRAME_COUNT)
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
#Demonstrates environmental factors by adding gravity to the environment.
|
|
152
|
+
def test_environmental_factors
|
|
153
|
+
|
|
154
|
+
populate(@environment)
|
|
155
|
+
|
|
156
|
+
thread = Thread.new do
|
|
157
|
+
|
|
158
|
+
say("Without gravity, objects just travel on forever.")
|
|
159
|
+
animate(FRAME_COUNT)
|
|
160
|
+
|
|
161
|
+
say("Let's add a new EnvironmentalFactor to simulate gravity.")
|
|
162
|
+
gravity = EnvironmentalFactor.new
|
|
163
|
+
|
|
164
|
+
say("We create a Behavior the EnvironmentalFactor will follow.")
|
|
165
|
+
accelerate = Behavior.new
|
|
166
|
+
say("The behavior will have a single action: to accelerate objects toward the 'ground' at 9.8 meters/second.")
|
|
167
|
+
accelerate.actions << lambda {|gravity, target| target.vector.y += 9.8}
|
|
168
|
+
say("We add the behavior to the EnvironmentalFactor.")
|
|
169
|
+
gravity.behaviors << accelerate
|
|
170
|
+
say("Then we add the EnvironmentalFactor to the Environment.")
|
|
171
|
+
@environment.environmental_factors << gravity
|
|
172
|
+
|
|
173
|
+
say("Everything immediately drops.")
|
|
174
|
+
animate(FRAME_COUNT)
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
#Demonstrates creature behaviors.
|
|
182
|
+
def test_behaviors
|
|
183
|
+
|
|
184
|
+
populate(@environment)
|
|
185
|
+
|
|
186
|
+
thread = Thread.new do
|
|
187
|
+
|
|
188
|
+
say("Let's add a behavior to our creatures.")
|
|
189
|
+
chase = Behavior.new
|
|
190
|
+
|
|
191
|
+
say("We'll have them head straight toward their target.")
|
|
192
|
+
chase.actions << lambda do |creature, target|
|
|
193
|
+
angle_to_target = Utility.find_angle(creature.location, target.location)
|
|
194
|
+
creature.vector.pitch = angle_to_target
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
say("So that they don't target every creature on the screen, we'll add a condition to the behavior saying the target must have the label 'food'.")
|
|
198
|
+
chase.conditions << lambda {|creature, target| target.tags.include?('food')}
|
|
199
|
+
|
|
200
|
+
say("We'll apply this behavior to all creatures currently in the environment.")
|
|
201
|
+
@environment.objects.each {|creature| creature.behaviors << chase}
|
|
202
|
+
animate(FRAME_COUNT)
|
|
203
|
+
|
|
204
|
+
say("Then we'll toss a piece of food (a GameObject with the label 'food') into the environment.")
|
|
205
|
+
@environment.objects << GameObject.new(
|
|
206
|
+
"target",
|
|
207
|
+
Location.new(@view.width / 2, @view.height / 2),
|
|
208
|
+
Color.new(1, 1, 1),
|
|
209
|
+
Vector.new(50, 315),
|
|
210
|
+
0, #Age.
|
|
211
|
+
["food"] #Tags.
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
say("Let's see what the creatures do.")
|
|
215
|
+
animate(FRAME_COUNT)
|
|
216
|
+
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
#A Creature that changes the colors of other objects.
|
|
223
|
+
class Morpher < Creature
|
|
224
|
+
def initialize(*arguments)
|
|
225
|
+
super
|
|
226
|
+
morph = Behavior.new
|
|
227
|
+
#Shift the target's color to match the creature's.
|
|
228
|
+
morph.actions << lambda do |creature, target|
|
|
229
|
+
target.color.red += 0.1 if target.color.red < creature.color.red
|
|
230
|
+
target.color.green += 0.1 if target.color.green < creature.color.green
|
|
231
|
+
target.color.blue += 0.1 if target.color.blue < creature.color.blue
|
|
232
|
+
end
|
|
233
|
+
#Act only on nearby targets.
|
|
234
|
+
morph.conditions << lambda {|creature, target| Utility.find_distance(creature.location, target.location) < 50}
|
|
235
|
+
@behaviors << morph
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
#Demonstrates changing object colors.
|
|
240
|
+
def test_change_color
|
|
241
|
+
|
|
242
|
+
populate(@environment)
|
|
243
|
+
|
|
244
|
+
say("Creatures can influence any attribute of their target, such as its color.")
|
|
245
|
+
say("This demo includes a Morpher class, which is a type of Creature.")
|
|
246
|
+
say("Morphers are created with a single behavior, which shifts the color of any nearby target to match the Morpher's color.")
|
|
247
|
+
|
|
248
|
+
say("Let's place a red Morpher...")
|
|
249
|
+
@environment.objects << Morpher.new(nil, Location.new(0, 100), Color.new(1, 0, 0), Vector.new(100, 0))
|
|
250
|
+
say("a green one...")
|
|
251
|
+
@environment.objects << Morpher.new(nil, Location.new(0, 150), Color.new(0, 1, 0), Vector.new(200, 0))
|
|
252
|
+
say("and a blue one...")
|
|
253
|
+
@environment.objects << Morpher.new(nil, Location.new(0, 200), Color.new(0, 0, 1), Vector.new(300, 0))
|
|
254
|
+
|
|
255
|
+
say("And see what they do.")
|
|
256
|
+
thread = Thread.new {animate(FRAME_COUNT)}
|
|
257
|
+
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
#Demonstrates altering object speed.
|
|
262
|
+
def test_accelerate
|
|
263
|
+
|
|
264
|
+
populate(@environment)
|
|
265
|
+
|
|
266
|
+
thread = Thread.new do
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
say("Many actions need to happen smoothly over time, such as accelerating at a given rate.")
|
|
270
|
+
|
|
271
|
+
say("Here are some Creatures, just plodding along.")
|
|
272
|
+
animate(FRAME_COUNT / 4)
|
|
273
|
+
say("We're going to have them pick up the pace.")
|
|
274
|
+
|
|
275
|
+
say("A Clock helps track the passage of time (obviously).")
|
|
276
|
+
say("We'll create a separate Clock object for each creature.")
|
|
277
|
+
clocks = Hash.new {|h, k| h[k] = Clock.new}
|
|
278
|
+
|
|
279
|
+
say("Our acceleration behavior is going to check to see how long it's been since it last took effect.")
|
|
280
|
+
say("It will multiply that time by a given rate, say 100 meters/second.")
|
|
281
|
+
say("Let's say it's been 0.1 seconds since this creature last accelerated.")
|
|
282
|
+
say("100 times 0.1 is 10.")
|
|
283
|
+
say("So we should add 10 meters/second to the creature's speed.")
|
|
284
|
+
accelerate = Behavior.new
|
|
285
|
+
accelerate.actions << lambda do |creature, target|
|
|
286
|
+
#Accelerate the appropriate amount for the elapsed time.
|
|
287
|
+
creature.vector.speed += 100 * clocks[creature].elapsed_time
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
say("We add acceleration to all the creatures...")
|
|
291
|
+
@environment.objects.each {|creature| creature.behaviors << accelerate}
|
|
292
|
+
|
|
293
|
+
say("And watch them rocket away.")
|
|
294
|
+
animate(FRAME_COUNT)
|
|
295
|
+
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
#Demonstrates altering object vectors.
|
|
302
|
+
def test_turn
|
|
303
|
+
|
|
304
|
+
populate(@environment, 20)
|
|
305
|
+
|
|
306
|
+
thread = Thread.new do
|
|
307
|
+
|
|
308
|
+
say("Turning smoothly requires tracking the rate of the turn as well.")
|
|
309
|
+
animate(FRAME_COUNT / 2)
|
|
310
|
+
|
|
311
|
+
say("Again, we keep a separate Clock for each Creature.")
|
|
312
|
+
clocks = Hash.new {|h, k| h[k] = Clock.new}
|
|
313
|
+
|
|
314
|
+
say("Our turn behavior follows the same principle as accelerating.")
|
|
315
|
+
say("We see how many seconds have elapsed, then multiply that by the turn rate.")
|
|
316
|
+
say("We add the result to the Vector angle.")
|
|
317
|
+
turn = Behavior.new
|
|
318
|
+
turn.actions << lambda do |creature, target|
|
|
319
|
+
#Turn the appropriate amount for the elapsed time.
|
|
320
|
+
creature.vector.pitch += 100 * clocks[creature].elapsed_time
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
say("We add the behavior to each Creature...")
|
|
324
|
+
@environment.objects.each {|creature| creature.behaviors << turn}
|
|
325
|
+
|
|
326
|
+
say("And watch things spiral out of control.")
|
|
327
|
+
animate(FRAME_COUNT)
|
|
328
|
+
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
#Demonstrates adding vectors.
|
|
335
|
+
def test_approach
|
|
336
|
+
|
|
337
|
+
populate(@environment, 50)
|
|
338
|
+
|
|
339
|
+
say("When your car skids on ice, you might steer in a different direction, but you're going to keep following your original vector for a while.")
|
|
340
|
+
say("Adding vectors together lets us simulate this.")
|
|
341
|
+
|
|
342
|
+
say("We'll keep a separate Vector for each Creature to track the direction it's 'steering' in.")
|
|
343
|
+
headings = Hash.new {|h, k| h[k] = Vector.new(k.vector.speed, k.vector.pitch)}
|
|
344
|
+
|
|
345
|
+
say("We'll only allow them to turn a maximum of 20 degrees in either direction.")
|
|
346
|
+
max_turn_angle = 20
|
|
347
|
+
|
|
348
|
+
say("We create a Behavior which adds the Vector the creature WANTS to follow to the Vector it's ACTUALLY following.")
|
|
349
|
+
approach = Behavior.new
|
|
350
|
+
approach.actions << lambda do |creature, target|
|
|
351
|
+
|
|
352
|
+
#Find the difference between the current heading and the angle to the target.
|
|
353
|
+
turn_angle = Utility.find_angle(creature.location, target.location) - headings[creature].pitch
|
|
354
|
+
|
|
355
|
+
#If the angle is the long way around from the current heading, change it to the smaller angle.
|
|
356
|
+
if turn_angle > 180 then
|
|
357
|
+
turn_angle -= 360.0
|
|
358
|
+
elsif turn_angle < -180 then
|
|
359
|
+
turn_angle += 360.0
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
#If turn angle is greater than allowed turn speed, reduce it.
|
|
363
|
+
turn_angle = Utility.constrain_value(turn_angle, max_turn_angle)
|
|
364
|
+
|
|
365
|
+
#Turn the appropriate amount.
|
|
366
|
+
headings[creature].pitch += turn_angle
|
|
367
|
+
|
|
368
|
+
#Apply the heading to the creature's movement vector.
|
|
369
|
+
creature.vector += headings[creature]
|
|
370
|
+
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
say("We add a condition that it should only target its prey.")
|
|
374
|
+
approach.conditions << lambda do |creature, target|
|
|
375
|
+
target.tags.include?('prey')
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
say("We add the behavior to all creatures...")
|
|
379
|
+
@environment.objects.each {|creature| creature.behaviors << approach}
|
|
380
|
+
|
|
381
|
+
say("Add a target...")
|
|
382
|
+
@environment.objects << Creature.new(
|
|
383
|
+
"target",
|
|
384
|
+
Location.new(@view.width / 2, @view.height / 2),
|
|
385
|
+
Color.new(1, 1, 1),
|
|
386
|
+
Vector.new(3, 0),
|
|
387
|
+
0, #Age.
|
|
388
|
+
["prey"] #Tags.
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
say("And watch them all TRY to catch it.")
|
|
392
|
+
thread = Thread.new {animate(FRAME_COUNT)}
|
|
393
|
+
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
#Demonstrates turning away from an object, instead of toward it.
|
|
398
|
+
def test_flee
|
|
399
|
+
|
|
400
|
+
populate(@environment, 20)
|
|
401
|
+
|
|
402
|
+
say("Fleeing from something is just like approaching it, but we head in the OPPOSITE direction.")
|
|
403
|
+
say("Just get the angle toward the object, then add 180 degrees.")
|
|
404
|
+
|
|
405
|
+
#Keep a separate heading for each object.
|
|
406
|
+
headings = Hash.new {|h, k| h[k] = Vector.new(k.vector.speed, k.vector.pitch)}
|
|
407
|
+
|
|
408
|
+
#Create a behavior.
|
|
409
|
+
max_turn_angle = 20
|
|
410
|
+
flee = Behavior.new
|
|
411
|
+
flee.actions << lambda do |creature, target|
|
|
412
|
+
|
|
413
|
+
#Find the difference between the current heading and the angle AWAY from the target.
|
|
414
|
+
turn_angle = Utility.find_angle(creature.location, target.location) - headings[creature].pitch + 180
|
|
415
|
+
|
|
416
|
+
#If the angle is the long way around from the current heading, change it to the smaller angle.
|
|
417
|
+
if turn_angle > 180 then
|
|
418
|
+
turn_angle -= 360.0
|
|
419
|
+
elsif turn_angle < -180 then
|
|
420
|
+
turn_angle += 360.0
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
#If turn angle is greater than allowed turn speed, reduce it.
|
|
424
|
+
turn_angle = Utility.constrain_value(turn_angle, max_turn_angle)
|
|
425
|
+
|
|
426
|
+
#Turn the appropriate amount for the elapsed time.
|
|
427
|
+
headings[creature].pitch += turn_angle
|
|
428
|
+
|
|
429
|
+
#Apply the heading to the creature's movement vector.
|
|
430
|
+
creature.vector += headings[creature]
|
|
431
|
+
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
#Flee from only the creature's predator.
|
|
435
|
+
flee.conditions << lambda do |creature, target|
|
|
436
|
+
target.tags.include?('predator')
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
#Add behavior to creatures.
|
|
440
|
+
@environment.objects.each {|creature| creature.behaviors << flee}
|
|
441
|
+
|
|
442
|
+
#Add a target.
|
|
443
|
+
@environment.objects << Creature.new(
|
|
444
|
+
"lion",
|
|
445
|
+
Location.new(@view.width / 2, @view.height / 2),
|
|
446
|
+
Color.new(1, 1, 1),
|
|
447
|
+
Vector.new(3, 0),
|
|
448
|
+
0, #Age.
|
|
449
|
+
["predator"] #Tags.
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
thread = Thread.new {animate(FRAME_COUNT)}
|
|
453
|
+
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
#A Behavior that destroys the target.
|
|
458
|
+
class Destroy < Behavior
|
|
459
|
+
#Environment from which targets will be removed.
|
|
460
|
+
attr_accessor :environment
|
|
461
|
+
def initialize(actions = [], conditions = [], environment = Environment.new)
|
|
462
|
+
super(actions, conditions)
|
|
463
|
+
@environment = environment
|
|
464
|
+
#"Kill" target.
|
|
465
|
+
self.actions << lambda do |creature, target|
|
|
466
|
+
@environment.objects.delete(target)
|
|
467
|
+
end
|
|
468
|
+
#Act only if target is close.
|
|
469
|
+
self.conditions << lambda do |creature, target|
|
|
470
|
+
Utility.find_distance(creature.location, target.location) < 25
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
#Demonstrates keeping a reference to an Environment so a Creature can alter it.
|
|
477
|
+
def test_destroy
|
|
478
|
+
|
|
479
|
+
populate(@environment)
|
|
480
|
+
|
|
481
|
+
say("Most games are all about destruction, but there hasn't been much so far.")
|
|
482
|
+
say("Let's create a creature that causes some havoc.")
|
|
483
|
+
delinquent = Creature.new(nil, Location.new(0, 150), Color.new(0, 1, 0), Vector.new(200, 0))
|
|
484
|
+
|
|
485
|
+
say("This demo code includes a subclass of Behavior, called Destroy.")
|
|
486
|
+
destroy = Destroy.new
|
|
487
|
+
|
|
488
|
+
say("We'll destroy our targets by removing them from their environment.")
|
|
489
|
+
say("Creatures and their Behaviors normally know nothing about the Environment they belong to, so we added an environment attribute to Destroy.")
|
|
490
|
+
say("Destroy finds the target in Environment.objects and removes it.")
|
|
491
|
+
destroy.environment = @environment
|
|
492
|
+
|
|
493
|
+
say("Add the Destroy instance to the creature's behaviors...")
|
|
494
|
+
delinquent.behaviors << destroy
|
|
495
|
+
|
|
496
|
+
say("Drop the creature into the actual environment...")
|
|
497
|
+
@environment.objects << delinquent
|
|
498
|
+
|
|
499
|
+
say("And - chomp!")
|
|
500
|
+
thread = Thread.new {animate(FRAME_COUNT)}
|
|
501
|
+
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
#Run all the demos.
|
|
506
|
+
def main
|
|
507
|
+
say "After each demo, close the window to proceed."
|
|
508
|
+
say("-" * 30)
|
|
509
|
+
demo {test_render}
|
|
510
|
+
demo {test_environmental_factors}
|
|
511
|
+
demo {test_behaviors}
|
|
512
|
+
demo {test_change_color}
|
|
513
|
+
demo {test_accelerate}
|
|
514
|
+
demo {test_turn}
|
|
515
|
+
demo {test_approach}
|
|
516
|
+
demo {test_flee}
|
|
517
|
+
demo {test_destroy}
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
#Run the demos.
|
|
525
|
+
Demo.new.main
|