Need Help Making a Tomb Raider Style PushBlock
by Nicolai Dutka · in Torque Game Engine Advanced · 04/01/2009 (9:26 am) · 16 replies
IDEA:
1. Create a static shape (cube).
2. Create a trigger slightly bigger than the cube, place directly over the cube.
3. When you enter the trigger and press the 'activate' button, the player attaches to the nearest mount point of the cube.
4. Controls are switched from the player to the cube, but camera stays on the player.
5. Player pushes movement keys causing the cube to move which in turn moves the attached player.
6. Player presses the 'activate' button again to release the cube and return to normal movement.
PROBLEM:
1. Cannot mount the player to the cube.
2. Cannot mount the cube to the player.
3. Cannot move the cube (probably have to make the cube be a player with just the cube as its shape)
4. Once I do make the cube a player, cannot attach the main character to the cube character... Cube moves, but player just stands there watching....
Am I doing something wrong or is there a better way?
I thought about just using a rigidBody and physics to make the cube really heavy, but the player cannot push it at all that way, and if the player shoots the cube, it goes FLYIN WAAAAAAY far away...
1. Create a static shape (cube).
2. Create a trigger slightly bigger than the cube, place directly over the cube.
3. When you enter the trigger and press the 'activate' button, the player attaches to the nearest mount point of the cube.
4. Controls are switched from the player to the cube, but camera stays on the player.
5. Player pushes movement keys causing the cube to move which in turn moves the attached player.
6. Player presses the 'activate' button again to release the cube and return to normal movement.
PROBLEM:
1. Cannot mount the player to the cube.
2. Cannot mount the cube to the player.
3. Cannot move the cube (probably have to make the cube be a player with just the cube as its shape)
4. Once I do make the cube a player, cannot attach the main character to the cube character... Cube moves, but player just stands there watching....
Am I doing something wrong or is there a better way?
I thought about just using a rigidBody and physics to make the cube really heavy, but the player cannot push it at all that way, and if the player shoots the cube, it goes FLYIN WAAAAAAY far away...
#2
04/01/2009 (11:41 am)
That's what I get for reading into it waaay too much.. let me give that a shot and I will post back with some results in the next couple of days.
#3
What if the player turns? The cube is supposed to be like a HUGE block of stone that is tough to move and would have to stay directly in front of the player at all times. I have the game locked to 3rd person, so that solves what to do when they look up/down.. nothing. But when they look left/right, it rotates the character as well which might look weird...
But ok, say we dont care that it looks weird... I still don't know how to get the block to always be directly in front of the character. Setting an "offset" I could always have it be +5 in the Y position, but if the player is looking north, it might be directly in front of him, but when he looks south, the offset would place the block directly behind him.
Clearly I need it to use some kind of vector calculation, but that's about as far as I can get... Not to great with 3D math yet...
So, how would I make the block always be directly in front of the player regardless of rotation?
04/01/2009 (11:48 am)
Ok, well... just thinking about it some more...What if the player turns? The cube is supposed to be like a HUGE block of stone that is tough to move and would have to stay directly in front of the player at all times. I have the game locked to 3rd person, so that solves what to do when they look up/down.. nothing. But when they look left/right, it rotates the character as well which might look weird...
But ok, say we dont care that it looks weird... I still don't know how to get the block to always be directly in front of the character. Setting an "offset" I could always have it be +5 in the Y position, but if the player is looking north, it might be directly in front of him, but when he looks south, the offset would place the block directly behind him.
Clearly I need it to use some kind of vector calculation, but that's about as far as I can get... Not to great with 3D math yet...
So, how would I make the block always be directly in front of the player regardless of rotation?
#4
1) Do not allow rotations while "pushing" (simply ignore the move->yaw in C++ if pushing).
2) Take the move->yaw value and instead of applying it directly to the player, use it to rotate the player around the cube's center (like in Tomb Raider Underworld). The player actually circle-strafes the cube.
04/01/2009 (12:21 pm)
You have two options, both which are design choices:1) Do not allow rotations while "pushing" (simply ignore the move->yaw in C++ if pushing).
2) Take the move->yaw value and instead of applying it directly to the player, use it to rotate the player around the cube's center (like in Tomb Raider Underworld). The player actually circle-strafes the cube.
#5
And this is 'doable'.
Now I am running a function to update the pushblock's position. I can do something like:
$pushBlock.setTransform($player.getTransform());
But how do I get the offset so the block is directly in front of the player? I am guessing with a raycast and some kind of vector math maybe? Anyone with some good 3d Math skills know?
04/01/2009 (12:31 pm)
OK, I went ahead and compensated for speeds:$movementSpeed = 0.3; // m/s
$turnSpeed = 0.01;
function yaw(%val)
{
$mvYaw += getMouseAdjustAmount(%val) * $turnSpeed;
}
function pitch(%val)
{
$mvPitch += getMouseAdjustAmount(%val) * $turnSpeed;
}And this is 'doable'.
Now I am running a function to update the pushblock's position. I can do something like:
$pushBlock.setTransform($player.getTransform());
But how do I get the offset so the block is directly in front of the player? I am guessing with a raycast and some kind of vector math maybe? Anyone with some good 3d Math skills know?
#6
When I move, the block moves... but doesn't render... If I click on it after moving it, it renders. Here's a video:
http://nicolaidutka.archongames.com/storage/video/setTransform.avi
04/01/2009 (1:46 pm)
Here I have the code working so far, but is based off a real-world offset rather than "player look" offset (just adjusting the Y position by +5).When I move, the block moves... but doesn't render... If I click on it after moving it, it renders. Here's a video:
http://nicolaidutka.archongames.com/storage/video/setTransform.avi
#7
Here is my current code, you can use it if you want:
04/01/2009 (1:48 pm)
So can someone help with those TWO problems?Here is my current code, you can use it if you want:
////////////////////////////////////////////////////////////
// Activate
////////////////////////////////////////////////////////////
function activate()
{
if($PushPull)
{
//Player is standing next to a PushPull object. Mount the player to that object, switch controls to the object, play 'lean' animation
if($player.pushing)
{
//Player is already in PushPull mode. Release controls from object and back to player.
echo("Player is already pushing. Releasing object"); //conveniently, this also protects us from grabbing more than one at a time
$player.pushing = 0;
$movementSpeed = 1;
$turnSpeed = 1;
$PushObject = "";
//Keyboard
moveMap.bind( keyboard, space, jump );
// Mouse
moveMap.bind( mouse, button0, mouseFire );
moveMap.bind( mouse, button1, altTrigger );
//Joystick0
moveMap.bind( joystick0, button0, altTrigger );
moveMap.bind( joystick0, button2, mouseFire );
moveMap.bind( joystick0, button1, jump);
}
else
{
//Set player to PushPull mode
//Cut move speed, turn speed
//Disable shooting, jumping
//Begin updating PushPull object transform
echo("Setting player to PushPull mode.");
HUDText.setText("");
HUDText.setVisible(0);
$player.pushing = 1;
$player.setVelocity("0 0 0");
$movementSpeed = 0.3; // m/s
$turnSpeed = 0.01;
//Keyboard
moveMap.bind( keyboard, space, "" );
// Mouse
moveMap.bind( mouse, button0, "" );
moveMap.bind( mouse, button1, "" );
//Joystick0
moveMap.bind( joystick0, button0, "" );
moveMap.bind( joystick0, button1, "" );
moveMap.bind( joystick0, button2, "" );
updatePushBlock();
}
}
}
function updatePushBlock()
{
if($player.pushing)
{
%t = $player.getTransform();
%x = getWord(%t, 0);
%y = getWord(%t, 1) + 5;
%z = getWord(%t, 2);
%r1 = getWord(%t, 3);
%r2 = getWord(%t, 4);
%r3 = getWord(%t, 5);
%r4 = getWord(%t, 6);
%t = %x SPC %y SPC %z SPC %r1 SPC %r2 SPC %r3 SPC %r4;
$PushObject.setTransform(%t);
schedule(33, 0, updatePushBlock);
}
}
#8
04/01/2009 (3:25 pm)
Quote:But how do I get the offset so the block is directly in front of the player? I am guessing with a raycast and some kind of vector math maybe? Anyone with some good 3d Math skills know?Get the player's forward vector (Player.getForwardVector()), scale it by how much you want to move the block (%newVec = VectorScale(%oldVec, %distance)) and add it to the block's position (%newPos = VectorAdd(%oldPos, %newVec)).
Quote:When I move, the block moves... but doesn't render... If I click on it after moving it, it renders. Here's a video:Use StaticShape instead of TSStatic. That's why you couldn't mount the box on the player: StaticShapes are mountable (and can move and animate), TSStatics are not.
#9
Got the object moving the way I want it. Have an issue with being able to shove it into other meshes/terrain, but this will do for now.
Next step is to see what I can do if I change the object to StaticShape instead of TSStatic...
04/01/2009 (4:30 pm)
Thanks!function updatePushBlock()
{
if($player.pushing)
{
%t = $player.getTransform();
%a = getWord(%t, 3);
%b = getWord(%t, 4);
%c = getWord(%t, 5);
%d = getWord(%t, 6);
%rot = %a SPC %b SPC %c SPC %d;
%distance = 1.65; // Distance in front of eyePoint
%start = $player.getEyePoint();
%vector = $player.getEyeVector();
%vectorN = VectorNormalize(%vector);
%vectorS = VectorScale(%vectorN,%distance);
%end = VectorAdd(%vectorS,%start);
$PushObject.setTransform(%end SPC %rot);
schedule(33, 0, updatePushBlock);
}
}Got the object moving the way I want it. Have an issue with being able to shove it into other meshes/terrain, but this will do for now.
Next step is to see what I can do if I change the object to StaticShape instead of TSStatic...
#10
04/01/2009 (4:31 pm)
Don't forget to test over networks, if it's multiplayer. Really great work though- you should turn this into a resource once you get it working :)
#11
Check this out:
Of course, there is still one issue I need help with now. I am having 'collision' issues and I will simply let this video demo show you the issue:
nicolaidutka.archongames.com/storage/video/PushBlockNoCollide.avi
04/07/2009 (12:36 pm)
Ok everyone, I have brand new code! This doesn't require triggers at all, just a simple Field added to your 'activatable' object.Check this out:
////////////////////////////////////////////////////////////
// Activate
////////////////////////////////////////////////////////////
function activate()
{
// Check for 'activatable' objects in front of the player
%distance = 1.65; // Distance in front of eyePoint
%start = $player.getEyePoint();
%vector = $player.getEyeVector();
%vectorN = VectorNormalize(%vector);
%vectorS = VectorScale(%vectorN,%distance);
%end = VectorAdd(%vectorS,%start);
%ray = ContainerRayCast(%start,%end,$TypeMasks::StaticShapeObjectType);
if(%ray)
{
//ray collided with a staticshape. check to see if it is 'activatable'
echo("Ray: "@ %ray);
%ray = getWord(%ray, 0);
echo("Ray: "@ %ray);
if(%ray.pushBlock)
{
$PushObject = %ray;
//Player is standing next to a PushBlock object. Mount the object to that player, play 'lean' animation
if($player.pushing)
{
//Player is already in PushPull mode. Release controls from object and back to player.
echo("Player is already pushing. Releasing object"); //conveniently, this also protects us from grabbing more than one at a time
$player.pushing = 0;
$movementSpeed = 1;
$turnSpeed = 1;
$player.unmountObject($PushObject);
$PushObject = "";
//Keyboard
moveMap.bind( keyboard, space, jump );
// Mouse
moveMap.bind( mouse, button0, mouseFire );
moveMap.bind( mouse, button1, altTrigger );
//Joystick0
moveMap.bind( joystick0, button0, altTrigger );
moveMap.bind( joystick0, button2, mouseFire );
moveMap.bind( joystick0, button1, jump);
}
else
{
//Set player to PushPull mode
//Cut move speed, turn speed
//Disable shooting, jumping
//Begin updating PushPull object transform
echo("Setting player to PushPull mode.");
$player.pushing = 1;
$player.setVelocity("0 0 0");
$movementSpeed = 0.3; // m/s
$turnSpeed = 0.01;
//Keyboard
moveMap.bind( keyboard, space, "" );
// Mouse
moveMap.bind( mouse, button0, "" );
moveMap.bind( mouse, button1, "" );
//Joystick0
moveMap.bind( joystick0, button0, "" );
moveMap.bind( joystick0, button1, "" );
moveMap.bind( joystick0, button2, "" );
$player.mountObject($PushObject,2);
}
} //End PushBlock
if(%ray.Lever)
{
$Lever = %ray;
//disable controls
toggleControls();
//play animations
//run a little code for the lever
//enable controls
schedule(1000,0,toggleControls);
}
} //End if(%ray)
}Of course, there is still one issue I need help with now. I am having 'collision' issues and I will simply let this video demo show you the issue:
nicolaidutka.archongames.com/storage/video/PushBlockNoCollide.avi
#12
04/07/2009 (1:26 pm)
The activate() function might be slower than the collision occurring, and so you hit the object before your game logic is able to do its thing. Maybe putting this into the onCollision() callback might work? This way, you don't always have to test for collisions with raycasts and such, just test that the player hit an object with a field in it like "activateable = 1;", and that the player itself is in "pushpull" mode. That takes a lot of the heavier math out of the equation and makes it a faster function.
#13
04/07/2009 (2:06 pm)
Well, as of right now, I am not checking for collisions at all.. I am just wondering how I would do that... You mentioned the onCollision callback.. so I could write something into the onCollision for the $PushObject that tells it to stop the player?
#14
If you really want nice collisions, you'll have to go into source and code the movement in C++. Not trivial if you're not used to this kind of stuff.
A script-only alternative would be using containerBoxEmpty() (call it without parameters to see how to use it on the console). You pass typemasks, a center and 3 radius values, and it does a intersection test using an axis-aligned box against the types of objects you specified, and returns true if the area is "empty" or false if otherwise.
Since you might have other objects of the same type as your pushblock (StaticShape) which you might want to collide with, all you need is to move it elsewhere, do the boxtest, then move it back (this won't have any visual impact, since it's done between frames).
Using this trick you can check if the new box position is "empty" and stop moving if not. Since the containerBoxEmpty() uses an axis-aligned box, the collision won't quite fit the pushblock shape, but it's tweakable.
04/07/2009 (2:42 pm)
The problem is that you're not actually moving the box, you're warping it into a new position. This will never generate a proper collision, since movable objects (Player, Vehicles and RigidShapes) are the ones who decide whether or not they have hit something, and if they hit a ShapeBase-derived object (StaticShapes=yes, TSStatic=no) they'll call onCollision on that object's datablock (and on their own datablocks as well).If you really want nice collisions, you'll have to go into source and code the movement in C++. Not trivial if you're not used to this kind of stuff.
A script-only alternative would be using containerBoxEmpty() (call it without parameters to see how to use it on the console). You pass typemasks, a center and 3 radius values, and it does a intersection test using an axis-aligned box against the types of objects you specified, and returns true if the area is "empty" or false if otherwise.
Since you might have other objects of the same type as your pushblock (StaticShape) which you might want to collide with, all you need is to move it elsewhere, do the boxtest, then move it back (this won't have any visual impact, since it's done between frames).
Using this trick you can check if the new box position is "empty" and stop moving if not. Since the containerBoxEmpty() uses an axis-aligned box, the collision won't quite fit the pushblock shape, but it's tweakable.
#15
I've used a schedule that calls on itself every 50ms. This scheduled function will unmount the object, move it to '0 0 0', do the containerBoxEmpty() check, then move the object back and remount.
It's working wonderfully, but there are a couple glitches:
1. When I hold 'W' to move forward my movement does indeed stop when the containerBoxEmpty finds one of my search masks, let's just use terrain as an example. Now, if I continuously push 'W' really fast, the player can move forward tiny bits at a time until eventually the box is mashed fully into the terrain. How would I prevent this? Should I put another containerBoxEmpty check in the movement code itself?
2. The code does nothing at all to stop me from rotating the player, box mounted to the front, using the mouse. I can move my player next to an object so that the object is on my side and the box mounted in front of me, then rotate my view towards the object and it doesn't stop at all... How would I prevent this as well?
10/13/2009 (11:56 pm)
Ok, time to rehash this as I've finally gotten some code working...I've used a schedule that calls on itself every 50ms. This scheduled function will unmount the object, move it to '0 0 0', do the containerBoxEmpty() check, then move the object back and remount.
It's working wonderfully, but there are a couple glitches:
1. When I hold 'W' to move forward my movement does indeed stop when the containerBoxEmpty finds one of my search masks, let's just use terrain as an example. Now, if I continuously push 'W' really fast, the player can move forward tiny bits at a time until eventually the box is mashed fully into the terrain. How would I prevent this? Should I put another containerBoxEmpty check in the movement code itself?
2. The code does nothing at all to stop me from rotating the player, box mounted to the front, using the mouse. I can move my player next to an object so that the object is on my side and the box mounted in front of me, then rotate my view towards the object and it doesn't stop at all... How would I prevent this as well?
#16
this fly or push back in the impulse script of the projectile, iterate trow objects and dont impulse these type of objects:
10/14/2009 (5:38 pm)
Quote: I thought about just using a rigidBody and physics to make the cube really heavy, but the player cannot push it at all that way, and if the player shoots the cube, it goes FLYIN WAAAAAAY far away...
this fly or push back in the impulse script of the projectile, iterate trow objects and dont impulse these type of objects:
function CrossbowProjectile::onCollision(%this,%obj,%col,%fade,%pos,%normal)
{
// Apply damage to the object all shape base objects
if (%col.getType() & $TypeMasks::ShapeBaseObjectType)
%col.damage(%obj,%pos,%this.directDamage,"CrossbowBolt");
// Radius damage is a support scripts defined in radiusDamage.cs
// Push the contact point away from the contact surface slightly
// along the contact normal to derive the explosion center. -dbs
radiusDamage(%obj, %pos, %this.damageRadius, %this.radiusDamage, "Radius", %this.areaImpulse);
}
function radiusDamage(%sourceObject, %position, %radius, %damage, %damageType, %impulse)
{
// Use the container system to iterate through all the objects
// within our explosion radius. We'll apply damage to all ShapeBase
// objects.
InitContainerRadiusSearch(%position, %radius, $TypeMasks::ShapeBaseObjectType);
%halfRadius = %radius / 2;
while ((%targetObject = containerSearchNext()) != 0) {
// Calculate how much exposure the current object has to
// the explosive force. The object types listed are objects
// that will block an explosion. If the object is totally blocked,
// then no damage is applied.
%coverage = calcExplosionCoverage(%position, %targetObject,
$TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType |
$TypeMasks::ForceFieldObjectType | $TypeMasks::VehicleObjectType);
if (%coverage == 0)
continue;
// Radius distance subtracts out the length of smallest bounding
// box axis to return an appriximate distance to the edge of the
// object's bounds, as opposed to the distance to it's center.
%dist = containerSearchCurrRadiusDist();
// Calculate a distance scale for the damage and the impulse.
// Full damage is applied to anything less than half the radius away,
// linear scale from there.
%distScale = (%dist < %halfRadius)? 1.0:
1.0 - ((%dist - %halfRadius) / %halfRadius);
// Apply the damage
%targetObject.damage(%sourceObject, %position,
%damage * %coverage * %distScale, %damageType);
// Apply the impulse
if (%impulse)
{
%impulseVec = VectorSub(%targetObject.getWorldBoxCenter(), %position);
%impulseVec = VectorNormalize(%impulseVec);
%impulseVec = VectorScale(%impulseVec, %impulse * %distScale);
%targetObject.applyImpulse(%position, %impulseVec);
}
}
}
Torque 3D Owner Ted Southard
There's an issue of smooth movement, but you can do something like a setPosition(%player.getPosition() + %cubeOffset) or something to that effect during the player's move updates. Hope that helps.