So I am trying to make a platformer for a promotional project. This platforming game is easy. Only static obstacles, some variations of platform levels such that the sprite can jump to the next platform and jump down to the previous platform (like the NES Contra). The games suppose to behave like Mario. The platforming/environment is not complicated. You can jump up and down to platform, jump across obstacle, jump to next elevation etc. Please do take note, there is a degree of a proper platforming scheme (similar to Mario). Its just that the game is very basic.
The problem is, if my sprite goes (1,1) direction (jumping and then falling forward), my sprite snaps to one of the corners of the obstacle's hitbox. I surmised probably it is because I set some collision detections on four vertices on the hitbox of my sprite and comparing this against other hitboxes of perceived platforms (which btw, are on different size), I made it similar with 'raycasting'. It appears my sprite shows up on a position overlapping the bounding box of the platform when code proxied and correct the position of the sprite. Causing it to jitter on such corner. Sometimes it even teleports on the other side of the obstacle.
My code is a bit crude. I wanted to make it simple without using too much physics (like Box2D). As I believe this game is a simple as it gets, I felt like simpler solution suits this project better. But the fact I encountered problem like this make me feel regrettable doing this.
Here is my code so far:
package nick.runnypants.entities;
import kit.Entity;
import kit.creator.SceneSprite;
import kit.input.KeyboardEvent;
import kit.creator.CreatorObject;
import kit.display.Sprite;
import kit.input.Key;
import kit.physics.PhysicsBody;
import kit.System;
import kit.physics.Box2D;
import box2d.dynamics.BodyType;
import box2d.common.math.Vec2;
import nick.runnypants.entities.Collider;
import nick.runnypants.utils.MathUtils;
class Runner extends BoundedObject
{
public var moveSpeed : Float = 1550;
public var jumpHeight : Float = 3;
public var timeToJumpApex : Float = 0.35;
public var showDebugLines : Bool = false;
private var position : Vec2;
private var velocity : Vec2;
private var acceleration : Vec2;
private var direction : Vec2;
private var sprite : Sprite;
private var sceneSprite : SceneSprite;
private var jumpSpeed : Float;
private var gravity : Float;
private var isGrounded : Bool;
private var keyPresses : Map<Key, Int>;
private var perceivedPlatforms : Array<BoundedObject>;
private var thisCollider : Collider;
private var collisionInfo : CollisionInfo;
public override function onStart() : Void
{
perceivedPlatforms = new Array<BoundedObject>();
sceneSprite = owner.getSceneSpriteFromParents();
sprite = owner.getSprite();
sprite.centerAnchor();
initPhysics();
initControls();
perceivePlatforms();
}
private function initPhysics()
{
collisionInfo = new CollisionInfo();
setBoundOffset(new Vec2(sprite.getNaturalWidth() / 2.0, sprite.getNaturalHeight() / 2.0));
thisCollider = getCollider();
position = new Vec2(sprite.x._, sprite.y._);
acceleration = new Vec2(0, 0);
velocity = new Vec2(0, 0);
direction = new Vec2(0, 0);
gravity = (2 * jumpHeight) / (timeToJumpApex * timeToJumpApex);
jumpSpeed = gravity * timeToJumpApex;
}
private function initControls()
{
keyPresses = new Map<Key, Int>();
System.keyboard.down.connect(onKeyDown);
System.keyboard.up.connect(onKeyUp);
}
public override function onUpdate(dt : Float) : Void
{
direction = new Vec2(0, 0);
collisionInfo.reset();
updateInput();
updateBounds();
updateColliderBounds();
velocity.x = direction.x * moveSpeed * dt;
velocity.y = direction.y * moveSpeed * dt;
// velocity.y += gravity * dt;
if (collisionInfo.collideLeft || collisionInfo.collideRight)
{
velocity.x = 0;
direction.x = 0;
}
if (collisionInfo.collideBottom)
{
velocity.y = 0;
direction.y = 0;
}
position.x += MathUtils.lerp(velocity.x, moveSpeed * direction.x, dt);
position.y += MathUtils.lerp(velocity.y, moveSpeed * direction.y, dt);
resolveCollisions(velocity);
sprite.x._ = position.x;
sprite.y._ = position.y;
}
private function resolveCollisions(velocity : Vec2)
{
for (platform in perceivedPlatforms)
{
var thatCollider = platform.getCollider();
checkVerticalCollision(velocity, thatCollider);
checkHorizontalCollision(velocity, thatCollider);
}
}
private function checkVerticalCollision(velocity : Vec2, thatCollider : Collider)
{
if (MathUtils.sign(velocity.y) == 1
&& (MathUtils.isBetweenHorizontal(thisCollider.bottomLeft.x, thatCollider.topLeft, thatCollider.topRight)
|| MathUtils.isBetweenHorizontal(thisCollider.bottomRight.x, thatCollider.topLeft, thatCollider.topRight)))
{
var dist = thatCollider.topLeft.y - thisCollider.bottomLeft.y;
if (dist <= 0 && thisCollider.topLeft.y < thatCollider.bottomLeft.y)
{
position.y = thatCollider.topLeft.y - (sprite.getNaturalHeight() / 2.0);
collisionInfo.collideBottom = true;
}
}
else if (MathUtils.sign(velocity.y) == -1
&& (MathUtils.isBetweenHorizontal(thisCollider.topLeft.x, thatCollider.bottomLeft, thatCollider.bottomRight)
|| MathUtils.isBetweenHorizontal(thisCollider.topRight.x, thatCollider.bottomLeft, thatCollider.bottomLeft)))
{
var dist = thisCollider.topLeft.y - thatCollider.bottomLeft.y;
if (dist <= 0 && thisCollider.topLeft.y > thatCollider.topLeft.y)
{
position.y = thatCollider.bottomLeft.y + (sprite.getNaturalHeight() / 2.0);
collisionInfo.collideBottom = true;
}
}
}
private function checkHorizontalCollision(velocity : Vec2, thatCollider : Collider)
{
if (MathUtils.sign(velocity.x) == 1
&& (MathUtils.isBetweenVertical(thisCollider.bottomRight.y, thatCollider.topLeft, thatCollider.bottomLeft)
|| MathUtils.isBetweenVertical(thisCollider.topRight.y, thatCollider.topLeft, thatCollider.bottomLeft)))
{
var dist = thatCollider.topLeft.x - thisCollider.bottomRight.x;
if (dist <= 0 && thisCollider.bottomRight.x < thatCollider.bottomRight.x)
{
position.x = thatCollider.topLeft.x - (sprite.getNaturalWidth() / 2.0);
collisionInfo.collideRight = true;
}
}
else if (MathUtils.sign(velocity.x) == -1
&& (MathUtils.isBetweenVertical(thisCollider.bottomLeft.y, thatCollider.topRight, thatCollider.bottomRight)
|| MathUtils.isBetweenVertical(thisCollider.topLeft.y, thatCollider.topRight, thatCollider.bottomRight)))
{
var dist = thisCollider.topLeft.x - thatCollider.bottomRight.x;
if (dist <= 0 && thisCollider.bottomRight.x > thatCollider.bottomLeft.x)
{
position.x = thatCollider.bottomRight.x + (sprite.getNaturalWidth() / 2.0);
collisionInfo.collideLeft = true;
}
}
}
private function perceivePlatforms()
{
var platforms : Array<Entity> = sceneSprite.getObjectsByType(BoundedObject);
for (e in platforms)
perceivedPlatforms.insert(0, e.get(BoundedObject));
}
private function updateInput() : Void
{
if (keyPresses.get(Key.Left) != null || keyPresses.get(Key.A) != null)
direction.x = -1;
if (keyPresses.get(Key.Right) != null || keyPresses.get(Key.D) != null)
direction.x = 1;
if (keyPresses.get(Key.Up) != null || keyPresses.get(Key.W) != null)
direction.y = -1;
if (keyPresses.get(Key.Down) != null || keyPresses.get(Key.S) != null)
direction.y = 1;
//if (keyPresses.get(Key.Space) != null)
// jump();
}
public function jump() : Void
{
velocity.y = -jumpSpeed;
isGrounded = false;
trace("**********JUMP*************");
}
public function onKeyDown(event : KeyboardEvent)
{
keyPresses.set(event.key, event.id);
}
public function onKeyUp(event : KeyboardEvent)
{
keyPresses.remove(event.key);
}
}
I disabled jumping/platforming mechanics and move the sprite on all directions. As you can see, if I move diagonally towards a particular obstacle, my sprite seems to collide a bit with platform's corner, and I had my code to correct its position. This doesn't look nice because, sometimes, the collision doesn't make sense and it looks jittery. Collision works well on a very strict movement, like coming from the left, right, top, bottom, but not combinations of those as my sprite teleports on the other side of the obstacle.
I've been searching through the web and spent countless hour figuring this out and I can't seem to find any useful article. Though I came to know 'bullet through paper problem', 'SAT', 'Sweep'
etc. I am not totally sure if this is the solution to my problem, hence this post.
This is using 2dKit + Haxe language.
I felt very dumb and I am really stuck. Any help will be greatly appreciated.
↧