Drawing Curves in GMS2

FactorX12

Member
Hey all,

I have searched and searched for some type of script that draws a curve using an x1, x2, y1, y2, and a beginning angle. I saw one for GMS1 posted 9 years ago, however, despite this I tried it (because why not?) but it obviously didn't work. If anyone could offer some sort of script that could do this, I would be ever so grateful.

Thanks in advance!
 
if you use a 3 point Bezier curve code you should be able to simply move the middle point out in a for loop until the line hits your actual middle point in a for loop, Ill see if I can get something working for you.
 
okay so some short explanations before is start, I have no mathematics knowledge on the math of bezzeir curves so I used a four point one instead of a 3 point one, you could make this code a little more efficient with a 3 point but this should work just fine. The method I used uses collision point with an object I called goal, its just an object with a 2x2 sprite for collision purposes Also Im posting a version that will work over a few steps so that you will understand what it is doing, then I will tell you how to make it instant drawing.
okay first a script called bezzier_four_point
Code:
///Bezier_curve(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y)
var t=.01//this is how many segments in your curve this currently makes 100 segments.
var p0x = argument0;
var p0y = argument1;
var p1x = argument2;
var p1y = argument3;
var p2x = argument4;
var p2y = argument5;
var p3x = argument6;
var p3y = argument7;
var px=p0x
var py=p0y
var prex=px
var prey=py

repeat (1/t)-1
{
    var tt  = t  * t;
    var ttt = tt * t;
    var l   = 1  - t; //Inverted
    var uu  = l  * l;
    var uuu = uu * l;
  
    //Calculate the point
    px =     uuu * p0x; //first term
    py =     uuu * p0y;
    px += 3 * uu * t * p1x; //second term
    py += 3 * uu * t * p1y;
    px += 3 * l * tt * p2x; //third term
    py += 3 * l * tt * p2y;
    px +=        ttt * p3x; //fourth term
    py +=        ttt * p3y;
  
    if set=1// if checkiing to transform
    if collision_line(prex,prey,px,py,goal,true,false)//it line is on the goal
    {
        set=2//set to done
        with a
        instance_destroy()
    }
    if set>0//if all points are done
    draw_line(prex,prey,px,py)//draw the line
    prex=px
    prey=py
    t+=.01
}
if set=1//if line needs transfomred
{
    point=point_direction(clk[0],clk[1],clk[6],clk[7])//angle between start and end
    pointmod=point_direction(clk[0],clk[1],goal.x,goal.y)//angle between start and goal
    if pointmod>point// move the middle points away at a 90 degree angle in the direction of the needed
    point+=90
    else
    point-=90
    clk[2]+=lengthdir_x(1,point)
    clk[3]+=lengthdir_y(1,point)
    clk[4]+=lengthdir_x(1,point)
    clk[5]+=lengthdir_y(1,point)
}
okay I know that's super big and scary the good news is the rest of my example is super thin and is basically just a test setup to draw_lines and confirm it works. so in any object
create
Code:
for (i=0 i<8 i++;)
{
clk[i]=0// a loop for the x and ys of the bezzier curve
}
count=0//help to translate a four point curve to 3 points
set=0// 0 is not set 1 is set but not transformed 2 is transformed
a=-4
step
Code:
if mouse_check_button_released(mb_right)
{
    if count=2//set point 3 to between middle clk and 4 which is last clk
    {
    clk[4]=(goal.x+mouse_x)*.5
    clk[5]=(goal.y+mouse_y)*.5
    clk[6]=mouse_x
    clk[7]=mouse_y
    set=1
    }
    if count=1//set point2 to between middle clk and first clk
    {
        a=instance_create(mouse_x,mouse_y,goal)
        clk[2]=(clk[0]+goal.x)*.5
        clk[3]=(clk[1]+goal.y)*.5
    }
    if count=0//set first point at first clk
    {
        clk[0]=mouse_x
        clk[1]=mouse_y
    }
    count++
}
if mouse_check_button_released(mb_left)//reset line info
{
    for (i=0 i<7 i++;)
    {
        clk[i]=-1
    }
    count=0
    set=0
    if a!=-4
    with a
    instance_destroy()
    a=-4
}
and finally draw
Code:
bezzier_four_point(clk[0],clk[1],clk[2],clk[3],clk[4],clk[5],clk[6],clk[7])
for (i=0 i<8 i+=2;)
{
    draw_circle(clk[i],clk[i+1],5,true)//draw_where the effecting points are
}
now to make this code adjust the line instantly one would simply put the script inside a do until set=2 or set=0 in the draw and change the draw line in the script that should make the code loop until the line is adjusted
 
