Perfect Tween Type Object Rotation and Falling (Tetris, Gear Platforms, etc)

tagwolf

Member
GM Version: 2.X
Target Platform: ALL
Download: N/A
Links: N/A

Summary:
This tutorial will get you perfect, snappy style, tween-ish with rebound object rotation. This is really useful and fun for Tetris-type games and would look great in 2d platformer games on platforms that rotate too.

UPDATE: Added object falling code too for those who are making a puzzle-type game. You can leave it out if you have other uses. This isn't supposed to be a full "Tetris style" tutorial. BUT, if people express interest in getting the rest of the code as I progress let me know. I'm trying to not make it too complicated for folks who want to use just the rotate tween stuff unless there's a demand for it so let me know and I'll accommodate.

Tutorial:

* Create a simple sprite to rotate. A 64x64 white square is fine for testing.

* Set the sprite's origin to MIDDLE CENTER (For other game types and stuff you could put it anywhere. This is the "axis" the object will rotate around.)

* Create an obj_whatever and assign the sprite to it.

* In obj_whatever's create event, put in the following:
GML:
right_pressed = false;
left_pressed = false;

//make sure degrees are multiples of each other
rotate = false;
rotate_increment = 90;
rotate_rate = 15;
rotate_overshoot = 20; //degrees to overshoot
rotate_overshoot_rate = 2; //rate to bounce back at

falling = true;
fall_goal = y + sprite_height;
fall_speed = 2;
fall_pause_time = 60;
fall_pause = fall_pause_time;
* In obj_whatever's step event, put the following:
GML:
//prevent keyhold repeat
if (keyboard_check_pressed(vk_left)) {
    left_pressed = true;
}

if (keyboard_check_pressed(vk_right)) {
    right_pressed = true;
}

//initiate rotate with arrows
if (keyboard_check_released(vk_left) && left_pressed) {
    left_pressed = false;
    rotate = 1;
    degrees = rotate_increment + rotate_overshoot;
}

if (keyboard_check_released(vk_right) && right_pressed) {
    right_pressed = false;
    rotate = -1;
    degrees = -rotate_increment + -rotate_overshoot;
}

if rotate <> 0 {
    // do tween type rotate here
    // left
    if rotate == 1 {
        if degrees > 0 {
            image_angle += rotate_rate;
            degrees -= rotate_rate;
        }
        if degrees <= 0 {
            degrees = rotate_overshoot;
            rotate = 2;
        }
    }

    // right
    if rotate == -1 {
        if degrees < 0 {
            image_angle += -rotate_rate;
            degrees += rotate_rate;
        }
        if degrees >= 0 {
            degrees = rotate_overshoot;
            rotate = 2;
        }
    }

    // overshoot
    if rotate == 2 {
        rotate_goal = round(image_angle / rotate_increment) * rotate_increment // find closest angle
        //show_debug_message("goal: " + string(rotate_goal));
        if image_angle > rotate_goal { image_angle -= rotate_overshoot_rate; }
        if image_angle < rotate_goal { image_angle += rotate_overshoot_rate; }
        if image_angle == rotate_goal { rotate = false; }
    }

}

//show_debug_message(string(image_angle));

// check if rotation if > 360 or  < 0 if not rotating currently and fix it back within range
if (image_angle < -360 && rotate == false) {
    image_angle += 360;
}
if (image_angle >= 360 && rotate == false) {
    image_angle -= 360;
}

// "falling code" here.
// want it to fall slowly one grid level down and pause there for X time. the repeat
if fall_pause < 0 {
    falling = true;
} else {
    fall_pause--;   
}

if falling {
    if y < fall_goal {
        y += fall_speed;
    }
    if y >= fall_goal {
        fall_goal = y + sprite_height;
        fall_pause = fall_pause_time;
        falling = false;
    }   
}
* Place obj_whatever in your room

* Test with the left and right arrow keys

* Profit!
 
Last edited:
It's debatable if this is better in terms of performance, but your code could be much simpler.

GML:
var dir = round(point_direction(x, y, whatever.x, whatever.y))
var ang_diff = angle_difference(image_angle, dir)
var add_to = 0;

if ang_diff != 0
{
ang_diff = abs(ang_diff)
add_to = min(rotate_rate, ang_diff)
}

//prevent keyhold repeat
if (keyboard_check_pressed(vk_left)) 
{
left_pressed = true;
}
if (keyboard_check_pressed(vk_right)) 
{
right_pressed = true;
}

//initiate rotate with arrows
if (keyboard_check_released(vk_left) && left_pressed) 
{
left_pressed = false;
image_angle += add_to
}
if (keyboard_check_released(vk_right) && right_pressed)
 {
right_pressed = false;
image_angle -= add_to
}

if image_angle < 0
{
image_angle += 360
}

if image_angle > 360
{
image_angle -= 360;
}
If this aiming at something then presumably you can't do so without using point_direction, and angle_difference is a modest cost on top. But it returns information that I think covers much of what you are checking here: namely a positive or negative value which is either clockwise or anticlockwise for the closest angle between two directions.

You can use this to influence rotation, or in the case above I'm just checking that there is any difference between image_angle and the direction of whatever. 'add_to' gets the minimum value of either 'rotate_rate' or the angle_difference, so there is no overshoot to check. It is either pointing at it, or it isn't, and will fit the situation regardless of the outcome. I'm not doing it as automatic here, because of your keyboard checks controlling the rotation, but you could easily change it to be less deliberately structured. In this case though it only needs to know whether rotate_rate is smaller than the angle difference, and that the angle difference is larger than 0.

Then you have your keyboard check where the value of 'add_to' is added or removed from image_angle. 'add_to' can be zero, and so nothing will happen, or it could be a positive value (which it will be, as I set it to absolute using abs) and it is which key that is released that determines how the value is applied.

Because angle_difference doesn't have a problem dealing with angles over 360, or under 0, I have included "resetting" image_angle to be between those ranges. As point_direction only deals between 0 to 360, this should keep the comparisons correct.

I think this would work, and I can't see anything it's missing from your code, but I could be wrong :)
 
Top