HopperBins: Difference between revisions

From Goodblox Wiki
Jump to navigationJump to search
(Created page with "{{CatUp|Tutorials}} __TOC__ ==What is a HopperBin?== HopperBins are considered old now. They were the first ways to make a weapon, a utility tool, or anything you could reall...")
 
No edit summary
 
Line 1: Line 1:
{{CatUp|Tutorials}}
{{CatUp|Tutorials}}
{{ScriptTutorial|Advanced|War Device}}
[[Image:Catapult.png | 600px]]
__TOC__
__TOC__


==What is a HopperBin?==
==Who this Tutorial is for ==
HopperBins are considered old now. They were the first ways to make a weapon, a utility tool, or anything you could really think of. They are mainly used for getting input from the player, and doing something with that input.
 
This tutorial is for people who are interested in the inner workings of my Catapult model. If you just want to put a catapult in your level, you do not need to read this tutorial. Just do a free model search for "Catapult".
 
I will be discussing OnTouch triggers, [[BodyForce]] objects, and the rather arcane but very powerful [[RunService]]. If you understand all this stuff, building moving mechanisms in GoodBlox becomes much easier. Anyone can read this tutorial, but I am going to assume you are a coder and know your way around [[GoodBlox Studio]].
 
==Introduction==
 
Let's spec out the catapult that we want to build. A catapult is basically a big lever that chucks rocks, but there are many ways to build one in GoodBlox.
 
== What we are trying to accomplish ==
 
Our catapult will:
 
* Fling rocks at least 512 studs (the width of the largest possible baseplate)
* Be hand-loaded by players with the Grab tool.
* Be fired by players pressing a lever on the catapult.
* Shoot rocks that explode when they hit something (this one is tricky).
 
[[Image:CatapultFire.png| 600px]]
 
== The Mechanical Mechanism ==
 
Go build a catapult - just the mechanical mechanism part of it. Come back when you're done.
 
Tips:
 
* The lever arm of the catapult should be longer on the side the ammo goes in (see my screenshots). This can be a precise science (there are equations that tell you what your mechanical advantage of the arm is, predict how much impulse you can impart to a projectile, and thus how far you can fling it), but I just eye-balled something that looked good for mine.
 
* The lever arm needs to be constrained so that it chucks the projectile '''Up''' into the air, preferably at close to a 45 degree angle for maximum range. I did this by placing a block under the lever arm so that it can only go down so far when shooting.


==Special Events==
* Use <b>two</b> hinge joints - one on each side of the arm. Using only one hinge is probably not a good idea.
The special events of a HopperBin are as follows:


#Selected
* Make the ammo carrier first, then size the rest of the catapult to whatever sized carrier you made. Make sure that the bottom of the carrier is only one piece, this will be important to our scripting effort later.
#Deselected


==Important events and properties==
* Anchor your support beams to the ground and your hinge joints to the support beams.  The force of the catapult is quite large, and your entire catapult can "hop" if you don't.
The Selected and Deselected events have an argument, called a MOUSE object. Here are its important events and properties.


'''Events'''
== Making it Fire ==
#Button1Down
#Button1Up
#Button2Down -- currently un-operational
#Button2Up -- also non-working
#WheelForward -- non-working
#WheelBackward -- non-working
'''Properties'''
#Hit -- CFrame value
#Target -- object value
#X -- mouse's X position on the screen.
#Y -- mouse's Y position on the screen.
#ViewSizeX -- your screen's X size.
#ViewSizeY -- your screen's Y size.
#Origin -- CFrame value
#Icon -- the cursor image path


==What do I do with it?==
Once you have the catapult built, now we need to script its function. Actually making the thing fire is pretty easy. All we want to do is apply a '''massive''' force down on the short end of the arm to send the rock flying. Enter the [[BodyForce]] object.
You can spawn a projectile when Button1Down is fired, have it spawn in the direction the mouse is from your head, and move in the direction of the mouse, for example.


