XNA 2D Platformer Collision and Gravity - xna

I know this question might get asked a lot and for that I am sorry. But I have had trouble with collisions in my game for a while and I would like some help.
First off, the game is a 2D Platformer. Each solid is put in a list. I have this code for collision detection which works pretty good for me:
if (player.rectangle.Intersects(rect))
{
player1Collision = true;
colSolid = solid;
colRectangle = rect;
}
if (player1Collision)
{
Vector2 pos = player.position;
Vector2 pLeft = new Vector2(player.BoundingBox.Left, 0);
Vector2 pRight = new Vector2(player.BoundingBox.Right, 0);
Vector2 pTop = new Vector2(0, player.BoundingBox.Top);
Vector2 pBottom = new Vector2(0, player.BoundingBox.Bottom);
Vector2 sLeft = new Vector2(colSolid.BoundingBox.Left, 0);
Vector2 sRight = new Vector2(colSolid.BoundingBox.Right, 0);
Vector2 sTop = new Vector2(0, colSolid.BoundingBox.Top);
Vector2 sBottom = new Vector2(0, colSolid.BoundingBox.Bottom);
if (player.rectangle.Intersects(colRectangle))
{
if (player.velocity.X > 0 && Vector2.Distance(pRight, sLeft) < player.texture.Width / 2)//left
{
player.velocity.X = 0f;
pos.X = colSolid.BoundingBox.Left - player.BoundingBox.Width;
}
else if (player.velocity.X < 0 && Vector2.Distance(pLeft, sRight) < player.texture.Width / 2)//right
{
player.velocity.X = 0f;
pos.X = colSolid.BoundingBox.Right;
}
if (player.velocity.Y > 0 && Vector2.Distance(pBottom, sTop) < player.texture.Height/ 2) //top
{
player.velocity.Y = 0f;
player.gravityOn = false;
pos.Y = colSolid.BoundingBox.Top - player.BoundingBox.Height;
}
else if (player.velocity.Y < 0 && Vector2.Distance(pTop, sBottom) < player.texture.Height / 2)//bottom
{
player.velocity.Y = 0f;
pos.Y = colSolid.BoundingBox.Bottom;
}
player.position = pos;
}
else
{
player.gravitySpeed = 0.15f;
player.gravityOn = true;
}
}
However the problem is that if the player is not intersecting with the rectangle I set the gravity to on, therefore he falls continuously as he collides with the solid and then is put on top to not collide with it. All I need to know is: how can I avoid this? Is there any other way that I could set the gravity to on without the player falling towards the solid continuously, only to be put back on top of the solid to fall again?
Any help is appreciated.