Last edited:

FactorX12

Member
Wait a minute, GMS1 BETA came out only 8 years ago. Nine years ago it was GM8.1, which does make a huge difference.

You should post the link to the code you found anyway.
okay so some short explanations before is start, I have no mathematics knowledge on the math of bezzeir curves so I used a four point one instead of a 3 point one, you could make this code a little more efficient with a 3 point but this should work just fine. The method I used uses collision point with an object I called goal, its just an object with a 2x2 sprite for collision purposes Also Im posting a version that will work over a few steps so that you will understand what it is doing, then I will tell you how to make it instant drawing.
okay first a script called bezzier_four_point
Code:
///Bezier_curve(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y)
var t=.01//this is how many segments in your curve this currently makes 100 segments.
var p0x = argument0;
var p0y = argument1;
var p1x = argument2;
var p1y = argument3;
var p2x = argument4;
var p2y = argument5;
var p3x = argument6;
var p3y = argument7;
var px=p0x
var py=p0y
var prex=px
var prey=py

repeat (1/t)-1
{
    var tt  = t  * t;
    var ttt = tt * t;
    var l   = 1  - t; //Inverted
    var uu  = l  * l;
    var uuu = uu * l;
 
    //Calculate the point
    px =     uuu * p0x; //first term
    py =     uuu * p0y;
    px += 3 * uu * t * p1x; //second term
    py += 3 * uu * t * p1y;
    px += 3 * l * tt * p2x; //third term
    py += 3 * l * tt * p2y;
    px +=        ttt * p3x; //fourth term
    py +=        ttt * p3y;
 
    if set=1// if checkiing to transform
    if collision_line(prex,prey,px,py,goal,true,false)//it line is on the goal
    {
        set=2//set to done
        with a
        instance_destroy()
    }
    if set>0//if all points are done
    draw_line(prex,prey,px,py)//draw the line
    prex=px
    prey=py
    t+=.01
}
if set=1//if line needs transfomred
{
    point=point_direction(clk[0],clk[1],clk[6],clk[7])//angle between start and end
    pointmod=point_direction(clk[0],clk[1],goal.x,goal.y)//angle between start and goal
    if pointmod>point// move the middle points away at a 90 degree angle in the direction of the needed
    point+=90
    else
    point-=90
    clk[2]+=lengthdir_x(1,point)
    clk[3]+=lengthdir_y(1,point)
    clk[4]+=lengthdir_x(1,point)
    clk[5]+=lengthdir_y(1,point)
}
okay I know that's super big and scary the good news is the rest of my example is super thin and is basically just a test setup to draw_lines and confirm it works. so in any object
create
Code:
for (i=0 i<8 i++;)
{
clk[i]=0// a loop for the x and ys of the bezzier curve
}
count=0//help to translate a four point curve to 3 points
set=0// 0 is not set 1 is set but not transformed 2 is transformed
a=-4
step
Code:
if mouse_check_button_released(mb_right)
{
    if count=2//set point 3 to between middle clk and 4 which is last clk
    {
    clk[4]=(goal.x+mouse_x)*.5
    clk[5]=(goal.y+mouse_y)*.5
    clk[6]=mouse_x
    clk[7]=mouse_y
    set=1
    }
    if count=1//set point2 to between middle clk and first clk
    {
        a=instance_create(mouse_x,mouse_y,goal)
        clk[2]=(clk[0]+goal.x)*.5
        clk[3]=(clk[1]+goal.y)*.5
    }
    if count=0//set first point at first clk
    {
        clk[0]=mouse_x
        clk[1]=mouse_y
    }
    count++
}
if mouse_check_button_released(mb_left)//reset line info
{
    for (i=0 i<7 i++;)
    {
        clk[i]=-1
    }
    count=0
    set=0
    if a!=-4
    with a
    instance_destroy()
    a=-4
}
and finally draw
Code:
bezzier_four_point(clk[0],clk[1],clk[2],clk[3],clk[4],clk[5],clk[6],clk[7])
for (i=0 i<8 i+=2;)
{
    draw_circle(clk[i],clk[i+1],5,true)//draw_where the effecting points are
}
now to make this code adjust the line instantly one would simply put the script inside a do until set=2 or set=0 in the draw and change the draw line in the script that should make the code loop until the line is adjusted
Wow, thanks. However this was obviously very hastily coded up, I got a ton of errors because of incorrect coding and I had to do a bunch of editing to the style to make it match mine. But after all of that, it worked.upload_2019-8-29_22-22-44.png
uh...where is it? oh there it is
upload_2019-8-29_22-23-41.png
very very tippy top left of the photo. I apologize, but I just have no idea how the heck to even work this thing. I tried changing the variables in the actual script, but nothing happened. that combined with the inconsistency of the coding style renders this code basically unusable. I once again apologize just in case anything was offensive, I promise it wasn't intended.
 

