Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

This was originally a forum post so I hope you guys don't mind it being 2 questions in one.

I am making a game and I got some basic physics implemented. I have 2 problems, 1 with particles being drawn in the wrong place and one with going through walls while jumping in corners.

Skip over to about 15 sec video showing the 2 problems : http://youtube.com/watch?v=Tm9nfWsWfiM

So the problem with the particles seems to be coming from the removal, as soon as I remove that piece of code it instantly works, but there shouldn't be a problem since they shouldn't even draw when their energy gets to 0 (and then they get removed)

So my first question is, how are these particles getting warped all over the screen ?

Relevant code :

Particle class :

class Particle
{
    //Physics
    public Vector2 position = new Vector2(0,0);
    public float direction = 180;
    public float speed = 100;
    public float energy = 1;
    protected float startEnergy = 1;

    //Visual
    public Sprite sprite;
    public float rotation = 0;
    public float scale = 1;
    public byte alpha = 255;
    public BlendMode blendMode
    {
        get
        {
            return sprite.BlendMode;
        }
        set
        {
            sprite.BlendMode = value;
        }
    }


    public Particle()
    {  
    }

    public virtual void Think(float frameTime)
    {
        if (energy - frameTime < 0)
            energy = 0;
        else
            energy -= frameTime;

        position += new Vector2((float)Math.Cos(MathHelper.DegToRad(direction)), (float)Math.Sin(MathHelper.DegToRad(direction))) * speed * frameTime;
        alpha = (byte)(255 * energy / startEnergy);
        sprite.Rotation = rotation;
        sprite.Position = position;
        sprite.Color = new Color(sprite.Color.R, sprite.Color.G, sprite.Color.B, alpha);
    }

    public virtual void Draw(float frameTime)
    {
        if (energy > 0)
        {
            World.camera.DrawSprite(sprite);
        }
    }
// Basic particle implementation
    class BasicSprite : Particle
    {
        public BasicSprite(Sprite _sprite)
        {
            sprite = _sprite;
        }
    }

Emitter :

    class Emitter
{
    protected static Random rand = new Random();
    protected List<Particle> particles = new List<Particle>();

    public BaseEntity target = null;
    public Vector2 position = new Vector2(0, 0);
    public bool Active = true;
    public float timeAlive = 0;
    public int particleCount = 0;
    public int ParticlesPerSeccond
    {
        get
        {
            return (int)(1 / particleSpawnTime);
        }
        set
        {
            particleSpawnTime = 1 / (float)value;
        }
    }
    public float dieTime = float.MaxValue;

    float particleSpawnTime = 0.05f;
    float spawnTime = 0;

    public Emitter()
    {

    }

    public virtual void Think(float frametime)
    {
            spawnTime += frametime;

            if (dieTime != float.MaxValue)
            {
                timeAlive += frametime;
                if (timeAlive >= dieTime)
                    Active = false;
            }

            if (Active)
            {

                if (target != null)
                    position = target.Position;

                while (spawnTime > particleSpawnTime)
                {
                    spawnTime -= particleSpawnTime;
                    AddParticle();
                    particleCount++;
                }

            }
            for (int i = 0; i < particles.Count; i++)
            {
                particles[i].Think(frametime);
                if (particles[i].energy <= 0)
                {
                    particles.Remove(particles[i]); // As soon as this is removed, it works
                    particleCount--;
                }
            }
    }

    public virtual void AddParticle()
    {

    }

    public virtual void Draw(float frametime)
    {
        foreach (Particle particle in particles)
        {
            particle.Draw(frametime);
        }
    }
}
class BloodEmitter : Emitter
{
    Image image;

    public BloodEmitter()
    {
        image = new Image(@"Content/Particles/TinyCircle.png");
        image.CreateMaskFromColor(new Color(255, 0, 255, 255));
        this.dieTime = 0.5f;
        this.ParticlesPerSeccond = 100;
    }

    public override void AddParticle()
    {
        Sprite sprite = new Sprite(image);
        sprite.Color = new Color((byte)(rand.NextDouble() * 255), (byte)(rand.NextDouble() * 255), (byte)(rand.NextDouble() * 255));

        BasicSprite particle = new BasicSprite(sprite);
        particle.direction = (float)rand.NextDouble() * 360;
        particle.position = position;
        particle.blendMode = BlendMode.Alpha;
        particles.Add(particle);
    }
}

The seccond problem is the physics problem, for some reason I can get through the right bottom corner while jumping. I think this is coming from me switching animations but I thought I made it compensate for that.

Relevant code : PhysicsEntity :

