DaVikingCode / Citrus-Engine

Modern AS3 Game Engine
http://citrusengine.com/
Other
550 stars 231 forks source link

objects on moving platform "jump" if it changes direction to downward #8

Closed makc closed 11 years ago

makc commented 11 years ago

it could be argued that this is perfectly physical behavior, however, if I leave the hero standing on the moving platform and go for my coffee, I do not expect it to be dead after a while because he moved to the edge and fell off.

could there be some kind of hack implemented that will change the inertia of all the bodies that contact moving platform when it changes direction?

alamboley commented 11 years ago

Hi, I've not be able to reproduce this behavior. This is my GameState :

override public function initialize():void { super.initialize();

var box2d:Box2D = new Box2D("box2D");
box2d.visible = true;
add(box2d);

var hero:Hero = new Hero("hero", {width:40, height:70, view:"PatchSpriteArt.swf"});
add(hero);

add(new Platform("platform", {x:stage.stageWidth/2, width:stage.stageWidth, y:stage.stageHeight}));

add(new MovingPlatform("movingPlatform", {oneWay:true, x:300, y:280, width:200}));

}

The moving platform doesn't change other objects velocity. However the Hero has some functions where a moving platform has an impact :

protected function getSlopeBasedMoveAngle():b2Vec2 { return Box2DPhysicsObject.Rotateb2Vec2(new b2Vec2(acceleration, 0), _combinedGroundAngle); }

protected function updateCombinedGroundAngle():void { _combinedGroundAngle = 0;

if (_groundContacts.length == 0)
    return;

for each (var contact:b2Fixture in _groundContacts)
    var angle:Number = contact.GetBody().GetAngle();

var turn:Number = 45 * Math.PI / 180;
angle = angle % turn;
_combinedGroundAngle += angle;
_combinedGroundAngle /= _groundContacts.length;

}

makc commented 11 years ago

I will try to give you simple game state code to reproduce this tomorrow (other pc here). However, this is fairly easy to understand. Imagine yourself in the elevator that accelerates downwards faster than 1g - the floor will basically say goodbye to your feet (and then the roof will hit your head). Now when moving platform reverses its direction and goes down - it does so instantly (basically infinite acceleration is applied, which sure is > 1g), so you see hero detached from platform for a short time. If the platform moves diagonally, this is enough for him to "jump" along the platform to one of edges and eventually fall off.

gsynuh commented 11 years ago

Hi makc,

Physics engines in the case of a platformer, are almost TOO close to reality in fact, like your example shows. So the laws must be broken when he is standing on a platform in one way or another to avoid this, I think that if "moving platforms" are part of the engine, then this behavior should also be taken care of inside of it.

either this or maybe your gravity isn't high enough? but I haven't tested this as moving platforms are not yet implemented in nape apparently (and I would love to be able to contribute to this so this discussion may help).

My main idea for your problem would be to "disable" the vertical velocity of the player (making it equal to the platform's at every update) only when he's in contact with it - that would involve using the onGround boolean I guess - and also this shouldn't prevent the player from voluntarily jumping off of the platform (because onGround would stop being true) or moving sideways on it by himself so long as the platforms friction is not equal to 0 i guess. does that sound doable?

alamboley commented 11 years ago

Thanks for the explanation, I understand this physics problem :) The physics is right, but most of the game don't need to be as complex. I think we can get around this issue adding to the MovingPlatform's passengers its y velocity.

For example, the Treadmill (which extends MovingPlatform) update function looks like this :

override public function update(timeDelta:Number):void {

super.update(timeDelta);

if (enableTreadmill) {

    for each (var passengers:b2Body in _passengers) {

        if (startingDirection == "right")
            passengers.GetUserData().x += speedTread;
        else
            passengers.GetUserData().x -= speedTread;
    }
}

_updateAnimation();

}

