Game Maker question: Tweening

edited in Questions and Answers
Hi guys :)

If you remember my little dwarf guy (attached), I'm now going to try and animate his bits instead of as a spritesheet, so that that I can build the base animation, and swap out sprites to give him different gear...

So I started looking up tweening in GMS... And found... nothing. Well, nothing that wasn't ancient and/or broken.

So I started writing my own code to stick to my individual objects, one to initialise a tween, one to step through and check if there's a tween to be run, and over time tween the object towards the destination, and another one to call the first one multiple times so that a loop can be achieved... etc.

BUT WAIT! In the spirit of not reinventing the wheel, especially since I'm a noob ass coder, can anyone point me to methods/libraries/code that's already been done for tweening and animating in GMS?
klepto_01.gif
128 x 128 - 18K

Comments

  • This program excites me to no end, currently in development but you can use the alpha builds for the time being. http://www.kickstarter.com/projects/539087245/spriter
  • I saw that before, it looks awesome, but to my understanding Spriter takes sprites as parts, then you tween it in the program, and it spits out cool sprites.

    The problem is that for me, the game I'm making will have many helmets, many weapons, many amulet things, etc. So, I need each part to be dynamically replacable within Game Maker, or else I'd have to make a trillion sprites based on possible configurations @_@

    So...... I'm looking for a means to tween within game maker, instead of a means to create sprite sheets.

    Make sense?
  • Right. I can't think of anything that can do this sort of stuff easily and simply off the top of my head in GM (although that doesn't mean that I've looked exhaustively, I haven't). All my animation stuff has been more like adding bones to 2D sprites with pivot points, so this is a new thing for me to think about, here goes:

    It seems like what you want is a series of relative-to-parent offsets for "child" sprites per frame of animation of a parent object. That wouldn't be too hard to set up manually, basically you want an x,y co-ordinate per attachment point per frame, which could be done a variety of ways. Then you need a list of sprites to put at those offsets as the animated object moved around and went through frames.

    A big part of implementing this right is going to be parenting and inheritance in GM. Basically you don't want to be copy-pasting code into each object, what if that code needs to be changed later on? It's a lot easier and simpler to set up one parent object called TweenedAnimatedObject and then just having your PlayerCharacter inherit from TweenedAnimatedObject (by setting that as its parent) and going from there. You'd probably also need to build a TweenAnimatedSprite that your helmets and axes and shit would inherit from too.

    So, the fast code way:
    Sets of arrays, tweenOffsetX[anchor point, frame] which stores the x location and tweenOffsetY[anchor point, frame] which stores the y location relative to the main sprite's origin to display the tweened sprite at. Another array for the tweened sprites themselves: tweenedSprites[anchor point] that returns either the sprite_index of the thing to draw (drawing would be handled by the TweenedAnimatedObject in that case), or a pointer to the object itself, if you want the object to be something you can manipulate and do stuff like change colour on, etc. If you want to change the sprite for a helmet, you'd just change what tweenedSprites[whatever the helmet anchor point was] pointed to. Easy!

    This isn't super friendly to the animator, because you'd have to sit there and populate the tween offset arrays by hand in the PlayerCharacter's creation event. So any changes to the animation mean sitting there comparing numbers and pixel locations, which would be annoying. Plus you'd have to do this for each thing you wanted to animate extra as well. But it'd work!

    The easy animatable way, but slow:
    Instead of storing your offsets per frame in hand-populated arrays, you could store them in the sprite itself! Basically, pick a crazy strange pixel colour for each anchor point and then just smack that on the main animation sprite where you want the tweened thing to appear. So each time you draw the main sprite, you'd have to step through it pixel by pixel, checking the colour of each against your list of pixel colours per tweened sprite. So you'd only have the one array: tweenedSprites[anchor pixel colour] - which then draws the same way as above (personally I prefer pointing to objects because it's then really easy to set up offsets for the tweened sprites without having to manage more arrays, so that you could just have the "center" point for your helmet and then different helmet sprites could be different sizes and just set up via their sprite origins).

    This has the wonderful benefit of allowing you to change your animations super easilty without going mad and staring at numbers. Going through every pixel of your main sprite every frame is slow though, so you might not want to have a million TweenedAnimatedObjects in your room at the same time ;)

    The easy AND fast way:
    Step through every pixel of every frame of your main sprite at object creation time, storing the offsets of your coloured anchor point pixels in offset arrays, much like the fast way. Then you're not looking up pixel colours each frame. This is a very easy thing to add on top of the easy animatable way above, so I'd go with easy animatable and then do this if your game got slow :)

    Hope that helps. Definitely an interesting toy problem.
    Thanked by 2Karuji Tuism
  • @dislekcia thanks a trill for the very detailed suggestions, I was going to do something like the fast code way, and potentially destroy all my brain cells in the process of working it out...

    The third method looks good!!! I can barely understand it (haven't learned how to read graphics pixel for pixel from a sprite yet) but I think I can get to it if I give it a bash.

    Also, what I wanted to achieve in the end was to tween with pixels bigger than the pixels of the art - so for example my art has been made at 4x4 pixels, but I wanted to tween them so they don't jump 4x4 per movement - so that means I'd either have to manually fill in the inbetweens, or create some kind of smoothing tweening method.

    But yeah I think I can totally add that to the methods you described :) AWESOME :D Will report back when/if I crack it/hit a brick wall :)
  • @Tuism: Hmm, ok, so your initial sprites are at 1px res, but you're then upscaling them in GM? If you're working at 4x4 pixels then you still need a definite anchor pixel (just one) which you can then animate per frame.

    Or, if you're wanting to run slow frames, you could store the anchor position as well as its previous position and then just interpolate between those for the current frame fraction you're drawing... That probably sounds like greek, sorry ;)
  • edited
    Actually, I know exactly what you're talking about, that's about the way I want to do it :) I *was* going to upscale all the stuff from within Game Maker, thinking that it'll save some memory, but it just got too irritating from management of the asset point of view, and I was worried that the interaction between the "upscaled sprites" and my code, whatever I do, might be super complex and then I'd just get confused...

    So I decided to manually upscale in PS first.

    I could make the thing run through the image and pick the top left (first pixel it runs across) of colour x, then use that as anchor point per part.

    Apparently draw_getpixel is super slow (according to the help file), but I'd only have to run it once, I think. Gonna have to figure out this array and ds_list type stuff, as well as saving variables to file, etc. Will give it a shot :)

    Then the animation will interpolate between the keyframes with linear smoothing. That shouldn't be hard :)
  • Wow, good luck with this. I'm actually fiddling about with upscale stuff in another proto right now (using the same "screw it, I'll just PS it first" route for pretty much the same reason).

    But I don't wanna touch that tween stuff with a ten-foot barge pole. I did *something* like it once or twice before, but the methods I used were always, well, a LOT of work (that, and I realised that I was thoroughly over doing all this animation stuff anyway, because AAARGH art).

    I, uhhh ... don't quite know where I'm going with this, sorry. I'm pretty sure this is just a "feel ya, bro" post more than anything actually helpful (though I can at least confirm that drawpixel stuff IS damn slow, but probably quite fine for the frequencies you'll be operating at).

    Hey, you wanna know how to do a hex grid in GM? That's all I've got. Found out a couple of nights ago and I was stoked.
  • edited
    What's a hexgrid? *googles* Ahhhh you mean like literally a hex grid? :P I thought it was some kind of way of storing hexxes in array and drawing sprites with code..... Dunno why I'd do that to myself :P

    I'm currently banging my head against this code. It looks right, and I can say with experience that it runs DAMN slow (I've tried to run it for 1 pixel over 10 frames and it takes like 4-5 seconds @_@

    Once it's figured out I'll store the array in a file and read it out of there, or at least try to.

    CODE EFFING CODE EFFFFF :P

    I've been banging my head against this for the better part of today, and I've reached what I think is an impasse...

    Can anyone please throw an eye over this and tell me why it's not giving me values? @_@

    I've got anchor_info_step on the 10 frame animation that has the coloured dots.

    i = 1;
    while (i < image_number)
        {
        draw_self();
        var check_y;
        for (check_y = 1; check_y < sprite_height; check_y+=1)
            {
            var check_x;
            for (check_x = 1; check_x < sprite_width; check_x+=1)
                {
                check_pixel = draw_getpixel(check_x,check_y);
                if (check_pixel == $FF9E14)
                    {
                    global.tween_backpack_x[i] = check_x;
                    global.tween_backpack_y[i] = check_y;
                    check_x = sprite_width;
                    check_y = sprite_height;
                    }
                }
            }
        i += 1;
        image_index += 1;
        }  
    
    
    room_goto(room0);


    Then I have code that reads it back and moves the backpack around according to x and y of the array. I know it works cos when comment the above out and manually populate global.twee_backpack_x and y, it does move about.

    x = global.tween_backpack_x[anim_step];
    y = global.tween_backpack_y[anim_step];
    anim_step += 1;
    if (anim_step == 11)
        {
        anim_step = 1;
        }


    Problem is when I run this, the code runs, the screen is however black, and when it gets through it to room0 (where the game happens), it immediately throws an error and tells me it can't find global.tween_backpack_y[i]

    Error:

    VMError!! Occurred - Push :: Execution Error - Variable Get tween_backpack_x
    at gml_Script_backpack_draw_step (line 0) - x = global.tween_backpack_x[anim_step];
    ###############
    Self Variables :
    anim_step = 1
    Global Variables :
    Are there special rules for using draw_getpixel I'm not understanding? Must it be used a draw event? If so how the heck do I do that... Just call the same code from a draw event?

    Do I need to manually let the sprite actually run so that it updates the screen so it works? Cos right now it's not showing the sprite on screen, and I read that draw_getpixel gets the value from the screen.....

    The sprite sheet is attached, 128x128 - 10 frames....

    HEADACHE @_@ And thanks guys (esp @dislekcia) for helping thus far!
    anchor_1.png
    2560 x 128 - 3K
  • edited
    Well, your error is happening because you're storing something in global.tween_backpack_x like it's a variable, but then you're accessing it like it's an array... The assignment line in your pixel reading for loops should be
    global.tween_backpack_x[i] = check_x;


    Also I note that you're never going through other frames, only the first one, and exiting straight to room0 after it's done that, so global.tween_backpack_x[1] (because you start i at 1, not 0) is the only array slot that will have data in it when you're running that drawing loop.

    Finally, yes you're going to have to draw your sprite to the screen so that draw_getpixel has something to reference. You should be able to draw ok outside the draw event, it'll just never appear because the draw event clears the screen when it starts going through all the objects in your game. But if you do a draw_sprite call and then do the getpixel stuff, it should have colour info on the "screen" memory space for you. Just make sure that your sprite is actually appearing in the right place and not oddly offset or anything. Personally, I'd create a surface (think of surfaces as intermediary textures or "screens" that you can then either draw or do stuff to after the fact) draw the sprite there and use surface_getpixel instead, just to be on the safe side.
  • Well, your error is happening because you're storing something in global.tween_backpack_x like it's a variable, but then you're accessing it like it's an array... The assignment line in your pixel reading for loops should be
    global.tween_backpack_x[i] = check_x;
    For some bizarro reason my code hat I pasted tossed out the square brackets... I was indeed using it like that, so thank goodness for that :) I've edited my original post to be accurate to what I have. And the room_goto is sitting outside the loop through all the frames as far as I can tell, could be another pasta fault :(

    Good to know I'm not derping on those things though, then.

    Gonna try the draw_sprite call, and if I haven't lost my mind I'll try learn about surfaces. Apparently it's very useful (google finds me nice introduction but never really lets me take them home)

    THANKS for the clue! :D
  • Also, I have no idea if global variables persist between room changes. You might want to create an object called InfoStore mark it as persistent (which means room changes won't kill it) and then access these arrays via InfoStore.backpack_tween_x, etc. (If you try to access a type via a dot operator in GM it will default to being a pointer to the first object of that type in your game, useful)
  • Oh no worries, globals definitely survive room shifts, it's my favourite method of keeping data alive in GM.
  • Yep and I googled and found the same result, thanks guys :)
    Researching surfaces...
  • OK it's not working, draw events and methods have always been a mystery to me, and apparently still are. Can't get surfaces to work, can't get draw_sprite, surface_getpixel or draw_getpixel to work in conjunction with stuff, don't know where to put draw events, dunno how to interweave between draw calls and not-draw calls....

    I'm going with manually putting values in @_@ It would have been done by now if I did @_@

    Thanks for the assist, I'm not cut out to be a coder @_@
  • edited
    This morning, GMS told me to update. For reasons unknown, as I usually don't, I started skimming the release notes of this latest update on GMS.

    Guess what I saw, 4 lines down, under "Fixed bugs"
    ■06839 Red and Blue are the opposite when using draw_getpixel()
    image
    Thanked by 1Nandrew
  • *Sends Tuism electronic pat on back*
  • Noob question about easing formula:

    I use this really easy one to do a ease out:

    x += (destination_x - x)/2

    However I can't find a similar simple way to do an ease in... Googled like crazy but I don't understand all that quadratic and whatever stuff. Is there a way to do an ease in simply?
  • Ok, I'll try to help. C# has a lot of built in tweening stuff(apparently) so I just went and looked at the formulas they used to get it done. The one I'm showing you is called circleEasing.

    The formula goes like this : y = 1-sqrt(1-t*t)

    For EasingIn : 0 <= t <=1
    For EasingOut : -1 <= t <= 0

    Is that of any help?
  • ok let me try to wrap my head around this...

    y = 1-sqrt(1-t*t)

    I assume t = time, but where's destination? Could you ease in/out without knowing the destination?

    And I don't understand the greater and less than thing @_@
  • That's for a parametric representation of the distance between the points. So y goes from 0 to 1, 0 being the starting point and 1 being the ending point. So to get your current position you'd do something like (and note that I'm changing y to something that makes more sense as a variable name):

    //Psuedocode, because I like explaining the type of variables used
    //Easing function, probably a script in GM:
    function EaseValue(float time)
    {
      float fractionalDistance
      fractionalDistance = 1 - squareRoot(1 - (time * time))
      return fractionalDistance
    }
    
    //Movement calculation:
    fractionalDistance = EaseValue(totalTimeSinceStartOfEasing / totalTravelTime)
    x = startX + (fractionalDistance * (destinationX - startX))
    y = startY + (fractionalDistance * (destinationY - startY))


    The whole 0 <= time <= 1 thing means that time is between 0 and 1, and can be either 0 or 1 (we call that inclusive).

    So basically, you're doing the following: Calculating a percentage of how far along the path of motion you should be between the start and the end point. That is the same for x, y, z or however many dimensions you happen to be working in... Then you start and the start position and add the distance between the start and end positions, multiplied by the fraction you want to be at now.

    It does have some issues, compared to the function you posted above: You have to know how long you want the motion to take and how long since you started the motion; On the flipside though, your formula above will always take the same amount of time to get to the point where the sqrt doesn't make any difference to the perceived distance traveled, so it'll move super fast from one side of the screen to the other and super slow from something 5px away.

    Of course, none of this helps you decide how to add easing in and out. For that, you have to split the motion into two distinct steps: start, end and halfway. Calculating halfway is easy: halfwayX = (startX + endX) / 2
  • ok I think I get the time thing... So I basically have to advance the time variable with the steps that's being taken, so before that I have to work out how many steps I want this action to take.

    I'll give it a shot... When my brain has sufficiently sponged up enough to.

    So the halving it all the time method that I had was the lazyman's easing, and that format won't be able to work for anything besides the half-life way of easing? I'm guessing that's so :P
Sign In or Register to comment.