I have been trying to use Unity’s physics engine for a platforming game for some time. Issues from going through corners or not leaving/exiting collisions when the player is falling off a corner of a platform are causes numerous issues. It seems everything has to be rewritten from using the physics engine to not using them. I will be online searching at the moment but thought to see if anyone can lead me to some helpful advice they have found online for this sort of thing. I have seen Unity’s platform video tutorial numerous times so something different please. Any suggestions for learning how to code jumping, and precise well timed collision detection for pixel perfect platforming? Also I only know Javascript at the moment so please only Javascript examples. Thank you.
Hi again!
So I am not going to attempt a pixel collision system because I can’t be bothered, however the following collision code I believe will solve the problems you are having.
I’m not sure of the efficiency of this system, as it uses 4-6 raycasts per frame to calculate collisions, however as long as you only have one of these character controllers, 8 raycasts should be well within the capablility of all platforms. It still uses parts of the Unity physics, collisions are only detected with an object containing a 2D collider (your character does not need a 2D collider, but any floors/obstacles do).
Basically it raycasts a short distance from a few positions in a direction given, and returns where it will need to stop its movement before. So for detecting collisions above itself, it sends 2 raycasts from just inside the top two corners upwards, and returns the minimum y positions of the hits e.g. the lowest part of the ceiling. This is done similary for down, left and right.
The with your movement constraints found, you apply your movement and then clamp the positions!
You can download a test .exe HERE (I tried to make a web player but I only have either an old unity or a beta unity and neither seem to work with the current unity webplayer download ^^)
Your character will need to be a sprite for this to work, and 2D colliders attached to your obstacles/ground tiles, if you want to ignore certain colliders there is the option to pass a tag in the Raycast call so just tag everything you want to collide with, with ‘obstacle’ for example, and change the raycast calls to match that.
CODE C#:
SpriteRenderer spriteRenderer;
Sprite sprite;
Transform this_transform;
float gridSize;
float offset;
Vector3 xOffset;
Vector3 yOffset;
float floorHeight = 0;
float ceilHeight = 0;
float rightConstraint = 0;
float leftConstraint = 0;
Vector3 pos;
Vector3 startPos;
bool isMoving = false;
bool isStopping = false;
bool isJumping = true;
float jumpVelocity = 0;
float moveDir;
public float jumpStrength = 10f;
public float moveSpeed = 5f;
public float gravity = 9.8f;
public bool airControl = false;
void Start () {
spriteRenderer = GetComponent<SpriteRenderer>();
sprite = spriteRenderer.sprite;
gridSize = sprite.bounds.size.x;
offset = gridSize/2f;
xOffset = new Vector3(offset*0.9f, 0, 0);
yOffset = new Vector3(0, offset*0.9f, 0);
this_transform = transform;
startPos = this_transform.position;
ceilHeight = Mathf.Min(Raycast(Vector2.up, pos+xOffset).y, Raycast(Vector2.up, pos-xOffset).y);
floorHeight = Mathf.Max(Raycast(-Vector2.up, pos+xOffset).y, Raycast(-Vector2.up, pos-xOffset).y);
rightConstraint = Mathf.Min(Raycast(Vector2.right, pos+yOffset).x, Raycast(Vector2.right, pos-yOffset).x);
leftConstraint = Mathf.Max(Raycast(-Vector2.right, pos+yOffset).x, Raycast(-Vector2.right, pos-yOffset).x);
}
void Update () {
pos = this_transform.position;
//Find Collisions
if(isJumping){
ceilHeight = Mathf.Min(Raycast(Vector2.up, pos+xOffset).y, Raycast(Vector2.up, pos-xOffset).y);
}
floorHeight = Mathf.Max(Raycast(-Vector2.up, pos+xOffset).y, Raycast(-Vector2.up, pos-xOffset).y);
if(moveDir > 0){
rightConstraint = Mathf.Min(Raycast(Vector2.right, pos+yOffset).x, Raycast(Vector2.right, pos-yOffset).x);
}else{
leftConstraint = Mathf.Max(Raycast(-Vector2.right, pos+yOffset).x, Raycast(-Vector2.right, pos-yOffset).x);
}
if(floorHeight < this_transform.position.y-offset){
isJumping = true;
}
//Read Inputs
if(!isJumping || airControl){
if(Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow)){
isMoving = true;
moveDir = moveSpeed;
}else if(Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow)){
isMoving = true;
moveDir = -moveSpeed;
}
if(isMoving){
if(Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow)){
isStopping = true;
}
}
}
if(!isJumping){
if(Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow)){
isJumping = true;
jumpVelocity = jumpStrength;
}
}
//Apply Movement
if(isJumping){
pos.y += jumpVelocity*Time.deltaTime;
jumpVelocity -= gravity*Time.deltaTime;
}else{
jumpVelocity = 0;
}
if(isMoving){
if(isStopping){
float temp = ((pos.x-startPos.x)/gridSize);
if(Mathf.Sign(moveDir) > 0){
temp = (Mathf.CeilToInt(temp)*gridSize)+startPos.x;
}else{
temp = (Mathf.FloorToInt(temp)*gridSize)+startPos.x;
}
pos.x = Mathf.MoveTowards(pos.x, temp, moveSpeed*Time.deltaTime);
if(Mathf.Approximately(pos.x,temp)){
pos.x = temp;
isMoving = false;
isStopping = false;
}
}else{
pos.x += moveDir*Time.deltaTime;
}
}
//Clamp to Constraints
if(pos.y < floorHeight+offset){
pos.y = floorHeight+offset;
isJumping = false;
}else if(pos.y > ceilHeight-offset){
pos.y = ceilHeight-offset;
jumpVelocity = 0;
}
if(pos.x > rightConstraint-offset){
pos.x = rightConstraint-offset;
isMoving = false;
isStopping = false;
}else if(pos.x < leftConstraint+offset){
pos.x = leftConstraint+offset;
isMoving = false;
isStopping = false;
}
//Set Position
this_transform.position = pos;
}
Vector3 Raycast(Vector3 dir){
RaycastHit2D hit = Physics2D.Raycast(this_transform.position, dir, gridSize*2);
if(hit.collider != null){
return hit.point;
}
return this_transform.position + dir*1000;
}
Vector3 Raycast(Vector3 dir, Vector3 position){
RaycastHit2D hit = Physics2D.Raycast(position, dir, gridSize*2);
if(hit.collider != null){
return hit.point;
}
return position + dir*1000;
}
Vector3 Raycast(Vector3 dir, Vector3 position, string tag){
RaycastHit2D hit = Physics2D.Raycast(position, dir, gridSize*2);
if(hit.collider != null && hit.collider.tag == tag){
return hit.point;
}
return position + dir*1000;
}
Unityscript:
private var spriteRenderer : SpriteRenderer;
private var sprite : Sprite;
private var this_transform : Transform;
private var gridSize : float;
private var offset : float;
private var xOffset : Vector3;
private var yOffset : Vector3;
private var floorHeight : float = 0;
private var ceilHeight : float = 0;
private var rightConstraint : float = 0;
private var leftConstraint : float = 0;
private var pos : Vector3;
private var startPos : Vector3;
private var isMoving : boolean = false;
private var isStopping : boolean = false;
private var isJumping : boolean = true;
private var jumpVelocity : float = 0;
private var moveDir : float;
var jumpStrength : float = 10f;
var moveSpeed : float = 5f;
var gravity : float = 9.8f;
var airControl : boolean = false;
function Start () {
spriteRenderer = GetComponent(SpriteRenderer);
sprite = spriteRenderer.sprite;
gridSize = sprite.bounds.size.x;
offset = gridSize/2f;
xOffset = new Vector3(offset*0.9f, 0, 0);
yOffset = new Vector3(0, offset*0.9f, 0);
this_transform = transform;
startPos = this_transform.position;
ceilHeight = Mathf.Min(Raycast(Vector2.up, pos+xOffset).y, Raycast(Vector2.up, pos-xOffset).y);
floorHeight = Mathf.Max(Raycast(-Vector2.up, pos+xOffset).y, Raycast(-Vector2.up, pos-xOffset).y);
rightConstraint = Mathf.Min(Raycast(Vector2.right, pos+yOffset).x, Raycast(Vector2.right, pos-yOffset).x);
leftConstraint = Mathf.Max(Raycast(-Vector2.right, pos+yOffset).x, Raycast(-Vector2.right, pos-yOffset).x);
}
function Update () {
pos = this_transform.position;
//Find Collisions
if(isJumping){
ceilHeight = Mathf.Min(Raycast(Vector2.up, pos+xOffset).y, Raycast(Vector2.up, pos-xOffset).y);
}
floorHeight = Mathf.Max(Raycast(-Vector2.up, pos+xOffset).y, Raycast(-Vector2.up, pos-xOffset).y);
if(moveDir > 0){
rightConstraint = Mathf.Min(Raycast(Vector2.right, pos+yOffset).x, Raycast(Vector2.right, pos-yOffset).x);
}else{
leftConstraint = Mathf.Max(Raycast(-Vector2.right, pos+yOffset).x, Raycast(-Vector2.right, pos-yOffset).x);
}
if(floorHeight < this_transform.position.y-offset){
isJumping = true;
}
//Read Inputs
if(!isJumping || airControl){
if(Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow)){
isMoving = true;
moveDir = moveSpeed;
}else if(Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow)){
isMoving = true;
moveDir = -moveSpeed;
}
if(isMoving){
if(Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow)){
isStopping = true;
}
}
}
if(!isJumping){
if(Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow)){
isJumping = true;
jumpVelocity = jumpStrength;
}
}
//Apply Movement
if(isJumping){
pos.y += jumpVelocity*Time.deltaTime;
jumpVelocity -= gravity*Time.deltaTime;
}else{
jumpVelocity = 0;
}
if(isMoving){
if(isStopping){
var temp : float = ((pos.x-startPos.x)/gridSize);
if(Mathf.Sign(moveDir) > 0){
temp = (Mathf.CeilToInt(temp)*gridSize)+startPos.x;
}else{
temp = (Mathf.FloorToInt(temp)*gridSize)+startPos.x;
}
pos.x = Mathf.MoveTowards(pos.x, temp, moveSpeed*Time.deltaTime);
if(Mathf.Approximately(pos.x,temp)){
pos.x = temp;
isMoving = false;
isStopping = false;
}
}else{
pos.x += moveDir*Time.deltaTime;
}
}
//Clamp to Constraints
if(pos.y < floorHeight+offset){
pos.y = floorHeight+offset;
isJumping = false;
}else if(pos.y > ceilHeight-offset){
pos.y = ceilHeight-offset;
jumpVelocity = 0;
}
if(pos.x > rightConstraint-offset){
pos.x = rightConstraint-offset;
isMoving = false;
isStopping = false;
}else if(pos.x < leftConstraint+offset){
pos.x = leftConstraint+offset;
isMoving = false;
isStopping = false;
}
//Set Position
this_transform.position = pos;
}
function Raycast(dir : Vector3, position : Vector3) : Vector3{
var hit : RaycastHit2D = Physics2D.Raycast(position, dir, gridSize*2);
if(hit.collider != null){
return hit.point;
}
return position + dir*1000;
}
function Raycast(dir : Vector3, position : Vector3, tag : String) : Vector3{
var hit : RaycastHit2D = Physics2D.Raycast(position, dir, gridSize*2);
if(hit.collider != null && hit.collider.tag == tag){
return hit.point;
}
return position + dir*1000;
}
Hope that helps!
Scribe