In our case, it would be something close to (I didn't test it) :

var newVelocity:b2Vec2 = passengers.GetLinearVelocity(); newVelocity.Add(new b2Vec2(0, _body.GetLinearVelocity().y)); passengers.SetLinearVelocity(newVelocity);

makc commented 11 years ago

Here is a test state to reproduce this:

package {

  import com.citrusengine.physics.box2d.Box2D;
  import com.citrusengine.objects.platformer.box2d.MovingPlatform;
  import com.citrusengine.objects.platformer.box2d.Hero;
  import com.citrusengine.core.StarlingState;

  public class TestState extends StarlingState {
    override public function initialize (  ) : void {
      super.initialize();

      var physics : Box2D  = new Box2D("box2d");
      physics.visible = true;
      add(physics);

      add(new MovingPlatform("moving", {x:200, y:600, width:140, height:95, startX:220, startY:600, endX:500, endY:170}))

      add(new Hero("hero", {x:300, y:450, width:60, height:135}))

    }
  }
}

Hero should land on very edge of the platform, and then fall off when it reverses the direction. The swf is set to 60fps, btw.

As @gsynuh I would like to see this fixed in MovingPlatform itself ;) There is a number of ways you could do that, so just pick one that works and easy to hack in.

gsynuh commented 11 years ago

Even though this might not be the best solution, it would be good to reset the passengers velocity to the new platform's velocity when it suddenly changes direction. That way, next frame will not make the passengers "jump", but already be "in phase" with the new direction/velocity of the moving platform. For this, something as simple as :

                    for each (var passengers:b2Body in _passengers)
                    {
                        passengers.SetLinearVelocity(velocity);
                    }

does it, in the update loop of MovingPlatform, after line 161 ( _forward = !_forward; ) .

I wish I knew how to attach a commit to a comment here, it would be easier to suggest larger changes.

alamboley commented 11 years ago

Hey, I've been able to reproduce the "bug".

I changed the class as @gsynuh said, it works great the first time, but the second time it falls :

else {

        //Destination is very close. Switch the travelling direction
        _forward = !_forward;

        for each (var passengers:b2Body in _passengers)
            passengers.SetLinearVelocity(velocity);
    }
}

_body.SetLinearVelocity(velocity);

I also tried to add this to the end of the update function, it was a bit better but not a success :

var passengersVelocity:b2Vec2;
for each (var passengers:b2Body in _passengers) {

    if (velocity.y > 0) {

        passengersVelocity = passengers.GetLinearVelocity();
        passengersVelocity.Add(velocity);
        passengers.SetLinearVelocity(passengersVelocity);
    }

}
makc commented 11 years ago

sorry I can't be much of help since I work with swc here. but why do you put it inside else {...} clause when the platform itself set velocity to its _body outside? apparently there is a number of cases where it is altered between lines 135 and 163, and passengers velocity change should be in sync with all of those cases, no?

gsynuh commented 11 years ago

your second try is much more logical than mine in fact.

Combining the two solutions, even if it sounds crazy, works nicely, I have been running the state for 4 minutes now, it doesn't look like it has moved at all. because I believe the conservation of momentum when it changes direction is still an issue unless

for each (var passengers:b2Body in _passengers)
            passengers.SetLinearVelocity(velocity);
    }

is still applied when _forward changes I guess.

gsynuh commented 11 years ago

@makc the "else" is not a problem, as this is the special case when the moving platform changes direction. being a special case it should be handled differently. in every other case what @alamboley did is perfect. I do think "resetting" the "momentum" of the passengers when changing direction is a logical thing to keep as this is where it all fails.

By the way, it's been running for a very long time now, still not out of the platform :D maybe we have a solution !

makc commented 11 years ago

"changing direction" is only a special case as far as I noticed the effect in this case. however, the actual case is "changing platform velocity instantly" - i.e. if the platform could double its speed in the same direction. I did not aim to understand what lines 135-163 do, but if they change the velocity - they do all participate in this "special case".

