Dear Community,
Here's another Tutorial with Code and Video for you!
What is Point Gravity?
Point gravity is like what you see in a large part of Mario Galaxy where the player unit moves relative to the surface of the object it is standing on as if that object is the only major source of gravity in the universe.
In this Tutorial I give you the code for:
1. Regular Point Gravity, where object moves along a completely convex surface which by definition always has a solid center. (0 to 3 minute of video)
2. Relative Point Gravity (my term), where object can move along any kind of surface, and will always cling to the nearest surface.
(Relative Point Gravity starts at 3:03 in video below)
See video to really observe the awesome difference between these two types of Gravity!
Algorithm Efficiency Seen Via Video
If you look carefully around 5:50 you can see how the 361 traces dont always all run, sometimes only a few lines shoot out, because the loop tries to exit as early as possible, but when the ball gets really far then you see more traces going out from the ball.
Convex Objects work with Regular Point Gravity

As you can see in my video my Relative Point Gravity system works with ANY surface, including a spiral stair case with no center at all!
~~~
Simulation of a True Magnetic Field
My Relative Point Gravity System is simulating your player object having a true magnetic field and seeking out the nearest surface to cling to if it is range of the magnetic field.
I say “true” magnetic field because my algorithm produces results as if calculating a full sphere around the object in all directions (it's much more efficient than that tho).
~~~
Entire Code for Point Gravity
Here's the entire code for regular Mario Galaxy Style Point Gravity using a Kactor.
If you are using a pawn you will need to set the pawn physics to PHYS_RigidBody while using the point gravity, and manually play movement animations.
I kept this code to only the code that you would want to add to your own player pawn/Kactor class and did not include jumping because getting basics to work first is far more important. Once you understand this code adding jumping is easy.
I comment out any code that I included more for your own information and use, code that is useful but not used in this sample below.
~~~
What is a Surface Normal?
The normals you see everywhere in this code are the surface normals, lines that are perpendicular to the surface at the point where the trace hits the surface.
The trace is like a projectile, and where the trace hits a surface, a line perpendicular to the surface at that point is the surface normal.
~~~
Point Gravity and the Vector Normal() Function
By contrast, the vector function Normal() reduces any vector to a unit vector (vect(1,1,1)).
This is the core of how Point Gravity is able to work!
I use Normal() in this code so I have the direction of the vector (preserved with Normal()) but I can exactly determine the length of the line without the length of the pre-normalized vector getting into the way of my exact calculations.
So the above code says
“Add a physics impulse to the player Object of size GravityPower*deltaTimePhyiscsBoostMultiplier IN THE DIRECTION OF the line between the player Object and the gravity Source.”
In other words:
“move the player object toward the gravitySource by this exact amount of GravityPower*deltaTimePhyiscsBoostMultiplier.”
If I did not normalize the vector, the actual distance between the player obj and the gravitySource would be included in the total impulse force, and this distance is variable and could be quite large.
But without that vector I do not have the exact line/angle between the player obj and the gravitySource.
So normal() is your friend!
Normal() enables point gravity to be accurate and consisent no matter where exactly the player OBJ is relative to the center of the chosen gravitySource.
~~~
Entire Code For My True Magnetic Field System
Relative Point Gravity Code
And here it is!

Here's the core function of this entire class code
This is the code that generates the True Magnetic Field around the player Object.
Understanding the Code
Basically this code runs every moment of game time via the tick, and:
1. A single trace is sent to center of current gravitySource object for Point Gravity case
2. 361 traces are sent from the Player Object center in a sphere with radius of magneticFieldSize
3. The loop tries as hard as it can to stop before all 361 traces are run, by finding a single match for a distance to a surface of size MagnetCloseEnoughRange. Loop also will not even run AT ALL if the point gravity case passes within MagnetCloseEnoughRange.
You can see the efficiency case very visually in the video when the traces are being drawn as black lines
If the loop of 361 is forced to complete all the way, the normal for the minimum distance traced / the closest surface is used, to ensure the proper behavior of a magnetically attractive object finding the nearest surface.
4. Once the ideal normal from the surface traces is found, it is applied as the gravitational force, with -1 because we are flipping the normal around so ball moves toward the surface rather than away from it.
End Result: The Player Object is always magnetically attracted to the nearest surface within its magnetic field range!
~~~
Summary
With this code I am providing to you you can either implement basic Point Gravity or my True Magnetic Field gravity system!
Have fun!
Rama
PS: if you are worried about performance, you could put the loop of 361 traces into a timer that runs every 0.3 seconds or so.
I recommend to first get the current code working before using the timer to make things efficient.
Here's another Tutorial with Code and Video for you!
What is Point Gravity?
Point gravity is like what you see in a large part of Mario Galaxy where the player unit moves relative to the surface of the object it is standing on as if that object is the only major source of gravity in the universe.
In this Tutorial I give you the code for:
1. Regular Point Gravity, where object moves along a completely convex surface which by definition always has a solid center. (0 to 3 minute of video)
2. Relative Point Gravity (my term), where object can move along any kind of surface, and will always cling to the nearest surface.
(Relative Point Gravity starts at 3:03 in video below)
See video to really observe the awesome difference between these two types of Gravity!
Algorithm Efficiency Seen Via Video
If you look carefully around 5:50 you can see how the 361 traces dont always all run, sometimes only a few lines shoot out, because the loop tries to exit as early as possible, but when the ball gets really far then you see more traces going out from the ball.
Convex Objects work with Regular Point Gravity

As you can see in my video my Relative Point Gravity system works with ANY surface, including a spiral stair case with no center at all!
~~~
Simulation of a True Magnetic Field
My Relative Point Gravity System is simulating your player object having a true magnetic field and seeking out the nearest surface to cling to if it is range of the magnetic field.
I say “true” magnetic field because my algorithm produces results as if calculating a full sphere around the object in all directions (it's much more efficient than that tho).
~~~
Entire Code for Point Gravity
Here's the entire code for regular Mario Galaxy Style Point Gravity using a Kactor.
If you are using a pawn you will need to set the pawn physics to PHYS_RigidBody while using the point gravity, and manually play movement animations.
Code:
Pawn.SetPhysics(PHYS_RigidBody);
//play move animations directly in code
I comment out any code that I included more for your own information and use, code that is useful but not used in this sample below.
Code:
//could put this code in a pawn class or Kactor class class KactorPawnOBJ extends KactorSpawnable placeable; //var float playerOBJRadius; //var float playerOBJHeight; var Actor mostRecentTouchedActor; //~~~ Magnet Mode ~~~ var bool magnetModeOn; var Actor gravitySource; var float gravityPower; var float magnetModeDisengageDistance; //Delta Time Adjustment var float deltaTimePhyiscsBoostMultiplier; Simulated Event PostBeginPlay() { super.postbeginplay(); //set gameinfo, this is my game info //enables me to access the instance of this class //after game starts CustomGameInfo(WorldInfo.Game).playerOBJ = Self; //call the below again any time you change/update size of this Object //retrieves bounding info and puts in these two vars //Self.GetBoundingCylinder(playerOBJHeight, playerOBJRadius); //Ensure RigidBodyCollision() gets called //assumes relevant collision impact velocities will be //way bigger than 1 (relative velocity) Self.StaticMeshComponent.ScriptRigidBodyCollisionThreshold = 1; } function clearTimers() { cleartimer('magnetAcquireTimer'); mostRecentTouchedActor = none; } function magnetAcquireTimer() { if (gravitySource != mostRecentTouchedActor) { mostRecentTouchedActor = none; } } event RigidBodyCollision ( PrimitiveComponent HitComponent, PrimitiveComponent OtherComponent, const out CollisionImpactData RigidCollisionData, int ContactIndex) { //local RigidBodyContactInfo r; //set most recently touched actor; mostRecentTouchedActor = OtherComponent.owner; //~~~ //player only has a 0.3 second window to //activate magnet mode with a button press //after physically connecting with object surface //I use this for interesting gameplay purposes //SetTimer(0.3, false, 'magnetAcquireTimer'); //for your own use and fyi //~~~~~~ Get RB Collision Data ~~~~~~ //r = RigidCollisionData.ContactInfos[0]; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //r.contactPosition = point of contact //r.contactNormal = the normal to surface at point of contact } //================= //MAGNET MODE //===================== function setMagnetMode(bool b) { if (mostRecentTouchedActor == none) { magnetModeOn = false; return; } magnetModeOn = b; if (b) { //disable regular gravity ( Self.StaticMeshComponent.GetRootBodyInstance() ).CustomGravityFactor = 0; gravitySource = mostRecentTouchedActor; } //turn magnet mode off else { //enable regular gravity ( Self.StaticMeshComponent.GetRootBodyInstance() ).CustomGravityFactor = 1; mostRecentTouchedActor = none; gravitySource = none; } } function applyMagnetModeGravity() { if (!magnetModeOn) return; /* //if you want object to disengage //after certain distance //using point gravity //if ball too far now, turn off magnet mode if (vsize(gravitySource.Location - Location) > magnetModeDisengageDistance) { setMagnetMode(false); return; }*/ //apply custom gravity StaticMeshComponent.AddImpulse( gravityPower * deltaTimePhyiscsBoostMultiplier * Normal(gravitySource.Location - Location ) ); } //runs every moment of game time //be careful how much stuff you put here Simulated Function tick( Float DeltaTime ) { super.tick(deltatime); //====================== //MAGNET MODE //====================== applyMagnetModeGravity(); //my computer averages 0.016 without fraps running //use this to account for time dilation. deltaTimePhyiscsBoostMultiplier = deltatime * 40; } defaultproperties { //I set the mesh in the UDK //you could put a static mesh here //if you liked //to use as is: //go into UDK editor and drag in //this class, and then press f4 //do a filter for static mesh //and enter a static mesh of your //choice into the appropriate fields //this class contains all the right code //to add to your pawn/kactor class as is. //except the settng of PHYS_RigidBody bCollideActors = true gravityPower = 570 magnetModeDisengageDistance = 700; }
What is a Surface Normal?
The normals you see everywhere in this code are the surface normals, lines that are perpendicular to the surface at the point where the trace hits the surface.
The trace is like a projectile, and where the trace hits a surface, a line perpendicular to the surface at that point is the surface normal.
~~~
Point Gravity and the Vector Normal() Function
By contrast, the vector function Normal() reduces any vector to a unit vector (vect(1,1,1)).
This is the core of how Point Gravity is able to work!
Code:
StaticMeshComponent.AddImpulse( gravityPower * deltaTimePhyiscsBoostMultiplier * Normal(gravitySource.Location - Location ) );
So the above code says
“Add a physics impulse to the player Object of size GravityPower*deltaTimePhyiscsBoostMultiplier IN THE DIRECTION OF the line between the player Object and the gravity Source.”
In other words:
“move the player object toward the gravitySource by this exact amount of GravityPower*deltaTimePhyiscsBoostMultiplier.”
If I did not normalize the vector, the actual distance between the player obj and the gravitySource would be included in the total impulse force, and this distance is variable and could be quite large.
But without that vector I do not have the exact line/angle between the player obj and the gravitySource.
So normal() is your friend!
Normal() enables point gravity to be accurate and consisent no matter where exactly the player OBJ is relative to the center of the chosen gravitySource.
~~~
Entire Code For My True Magnetic Field System
Relative Point Gravity Code
And here it is!

Code:
var Actor mostRecentTouchedActor; //~~~ Magnet Mode ~~~ var bool magnetModeOn; var Actor gravitySource; var float gravityPower; var float magneticFieldSize; var float magnetCurDistToSurface; var float magnetCloseEnoughRange; var vector magnetSurfaceNormal; //Delta Time Adjustment var float deltaTimePhyiscsBoostMultiplier; Simulated Event PostBeginPlay() { super.postbeginplay(); //set gameinfo, this is my game info //enables me to access the instance of this class //after game starts CustomGameInfo(WorldInfo.Game).playerOBJ = Self; //see comment on this line in code above //it's important Self.StaticMeshComponent.ScriptRigidBodyCollisionThreshold = 1; } //======= //EVENTS //========= function clearTimers() { cleartimer('magnetAcquireTimer'); mostRecentTouchedActor = none; } function magnetAcquireTimer() { if (gravitySource != mostRecentTouchedActor) { mostRecentTouchedActor = none; } } event RigidBodyCollision ( PrimitiveComponent HitComponent, PrimitiveComponent OtherComponent, const out CollisionImpactData RigidCollisionData, int ContactIndex) { //set most recently touched actor; mostRecentTouchedActor = OtherComponent.owner; //see comment in code sample above //SetTimer(0.3, false, 'magnetAcquireTimer'); } //================= //MAGNET MODE //===================== function setMagnetMode(bool b) { if (mostRecentTouchedActor == none) { magnetModeOn = false; return; } magnetModeOn = b; if (b) { //disable regular gravity ( Self.StaticMeshComponent.GetRootBodyInstance() ).CustomGravityFactor = 0; gravitySource = mostRecentTouchedActor; } //turn magnet mode off else { //enable regular gravity ( Self.StaticMeshComponent.GetRootBodyInstance() ).CustomGravityFactor = 1; mostRecentTouchedActor = none; gravitySource = none; } } function acquireClosestSurface() { local vector hitLoc; local actor a; local float minDistToSurface; local vector bestNormal; local int v; //initialize minimum minDistToSurface = 100000000; //do a single trace from ball to center //of gravitySource for point gravity case a = trace( hitLoc, magnetSurfaceNormal, Location + Normal(gravitySource.Location - Location ) * magneticFieldSize, Location); //traced gravity source? if (a == gravitySource){ //dist to traced surface magnetCurDistToSurface = vsize(hitLoc - Location); //Close Enough Range if (magnetCurDistToSurface <= magnetCloseEnoughRange) { //done happily, //magnetSurfaceNormal & magnetCurDistToSurface are global return; } //not yet sure of closest/close enough trace else { minDistToSurface= magnetCurDistToSurface; bestNormal = magnetSurfaceNormal; } } //Do a sphere of traces from ball to environment //*** simulating the ball having a gravitational field *** for (v = 0; v < 361; v++ ) { a = trace( hitLoc, magnetSurfaceNormal, Location + VRand() * magneticFieldSize, Location); //skip if traced nothing or other object besides //original gravitySource if (a != gravitySource) continue; //dist to traced surface magnetCurDistToSurface = vsize(hitLoc - Location); //testing //DrawDebugLine(Location, Location + VRand() * magneticFieldSize, 0, 0, 0); //`log("dist to surface:"@magnetCurDistToSurface); //Close Enough Range if (magnetCurDistToSurface <= magnetCloseEnoughRange) { //done happily, //magnetSurfaceNormal & magnetCurDistToSurface are global //and were filled during cur loop iteration return; } //not Close Enough //check what cur min is else if(magnetCurDistToSurface < minDistToSurface ){ minDistToSurface= magnetCurDistToSurface; bestNormal = magnetSurfaceNormal; } } //end for loop //output, global variables magnetSurfaceNormal = bestNormal; magnetCurDistToSurface = minDistToSurface; } function applyMagnetModeGravity() { if (!magnetModeOn) return; if (gravitySource == none) return; //re-initialize to 0 in case no traces hit //result is that no gravity is applied //if no trace hit gravitySource. magnetSurfaceNormal = vect(0, 0, 0); acquireClosestSurface(); //no longer in magnetic field range //turn off magnet mode if( magnetCurDistToSurface > magneticFieldSize ){ setMagnetMode(false); return; } //apply relative point gravity StaticMeshComponent.AddImpulse( gravityPower * deltaTimePhyiscsBoostMultiplier * -1 * magnetSurfaceNormal ); } Simulated Function tick( Float DeltaTime ) { super.tick(deltatime); //====================== //MAGNET MODE //====================== applyMagnetModeGravity(); //averages 0.016 without fraps deltaTimePhyiscsBoostMultiplier = deltatime * 40; } defaultproperties { bCollideActors = true gravityPower = 570 magneticFieldSize = 700 magnetCloseEnoughRange = 160 deltaTimePhyiscsBoostMultiplier = 2 }
This is the code that generates the True Magnetic Field around the player Object.
Code:
function acquireClosestSurface() { local vector hitLoc; local actor a; local float minDistToSurface; local vector bestNormal; local int v; //initialize minimum minDistToSurface = 100000000; //do a single trace from ball to center //of gravitySource for point gravity case a = trace( hitLoc, magnetSurfaceNormal, Location + Normal(gravitySource.Location - Location ) * magneticFieldSize, Location); //traced gravity source? if (a == gravitySource){ //dist to traced surface magnetCurDistToSurface = vsize(hitLoc - Location); //Close Enough Range if (magnetCurDistToSurface <= magnetCloseEnoughRange) { //done happily, //magnetSurfaceNormal & magnetCurDistToSurface are global return; } //not yet sure of closest/close enough trace else { minDistToSurface= magnetCurDistToSurface; bestNormal = magnetSurfaceNormal; } } //Do a sphere of traces from ball to environment //*** simulating the ball having a gravitational field *** for (v = 0; v < 361; v++ ) { a = trace( hitLoc, magnetSurfaceNormal, Location + VRand() * magneticFieldSize, Location); //skip if traced nothing or other object besides //original gravitySource if (a != gravitySource) continue; //dist to traced surface magnetCurDistToSurface = vsize(hitLoc - Location); //testing //DrawDebugLine(Location, Location + VRand() * magneticFieldSize, 0, 0, 0); //`log("dist to surface:"@magnetCurDistToSurface); //Close Enough Range if (magnetCurDistToSurface <= magnetCloseEnoughRange) { //done happily, //magnetSurfaceNormal & magnetCurDistToSurface are global //and were filled during cur loop iteration return; } //not Close Enough //check what cur min is else if(magnetCurDistToSurface < minDistToSurface ){ minDistToSurface= magnetCurDistToSurface; bestNormal = magnetSurfaceNormal; } } //end for loop //output, global variables magnetSurfaceNormal = bestNormal; magnetCurDistToSurface = minDistToSurface; }
Basically this code runs every moment of game time via the tick, and:
1. A single trace is sent to center of current gravitySource object for Point Gravity case
2. 361 traces are sent from the Player Object center in a sphere with radius of magneticFieldSize
3. The loop tries as hard as it can to stop before all 361 traces are run, by finding a single match for a distance to a surface of size MagnetCloseEnoughRange. Loop also will not even run AT ALL if the point gravity case passes within MagnetCloseEnoughRange.
You can see the efficiency case very visually in the video when the traces are being drawn as black lines
If the loop of 361 is forced to complete all the way, the normal for the minimum distance traced / the closest surface is used, to ensure the proper behavior of a magnetically attractive object finding the nearest surface.
4. Once the ideal normal from the surface traces is found, it is applied as the gravitational force, with -1 because we are flipping the normal around so ball moves toward the surface rather than away from it.
Code:
StaticMeshComponent.AddImpulse( gravityPower * deltaTimePhyiscsBoostMultiplier * -1 * magnetSurfaceNormal );
~~~
Summary
With this code I am providing to you you can either implement basic Point Gravity or my True Magnetic Field gravity system!
Have fun!
Rama
PS: if you are worried about performance, you could put the loop of 361 traces into a timer that runs every 0.3 seconds or so.
I recommend to first get the current code working before using the timer to make things efficient.
Comment