Here's an example of a Boulder Summoner:
The BodyForce object can be used to apply a force vector to the part that it is placed under (in the GoodBlox Studio treeview).


1) Insert / Object / HopperBin into the StarterPack.<br>
All we need to do is:
2) Insert / Object / Script into the HopperBin you just created.<br>
 
3) Insert the following into the script file you just created:<br>
1. Create a BodyForce object under a part.<br>
2. Set its force vector to (0,0,0) - i.e. it is off by default.<br>
3. Place the part on the short end of the arm.<br>
4. Write a script that detects when the firing lever is touched by a player.<br>
5. When this happens, apply a huge downward force.<br>
6. Wait a couple of seconds.<br>
7. Reset force vector to (0,0,0)<br>
 
If you look carefully at the first catapult screenshot at the top of this article, you will see a little red 2x2 that I placed at the end of the catapult arm. This is where I put the BodyForce object.
 
Here is some sample source code. You will probably need to edit the variables to fit your own part names:


<pre>
<pre>
lifetime = 5 -- how long the boulder stays there. 5 is a good value.
local powerblock = script.Parent.Parent.PowerBlock
local bodyforce = powerblock.BodyForce


function onButton1Down(mouse)
local debounce = false
local b = Instance.new("Part")
b.BrickColor = BrickColor.new(217)
b.Position = mouse.Hit.p
b.Size = Vector3.new(5, 5, 5)
b.Shape = 0
b.Parent = game.Workspace
b.Velocity = (script.Parent.Parent.Parent.Character.Head.Position - mouse.Hit.p).unit * -50 + Vector3.new(0, 10, 0)
-- pushes it away from you, and pops it up a bit, making it seem to come out of the ground.
game:GetService("Debris"):AddItem(b, lifetime)
end


function onSelected(mouse)
function onTouched(hit)
mouse.Button1Down:connect(function() onButton1Down(mouse) end) -- Button1Down doesn't come with a mouse, seeing as its
local humanoid = hit.Parent:findFirstChild("Humanoid")
-- an event of the mouse itself. We need to give the function the mouse object to work off of.
if humanoid~=nil and debounce == false then
debounce = true
bodyforce.force = Vector3.new(0,1000000,0)
wait(3)
bodyforce.force = Vector3.new(0,0,0)
wait(3)
debounce = false
end
end
end


script.Parent.Selected:connect(onSelected) -- Selected comes with a mouse.
script.Parent.Touched:connect(onTouched)
</pre>
</pre>


4) Test in Solo mode.  
'''Q & A'''
 
You might have a couple of questions about this script:
 
''What does debounce do?'' - In my experiments I found that that [[Touched]] event happens multiple times whenever a player touches a brick. I use the debounce flag to make sure I only handle one Touched event per firing cycle.
 
''How did you pick the force vector?'' - Trial and error. In my catapult, I have tested forces up to 3 million units (GBX Newtons?) without a problem. Obviously, the more force you apply the farther the projectile is flung. Making a long range catapult is a less interesting problem than making the most efficient catapult possible, since you can always make something shoot further by upping the force you apply.


That is a simple thing to spawn a boulder wherever you click. It also gets rid of the boulder after a set amount of time.
''Why is the force vector upside down?'' - Honestly, I have no idea. I think I have probably found a bug in the BodyForce object. In my experiments, this is what worked.


==Simple Toggle Utility==
'''Tip:''' The more efficient your catapult design, the better. An efficient design requires less force to chuck the rock the same distance. The GoodBlox physics engine doesn't like dealing with huge forces - it can make your simulation unstable (you will see jittering). This becomes especially important if you plan on mounting your catapult on a hinge, swivel, or other un-anchored base.
Insert a Brick in the Workspace. Insert a BoolValue into that Brick. Name that BoolValue "OnOff".
Just as before, you have a Hopperbin object in the StarterPack, and a script in the HopperBin. Insert the following into that script:


<pre>
==Making Rocks Blow Up When they Hit Stuff==
brickName = "Toggle"
 