The way I address this problem may not be optimal (in fact I'm sure it probably isn't) but it has worked for me in all my 2D platforming projects so far.
I begin by defining a second rectangle for the sprite class. This rectangle will have the same Width and X coordinate as the main bounding box, but it will be slightly taller (in my case 2 or 3). You will also need to offset it so that the bottom edges of both rectangles are inline, to illustrate:
Rectangle boundingRect = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
Rectangle gravityRect = new Rectangle((int)boundingRect.X, (int)boundingRect.Y - 3, texture.Width, texture.Height + 3);
The sprite class also needs a bool to keep track of if the player should be falling. And one to keep track of whether it is solid (which you obviously assign as desired, during initialization).
public bool isGrounded = false;
bool isSolid;
At the top of my Game1 class, I declare 2 ints:
int gravityTotalI = 0;
int gravityCounterI = 0;
When initializing my sprites, I usually add them all to a List. So that I can do this:
foreach (Sprite s in spriteList)
{
if (s.isSolid)
{
gravityTotalI++;
}
}
Now, I use this bit of logic in the Game1 Update Method:
foreach (Sprite s in spriteList)
{
if (!s.Equals(player)
{
if (player.boundingRect.Intersects(s.boundingRect) || player.boundingRect.Intersects(s.gravityRect))
{
player.isGrounded = true;
gravityCounterI = 0;
}
else
{
gravCounterI++;
if (gravCounterI >= gravTotalI)
{
player.isGrounded = false;
gravCounterI = 0;
}
}
if (player.boundingRect.Intersects(s.boundingRect))
{
player.position.Y -= 2f; //set the resistance of the platform here
}
}
} //end of foreach loop.
if (!player.isGrounded)
{
player.position.Y += 2f; //set the force of gravity here.
}
Building a decent directional collision engine is a different thing, but this technique will handle the basics (and get rid of that infernal bouncing).
Hope this isn't too long-winded/doesn't miss out anything important, and I really hope it helps - I struggled with exactly the same problem as you for a long time, and I know how frustrating it can be!
I'm looking forward to seeing others' techniques for handling this!

Related

Irregular iOS Touch Rate on iPhone

I posted this question three days ago, but I received the feedback that the question was not clear so I would like to ask again. Also, the scope of the problem has changed as I researched it further (no Unity issue!)
I'm making a game for iOS where you rotate an object left and right by touching the left or right side of the screen. The Object rotates as long as the display is touched and stops rotating when the touch ends. When I run the game on an actual iPad/iPhone for a while every few touches the Rotation of the Objects stutters for about 2 seconds. This happends in about 5% of the touches. All other touches work perfectly smooth.
All other Game Actions work fine at the set frame rate of 60 fps. The only thing that doesn't move smoothly is the rotated object, while the rest of the game is all running perfectly smooth. I tried to visualize the problem on the attached image. See here
It visually looks like the Touch Refresh Rate is freezed for two seconds.
What may be the cause for this and how do I fix this?
I created the game with the game engine Unity. I use these versions:
- Unity: 2019.3.15f1
- Xcode: 11
- Device: iPhone x
- iOS Version: 13.5.1
After a lot of research I found out, that this is not an issue related to Unity. Also, there is no issue when building the game on an Android device.
Steps to reproduce:
int rotation
private void FixedUpdate(){
for (int i = 0; i < Input.touchCount; i++)
{
Vector3 touchPos = Camera.main.ScreenToWorldPoint(Input.touches[i].position);
if (touchPos.x < 0)
{
rotation += 10;
}
else if (touchPos.x > 0)
{
rotation -= 10;
}
}
transform.Rotate(0, 0, rotation);
rotation = 0;
}
Coding the Touch Input via c# in Unity (see above)
Building the Project on iOS Platform (creating an xcodeproject)
Open the Project in XCode and running it on the iPhone
Does anybody have a solution for this?
Any "Input" class should be called in Update(), instead of FixedUpdate().
The Input data loss is expected if you are trying to get them in FixedUpdate().
Just for example, FixedUpdate can be execute twice in one frame, or skipped in one frame. That's how it causes data lost or inaccurate.
The proper solution will be sth like below.
int rotation
private void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Vector3 touchPos = Camera.main.ScreenToWorldPoint(Input.touches[i].position);
if (touchPos.x < 0)
{
rotation += 10 * time.deltaTime;
}
else if (touchPos.x > 0)
{
rotation -= 10 * time.deltaTime;
}
}
transform.Rotate(0, 0, rotation);
rotation = 0;
}
and please be noted that your current rotation direction is determined by a ray casting from camera.
If you want to rotate left/right by screen space, this should work better.
private void Update()
{
int rotation = 0;
for (int i = 0; i < Input.touchCount; i++)
{
if (Input.touches[i].position.x < Screen.width/2)
{
rotation += 10 * time.deltaTime;
}
else
{
rotation -= 10 * time.deltaTime;
}
}
transform.Rotate(0, 0, rotation);
}
In general, I prefer a simple rotation script by checking last touch.
private void Update()
{
if(Input.touchCount > 0)
{
if (Input.touches[Input.touchCount-1].position.x < Screen.width/2)
{
transform.Rotate(0, 0, 10f * time.deltaTime);
}
else
{
transform.Rotate(0, 0, -10f * time.deltaTime);
}
}
}
Or, if you really want to sync the input with FixedUpdate(), you can do sth like below.
bool HasTouch = false;
float SpeedRot = 0;
private void Update()
{
if(Input.touchCount > 0)
{
HasTouch = true;
if (Input.touches[Input.touchCount-1].position.x < Screen.width/2)
{
SpeedRot = 10f;
}
else
{
SpeedRot = -10f;
}
}
else
{
HasTouch = false;
}
}
private void FixedUpdate()
{
if(HasTouch) transform.Rotate(0, 0, SpeedRot * Time.fixedDeltaTime);
}

How to know when all physics bodies have stopped moving in Cocos2d V3.0 with Chipmunk

