F
Felipe Rybakovas
Guest
Hey That´s very similar from what I´m doing... but I have no control on the index... What i generally do is put a alarm for the animation on some casesSure, but it's probably less cool than I made it sound.
Technically there are 2 (pretty different) animation systems; the one for the Player, which is all about messing around with sprite offsets and rotation, mostly based on movement and mouse position.
Gonna spoiler the rest because it's big-ish.
As for the code that animates the player sprites, It's mostly a lot of stuff like this:
All hand animations are run by this thing below, which basically tweens the hand positions to points that are stored in a bunch of Ds_queues, and goes to the next queue when its got to where it's supposed to be. The Queues are filled by animation script that are called by swinging a weapon or some other trigger.Code:// (adding some extra comments so you know what does what) // facingH = 'horizontal facing' ( 1 or -1) (this sets our image_xscale when we draw) // facingFB = 'facing Front/BACK' (0 or 1) (indexes an array that holds front/back versions of the playr sprite.) // z = Fake z axis variable. Basically it just gets added to the y axis in most case to simulate going 'up' when the player jumps. Mostly used for drawing. But it also gets used to check against the 'height' of blocker objects, which the player can get up on. if isPlayerControlled // if you're possessed by the player, orient body towards mouse. { if mouse_x < x {facingH=-1} else{facingH=1;} if mouse_y < y-70+z{facingFB=1} else{facingFB=0;} } else // Facing for AI ----------------------------------------------- { if instance_exists(targetID) //if there is a target, face target. { if targetID.x < x {facingH=-1} else{facingH=1;} if targetID.y < y+z{facingFB=1} else{facingFB=0;} } else //if not, face where you are moving to. { if point_distance(x,y,moveTargetX,moveTargetY) > 5 // only update if you are moving. { if moveTargetX < x {facingH=-1} else{facingH=1;} if moveTargetY < y-70+z{facingFB=1} else{facingFB=0;} mouseDir = point_direction(x,y-70+z,moveTargetX,moveTargetY-70) } }
====================================================================================================
====================================================================================================Code:// ARM ANIMATION QUEUE --------------------------------------------------------------------------------------- var i = 0; repeat(2) // do for both hands. { if handActionDone[i] // ((huh.. okay I just noticed this.. it's wierd and I don't know why it's here..but I'm afraid to remove it now... )) if !ds_queue_empty(dsqHandRotationTarget[i]) { handRotationTarget[i] = ds_queue_dequeue(dsqHandRotationTarget[i]) handRotationTween[i] = ds_queue_dequeue(dsqHandRotationTween[i]) handXoffsetTarget[i] = ds_queue_dequeue(dsqHandXoffsetTarget[i]) handYoffsetTarget[i] = ds_queue_dequeue(dsqHandYoffsetTarget[i]) handTween[i] = ds_queue_dequeue(dsqHandTween[i]) handAnimTimer[i] = ds_queue_dequeue(dsqHandAnimTimer[i]) handActionDone[i]=false; } else { // figure out what your idle position should be, and go back to it. // I snipped this because it makes no sense without knowing what the variables involved are. } // Next is a section that tweens the variables you see above. --------------------------------------------------- // and finally down here is another section that checks if the hand position and rotation are close enough to their destination to call it good. If they are, handActionDone[i] = true; and hands will tween to their idle positions.
Enemies use something a lot simpler, essentially I just have a ton of 2D arrays filled in with sprite info. ex:
==========================================================================================================================================Code:var _sprF = spr_slime_idle_0; // 'sprite Front' sprite to use to initialize ALL front sprite entries. var _sprB = spr_slime_idle_0; // 'sprite Back' sprite to use to initialize ALL back sprite entries. // IDLE ANIMATIONS -------------------------------------------------------------------------------------------------------------------- animIdle[0,0] = _sprF; /**/ animIdle[0,1] = _sprF; /**/ animIdle[0,2] = _sprF; /**/ animIdle[0,3] = _sprF; /**/ animIdle[1,0] = _sprB; /**/ animIdle[1,1] = _sprB; /**/ animIdle[1,2] = _sprB; /**/ animIdle[1,3] = _sprB; /**/ // MOVE ANIMATIONS --------------------------------------------------------------------------------------------------------------------- animMove[0,0] = _sprF; /**/ animMove[0,1] = _sprF; /**/ animMove[0,2] = _sprF; /**/ animMove[0,3] = _sprF; /**/ animMove[1,0] = _sprB; /**/ animMove[1,1] = _sprB; /**/ animMove[1,2] = _sprB; /**/ animMove[1,3] = _sprB; /**/ //and many others...
I'm just initializing default variables above... In a real case at least some of these array locations would have different sprites in them. But it's important that all they arrays be filled by -something-. I initialize everything to the idle anim, so if an animation that doesn't really exist gets called, it just plays the idle and isn't tooo noticeable.
Anyway. in this case, slimes don't really have a 'front' or 'back', so everything gets the same sprite for both categories. The First index of the 2D array corresponds to Front or Back (Enemies also have the facingH and facingFB variables) and the second number is the index for the actual animation. So right now I can have 4 sprites per category, but practically it can be any number. I'm considering using a format besides an array (grid?) that might be more versatile. But arrays are simple so for now that's what we got.
It's also important to note that both the front and back animations should the have the same number of frames, or everything might explode catastrophically.
Anyway, to trigger an animation, I have this script:
The priority check happens so keep animations making 'sense'. for instance, if the enemy is moving, it'll probably be scripted to play a moving animation. But if it's also attacking, it'll also want to be playing an attacking animation. Obviously we want to show the attack more than just the enemy walking, so we give the attack a higher animation a higher priority than the move.Code://scr_sprite_anim_set ====================================================================== var _anim = argument[0]; // animation category (i.e animIdle or animMove, from above) var _index = argument[1]; // the animation Number (0 - 3, or -1 to choose randomly) ((the 2nd array index from above)) var _time = argument[2]; // How many frames to play animation for. (or if -1, set length to number of frames.) var _prio = argument[3]; // animation priority (higher values interrupt and override lower ones.) // SUGGESTED ANIM PRIORITIES [Idle = 0] [fiddle = 1] [Move = 2] [Attack Charging = 3] [Hit = 4] [Knockdown = 5 ] [All attacks = 6] [Dead = 7] if _prio >= animCurrentPriority // if the anim priority we are setting is higher than the current one. { if _index == -1 { _index = irandom(array_length_2d(_anim,1)) } //choose a random animation from the category. animCurrent[0] = _anim[0,_index]; //set the front animation from the category, with the index we chose. animCurrent[1] = _anim[1,_index]; // set the rear version of the animation. if _time = -1 {_time = sprite_get_number(animCurrent[0]) * 4 } // *4 as sprite speed is 15 and room speed is 60. animTimer = _time; image_index=0; // start from the first frame. image_speed=1; // just in case... animCurrentPriority = _prio; }
And in the step event, we basically just count down the animation timer. When it hits zero, we go back to idle:
...and elsewhere the sprite_index is set to animCurrent[facingFB] before drawing.Code://scr_sprite_animaton ======= (yes, it's actually misspelled. -_- I never noticed until just now. That's autofill for you.) ==================================================================== // processes animations. // time out animation, and Reset to idle pose if other animations have timed out. if image_index = sprite_get_number(animCurrent[0]) {image_speed=0}else{image_speed=1} // hold on the last frame if timer > animation length.. Not sure if this works, actucally, I haven't tested it. if animTimer <= 0 { animCurrent[0] = animIdle[0,0]; animCurrent[1] = animIdle[1,0]; animTimer = 60; animCurrentPriority = 0; image_speed = 1; } else { animTimer--; //count down animtimer. }
IMPORTANT TO NOTE!!! The current system does NOT have mechanism for switching correctly between front and back sprites. (currently it'll jump back to frame 0 with every switch) That's another of the things I have to fix later, I just wanted to get the basics working before my time was up. -_- I think I may need to either find a way to preserve the image_index through sprite changes, or just make my own frame index variable. Haven't thought about it that much yet. : /
So that's it, pretty much.. hope it makes some sort of sense. I'm a self-taught programmer so I have no idea what best practice in most cases. Apologies if this made anyone cry. :/
But if you have any questions feel free to hit me in PM's.
edit: Just now remembered where I remember your name from. Your project is totally amazing. Best of luck!
For the player animation I need to handle many different equipments and directions for the isometric perspective... So i also use an array of sprites and run a script to render
Code:
///scr_draw_equipment(action,direction)
var action = argument0;
var direct = argument1;
if(inventory.equip_arr[0] != 0){ //WPN
var sprWpn = inventory.currWpn[action, direct];
draw_sprite(sprWpn,image_index,x,y);
}
if(inventory.equip_arr[1] != 0){//ARMOR
var sprArm = inventory.currArmor[action, direct];
sprite_index = sprArm;
//draw_self();
draw_sprite(sprArm,image_index,x,y);
}else{
sprite_index = arr_default_spr[action, direct];
// draw_self();
draw_sprite(sprite_index,image_index,x,y);
}
Here it´s the item creation code when the equip drops on the floor :
When I equip the item, all the information is contained into that ds_map object:
Code:
//equiped sprites
/* ATTACK */
item.arr_item_equiped_spr[ATTACK, RIGHT] = asset_get_index(string(sprAux)+'_atk_r');
item.arr_item_equiped_spr[ATTACK, UP_RIGHT] = asset_get_index(string(sprAux)+'_atk_ur');
item.arr_item_equiped_spr[ATTACK, UP] = asset_get_index(string(sprAux)+'_atk_u');
item.arr_item_equiped_spr[ATTACK, UP_LEFT] = asset_get_index(string(sprAux)+'_atk_ul');
item.arr_item_equiped_spr[ATTACK, LEFT] = asset_get_index(string(sprAux)+'_atk_l');
item.arr_item_equiped_spr[ATTACK, DOWN_LEFT] = asset_get_index(string(sprAux)+'_atk_dl');
item.arr_item_equiped_spr[ATTACK, DOWN] = asset_get_index(string(sprAux)+'_atk_d');
item.arr_item_equiped_spr[ATTACK, DOWN_RIGHT]= asset_get_index(string(sprAux)+'_atk_dr');
/* BOW ATTACK */
item.arr_item_equiped_spr[BOW, RIGHT] = asset_get_index(string(sprAux)+'_trow_r');
item.arr_item_equiped_spr[BOW, UP_RIGHT] = asset_get_index(string(sprAux)+'_trow_ur');
item.arr_item_equiped_spr[BOW, UP] = asset_get_index(string(sprAux)+'_trow_u');
item.arr_item_equiped_spr[BOW, UP_LEFT] = asset_get_index(string(sprAux)+'_trow_ul');
item.arr_item_equiped_spr[BOW, LEFT] = asset_get_index(string(sprAux)+'_trow_l');
item.arr_item_equiped_spr[BOW, DOWN_LEFT] = asset_get_index(string(sprAux)+'_trow_dl');
item.arr_item_equiped_spr[BOW, DOWN] = asset_get_index(string(sprAux)+'_trow_d');
item.arr_item_equiped_spr[BOW, DOWN_RIGHT]= asset_get_index(string(sprAux)+'_trow_dr');
/* BLOCK */
item.arr_item_equiped_spr[BLOCK, RIGHT] = asset_get_index(string(sprAux)+'_block_r');
item.arr_item_equiped_spr[BLOCK, UP_RIGHT] = asset_get_index(string(sprAux)+'_block_ur');
item.arr_item_equiped_spr[BLOCK, UP] = asset_get_index(string(sprAux)+'_block_u');
item.arr_item_equiped_spr[BLOCK, UP_LEFT] = asset_get_index(string(sprAux)+'_block_ul');
item.arr_item_equiped_spr[BLOCK, LEFT] = asset_get_index(string(sprAux)+'_block_l');
item.arr_item_equiped_spr[BLOCK, DOWN_LEFT] = asset_get_index(string(sprAux)+'_block_dl');
item.arr_item_equiped_spr[BLOCK, DOWN] = asset_get_index(string(sprAux)+'_block_d');
item.arr_item_equiped_spr[BLOCK, DOWN_RIGHT]= asset_get_index(string(sprAux)+'_block_dr');
AND SO ON.....
Code:
if(invAux.box[# xx, yy] == 0){
invAux.box[# xx, yy] = item_id;
invAux.count[# xx,yy]++;
show_debug_message("the inventory added" + string(item_id));
//add the item unique atributes to the item_box
var itemDS = ds_map_create();
ds_map_add(itemDS,'item_name',item.item_name);
ds_map_add(itemDS,'item_id',item.item_id);
ds_map_add(itemDS,'item_lvl',item.item_lvl);
ds_map_add(itemDS,'item_unique',item.item_unique);
ds_map_add(itemDS,'item_unique_id',item.item_unique_id);
ds_map_add(itemDS,'item_icon_spr',item.item_icon_spr);
ds_map_add(itemDS,'item_drop_spr',item.item_drop_spr);
ds_map_add(itemDS,'arr_item_equiped_spr',item.arr_item_equiped_spr);
ds_map_add(itemDS,'type',item.type);
ds_map_add(itemDS,'dmg',item.dmg);
ds_map_add(itemDS,'cost',item.cost);
ds_map_add(itemDS,'atk_rating',item.atk_rating);
ds_map_add(itemDS,'item_atr',item.item_atr);
ds_map_add(itemDS,'equiped',item.equiped);
ds_map_add(itemDS,'hotkey_slot',item.hotkey_slot);
ds_map_add(itemDS,'item_atk_scr',item.item_atk_scr);
//add to the box
ds_map_add(invAux.box_items,item.item_id,itemDS);
return true;
}
}
Hey and thanks for the cheers! Maybe I´ll set a demo in the end of this month! =)