I am working on my game using XNA. I have found a series of tutorials by Oyyou from YouTube and have gone to the collision tutorial so far. I have successfully processed gravity, movement and animation. But I have a problem with collision. The spawn point of my character is up in the air.
The first value is my bool hasJumped. And the second is y velocity. It is accelerating by 0.25. The character is falling down slowly accelerating. All four platforms are made the same way. All four are in the list and I don't make anything to work with any of them particularly.
When I land on the lowest (ground) I get the parameters I need. hasJumped is false and velocity.Y is 0.
But when I'm on one of three non-ground platforms results are not as I want them. The velocity is 0.25 even though I am making it 0. And hasJumped doesn't become true because my velocity is a non-zero variable. Unluckily I can't post images so I can't show it. But I will post my code: this is the maingame class where I add players and platforms.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
player = new Animation(Content.Load<Texture2D>("player"), Content.Load<Texture2D>("rect"), new Vector2(300 + 28, graphics.PreferredBackBufferHeight - 500), 47, 44, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(300,graphics.PreferredBackBufferHeight-100), 80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(500, graphics.PreferredBackBufferHeight - 50), 80, 50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(700, graphics.PreferredBackBufferHeight - 60),80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(0, graphics.PreferredBackBufferHeight - 10), graphics.PreferredBackBufferWidth, 10));
// TODO: use this.Content to load your game content here
}
This is my Animation class:
class Animation
{
Texture2D texture;
Texture2D rectText;
public Rectangle rectangle;
public Rectangle collisionRect;
public Vector2 position;
Vector2 origin;
public Vector2 velocity;
int currentFrame;
int frameHeight;
int frameWidth;
int screenHeight;
int screenWidth;
float timer;
float interval = 60;
bool movingRight;
public bool hasJumped;
public Animation(Texture2D newTexture, Texture2D newRectText, Vector2 newPosition, int newHeight, int newWidth, int screenH, int screenW)
{
rectText = newRectText;
texture = newTexture;
position = newPosition;
frameHeight = newHeight;
frameWidth = newWidth;
screenHeight = screenH;
screenWidth = screenW;
hasJumped = true;
}
public void Update(GameTime gameTime)
{
rectangle = new Rectangle(currentFrame * frameWidth, 0, frameWidth, frameHeight);
collisionRect = new Rectangle((int)position.X-frameWidth/2, (int)position.Y - frameHeight/2, frameWidth, frameHeight);
origin = new Vector2(rectangle.Width / 2, rectangle.Height / 2);
position = position + velocity;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
AnimateRight(gameTime);
velocity.X = 5;
movingRight = true;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
AnimateLeft(gameTime);
velocity.X = -5;
movingRight = false;
}
else
{
velocity.X = 0f;
if (movingRight)
currentFrame = 0;
else currentFrame = 4;
}
if(Keyboard.GetState().IsKeyDown(Keys.Space) && !hasJumped)
{
position.Y -= 5f;
velocity.Y = -10;
hasJumped = true;
}
if(hasJumped)
{
velocity.Y += 0.25f;
}
if(position.X<0+frameWidth/2)
{
position.X = 0 + frameWidth / 2;
}
else if (position.X>screenWidth-frameWidth/2)
{
position.X = screenWidth - frameWidth / 2;
}
}
public void AnimateRight(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds/2;
if(timer>interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 3)
{
currentFrame = 0;
}
}
}
public void AnimateLeft(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds / 2;
if (timer > interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 7 || currentFrame < 4)
{
currentFrame = 4;
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture,position,rectangle,Color.White, 0f, origin, 1.0f, SpriteEffects.None, 0);
// spriteBatch.Draw(rectText,collisionRect,Color.White);
}
}}
The platform class is pretty simple:
class Platform
{
Texture2D texture;
Vector2 position;
public Rectangle rectangle;
public Platform(Texture2D newTexture, Vector2 newPosition, int platformWidth, int platformHeight)
{
texture = newTexture;
position = newPosition;
rectangle = new Rectangle((int)position.X, (int)position.Y, platformWidth, platformHeight);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle, Color.White);
}
}
and the collision method:
public static bool isOnTopOf(this Rectangle r1, Rectangle r2)
{
return (r1.Bottom >= r2.Top - 10 &&
r1.Bottom <= r2.Top + 10 &&
r1.Right >= r2.Left + 10 &&
r1.Left <= r2.Right - 10);
}
The problem is supposed to be here:
foreach (Platform a in platformList)
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
}
else
{
player.hasJumped = true;
}
}
But if I exclude the last else it won't fall of the platform.
Thank you for upvoting the question. I have added images.
Your problem is that when the user is on top of a platform that is not the lowest one, during the else when you check if the user is on a platform, then you explicitly set the hasJumped to true.
What you must do is in your foreach where you check your platforms, to order them by INCREASING Y-coordinate (very much depending on your camera settings) and break after you set hasJumped to false. This way you will avoid your issue:
foreach (Platform a in platformList.OrderByY())
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
break;
}
else
{
player.hasJumped = true;
}
}
Unfortunately, it is often the case with 3D development that we have to do some hacks to get the application to render correctly. This is especially the case when rendering a complex item with advanced transparencies. In your case however, I would advise to re-imagine the way you work with collisions.
Games development is not an easy task. Superficially, the constraints under which you are working enforce a less than ideal architecture. Everything in an XNA game revolves around the update loop and the ability to control which item gets updated first is so easy and tempting that people tend to use that. It is however generally a much better approach to try and extrapolate functionality in such a way that your models behave in an event-driven way. One way to achieve this is to split the update cycle of your game in the following phases:
The above is not necessarily the best way to do it, but it works. If you rely only on the update cycle you will very soon reach a point where the interdependencies between models and the requirement to have some events fired in order will be a nightmare to support. Enforcing an event driven mechanism will save you from a lot of scrapped work with a very small initial overhead.
My suggestion to use Rx may sound a little strange for an XNA game, but trust me, it is an awesome way to work with events. Finally, I would like to mention MonoGame, an excellent framework to develop games for Windows, Mac, Linux, Android, iPhone, Windows Phone, WinRT (and even more) if you do not already know it.