The only way I can think to do it is to check velocities for all physics bodies during every collisions.
- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair piece:(CCNode *)pieceA piece:(CCNode *)pieceB{
float x = 0;
float y = 0;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
x = x + [[[_physicsWorld children][i] physicsBody] velocity].x;
y = y + [[[_physicsWorld children][i] physicsBody] velocity].y;
}
if ( x == 0 && y == 0 ) {
NSLog(#"stopped");
}
return YES;
}
This logs “stopped” multiple times when the scene first loads, then doesn’t log “stopped” again, even after physics bodies have clearly started moving and colliding and then come to a stop.
Ideally I'd like a delegate method that would notify me when all physics bodies have stopped moving, but I can't seem to find one.
FYI: I'm using the standard Chipmunk physics engine that's baked into Cocos2d V3.0
Chipmunk has a internal mechanism, which can, if activated, automatically deactivate physics bodies. My approach (I am using cocos2dx 3.11.1 and not -obj version with chipmunk 7.0.1) is:
activate the chipmunk idle mechanism (0.5 second - meaning, if an object is not moving for longer than 0.5 second it will be deactivated):
cpSpaceSetSleepTimeThreshold(space, 0.5f);
You do not need to use
cpSpaceSetIdleSpeedThreshold(space, <speed>);
because chipmunk calculates the threshold speed for you (according the gravitation used).
use this code for determination if all objects are not moving (static and kinetic bodies never sleep):
bool isAnyPhysicsBodyMoving(){
int i = 0; bool isMoving = false;
const Vector<PhysicsBody*>& bodies = getPhysicsWorld()->getAllBodies();
while( i < bodies.size() && !isMoving){
PhysicsBody *body = bodies.at(i);
isMoving = cpBodyGetType(body->getCPBody()) == CP_BODY_TYPE_DYNAMIC
&& !body->isResting();
i++;
}
return isMoving;
}
use static (and not kinetic) body for walls, in order to let objects sleep:
// wall
Size visibleSize = Director::getInstance()->getWinSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
float border = 10.0f;
Size wallBodySize = Size(visibleSize.width+2*border, visibleSize.height+2*border);
PhysicsBody *wallBody = PhysicsBody::createEdgeBox(wallBodySize, PhysicsMaterial(1.0f, 1.0f, 0.5f), border);
Node* wall = Node::create();
wall->addComponent(wallBody);
wall->setAnchorPoint(Vec2(0.5f, 0.5f));
wall->setPosition(Point(visibleSize.width/2+origin.x, visibleSize.height/2+origin.y));
cpVect tt;
tt.x = wall->getPosition().x; tt.y = wall->getPosition().y;
//set position manually and BEFORE adding the object into the space
cpBodySetPosition(wallBody->getCPBody(), tt);
cpBodySetType(wallBody->getCPBody(), CP_BODY_TYPE_STATIC);
addChild(wall);
Any dynamic body connected to a kinetic body (for example laying on) will never sleep.
test it with DEBUG activated
getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
the boxes (their content) must become grey (=sleeping) and not red (=active):
In order to let it work, I have:
added an access method (to get cpSpace) in CCPhysicsWorld.h:
inline cpSpace* getSpace() const { return _cpSpace; }
Fix call of
cpBodySetTorque(body, 0.0f);`
in CCPhysicsBody.cpp to
if (body->t != 0.0f){
cpBodySetTorque(body, 0.0f);
}
Fix call of
cpBodySetPosition(_cpBody, tt);`
in CCPhysicsBody.cpp to
if (!cpveql(tt, cpBodyGetPosition(_cpBody))){
cpBodySetPosition(_cpBody, tt);
}
Steps 2. and 3. are necessary to avoid setting of the same physics body properties, which wake up a sleeping body.
The advantage of this approach is, that the chipmunk does not make any calculations for such physical bodies - saving CPU and battery.
I found something that works.
tl;dr
The basic idea is to keep track of the positions of the sprites myself, and then periodically check them to see if any of them have moved since they were last checked.
Longer version
I created a subclass of CCNode with the class name Piece.
These are my objects that are added to the physics world.
#implementation Piece {
float _previousX;
float _previousY;
}
-(void)updatePreviousScreenXandY{
_previousX = self.position.x;
_previousY = self.position.y;
}
-(BOOL)hasntMoved{
float currentX = self.position.x;
float currentY = self.position.y;
if ( currentX == _previousX && currentY == _previousY ) {
return TRUE;
}else{
return FALSE;
}
}
This is in my CCNode that acts as the game scene
-(void)doStuffAfterPiecesStopMoving:(NSTimer*)timer{
BOOL noPiecesHaveMoved = TRUE;
for (int i = 0; i < [[_physicsWorld children] count]; i++) {
if ( [[_physicsWorld children][i] hasntMoved] == FALSE ) {
noPiecesHaveMoved = FALSE;
break;
}
}
if ( noPiecesHaveMoved ) {
[timer invalidate];
NSLog(“Pieces have stopped moving”);
}else{
NSLog(“Pieces are still moving”);
[self updateAllPreviousPiecePositions];
}
}
-(void)updateAllPreviousPiecePositions{
for (int i=0; i < [[_physicsWorld children] count]; i++) {
Piece *piece = (Piece*)[_physicsWorld children][i];
[piece updatePreviousScreenXandY];
}
}
All I have to do is
[NSTimer scheduledTimerWithTimeInterval:TIME_BETWEEN_CHECKS
target:_gamePlay
selector:#selector(doStuffAfterPiecesStopMoving:)
userInfo:nil
repeats:YES];
and it’ll run whatever code I want after all Piece nodes have stopped moving.
The key to getting it to work well is to get the values for the Chipmunk space’s sleepTimeThreshold and the timer above’s time as low as possible.
My experimenting suggests the following settings work okay, but anything lower will cause problems (i.e. collisions not taking place properly):
sleepTimeThreshold = 0.15
my timer = 0.05
If anyone has a different/better solution or improvements to the above code, please post.