    class PhysicsEntity : BaseEntity
{
    // Horizontal movement constants
    protected const float maxHorizontalSpeed = 1000;
    protected const float horizontalAcceleration = 15;
    protected const float horizontalDragAir = 0.95f;
    protected const float horizontalDragGround = 0.95f;

    // Vertical movement constants
    protected const float maxVerticalSpeed = 1000;
    protected const float verticalAcceleration = 20;

    // Everything needed for movement and correct animations
    protected float movement = 0;
    protected bool onGround = false;
    protected Vector2 Velocity = new Vector2(0, 0);
    protected float maxSpeed = 0;
    float lastThink = 0;
    float thinkTime = 1f/60f;

    public PhysicsEntity(Vector2 position, Sprite sprite) :
        base(position, sprite)
    {

    }

    public override void Draw(float frameTime)
    {
        base.Draw(frameTime);
    }

    public override void Think(float frameTime)
    {
        CalculateMovement(frameTime);
        base.Think(frameTime);
    }

    protected void CalculateMovement(float frameTime)
    {
        lastThink += frameTime;

        while (lastThink > thinkTime)
        {
            onGround = false;

            Velocity.X = MathHelper.Clamp(Velocity.X + horizontalAcceleration * movement, -maxHorizontalSpeed, maxHorizontalSpeed);
            if (onGround)
                Velocity.X *= horizontalDragGround;
            else
                Velocity.X *= horizontalDragAir;

            if (maxSpeed < Velocity.X)
                maxSpeed = Velocity.X;

            Velocity.Y = MathHelper.Clamp(Velocity.Y + verticalAcceleration, -maxVerticalSpeed, maxVerticalSpeed);

            lastThink -= thinkTime;
            DoCollisions(thinkTime);
            DoAnimations(thinkTime);
        }
    }
    public virtual void DoAnimations(float frameTime)
    {

    }
    public void DoCollisions(float frameTime)
    {
        Position.Y += Velocity.Y * frameTime;
        Vector2 tileCollision = GetTileCollision();
        if (tileCollision.X != -1 || tileCollision.Y != -1)
        {
            Vector2 collisionDepth = CollisionRectangle.DepthIntersection(
                new Rectangle(
                    tileCollision.X * World.tileEngine.TileWidth,
                    tileCollision.Y * World.tileEngine.TileHeight,
                    World.tileEngine.TileWidth,
                    World.tileEngine.TileHeight
                )
            );
            Position.Y += collisionDepth.Y;
            if (collisionDepth.Y < 0)
                onGround = true;

            Velocity.Y = 0;
        }

        Position.X += Velocity.X * frameTime;
        tileCollision = GetTileCollision();
        if (tileCollision.X != -1 || tileCollision.Y != -1)
        {
            Vector2 collisionDepth = CollisionRectangle.DepthIntersection(
                new Rectangle(
                    tileCollision.X * World.tileEngine.TileWidth,
                    tileCollision.Y * World.tileEngine.TileHeight,
                    World.tileEngine.TileWidth,
                    World.tileEngine.TileHeight
                )
            );
            Position.X += collisionDepth.X;
            Velocity.X = 0;
        }
    }
    public void DoCollisions(Vector2 difference)
    {
        CollisionRectangle.Y = Position.Y - difference.Y;
        CollisionRectangle.Height += difference.Y;
        Vector2 tileCollision = GetTileCollision();
        if (tileCollision.X != -1 || tileCollision.Y != -1)
        {
            Vector2 collisionDepth = CollisionRectangle.DepthIntersection(
                new Rectangle(
                    tileCollision.X * World.tileEngine.TileWidth,
                    tileCollision.Y * World.tileEngine.TileHeight,
                    World.tileEngine.TileWidth,
                    World.tileEngine.TileHeight
                )
            );
            Position.Y += collisionDepth.Y;
            if (collisionDepth.Y < 0)
                onGround = true;

            Velocity.Y = 0;
        }

        CollisionRectangle.X = Position.X - difference.X;
        CollisionRectangle.Width += difference.X;
        tileCollision = GetTileCollision();
        if (tileCollision.X != -1 || tileCollision.Y != -1)
        {
            Vector2 collisionDepth = CollisionRectangle.DepthIntersection(
                new Rectangle(
                    tileCollision.X * World.tileEngine.TileWidth,
                    tileCollision.Y * World.tileEngine.TileHeight,
                    World.tileEngine.TileWidth,
                    World.tileEngine.TileHeight
                )
            );
            Position.X += collisionDepth.X;
            Velocity.X = 0;
        }
    }

