4
\$\begingroup\$

What I'm trying to achieve is that the entity moves in a parabolic curve, and no this has nothing to do with gravity before you ask.

I give the entity a target position which it should move to, but not in a straight line, it should move to that target position in a parabolic curve.

I'll Show an example below:
example

It doesn't matter where the start or target it, I just want the calculate the proper velocity separately for x and y. so that the x, y of the entity arrive at the target position at the exact same time. My entity can move in 8 directions based on targetXY.

What I do is calculate the middle point for each x ,y I separately between startPositionXY and targetPositionXY. I increase the velocity until the middle point and after the middle point I decrease it. But it the Entity x and y do not reach the intented target at the same time instead one either x or y always gets there before the other depending on which direction it's moving in.

Here is what I have so far.

    public function update():void {

        if(!_hasInitialized){throw new ReferenceError("Fish.initialize must be called once before calling update");}

        determineDirection();
        determineVelocityBreakpoint();
        determineDistance();
        determineVelocity();
        move();
    }

    private function move():void {
        switch(this._direction){
            case "left":
                if(getX() > this.pos_target_x){
                    this. x -= velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                break;

            case "right":
                if(getX() < this.pos_target_x){
                    this.x += velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                break;

            case "up":
                if(getY() > this.pos_target_y){
                    this.y -= velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "down":
                if(getY() < this.pos_target_y){
                    this.y += velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "left-up":
                if(getX() > this.pos_target_x){
                    this.x -= velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                if(getY() > this.pos_target_y){
                    this.y -= velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "left-down":
                if(getX() > this.pos_target_x){
                    this.x -= velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                if(getY() < this.pos_target_y){
                    this.y += velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "right-up":
                if(getX() < this.pos_target_x){
                    this.x += velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                if(getY() > this.pos_target_y){
                    this.y -= velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "right-down":
                if(getX() < this.pos_target_x){
                    this.x += velocity_x;
                }else{
                    setX(this.pos_target_x);
                }
                if(getY() < this.pos_target_y){
                    this.y += velocity_y;
                }else{
                    setY(this.pos_target_y);
                }
                break;

            case "idle":
                break;
        }
    }

    /**
     * Determines the Direction
     */
    private function determineDirection():void {
        if( getX() as int == pos_target_x && getY() as int == pos_target_y){//Idle
            this._direction = "idle";
        }else{//No Idle
            if( getX() != pos_target_x && getY() == pos_target_y ){//Linear X

                if(getX() < pos_target_x){//right
                    this._direction = "right";
                }else if(getX() > pos_target_x){//left
                    this._direction = "left";
                }

            }else if( getX() == pos_target_x && getY() != pos_target_y ){//Linear Y

                if(getY() < pos_target_y){//down
                    this._direction = "down";
                }else if(getY() > pos_target_y){//up
                    this._direction = "up";
                }

            }else if(getX() != pos_target_x && getY() != pos_target_y ){//Diagonal

                if(getX() < pos_target_x && getY() < pos_target_y){//Right Down
                    this._direction = "right-down";
                }else if(getX() > pos_target_x && getY() > pos_target_y){//Left Up
                    this._direction = "left-up";
                }else if(getX() > pos_target_x && getY() < pos_target_y){//Left Down
                    this._direction = "left-down";
                }else if(getX() < pos_target_x && getY() > pos_target_y){//Right Up
                    this._direction = "right-up";
                }
            }
        }
    }

    /**
     * Determines when the velocity should be decreased.
     */
    private function determineVelocityBreakpoint():void {

        _mPointX = (this.pos_old_x + this.pos_target_x) / 2;
        _mPointY = (this.pos_old_y + this.pos_target_y) / 2;

    }

    private function determineDistance():void {
        //Distance for x, y combined
        this.distance_point = Math.sqrt((getX() - this.pos_target_x) *  (getX() - this.pos_target_x) + 
            (getY() - this.pos_target_y) *  (getY() - this.pos_target_y))

        this.distance_x = Math.sqrt((getX() - this.pos_target_x) *  (getX() - this.pos_target_x));
        this.distance_y = Math.sqrt((getY() - this.pos_target_y) *  (getY() - this.pos_target_y));
    }

    /**
     * Increases and decreased the velocity.
     */
    private function determineVelocity():void {
        switch(this._direction){
            case "left":
                if(getX() <= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                break;

            case "right":
                if(getX() >= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                break;

            case "up":
                if(getY() <= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }
                break;

            case "down":
                if(getY() >= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }
                break;

            case "left-up":
                if(getX() <= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                if(getY() <= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }
                break;

            case "left-down":
                if(getX() <= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                if(getY() >= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }

                break;

            case "right-up":
                if(getX() >= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                if(getY() <= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }
                break;

            case "right-down":
                if(getX() >= this._mPointX){
                    if(this.velocity_x > this.velocity_min){
                        this.velocity_x -= this.velocity_inc;
                    }
                }else{
                    if(velocity_x < velocity_max){
                        this.velocity_x += this.velocity_inc;
                    }
                }
                if(getY() >= this._mPointY){
                    if(this.velocity_y > this.velocity_min){
                        this.velocity_y -= this.velocity_inc;
                    }
                }else{
                    if(velocity_y < velocity_max){
                        this.velocity_y += this.velocity_inc;
                    }
                }
                break;

            case "idle":
                break;
        }
    }

    /**
     * Set the target position and start position
     */
    public function setTargetPosition(target_x:int, target_y:int):void {
        this.pos_target_x = target_x;
        this.pos_target_y = target_y;
        this.pos_old_x = getX();
        this.pos_old_y = getY();
    }
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Background info: line drawing algorithms

A similar problem to yours is the line drawing problem, i.e. how to draw a "smooth" line on a raster display (a display with discrete pixels). A famous solution is Bresenham's line algorithm (https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).

Its core idea is very simple. Let's start with only lines at an angle between 0 and 45 degrees. In this case, if we draw our line pixel by pixel, we have two options:

  • we draw the next pixel to the east of the previous pixel (E)
  • we draw the next pixel to the north-east of the previous pixel (NE)

The algorithm simply evaluates the explicit equation of the line in each step, and keeps track of the error (how far the previously drawn pixel's center is off the actual line y-position). If the error is bigger than half a pixel, its time to make a N-E move. If not, we just make an E move.

Lines of degrees over 45 degrees or below 0 degrees are simply handled by solving the base algorithm and mirroring the coordinates (x -> -x and/or y -> -y) and/or swapping them (x = y).

Background info: curve drawing algorithms

To rasterize a curve different from a straight line, you simply substitute the line equation for a different one. E.g. in your case you would use the equation of a parabola: y^2 = 4*a*x (see Makcheese's answer). The rest remains the same: keep track of the error and make E-moves or N-E moves depending on the error magnitude.

One important different, the slope of a parabola is not constant. As Bresenham's line algorithm only works for slopes from 0 degrees to 45 degrees, you'll need to split the problem in two: from the start of the parabola to the point where the slope becomes 45 degrees, and from that point to the end of the parabola.

Solution to your problem

I would use Bresenham's line algorithm with the equation of a parabola. You mentions your entity has 8 directions of movement, which is exactly what this algorithm does! If you would like 4 directional movement, you'd simple replace NE steps with a N and an E step in sequence.

You also mentioned wanting a specific timing. The time it takes to traverse the parabola will be determined by the amount of movements you make per unit of time (aka the speed). Assuming you want a constant x-speed, you simply wanna perform (x_end - x_start) / desired_time_units movements per time_unit.

\$\endgroup\$
0
\$\begingroup\$

You might benefit from knowing the equation of a parabola:

y^2 = 4ax,

where x = -a is called te directrix.

You know your current x, y, so you can easily calculate a, and can then calculate the required position of your object at each timestep.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .