I’ve been stuck on this bug for over a week, and I’ve hit a wall. Total mental block on this one!
The problem is with my raycasting, which I’m using for all player collision. Some of the raycasting acts as if it’s lagging behind by a frame consistently in some situations, but not others, and I can’t figure out what I’m doing wrong.
I’ve simplified my game into a little demo to show the bug. Basic movement of arrow keys + space to jump.
http://www.blobvandam.com/unity/WebPlayer.html
If you jump onto the moving platform just above you when you start, you’ll see the platform works just fine there. No issue. Now move over to the right and jump onto the moving block, and it also works just fine, if you’re stationary. Now keep holding right so the player is pushing against the right edge block of the platform.
Note how when the block is moving right, the collision is just fine, but when the block is moving to the left, the player jitters into the wall.
This is a stripped down version of the much more complete game. The collision bug actually affects my gameplay mechanic in more pronounced ways than this, but it all comes down to this same issue of the raycast apparently lagging behind by one frame.
I use script execution order setting to ensure that the player script runs last, after all of the moving platforms have updated. The platforms run in Update, while the player code runs in LateUpdate (although I have tried every single combination of what runs where to no avail).
The strange part is that if I update the platforms after the player movement, the collision works flawlessly, but obviously with the problem that the platforms are now visibly a frame ahead, because they’ve updated after the player has, but before the rendering. But logically I can’t figure out any reason why this works any different to the buggy version (ignoring the visual difference).
Here’s a quick rundown of how my moving platform code works-
When you land on a block, it stores a reference to the parent in player_parent_transform
At the end of the frame, it stores the local offset of the player from the platform in player_parent_offset
At the start of the frame (which occurs after the platforms have moved), it updates the player position by using the local space offset.
So in theory, moving platforms shouldn’t be an issue, because it’s handled and updated before the player collision occurs. In theory…
Well, hopefully that’s enough info to get the ball rolling. Thanks!
static var script : Player_Movement;
private var input_x : float;
private var input_y : float;
@System.NonSerialized
var player_position : Vector3 = Vector3.zero; //current position
@System.NonSerialized
var player_movement : Vector3 = Vector3.zero; //intended movement for the frame
@System.NonSerialized
var player_speed : Vector3 = Vector3.zero; //intended movement speed in units per second
@System.NonSerialized
var player_groundspeed : float = 0; //player's ground speed
@System.NonSerialized
var player_direction : int = 1; //direction player is facing - 0 = left, 1 = right
private var player_maxspeed : float = 12.0; //player's maximum running speed
private var player_airdrag : float = Mathf.Pow(0.97, 60.0); //air drag
private var player_acc : float = 12.0; //player's acceleration to top running speed
private var player_dec : float = 40.0; //player's deceleration when changing direction
private var player_acc_air : float = 12.0; //player's acceleration in the air
private var player_friction : float = 10.0; //slowing down when not pressing any button (lower for ice)
private var player_jumpvelocity : float = 0.0; //player's jump force
private var player_jumpforce : float = 20; //player's maximum jump force
private var player_gravity : float = 40.0; //player's gravity
private var player_terminalvelocity : float = -20.0; //player's maximum downward speed
private var player_jumpreleased : boolean = false; //state check for releasing jump button for short jump
@System.NonSerialized
var player_air : boolean = false; //whether or not player is in the air
class hitinfo
{
var hitdistance : float; //length of raycast until it hit the surface
var hitangle : float; //angle of surface at hit point
var hit : boolean; //whether or not the ray hit anything
var raycast : RaycastHit; //stores the raycast info
}
private var player_hitarray : hitinfo[];
player_hitarray = new hitinfo[10];
@System.NonSerialized
var player_parent_transform : Transform; //stores the transform for the "parent", ie. on a moving platform
private var player_parent_offset : Vector3 = Vector3.zero; //stores the local offset of the player from the platform inbetween frames
private var player_transform : Transform; //the transform for the player object
//__________________________________________________________________________________________________________________
function Start()
{
script = gameObject.GetComponent(Player_Movement) as Player_Movement;
player_transform = transform;
for (var i : int=0 ; i < 10 ; i++ )
{
player_hitarray[i] = new hitinfo();
player_hitarray[i].raycast = new RaycastHit();
}
player_position = transform.position; //sets position var to current model position
}
//__________________________________________________________________________________________________________________
function LateUpdate()
{
//grabs inputs
input_x = Input.GetAxis("Horizontal");
input_y = Input.GetAxis("Vertical");
//updates player position to reflect new position of moving platforms
if (player_parent_transform != null)
{
player_position = player_parent_transform.TransformPoint(player_parent_offset);
}
StandardControls(); //handles keyboard input
NewPosition(); //calculates intended new player position
WallCollision(); //wall collision. duh
RoofCollision(); //collision for the top of the player
if (player_air)
{
AirCollision(); //"foot" collision while in the air
}
else
{
GroundCollision(); //"foot" collision while on the ground
}
//positions player object
player_transform.localPosition = player_position;
//stores offset to account for moving platforms inbetween frames
if (player_parent_transform != null)
{
player_parent_offset = player_parent_transform.InverseTransformPoint(player_position);
}
else
{
player_parent_offset = Vector3.zero;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function GroundCollision() {
var temp_float : float = 0;
var temp_tag : String;
var vector_length : float = (0.9 + 0.35); //second number is maximum step down
var bitmask : int = (1 << 10) + (1 << 11) + (1 << 15) + (1 << 16);
player_hitarray[0].hit = Physics.Raycast(player_position + Vector3(-0.3,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[0].raycast, vector_length, bitmask);
player_hitarray[1].hit = Physics.Raycast(player_position + Vector3(0,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[1].raycast, vector_length, bitmask);
player_hitarray[2].hit = Physics.Raycast(player_position + Vector3(0.3,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[2].raycast, vector_length, bitmask);
//middle ray
if (player_hitarray[1].hit)
{
player_hitarray[1].hitangle = Mathf.Atan2(player_hitarray[1].raycast.normal.x,player_hitarray[1].raycast.normal.y) * Mathf.Rad2Deg;
}
//left ray
if (player_hitarray[0].hit)
{
player_hitarray[0].hitangle = Mathf.Atan2(player_hitarray[0].raycast.normal.x,player_hitarray[0].raycast.normal.y) * Mathf.Rad2Deg;
}
//right ray
if (player_hitarray[2].hit)
{
player_hitarray[2].hitangle = Mathf.Atan2(player_hitarray[2].raycast.normal.x,player_hitarray[2].raycast.normal.y) * Mathf.Rad2Deg;
}
//calculates ground position
//middle raycast
if (player_hitarray[1].hit Mathf.Abs(player_hitarray[1].hitangle) < 50)
{
temp_tag = player_hitarray[1].raycast.transform.tag;
player_position.y = player_hitarray[1].raycast.point.y;
if (temp_tag == "Block")
{
PlatformAttach(1);
}
else
{
PlatformDetach();
}
}
//left raycast
else if (player_hitarray[0].hit Mathf.Abs(player_hitarray[0].hitangle) < 50)
{
temp_tag = player_hitarray[0].raycast.transform.tag;
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[0].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[0].raycast.point.y - temp_float;
if (temp_tag == "Block")
{
PlatformAttach(0);
}
else
{
PlatformDetach();
}
}
//right raycast
else if (player_hitarray[2].hit Mathf.Abs(player_hitarray[2].hitangle) < 50)
{
temp_tag = player_hitarray[2].raycast.transform.tag;
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[2].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[2].raycast.point.y + temp_float;
if (temp_tag == "Block")
{
PlatformAttach(2);
}
else
{
PlatformDetach();
}
}
else
{
PlatformDetach();
player_position.y += player_movement.y;
player_air = true;
player_jumpvelocity = 0;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function AirCollision() {
var temp_float : float = 10.0;
var vector_length : float = Mathf.Clamp((0.9 - player_movement.y), 0.9, 10.0);
var bitmask : int = (1 << 10) + (1 << 11) + (1 << 15) + (1 << 16);
var temp_tag : String;
player_hitarray[0].hit = Physics.Raycast(player_position + Vector3(-0.3,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[0].raycast, 4.0, bitmask);
player_hitarray[1].hit = Physics.Raycast(player_position + Vector3(0,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[1].raycast, 4.0, bitmask);
player_hitarray[2].hit = Physics.Raycast(player_position + Vector3(0.3,0.9,0), Vector3(0.0,-1.0,0.0), player_hitarray[2].raycast, 4.0, bitmask);
//middle
if (player_hitarray[1].hit)
{
player_hitarray[1].hitangle = Mathf.Atan2(player_hitarray[1].raycast.normal.x,player_hitarray[1].raycast.normal.y) * Mathf.Rad2Deg;
player_hitarray[1].hitdistance = player_hitarray[1].raycast.distance;
}
//left
if (player_hitarray[0].hit)
{
player_hitarray[0].hitangle = Mathf.Atan2(player_hitarray[0].raycast.normal.x,player_hitarray[0].raycast.normal.y) * Mathf.Rad2Deg;
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[0].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_hitarray[0].hitdistance = player_hitarray[0].raycast.distance + temp_float;
}
//right
if (player_hitarray[2].hit)
{
player_hitarray[2].hitangle = Mathf.Atan2(player_hitarray[2].raycast.normal.x,player_hitarray[2].raycast.normal.y) * Mathf.Rad2Deg;
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[2].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_hitarray[2].hitdistance = player_hitarray[2].raycast.distance - temp_float;
}
//checks middle raycast
if (player_hitarray[1].hit Mathf.Abs(player_hitarray[1].hitangle) < 50 player_hitarray[1].hitdistance <= vector_length player_jumpvelocity < 19.0)
{
temp_tag = player_hitarray[1].raycast.transform.tag;
if (temp_tag == "Block")
{
player_position.y = player_hitarray[1].raycast.point.y;
player_air = false;
PlatformAttach(1);
return;
}
else
{
PlatformDetach();
player_position.y = player_hitarray[1].raycast.point.y;
player_air = false;
return;
}
}
//checks left raycast
if (player_hitarray[0].hit Mathf.Abs(player_hitarray[0].hitangle) < 50 player_hitarray[0].hitdistance <= vector_length player_jumpvelocity < 19.0)
{
temp_tag = player_hitarray[0].raycast.transform.tag;
if (temp_tag == "Block")
{
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[0].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[0].raycast.point.y - temp_float;
player_air = false;
PlatformAttach(0);
return;
}
else
{
PlatformDetach();
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[0].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[0].raycast.point.y - temp_float;
player_air = false;
return;
}
}
//checks right raycast
if (player_hitarray[2].hit Mathf.Abs(player_hitarray[2].hitangle) < 50 player_hitarray[2].hitdistance <= vector_length player_jumpvelocity < 19.0)
{
temp_tag = player_hitarray[2].raycast.transform.tag;
if (temp_tag == "Block")
{
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[2].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[2].raycast.point.y + temp_float;
player_air = false;
PlatformAttach(2);
return;
}
else
{
PlatformDetach();
temp_float = Mathf.Tan(Mathf.Repeat(player_hitarray[2].hitangle,360.0) * Mathf.Deg2Rad) * 0.3;
player_position.y = player_hitarray[2].raycast.point.y + temp_float;
player_air = false;
return;
}
}
if (player_movement.y < 0)
{
player_position.y += player_movement.y;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function RoofCollision() {
var temp_float : float = 10.0;
var temp_int :int = 0;
var i : int = 0;
var vector_length : float = 0.0;
var temp_tag : String;
var bitmask : int = (1 << 10) + (1 << 15);
vector_length = Mathf.Clamp((0.9 + player_movement.y), 0.9, 10.0);
player_hitarray[1].hit = Physics.Raycast(player_position + Vector3(-0.3, 0.9, 0), Vector3(0.0, 1.0, 0.0), player_hitarray[1].raycast, vector_length, bitmask);
player_hitarray[2].hit = Physics.Raycast(player_position + Vector3(0.0, 0.9, 0), Vector3(0.0, 1.0, 0.0), player_hitarray[2].raycast, vector_length, bitmask);
player_hitarray[3].hit = Physics.Raycast(player_position + Vector3(0.3, 0.9, 0), Vector3(0.0, 1.0, 0.0), player_hitarray[3].raycast, vector_length, bitmask);
for (i = 1 ; i < 4 ; i++ ) {
if (player_hitarray[i].hit) {
player_hitarray[i].hitangle = Vector3.Angle(Vector3(0,1,0), player_hitarray[i].raycast.normal);
if (player_hitarray[i].hitangle > 135.0 player_hitarray[i].hitangle < 225.0) {
if (player_hitarray[i].raycast.distance < temp_float)
{
temp_float = player_hitarray[i].raycast.distance;
temp_int = i;
}
}
}
}
if (temp_int != 0)
{
if (player_jumpvelocity > 0.0)
{
player_jumpvelocity = -1.0; //if you hit your head while jumping upwards, it gives you a very slight downward speed
}
else
{
player_jumpvelocity = -10.0; //if you hit your head while already travelling downward, you're probably being hit by something also travelling downward, so it gives you a faster speed to compensate
}
player_position.y += (temp_float - 0.95);
temp_tag = player_hitarray[temp_int].raycast.transform.tag;
}
else
{
if (player_air player_movement.y > 0) player_position.y += player_movement.y;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function WallCollision() {
var temp_float : float = 1.0;
var temp_int : int = 0;
var temp_tag : String;
var i : int = 0;
var vector_length : float = 0;
var vector_direction : Vector3;
var bitmask : int = (1 << 10) + (1 << 15) + (1 << 16);
if (player_movement.x < 0)
{
vector_direction = Vector3(-1.0,0,0); //casts rays while traveling left
}
else
{
vector_direction = Vector3(1.0,0,0); //casts rays while traveling right
}
vector_length = (0.35 + Mathf.Abs(player_movement.x));
Debug.Log(vector_length);
if (player_air (player_speed.y > 0))
{
player_hitarray[1].hit = Physics.Raycast(player_position + Vector3(0.0, 0.01, 0), vector_direction, player_hitarray[1].raycast, vector_length, bitmask);
}
else
{
player_hitarray[1].hit = Physics.Raycast(player_position + Vector3(0.0, 0.2, 0), vector_direction, player_hitarray[1].raycast, vector_length, bitmask);
}
player_hitarray[2].hit = Physics.Raycast(player_position + Vector3(0.0, 0.45, 0), vector_direction, player_hitarray[2].raycast, vector_length, bitmask);
player_hitarray[3].hit = Physics.Raycast(player_position + Vector3(0.0, 0.9, 0), vector_direction, player_hitarray[3].raycast, vector_length, bitmask);
player_hitarray[4].hit = Physics.Raycast(player_position + Vector3(0.0, 1.35, 0), vector_direction, player_hitarray[4].raycast, vector_length, bitmask);
player_hitarray[5].hit = Physics.Raycast(player_position + Vector3(0.0, 1.75, 0), vector_direction, player_hitarray[5].raycast, vector_length, bitmask);
temp_float = 1.0;
temp_int = 0;
for (i = 1 ; i < 6 ; i++)
{
if (player_hitarray[i].hit)
{
player_hitarray[i].hitangle = Vector3.Angle(Vector3(0,1,0), player_hitarray[i].raycast.normal);
if (player_hitarray[i].hitangle > 50.0 player_hitarray[i].hitangle <= 135.0)
{
if (player_hitarray[i].raycast.distance < temp_float)
{
temp_float = player_hitarray[i].raycast.distance;
temp_int = i;
}
}
}
}
if (temp_int != 0)
{
temp_tag = player_hitarray[temp_int].raycast.transform.tag;
player_groundspeed = 0.0;
player_position.x = player_hitarray[temp_int].raycast.point.x - (0.35 * vector_direction.x);
}
else
{
player_position.x += player_movement.x;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function NewPosition()
{
if (!player_air) //calculates player movement while player is on the ground
{
player_speed.x = player_groundspeed;
player_speed.y = 0;
player_movement = player_speed * Time.deltaTime; //converts player speed to movement for this frame
}
else //calculates new player position while in the air
{
PlatformDetach();
player_jumpvelocity -= (player_gravity * Time.deltaTime);
if (player_jumpvelocity < player_terminalvelocity) { player_jumpvelocity = player_terminalvelocity; }
player_speed.x = player_groundspeed;
player_speed.y = player_jumpvelocity;
player_movement = player_speed * Time.deltaTime;
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function StandardControls()
{
//REGULAR CONTROLS
//ground controls
if (!player_air)
{
player_jumpvelocity = 0;
//JUMP BUTTON
if (Input.GetButtonDown("Jump"))
{
player_jumpreleased = false; //resets jump button release (for short jumps)
player_jumpvelocity = player_jumpforce; //sets initial jump velocity
player_air = true; //sets player flag to in the air
}
//PLAYER PRESSING RIGHT
if (input_x > 0)
{
if (player_groundspeed < 0)
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_dec * Time.deltaTime));
}
else
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_acc * Time.deltaTime));
player_direction = 1;
}
}
//PLAYER PRESSING LEFT
else if (input_x < 0)
{
if (player_groundspeed > 0)
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_dec * Time.deltaTime));
}
else
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_acc * Time.deltaTime));
player_direction = 0;
}
}
//PLAYER NOT PRESSING LEFT OR RIGHT
else
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, 0, (player_friction * Time.deltaTime));
}
player_groundspeed = Mathf.Clamp(player_groundspeed, -player_maxspeed, player_maxspeed);
}
else
{ //air controls
if (input_x > 0)
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_acc_air * Time.deltaTime));
player_direction = 1;
}
else if (input_x < 0)
{
player_groundspeed = Mathf.MoveTowards(player_groundspeed, player_maxspeed * input_x, (player_acc_air * Time.deltaTime));
player_direction = 0;
}
else
{
player_groundspeed *= Mathf.Pow(player_airdrag, Time.deltaTime);
}
player_groundspeed = Mathf.Clamp(player_groundspeed, -player_maxspeed, player_maxspeed);
if (!Input.GetButton("Jump")) //releasing jump button for short jump
{
if (player_jumpvelocity > 10.0 !player_jumpreleased)
{
player_jumpvelocity = 10.0;
player_jumpreleased = true;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function PlatformAttach(index : int)
{
//sets new "parent", for when player is on moving platform
player_parent_transform = player_hitarray[index].raycast.transform;
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
function PlatformDetach()
{
//sets player back to world space
player_parent_transform = null;
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------