    Vector2 GetTileCollision()
    {
        int topLeftTileX = (int)(CollisionRectangle.TopLeft.X / World.tileEngine.TileWidth);
        int topLeftTileY = (int)(CollisionRectangle.TopLeft.Y / World.tileEngine.TileHeight);
        int BottomRightTileX = (int)(CollisionRectangle.DownRight.X / World.tileEngine.TileWidth);
        int BottomRightTileY = (int)(CollisionRectangle.DownRight.Y / World.tileEngine.TileHeight);

        if (CollisionRectangle.DownRight.Y % World.tileEngine.TileHeight == 0) // If your exactly against the tile don't count that as being inside the tile
            BottomRightTileY -= 1;
        if (CollisionRectangle.DownRight.X % World.tileEngine.TileWidth == 0) // If your exactly against the tile don't count that as being inside the tile
            BottomRightTileX -= 1;

        for (int i = topLeftTileX; i <= BottomRightTileX; i++)
        {
            for (int j = topLeftTileY; j <= BottomRightTileY; j++)
            {
                if (World.tileEngine.TileIsSolid(i, j))
                {
                    return new Vector2(i, j);
                }
            }
        }

        return new Vector2(-1, -1);
    }
}

Player :

   enum State
{
    Standing,
    Running,
    Jumping,
    Falling,
    Sliding,
    WallSlide
}

class Player : PhysicsEntity
{
    private State state
    {
        get
        {
            return currentState;
        }
        set
        {
            if (currentState != value)
            {
                currentState = value;
                animationChanged = true;
            }
        }
    }

    private State currentState = State.Standing;
    private BasicEmitter basicEmitter = new BasicEmitter();
    public bool flipped;
    public bool animationChanged = false;

    protected const float jumpPower = 600;

    AnimationManager animationManager;
    Rectangle DrawRectangle;
    public override Rectangle CollisionRectangle
    {
        get
        {
            return new Rectangle(
                Position.X - DrawRectangle.Width / 2f,
                Position.Y - DrawRectangle.Height / 2f,
                DrawRectangle.Width,
                DrawRectangle.Height
            );
        }
    }

    public Player(Vector2 position, Sprite sprite) :
        base(position, sprite)
    {
        // Only posted the relevant bit
        DrawRectangle = animationManager.currentAnimation.drawingRectangle;
    }

    public override void Draw(float frameTime)
    {
        World.camera.DrawSprite(
            Sprite, 
            Position + new Vector2(DrawRectangle.X, DrawRectangle.Y),
            animationManager.currentAnimation.drawingRectangle
        );
    }

    public override void Think(float frameTime)
    {
        //I only posted the relevant stuff
        if (animationChanged)  
        {
            // if the animation has changed make sure we compensate for the change in with and height
            animationChanged = false;
            DoCollisions(animationManager.getSizeDifference());
        }
        DoCustomMovement();
        base.Think(frameTime);
        if (!onGround && Velocity.Y > 0)
        {
            state = State.Falling;
        }
    }

    void DoCustomMovement()
    {
        if (onGround)
        {
            if (World.renderWindow.Input.IsKeyDown(KeyCode.W))
            {
                Velocity.Y = -jumpPower;
                state = State.Jumping;
            }
        }
    }
    public override void DoAnimations(float frameTime)
    {
        string stateName = Enum.GetName(typeof(State), state);

        if (!animationManager.currentAnimationIs(stateName))
        {
            animationManager.PlayAnimation(stateName);
        }

        animationManager.Think(frameTime);

        DrawRectangle = animationManager.currentAnimation.drawingRectangle;
        Sprite.Center = new Vector2(
            DrawRectangle.X + DrawRectangle.Width / 2,
            DrawRectangle.Y + DrawRectangle.Height / 2
        );
        Sprite.FlipX(flipped);
    }

So why am I warping through walls ?

I have given this some thought but I just can't seem to find out why this is happening.

Full source if needed : source : http://www.mediafire.com/?rc7ddo09gnr68zd (download link)

share|improve this question
3  
You're probably warping through walls because your speed is so great that in one frame you're above the wall, in the next you're below it, and you never check for collision. You should check collision against the path swept, not against the endpoints. –  Peter Taylor Mar 13 '11 at 14:42
1  
You should look into object pooling for the particle emmiter, the way you have it set up now, it will hit the garbage collector a lot compared to creating all the instances you will need in the begining, and only drawing and updating the ones that are active. –  AttackingHobo Mar 13 '11 at 23:09
    
Peter, the problem you are describing ins't going to be a problem since the the maximum amount of pixels that can be moved = 1/60 * 1000 = 16.667 units, since all of my objects that are colidable are bigger then 34 pixels this shouldn't be a problem. –  Quincy Mar 14 '11 at 10:28

2 Answers 2

2 things that might be up.

1) When you remove the particles, that loop is going to skip the "next" particle after one that's been removed in the loop that processes them. This is because you iterate through the list using "i", but during that loop you also remove items from the array, shifting future entries down, but then also increment "i" which then skips what was effectively the one after the one you just deleted (that's now in our slot).

