A module that uses prefabs to demonstrate how to set up classic traps in an artist and mapper freindly way
Copy entire trapsNTriggers folder into the Torque3D project's data/ folder, restart the project if it's running and it'll be integrated.
None. These are all prefabricated drop in examples
electricHazzard
trigger
referencing the EnvironmentalDamager
triggerData
datablock definiton
ParticleEmitterNode
VolumetricFog
Uses an EnvironmentalDamager::onTickTrigger
call to see if it should damage an object within
Add any new visual representation, and use the same or similar EnvironmentalDamager
datablock for a trigger, and bake it into a new prefab for placement
Example 2) a more complex case:: enter one trigger, activate a trap. if you're within a second when it goes off, actually take damage
spikes
, wallSpikesD
, wallSpikesL
, and wallSpikesR
are all effectively the same prefab in different orientations
one trigger
referencing the TrapTrigger
triggerData
datablock definiton with the object's internalname
of activator
a second trigger
referencing the TrapDamager
triggerData
datablock definiton with object's internalname
of damager
a StaticShape
referencing the SpikeTrap
StaticShapeData
datablock definiton with the object's internalname
of vis
-
TrapTriggerData::onTickTrigger
operates similarly toEnvironmentalDamager::onTickTrigger
, Hoewever, instead of directly dealing damage, it leverages theparentGroup
lookup command to first look up the group that trigger is within, in this case the prfab, in conjunction with the-->
syntax sugar to look up another object within that group by it'sinternalname
. This is to avoid namespace collisions, as giving objects normal names creates an entire namespace to hook against for direct-commands, so those must be unique.internalname
s do not. -
Having found the internalname of
vis
we then trigger thetrapData::setActive(%this, %obj, %activate)
command. We runtime add%obj.activeState
to track if that specific object has been activated or not and play aTrip
via thePlayThread
command animation if the state is true -
Any
Shapebase
derivitive, having completed a non-looping animation triggered byPlayThread
executes aonEndSequence(%this, %obj, %slot, %name)
command at the datablock namespace level. Here we leverage that to create a general hook based on the animation used to create a call (if it is defined) of, in this case,onTrip
-
The animation having been completed,
trapData::onTrip(%this,%obj)
is then triggered, and we use the seconddamager
triggers'sgetNumObjects()
andgetObject(%i)
commands to apply damage to all objects within that, just as we do the prior case, thenplaythread
theTripDone
animation to complete and reset the cycle
Add a model with a Trip
and TripDone
animation, as well as a collision mesh.
Add a TrapTrigger
,a TrapDamager
with an internalname of damager
, and a StaticShape
referencing a StaticShapeData
pointing to said model with an internalname
of vis
and bake it to a prefab.
datablock DebrisData( barrelDebris )
datablock ExplosionData(BarrelExplosion)
datablock RigidShapeData(boomBarrel)
boomBarrel
references the other two via debris = barrelDebris;
and explosion = BarrelExplosion;
respectively
function boomBarrel::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)
leverages the following:
%obj.applyDamage(%damage);
calls in to source side to look up the cumulative damage that object instance has taken, and lerps an explicitly named damage
animation to blend the visual result based totalDamage/destroyedLevel
%obj.applyImpulse
is used in conjunction with a normalized vector of where the object's collision mesh was struck, in combination with a scalar based on the amount of damage that was sent that hit that specific object, as a percentage of the maxDamage
value stored off in the referenced datablock shared by all instances to move the rigidshape so that harder hits move the barels further
getDamageState()
and %obj.setDamageState("Destroyed");
are used to ensure that when this object blows up, it does not infinitely damage itself causing it to re-blowup()
.
%obj.blowUp();
references the referenced datablocks debris
and explosion
to spew debris meshes and (in the provided configuration) 1 animated particle, respectively
- for the thing to destroy, a model with a colMesh-1 submesh, and (optionally) an animation sequence that can be tied to a
damage
animation - for debris, a mesh containing origin centered submeshes of any amount
- for the explosion, a texture atlas using
animTexTiling
,animTexFrames
,framesPerSec
andanimateTexture
for the particle to be used (or standard particle emission) - a
RigidShapeData
pointing at the new mesh with adestroyedLevel
and apropriatemass
,bodyFriction
,bodyRestitution
for general physics as well asintegration
,collisionTol
, andcontactTol
entries for physics subtick rate, and collision vs contact resolution speeds and distances, respectively
Waterblock
function playerData::onEnterLiquid(%this, %obj, %coverage, %type)
is, at time of writing, a player-specific callback (there are similar callbacks for other classes but no shared root)
By specifying a liquidType = "Ick";
entry in the waterblock, onEnterLiquid
knows what type of liquid you submerged yourself in.
We then use a schedule
call to execute an infinitely repeating function playerData::takeDot(%this, %obj, %type, %damage)
, and use the cancel
command to stop that cycle when you function playerData::onLeaveLiquid(%this, %obj, %type)
Specify a different liquidType
, and call %obj.getDatablock().schedule(1000, "takeDot", %obj, %type, 5);
with different values if using that instead