Working out the orientation of a target to a rotating sprite

I've got a player sprite that moves by rotation and its rotation is constantly changing but I also need to work out if a target is to the left or right of it and not within 45 degrees of the front or rear rotation.
I've written this code which I think should work but it seems only occasional pick up one side and slightly more on the other.
public void GrappleCheck(AsteroidSprite target)
{
float targetTragectory = (float)Math.Atan2(Position.Y - target.Position.Y, Position.X - target.Position.X);
if (targetTragectory < 0)
targetTragectory += (float)(Math.PI * 2);
if (Rotation < 0)
Rotation += (float)(Math.PI * 2);
if ((targetTragectory > Rotation + (float)(MathHelper.PiOver4 / 2)) && (targetTragectory < Rotation + (float)(Math.PI - (MathHelper.PiOver4 / 2))))
{
target.Distance = Vector2.Distance(Position, target.Position);
if (RightTarget != null)
{
if (RightTarget.Distance > target.Distance)
{
RightTarget.isTarget = false;
RightTarget = target;
RightTarget.ColorTint = Color.Blue;
RightTarget.isTarget = true;
}
}
else
{
RightTarget = target;
RightTarget.ColorTint = Color.Blue;
RightTarget.isTarget = true;
}
}
else if ((targetTragectory < Rotation - (float)(MathHelper.PiOver4 / 2)) && (targetTragectory > Rotation - (float)(Math.PI - (MathHelper.PiOver4 / 2))))
{
target.Distance = Vector2.Distance(Position, target.Position);
if (LeftTarget != null)
{
if (LeftTarget.Distance > target.Distance)
{
LeftTarget.isTarget = false;
LeftTarget = target;
LeftTarget.ColorTint = Color.Red;
LeftTarget.isTarget = true;
}
}
else
{
LeftTarget = target;
LeftTarget.ColorTint = Color.Red;
LeftTarget.isTarget = true;
}
}
else
{
target.isTarget = false;
}
if (controlInput.IsHeld(Keys.X))
{
Speed = Speed;
}
Working with angles can be quite annoying. Here are some ways to solve your problems without using angles:
First, we need the direction to the target and the movement direction:
var targetDirection = target.Positon - Position;
// Update this to match the actual direction. The following line assumes that
// a rotation of 0 results in the right direction.
var movementDirection = new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation));
The first problem you want to solve is determining, if the target is within a 45° cone. You can calculate the actual angle with the following formula:
var dot = Vector2.Dot(myDirection, targetDirection);
//if dot is negative, then target is behind me, so just use the absolute value
var cos = Math.Abs(dot) / myDirection.Length() / targetDirection.Length();
var angle = Math.Acos(cos);
if(angle < MathHelper.PiOver4 / 2) //45° opening angle
; // within cone
else
; // outside cone
Your second problem is determining, if the target is on the left or right side. We use a vector that is ortogonal to myDirection and points to the left for this:
//assuming that +x is the right axis and +y is the down axis
var normal = new Vector2(myDirection.Y, -myDirection.X);
dot = Vector2.Dot(normal, targetDirection);
if(dot > 0)
; // target is on the left side
else
; // target is on the right side
I hope that makes cleaning up your code a bit easier and more comprehensible. You should consider extracting some code in separate methods to make it more readable.
Okay I've solved it, the player rotation can be from 0 to 2 x PI + or -, to keep it + though I put in
if (Rotation < 0)
Rotation += (float)Math.PI * 2;
the rotation to the target can be 0-PI or 0 - Negative PI depending on the way you declare the atan2 and where the player is.
//This works out the difference from the targets rotation to the players rotation.
RotationDif = TargetRotation - PlayerRotation;
//If the difference is greater than PI then when we check to see if its is within
//the range 0-PI or 0-Negative PI it will be missed whilst still possibly being on
//either side of the player, adding PI * 2 to the targets rotation basically spins
//it past the player so the Difference will be the shortest angle.
if(Math.Abs(RotationDif) > Math.PI)
RotationDif = TargetRotation + (float)(Math.PI * 2) - PlayerRotation;
//Next we check to see if the target is left(negative) or
//the right(positive), the negative/positive assignment will depend
//on which way round you did the atan2 to get the target rotation.
if ((RotationDif > 0) && (RotationDif < (float)Math.PI))
//Insert right target code here
else if ((RotationDif < 0) && (RotationDif > -(float)Math.PI))
//Insert left target code here
else
//Insert no target code here to cover all basis
And that's it, I've made the If (RotationDif > 0) etc differently so the a 45 degree angle front and back is ignored by making it
If ((RotationDif > (float)(Math.PI / 8) &&
(RotationDif < (float)(Math.PI - (Math.PI / 8)))
and the opposite for the other side, hope this helps someone else as it took me nearly 2 sodding weeks to work out :/

Newly loaded sprites not behaving properly in XNA 4.0

Here is the code for a project im working on, where an enemy moves back and forth at the bottom of the screen.
class enemy1
{
Texture2D texture;
public Vector2 position;
bool isAlive = false;
Random rand;
int whichSide;
public enemy1(Texture2D texture, Vector2 position)
{
this.texture = texture;
this.position = position;
}
public void Update()
{
if (isAlive)
{
if (whichSide == 1)
{
position.X += 4;
if (position.X > 1000 + texture.Width)
isAlive = false;
}
if (whichSide == 2)
{
position.X -= 4;
if (position.X < 0)
isAlive = false;
}
}
else
{
rand = new Random();
whichSide = rand.Next(1, 3);
SetInStartPosition();
}
}
private void SetInStartPosition()
{
isAlive = true;
if (whichSide == 1)
position = new Vector2(0 - texture.Width, 563 - texture.Height);
if (whichSide == 2)
position = new Vector2(1000 + texture.Width, 563 - texture.Height);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, position, Color.White);
}
}
Now i want there to be a few enemys going back and forth but they start at differant positions so it looks like there is a few enemys going back and forth at the bottom of the screen. I have managed to draw a few other enemies on the screen, except they do not behave like the first enemy. They just are pictures on a screen not moving anywhere. So now all i have is the hero moving around and one enemy at the bottom of the screen, along with 5 other enemys sitting at the top of the screen doing nothing. How do i easily add a new sprite from a class that has the same behavior, at any time, while not making a billion variables to store them in?
Generally it's a good idea to have similar logic contained within the proper class, so if all Sprites where to do the same thing, then all you would need to do is put your movement code inside a public method and then call that method inside Update().
So, if your Sprite class looks something like this:
public class Sprite
{
private Vector2 Position;
public Sprite(Texture2D texture, Vector2 position)
{
Position = position;
}
//then just add this
public void MoveSprite(int amount)
{
position.X += amount;
}
}
Now, the object name "Sprite" is pretty generic, you will more than likely have many "Sprites" in your game.
So you're going to want to follow good OOP practices and maybe name this specific sprite something different and then have it derive from this class we're looking at right now. (But i'm not going to make design decisions for you)
This was a vague question, but that's my best shot at an answer for you.

XNA how to implement gravity?

I've tried following the example on XNA Development website but when the character jumps, they cant be controlled/cant stop the jump movement until its completed.
How do I get around that? Here is my jump code
private void Jump()
{
if (mCurrentState != FoxState.Jumping)
{
mCurrentState = FoxState.Jumping;
mStartingPosition = Position;
Direction.Y = Fox_vSpeed;
Speed = new Vector2(Fox_Speed, Fox_Speed);
}
}
Gravity is simply a force that affects the acceleration of an object. Acceleration changes the velocity of an object so you can do just that: Speed -= Vector2.UnitY * -2; You can then check collision with an object on the ground and once such collision happens you can zero the Y component of Speed (and possibly incur damage on the poor fox if the Y component is too high in magnitude (absolute value)).
Something like:
Update(GameTime gt)
{
if(mCurrentState == FoxState.Jumping)
{
Speed -= Vector2.UnitY * -2;
Position += Speed;
if(Position.Y > GroundLevel)
{
Position.Y = 0;
mCurrentState = FoxState.Walking;
}
}
}
Modify your question with details if you need more information.

Resources