2) I might have missed it, but I didn't see the code when I had a quick look above that handled the actual "how far inside a block" test. What I'm wondering is that based off the depth inside you decide which way to push the colliding object out. You don't seem to take into consideration the velocity of the player at the time (i.e. was he moving down and is now colliding) which you could then also use to judge if he should be pushed back upwards when colliding. Instead I'm guessing you decide which direction to move the player based on which edge it's closest to of the tile. If the player is moving fast enough then you might get so far inside a tile that you think he should be pushed out the bottom.

share|improve this answer
    
Thanks a bunch bro your a lifesaver, the particle problem is fixed, I'm currently looking at the physics problem. Going to set the max speed to 900 so the maximum travel distance is smaller then 16 if that works :) –  Quincy Mar 13 '11 at 18:58
    
Damm that isn't the problem :( –  Quincy Mar 13 '11 at 19:00
    
@Quincy Glad the particle issue is gone. Have you tried doing some debug prints when collisions occur so that when a bad collision happens you can see what values have been passed in? If you print out enough you should then be able to walk through the logic with known values when the problem occurs to be able to diagnose the problem –  Roger Perkins Mar 13 '11 at 19:05
    
The problem is I do not know how to detect a bad collision and if everything gets checked every 1/60 of a second and then printed I'll never be able to see what the bad collision values where. –  Quincy Mar 13 '11 at 21:42
    
@Quincy You only need to print something when a collision takes place (i.e. TileCollision reports X or Y is != -1). Print out the relevant values to the calculation. As you set velocities to zero once a collision has been resolved you shouldn't get snowed under with output. One thing I would say is you update some parts of "CollisionRectangle" then run a check for a collision. For example, when you check for Y collision you then process the result if "tileCollision.X" is not equal to -1, despite not having updated the X information since the previous frame I'm guessing (can't see all code) –  Roger Perkins Mar 14 '11 at 0:11

Your particle problem is because you're removing elements while looping by index. Unless you adjust the index when you remove, you'll skip over a particle the next time the loop comes around.

Let's say you have particles [a, b, c, d]. The loop will do this:

  1. Set i to 0.
  2. Update particles[0]
  3. Check it's energy. Let's say it's less than zero now.
  4. Remove particles[0]. This shifts [b, c, d] up so that b is now at index zero.
  5. Loop around and increment i to 1.
  6. Update particles[1]. This is now c and not b. So we skip b entirely which is your bug.

A simple fix is:

for (int i = 0; i < particles.Count; i++)
{
    particles[i].Think(frametime);
    if (particles[i].energy <= 0)
    {
        particles.Remove(particles[i]);
        i--; // <-- Shift the index up too since the particles have shifted.
        particleCount--;
    }
}

Remove() can do a lot of work, though (it's O(n)). If you want something more efficient, do all of the removes in one step like this:

// First update all of the particles.
for (int i = 0; i < particles.Count; i++)
{
    particles[i].Think(frametime);
}

// Now remove all of the dead ones in one pass by overwriting them with
// the remaining lives ones.
int dest = 0;
for (int i = 0; i < particles.Count; i++)
{
    if (particles[i].energy > 0)
    {
        particles[dest] = particles[i];
        dest++;
    }
}

// Now trim the excess off the end.
particles.RemoveRange(dest, particles.Count - dest);

Also, you don't need particleCount. Lists already know their size.

I'll leave your other question to others to answer. :)

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.