Attachments

TheouAegis

Member
for (i=0 i<8 i++; )
I know for loops are pretty flexible, but you need the semicolons between the first two parts. The final semi-colon is optional.
Code:
for (i=0 ; i<7 ;  i++;)
         ^     ^     ^
         required  optional
That's the only real issue i saw in his code. It's not unusable, you just need to know GM syntax to fix it. lol

Also his i should be temporary, but that's a nitpick.

To recap the rules of a FOR loop:
  1. All white space is ignored.
  2. The parentheses are required.
  3. The first 2 semicolons are required, the third is not (more on this later).
  4. The format of a FOR loop is for(A;B;C) where A,B and C are defined as:
    • A must be a single variable declaration. The following would be valid:
      i = 0;
      var i = 0;
      var i = 0, k = 4;
      score++;

      Since score was already initialized, modifying it counts as a declaration. The var keyword counts as the start of a declaration, so even if multiple temporary variables are defined in it, separated by commas, it still counts as a single declaration. It is because of this "variable declaration" requirement that A cannot be a code block. Don't forget the semicolon afterward.
    • B can be any single expression as long as it can be interpreted as TRUE or FALSE. In other words, any of the following would work:
      i<10;
      k;
      current_second != 0
      place_meeting(x+hspeed,y,obj_wall);
      event_user(0);

      !mouse_check_button_pressed(mb_left)

      Now, I should point out that while all of those examples would work, they wouldn't all work as intended. The first two are what people use normally, so obviously they work as you'd expect. The third example (current_second) also works, albeit somewhat randomly (it would depend what the current time is when the FOR loop is called) and would have the drawback of temporarily freezing the program -- not good on some OSes. The fourth example (place_meeting) is a mixed bag. If there is no collision at all, the loop would never run past the variable declaration, whereas if there is a collision, it would run until there isn't one any more (e.g., if within the loop you changed hspeed, x, y, or destroyed the offending obj_wall so that there was no longer a collision). The fifth example (event_user) would be pointless, since user events don't return values, thus resulting in FALSE, causing the loop to terminate prematurely. The last example would just freeze up the game (or not run at all if the loop was called right when the mouse button was clicked), since I/O status is only updated after the Begin Step Event. Also, don't forget that semicolon afterward.
    • C must be a code block. This means it can be practically anything except an incomplete expression. So (get ready to be blown away) for example:
      i++
      k = 4
      { i++; k=4; }
      event_user(0)
      with instance_create(x,y,obj_bullet) {speed=4; direction=image_angle}
      { i++; with instance_create(x+i*8,y,obj_bullet) hspeed = i / 8; }

      And as I said earlier, you don't need to stress about the semicolon after this part, because GM doesn't care.
    • Code order is A --> B --> main code --> C --> B --> main code --> C and so forth.
 
Last edited:
Top