p.s.:

still not out of the platform :D maybe we have a solution !

I'm quite happy if so.

gsynuh commented 11 years ago

Right. For now MovingPlatform has linear speed so it may not be a solution if you create a moving platform that has easing effects on its velocity . Lines 135 to 163 are all assuming the moving platform will have a linear speed. In another case, it should be coded differently - and maybe be coded in another extending class.

gsynuh commented 11 years ago

sorry I think to be exact I should've said linear movement rather than linear speed.

alamboley commented 11 years ago

I've updated the class with @gsynuh code which combined both. https://github.com/alamboley/Citrus-Engine/blob/master/src/com/citrusengine/objects/platformer/box2d/MovingPlatform.as

I've to admit that combine both is a bit weird. I'm not sure to fully understand why it works. @makc the problem occurs only when the MovingPlatform change its direction, so it should be in the final else{...}, isn't it? @gsynuh indeed, the moving platform works only with a linear movement. Its class should be extended to implement easing effect. Thanks for your help guys!

gsynuh commented 11 years ago

Our hero here, has a velocity relative to the world. What should really be happening is to have its velocity relative to the platform always. - this is exactly what your code attempts to replicate.

if the platform moves up, we can jump in place, we land on the same spot (this is not the case if we jump exactly before the moving platform starts changing direction, in this case we simply jump off because the moving platform goes away from us while we are in the air).

However, when the platform moves down, the hero lands always slightly on the left. it's not logical at all, because at least we should land on the right .

In an attempt to hack away some weird solution, this came up as "working"

            //prevent bodies to fall if they are on a edge.
            var passengerVelocity:b2Vec2;
            for each (passenger in _passengers) {

                if (velocity.y > 0)
                {
                    passengerVelocity = passenger.GetLinearVelocity();
                    //passengerVelocity.Add(velocity);
                    passengerVelocity.y += velocity.y;
                    passenger.SetLinearVelocity(passengerVelocity);
                }

            }               

The Hero no longer drifts when jumping on the moving platform when its going down.

Let's not forget that one is no longer a passenger when one is not touching (even slightly) the platform so the fact that we are supposed to be focused on vertical velocity causing the problem is right, however the code that does make it all work feels like a bit of nonsense. here I just had a feeling that adding in the velocity.x to the passengerVelocity was wrong in the first place. I still don't get most of it though.

gsynuh commented 11 years ago

I don't want to go on about it for ages and spam your inboxes, but for future reference and other visitors that would come here, It's best to clarify what's happening as much as possible, also to help people that would try creating other types of platforms.

What I did right after I took a look at this issue in the first place was set the passenger's velocity to the platform's velocity on every update using

            for each (passenger in _passengers) 
                    passenger.SetLinearVelocity(velocity);            

What happened then was that it fixed the problem but your jumping height was restricted and you would move very slowly left or right, because in fact your 'initial' velocity was the platform's velocity so in order to jump as you would on the floor you needed more velocity impulse to counter the platform's forces, and the same for moving left or right.

Adding the velocity to the passengers velocity would fix the problem, but not for its passenger's x velocity because it would already be the same as the platform's velocity , and that's because of the friction !

That's why I think we only need to fix the y velocity on each frame to make the passenger "stick" to the platform on each frame, the physics engine then, thanks to the positive friction, will fix the x velocity by itself. If we would leave Add(velocity) then there would be twice as much x velocity to the hero than it's supposed to, and that's why he would drift left when jumping.

problem solved?

alamboley commented 11 years ago

Firstly, don't worry about my inboxes. It's an amazing feeling to see people interested in this engine :)

Setting a linear velocity on each update to the passenger, is very wrong because it would prevent objects to be able to move. Well, I was asking why we should removed the x velocity. According to Box2D Manual, friction is used to make objects slide along each other realistically. So it has an impact on the x-axis displacement. You had a good feeling ;)

I've made the update, so problem solved? ;)