valueName = "On/Off"
The last part of the puzzle, and the hardest. If you have gotten to this step, you have a cool catapult that can launch rocks into orbit. But they don't do much damage when they hit something, do they? That's just not satisfying.
 
This problem is actually two problems:


function onButton1Down(mouse)
1. How do you make projectiles explode when they hit something?<br>
local targ = mouse.Target
2. How do you make projectiles not explode when you are loading them into the Catapult?<br>
if targ~=nil then
if targ.Name == brickName then
if targ:findFirstChild(valueName)~=nil then
if targ[valueName].Value == true then
targ[valueName].Value = false
print("one")
else
targ[valueName].Value = true
print("two")
end
end
end
end
end


function onSelected(mouse)
Number 1 is easy - just look at the GoodBlox weapon scripts for inspiration. They basically all work the same way - there is a script that is copied and attached to every projectile a weapon fires that handles doing damage OnTouch.  
mouse.Button1Down:connect(function() onButton1Down(mouse) end)
end


script.Parent.Selected:connect(onSelected)
Number 2 is hard. Here is one solution:
</pre>


Upon clicking the mouse on the brick in Solo Mode, you can see the Output toggle between "one" and "two".
1. Name all of your ammo rocks a special name - I choose "WarRock" (As in Homer's War Rocks, kudos if you get the reference)<br>
You can easily manipulate this script to work for anything.
2. Make the bottom of your ammo carrier listen to the OnTouch event. When it detects that a WarRock has touched it, the WarRock goes "hot". This means a projectile script is attached to the rock that will make it blow up when hit.<br>
3. Make the projectile script periodically (like 30 times a second), check the WarRock's position and compare it to the WarRock's previous position. Go "critical" when the WarRock's velocity exceeds a sustained velocity of 16 studs per second over .5 seconds. Once a WarRock has gone critical it will explode the next time it touches anything.<br>


==Mouse Without HopperBin==
The beauty of this scheme: The WarRock "knows" when it has been launched because it is checking its own velocity. There is no need for the trigger to communite with the WarRock's projectile script. When I was originally trying to figure this out, some people on the forums suggested using a wait() timer instead to make the WarRock go critical X seconds after the trigger was flipped. I have not tried this solution, but I would bet that there would be issue with lag, making the rock explode too late. It also requires Trigger To Rock communication - which is fugly.


===Setting up the HopperBin===
I'm going to give my final code, just as an example of what I did. I doubt you can plug it into your catapult without mods.
You need a [[RBX.lua.HopperBin (Object)|HopperBin object]]. This is what the player selects to activate the whole system.


You also need two scripts.  Here is how they should look in the Explorer:
'''Catapult Trigger Script:'''


<pre>
<pre>
[-] HopperBin
local powerblock = script.Parent.Parent.PowerBlock
ConnectionScript
local bodyforce = powerblock.BodyForce
ControlScript
local hotplate = script.Parent.Parent.HotPlate
</pre>


The script named ConnectionScript is the one that sets it all up, and links the mouse to some values. The HopperBin gets removed, the two scripts moved, but the mouse is still active until you select a different HopperBin.
local debounce = false


=== ConnectionScript ===
function onTouched(hit)
Here is what goes in "ConnectionScript":
local humanoid = hit.Parent:findFirstChild("Humanoid")
if humanoid~=nil and debounce == false then
debounce = true
bodyforce.force = Vector3.new(0,1000000,0) --[[you may have to adjust this number,
depending on the design of your catapult --]]
wait(3)
bodyforce.force = Vector3.new(0,0,0)
wait(3)
debounce = false
end
end


<pre>
function onHotPlateTouched(hit)
function onSelected(mouse)
if (hit.Name ~= "WarRock") then return end
local m = Instance.new("CFrameValue")
if (hit:findFirstChild("WarRockScript") ~= nil) then return end -- debounce
m.Parent = script.Parent.Parent.Parent
hit.BrickColor = BrickColor.new(105)
m.Name = "MouseCFrame"
local code = script.Parent.WarRockScript:clone()
local b = Instance.new("BoolValue")
code.Disabled = false
b.Parent = script.Parent.Parent.Parent
code.Parent = hit
b.Name = "Button1Down"
local k = Instance.new("StringValue")
k.Parent = script.Parent.Parent.Parent
k.Name = "KeyPressed"
local t = Instance.new("ObjectValue")
t.Parent = script.Parent.Parent.Parent
t.Name = "MouseTarget"
mouse.Move:connect(function() m.Value = mouse.Hit end)
mouse.Button1Down:connect(function() b.Value = true end)
mouse.Button1Up:connect(function() b.Value = false end)
mouse.KeyDown:connect(function(key) k.Value = key k.Value = "" end)
mouse.Changed:connect(function(property)
if (property == "Target") then
t.Value = mouse.Target
end
end)
local hb = script.Parent
script.Parent = hb.Parent
hb.ControlScript.Parent = script.Parent
hb:Remove()
end
end


script.Parent.Selected:connect(onSelected)
script.Parent.Touched:connect(onTouched)
hotplate.Touched:connect(onHotPlateTouched)
</pre>
</pre>


=== ControlScript ===
'''WarRock Script:'''


Here is the ControlScript:
I got a litle fancy in this script, so not all the code is necessary for a bare-bones implementation. The trick here is that the velocity-tracking code runs 30 times per second, on the physics engine heartbeat, using the RunService to do a [[SteppedWait]]. The GoodBlox Rocket Launcher does a similar thing in order to override gravity. I can't take full credit for figuring this out, Telamon had to explain it to me.


<pre>
<pre>
-- attached to WarRock
print("WarRock script running")
r = game:service("RunService")
local warRock = script.Parent
local flightTime = 0              -- rock is only hot after we detect it has been flying for .5 seconds
local hotRock = false
local lastPos = nil


while true do
function onTouched(hit)
if script.Parent.Name == "Backpack" then break end -- this is to pause the script until it gets moved, no bugging up.
-- We hit something, time to blow up
wait()
if (hotRock == true) then
blowUp()
end
end
end


function newText(text)
function getExplosionRadius(mass)
local nm = Instance.new("Hint")
if (mass > 50) then return 16 end
nm.Parent = p
if (mass > 40) then return 13 end
nm.Text = text
if (mass > 5) then return 10 end
return 6
end
end


function onKeyDown(key)
function getExplosionPressure(mass)
if script.Parent.Parent.Character==nil then return end
if (mass > 50) then return 2000000 end
local key = string.lower(key)
if (mass > 40) then return 1000000 end
if (mass > 5) then return 500000 end
return 200000
end
end


function onButton1Down()
 
if script.Parent.Parent.Character==nil then return end
function blowUp()
 
local sound = Instance.new("Sound")
sound.SoundId = "rbxasset://sounds\\Rocket shot.wav"
sound.Parent = warRock
sound.Volume = 1
sound:play()
explosion = Instance.new("Explosion")
 
local mass = warRock:getMass()
-- in default war rocks, mass varies from 1 to 57
 
explosion.BlastRadius = getExplosionRadius(mass)
explosion.BlastPressure = getExplosionPressure(mass)
explosion.Position = warRock.Position
explosion.Parent = game.Workspace
warRock.Parent = nil
end
end


function onButton1Up()
function getDistance(a, b)
if script.Parent.Parent.Character==nil then return end
local dx = a.x - b.x --[[change in x distance from a to b--]]
local dy = a.y - b.y --[[change in y distance from a to b--]]
local dz = a.z - b.z --[[change in z distance from a to b--]]
return math.sqrt(dx * dx + dy * dy + dz * dz) --[[Sphere: x^2 + y^2 + z^2 = radius^2, therefore we need the sqrt of x^2 + y^2 + z^2 to get the radius. --]]
end
end


function onMove()
if script.Parent.Parent.Character==nil then return end
end


script.Parent.Parent.KeyPressed.Changed:connect(function() onKeyDown(script.Parent.Parent.KeyPressed.Value) end)
function checkFlying(dt)
script.Parent.Parent.MouseCFrame.Changed:connect(function() onMove() end)
-- "flying" is movement of over 16 studs per second
script.Parent.Parent.Button1Down.Changed:connect(function()
-- when this condition holds, increment the flightTime
if script.Parent.Parent.Button1Down.Value == true then  
-- when flightTime > .5, WarRock goes hot!
onButton1Down()
if (lastPos == nil) then  
else
lastPos = warRock.Position
onButton1Up()
return
end
end
end)</pre>


==How To Make onKeyDown Scripts with HopperBins==
local velocity = getDistance(lastPos, warRock.Position) / dt --[[rate of change of distance over time,
derivative --]]


As above, you will need a HopperBin in the StarterPack, and an empty Script in the HopperBin.
if (velocity > 16) then
flightTime = flightTime + dt
else
flightTime = 0
end


==The Basic Script==
You will need to add different parts to the basic script to make it work. It '''must''' have :


*An '''"if key == "(key)" then"''' line.
if (flightTime > .5) then
*Sometimes an '''or''' line. There '''can''' be more than one.
hotRock = true
warRock.BrickColor = BrickColor.new(21)
end


Here is the unedited, basic script:
lastPos = warRock.Position
end


<pre>
warRock.Touched:connect(onTouched)
function onKeyDown(key)
key:lower()  
--*********************--
--*********************--
--*********************--
--*********************--
--*********************--
end


local lastTime = r.Stepped:wait()
local curTime


function onSelected(mouse)
mouse.KeyDown:connect(onKeyDown)
end


script.Parent.Selected:connect(onSelected)
while true do
curTime = r.Stepped:wait() -- should be 1/30th a second, but take no chances
if (hotRock == false) then
checkFlying(curTime - lastTime)  
lastTime = curTime
else break end
end
</pre>
</pre>


Put the '''if''' line where the commented stars are. The lines where the stars are, are also where the stuff happens. For example, if you press "f", you die. So the lines would be


<pre>
== Going Advanced ==
if key == "f" then
script.Parent.Parent.Parent.Character.Humanoid.Health = 0
end
</pre>


*This finds the script's parent [your HopperBin], its parent [your backpack] its parent [your player] then your Character [you in the workspace] then your Humanoid, and finally its health.
Make sure that all the scripts in your WarRocks have been named to "WarRockScript".
*You can also change '''onKeyDown(key)''' to '''onButton1Down(mouse)'''. Make sure you change the '''connection''' line as well.  


A line with '''or''' in it would look like this :
Now, this is an onClick script. That means you will need to insert > object > clickdetector in your catapult. 


<pre>
Now enter the following script into your catapult.
if key == "b" or "h" or "k" then --Etc.
--The things you want to happen go here.
end
</pre>


An example of a script that kills the player via random explosion would be :


<pre>
<pre>
e=Instance.new("Explosion")
local powerblock = script.Parent.Parent.PowerBlock
function onKeyDown(key)
local bodyforce = powerblock.BodyForce
key:lower()
local hotplate = script.Parent.Parent.HotPlate
if key == "f" then
 
e.Position = script.Parent.Parent.Parent.Character.Head.Position
function onClicked()
e.Parent = game.Workspace
bodyforce.force = Vector3.new(0,1000000,0)
e.BlastRadius = 10
wait(3)
e.BlastPressure = 100000
bodyforce.force = Vector3.new(0,0,0)
wait(3)
end
end


end  
function onHotPlateTouched(hit)
if (hit.Name ~= "WarRock") then return end
if (hit:findFirstChild("WarRockScript") ~= nil) then return end -- debounce
hit.BrickColor = BrickColor.new(105)
local code = script.Parent.WarRockScript:clone()
code.Disabled = false
code.Parent = hit
end


 
script.Parent.ClickDetector.MouseClick:connect(onClicked)
function onSelected(mouse)
hotplate.Touched:connect(onHotPlateTouched)
mouse.KeyDown:connect(onKeyDown)  
end
 
script.Parent.Selected:connect(onSelected)
</pre>
</pre>
To make sure that the script works, it is recommended to use Output. It will tell you exactly what you messed up on, and even what line.


[[Category:Scripting Tutorials]]
[[Category:Scripting Tutorials]]

Latest revision as of 16:55, 22 June 2020


This is an Advanced, War Device related tutorial.


Who this Tutorial is for

This tutorial is for people who are interested in the inner workings of my Catapult model. If you just want to put a catapult in your level, you do not need to read this tutorial. Just do a free model search for "Catapult".

I will be discussing OnTouch triggers, BodyForce objects, and the rather arcane but very powerful RunService. If you understand all this stuff, building moving mechanisms in GoodBlox becomes much easier. Anyone can read this tutorial, but I am going to assume you are a coder and know your way around GoodBlox Studio.

Introduction

Let's spec out the catapult that we want to build. A catapult is basically a big lever that chucks rocks, but there are many ways to build one in GoodBlox.

What we are trying to accomplish

Our catapult will:

  • Fling rocks at least 512 studs (the width of the largest possible baseplate)
  • Be hand-loaded by players with the Grab tool.
  • Be fired by players pressing a lever on the catapult.
  • Shoot rocks that explode when they hit something (this one is tricky).

The Mechanical Mechanism

Go build a catapult - just the mechanical mechanism part of it. Come back when you're done.

Tips:

  • The lever arm of the catapult should be longer on the side the ammo goes in (see my screenshots). This can be a precise science (there are equations that tell you what your mechanical advantage of the arm is, predict how much impulse you can impart to a projectile, and thus how far you can fling it), but I just eye-balled something that looked good for mine.
  • The lever arm needs to be constrained so that it chucks the projectile Up into the air, preferably at close to a 45 degree angle for maximum range. I did this by placing a block under the lever arm so that it can only go down so far when shooting.
  • Use two hinge joints - one on each side of the arm. Using only one hinge is probably not a good idea.
  • Make the ammo carrier first, then size the rest of the catapult to whatever sized carrier you made. Make sure that the bottom of the carrier is only one piece, this will be important to our scripting effort later.
  • Anchor your support beams to the ground and your hinge joints to the support beams. The force of the catapult is quite large, and your entire catapult can "hop" if you don't.

Making it Fire

Once you have the catapult built, now we need to script its function. Actually making the thing fire is pretty easy. All we want to do is apply a massive force down on the short end of the arm to send the rock flying. Enter the BodyForce object.

The BodyForce object can be used to apply a force vector to the part that it is placed under (in the GoodBlox Studio treeview).

All we need to do is:

1. Create a BodyForce object under a part.
2. Set its force vector to (0,0,0) - i.e. it is off by default.
3. Place the part on the short end of the arm.
4. Write a script that detects when the firing lever is touched by a player.
5. When this happens, apply a huge downward force.
6. Wait a couple of seconds.
7. Reset force vector to (0,0,0)

If you look carefully at the first catapult screenshot at the top of this article, you will see a little red 2x2 that I placed at the end of the catapult arm. This is where I put the BodyForce object.

Here is some sample source code. You will probably need to edit the variables to fit your own part names:

local powerblock = script.Parent.Parent.PowerBlock
local bodyforce = powerblock.BodyForce

local debounce = false

function onTouched(hit)
	local humanoid = hit.Parent:findFirstChild("Humanoid")
	if humanoid~=nil and debounce == false then
		debounce = true
		bodyforce.force = Vector3.new(0,1000000,0)
		wait(3)
		bodyforce.force = Vector3.new(0,0,0)
		wait(3)
		debounce = false
	end
end

script.Parent.Touched:connect(onTouched)

Q & A

You might have a couple of questions about this script:

What does debounce do? - In my experiments I found that that Touched event happens multiple times whenever a player touches a brick. I use the debounce flag to make sure I only handle one Touched event per firing cycle.

How did you pick the force vector? - Trial and error. In my catapult, I have tested forces up to 3 million units (GBX Newtons?) without a problem. Obviously, the more force you apply the farther the projectile is flung. Making a long range catapult is a less interesting problem than making the most efficient catapult possible, since you can always make something shoot further by upping the force you apply.

Why is the force vector upside down? - Honestly, I have no idea. I think I have probably found a bug in the BodyForce object. In my experiments, this is what worked.

Tip: The more efficient your catapult design, the better. An efficient design requires less force to chuck the rock the same distance. The GoodBlox physics engine doesn't like dealing with huge forces - it can make your simulation unstable (you will see jittering). This becomes especially important if you plan on mounting your catapult on a hinge, swivel, or other un-anchored base.

Making Rocks Blow Up When they Hit Stuff

The last part of the puzzle, and the hardest. If you have gotten to this step, you have a cool catapult that can launch rocks into orbit. But they don't do much damage when they hit something, do they? That's just not satisfying.

This problem is actually two problems:

1. How do you make projectiles explode when they hit something?
2. How do you make projectiles not explode when you are loading them into the Catapult?

Number 1 is easy - just look at the GoodBlox weapon scripts for inspiration. They basically all work the same way - there is a script that is copied and attached to every projectile a weapon fires that handles doing damage OnTouch.

Number 2 is hard. Here is one solution:

1. Name all of your ammo rocks a special name - I choose "WarRock" (As in Homer's War Rocks, kudos if you get the reference)
2. Make the bottom of your ammo carrier listen to the OnTouch event. When it detects that a WarRock has touched it, the WarRock goes "hot". This means a projectile script is attached to the rock that will make it blow up when hit.
3. Make the projectile script periodically (like 30 times a second), check the WarRock's position and compare it to the WarRock's previous position. Go "critical" when the WarRock's velocity exceeds a sustained velocity of 16 studs per second over .5 seconds. Once a WarRock has gone critical it will explode the next time it touches anything.

The beauty of this scheme: The WarRock "knows" when it has been launched because it is checking its own velocity. There is no need for the trigger to communite with the WarRock's projectile script. When I was originally trying to figure this out, some people on the forums suggested using a wait() timer instead to make the WarRock go critical X seconds after the trigger was flipped. I have not tried this solution, but I would bet that there would be issue with lag, making the rock explode too late. It also requires Trigger To Rock communication - which is fugly.

I'm going to give my final code, just as an example of what I did. I doubt you can plug it into your catapult without mods.

Catapult Trigger Script:

local powerblock = script.Parent.Parent.PowerBlock
local bodyforce = powerblock.BodyForce
local hotplate = script.Parent.Parent.HotPlate

local debounce = false

function onTouched(hit)
	local humanoid = hit.Parent:findFirstChild("Humanoid")
	if humanoid~=nil and debounce == false then
		debounce = true
		bodyforce.force = Vector3.new(0,1000000,0) --[[you may have to adjust this number, 
depending on the design of your catapult --]]
		wait(3)
		bodyforce.force = Vector3.new(0,0,0)
		wait(3)
		debounce = false
	end
end

function onHotPlateTouched(hit)
	if (hit.Name ~= "WarRock") then return end
	if (hit:findFirstChild("WarRockScript") ~= nil) then return end -- debounce
	hit.BrickColor = BrickColor.new(105)
	local code = script.Parent.WarRockScript:clone()
	code.Disabled = false
	code.Parent = hit
end

script.Parent.Touched:connect(onTouched)
hotplate.Touched:connect(onHotPlateTouched)

WarRock Script:

I got a litle fancy in this script, so not all the code is necessary for a bare-bones implementation. The trick here is that the velocity-tracking code runs 30 times per second, on the physics engine heartbeat, using the RunService to do a SteppedWait. The GoodBlox Rocket Launcher does a similar thing in order to override gravity. I can't take full credit for figuring this out, Telamon had to explain it to me.

-- attached to WarRock
print("WarRock script running")

r = game:service("RunService")

local warRock = script.Parent
local flightTime = 0               -- rock is only hot after we detect it has been flying for .5 seconds
local hotRock = false
local lastPos = nil

function onTouched(hit)
	-- We hit something, time to blow up
	if (hotRock == true) then
		blowUp()
	end
end

function getExplosionRadius(mass)
	if (mass > 50) then return 16 end
	if (mass > 40) then return 13 end
	if (mass > 5) then return 10 end
	return 6
end

function getExplosionPressure(mass)
	if (mass > 50) then return 2000000 end
	if (mass > 40) then return 1000000 end
	if (mass > 5) then return 500000 end
	return 200000
end


function blowUp()

	local sound = Instance.new("Sound")
		sound.SoundId = "rbxasset://sounds\\Rocket shot.wav"
		sound.Parent = warRock
		sound.Volume = 1
		sound:play()
	explosion = Instance.new("Explosion")

	local mass = warRock:getMass()
	-- in default war rocks, mass varies from 1 to 57

	explosion.BlastRadius = getExplosionRadius(mass)
	explosion.BlastPressure = getExplosionPressure(mass)
	explosion.Position = warRock.Position
	explosion.Parent = game.Workspace
	warRock.Parent = nil
end

function getDistance(a, b)
	local dx = a.x - b.x --[[change in x distance from a to b--]]
	local dy = a.y - b.y --[[change in y distance from a to b--]]
	local dz = a.z - b.z --[[change in z distance from a to b--]]
	return math.sqrt(dx * dx + dy * dy + dz * dz) --[[Sphere: x^2 + y^2 + z^2 = radius^2, therefore we need the sqrt of x^2 + y^2 + z^2 to get the radius. --]]
end


function checkFlying(dt)
	-- "flying" is movement of over 16 studs per second
	-- when this condition holds, increment the flightTime
	-- when flightTime > .5, WarRock goes hot!
	if (lastPos == nil) then 
		lastPos = warRock.Position 
		return
	end

	local velocity = getDistance(lastPos, warRock.Position) / dt --[[rate of change of distance over time, 
derivative --]]

	if (velocity > 16) then
		flightTime = flightTime + dt
	else
		flightTime = 0
	end


	if (flightTime > .5) then
		hotRock = true
		warRock.BrickColor = BrickColor.new(21)
	end

	lastPos = warRock.Position
end

warRock.Touched:connect(onTouched)

local lastTime = r.Stepped:wait()
local curTime


while true do
	curTime = r.Stepped:wait() -- should be 1/30th a second, but take no chances
	if (hotRock == false) then 
		checkFlying(curTime - lastTime) 
		lastTime = curTime
	else break end
end


Going Advanced

Make sure that all the scripts in your WarRocks have been named to "WarRockScript".

Now, this is an onClick script. That means you will need to insert > object > clickdetector in your catapult.

Now enter the following script into your catapult.


local powerblock = script.Parent.Parent.PowerBlock
local bodyforce = powerblock.BodyForce
local hotplate = script.Parent.Parent.HotPlate

function onClicked()
	bodyforce.force = Vector3.new(0,1000000,0)
	wait(3)
	bodyforce.force = Vector3.new(0,0,0)
	wait(3)
end

function onHotPlateTouched(hit)
	if (hit.Name ~= "WarRock") then return end
	if (hit:findFirstChild("WarRockScript") ~= nil) then return end -- debounce
	hit.BrickColor = BrickColor.new(105)
	local code = script.Parent.WarRockScript:clone()
	code.Disabled = false
	code.Parent = hit
end

script.Parent.ClickDetector.MouseClick:connect(onClicked)
hotplate.Touched:connect(onHotPlateTouched)