X_>`1ٮ4kI#$#4X ( @ (//(  0/80@0?/8 0( ?G@POWOXOP?H0@/7@O_``h_hPX 0(P_oppxow_g@O/8'' 7p`o?G/7 0??H/8`/o?GOX@H0?/pOW?H/8 7OP?G/7( P0@PX@O0@ 0O @H/@' 00@@P0?'_OP@H( 7/7' ^^^^^^^^^^^^^^^^^^^^^^^^^^TeeBBBBeTT^^^^^^^eBDJ33333JJDBeZ^^^^TD33==#####==3JDBT^^^^eJ=###=3DeZ^^TB3# #=3JBZ^^T3#  #=JBT^^J# Sll #=JBZ^T# %,68??86,/@A*< ##45('.6787-9%:;*<=#$+&,--.,/01)2 3 $%&&'%($!)* ##  !"#     ??dJJ@ GRID_HEIGHT32 GRID_WIDTH64 spr_char_standing"!  xsf@R@Č `Pyd*nW Ѐt5H Iy8D_Iԋ" xsf@R@Č `Pyd*nW!И0@dD6 b2{2Ⱥg)0͉pRfZJCv.3yFd(HJę*&q j.(fV#)K/Qe 4jsS xsf@R@Č `Pyd*a`ICX?*o$]̓&1gjDG%> A[ӧ$i"#)J_0TWP}Q<ϴ1T/q#A aGL.k242aLX+dGFUc$DI7qN$ CN&?eѼ$$I7Dg"cy ͣ)6T4j/N xsf@R@Č `Pyd*nW!И0@U!1g/a5\E1<)_dQe",*l eE3(!< wl 5o?RJFI7JEGmRf w?h(OQ(5oYb xsf@R@Č `Pyd*acCy@c~# # X7bHmVbRCRf(6-PhP<(y!f1G\z!0#!)NQ< +GIqˀ +&F~43Qf+)oXC$JH4`(yTHEQjA xsf@R@Č `Pyd*Q"6[n -ͣhPlV'<<MyV R fg 0 aL0Vfdp ToD xsf@R@Č `Pyd*!ma[-И0@i1'~pt.G0Cc28z8a 9q3Wazh_ÚLhc$;>p4(o$+$+ Qmn$1)hHBQjEG xsf@R@Č `Pyd*!ma[-И0@i1'<"K(>hocP$4'8YD#}F0D䚇H<,B)+_#520T'<T4jE@ xsf@R@Č `Pyd*1x? 4<°n5<&47W6Dbd /XDh><Fj(HJę*&qKUR?b$< oH~GYz!GyKKspr_char_walk_4"!  xsf@R@Č `Pyd*a`ICX?*o$]̓&1gjDG%> A[ӧ$i"#)J_0TWP}Q<ϴ1T/jm#d}JyZ$&Uaك¿1Kyp'<"/(HlIkaT2 ^mn$h(H/Q# 1󨚞jI # U* \^ԮnԆ4ϸ,{mX+ݖp"1뷀$$! -;džB/w''e&rp!ωbRnkbˆeW HȔe,iSɮ^dy]^rZo xԱ 0P2JI >;flA(pB:/?gSXXB=|f#́fOt';/x'#fهMM"yKxrTQ4Eʂ~+?S1JZ u@s8/{k GrlLw}a qʟ\WC /3Cy>K\eV?|Y^LmZPW#3'#_$.3Wu^E}ő?r}g^Ñ8󒷪N*?L xsf@R@Č `Pyd*kyC9И0l$AK0n$.K/p7x#DͣhÈ6b~Q`eV'Rć";#&ɐ>#0 9BLYM)p܃1KAC(H/Q(556spr_char_walk_6"!  xsf@R@Č `Pyd*QP$Q˜ -  s!zly^|U=#ٰG*sOR=R\k"d筏܀Qwr/N2 {U9;e{xWӞ#_NlZ7 xA@@ PN VN&na(4¤vLf0䥝d,(K4ɗn}ϣ'{[1|%bǍo{ޙSsK{JWR}^D'8תUbLQ_Pjio5בS{Ab;spr_char_walk_7"!  x 0 Eѓ8wwt(5X-1i*"(My'!øTn=xހeQ6&:OZ#`i̒ǃfy]:7KMœ fI1ęyHy yhBFU$y1Bm <ȰV?/++x ':?O|7 x 0 E GNp݄-2JBeqĕ8`"d~. 9BN}7~c\:C韸b|Q+|ߥ+֑;gŚ}߭X_orM{}skΊ[+?E_/ bF~8;\}ƣȥ8vObooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo@'jspr_highlight_move@ ?  x; 0EѸ KKW`S4H!{7a %l]ZɄ=qƼ N~~FKE,R3W* Q}89BF+oVps*qv[pj?9ETS"[K._T IyߊQ y]= 8) { dir = dir - 8; } return dir * 45; } dir_360_to_8// // dir_360_to_8(direction) // //Returns 0 to 7, 0 being to the right, and 7 being down and right. // direction : 360 degree direction, 0 to 360 // //Created by : Nailog //Last Update : NULL { var dir; dir = argument0 + 22.5; while (dir < 0) { dir = dir + 360; } while (dir >= 360) { dir = dir - 360; } return floor(dir / 45); }dir_opposite_360// // dir_opposite_360(direction) // //Returns the direction opposite to the specified direction. // direction : A direction (0 to 359) // //Created by : Nailog //Last Update : NULL { var dir; dir = argument0 - 180; while (dir < 0) { dir = dir + 360; } while (dir >= 360) { dir = dir - 360; } return dir; }iso_adjacent_grid_x// // iso_adjacent_grid_x(x, y, 8_direction) // //Returns the x coordinate adjacent to the specified grid square in the direction specified. // x : x coordinate of the original grid square // y : y coordinate of the original grid square // 8_direction : 8 directional value, 0 to 7. 0 being to the right, and 7 being down and right // //Created by : Nailog //Last Update : NULL { var curr_x, curr_y, dir; curr_x = iso_grid_x(argument0, argument1); curr_y = iso_grid_y(argument0, argument1); dir = dir_8_to_360(argument2); curr_x = curr_x + lengthdir_x(GRID_HEIGHT * 1.3, dir); curr_y = curr_y + lengthdir_y(GRID_HEIGHT * 1.3, dir); return iso_grid_x(curr_x, curr_y); }iso_adjacent_grid_y// // iso_adjacent_grid_y(x, y, 8_direction) // //Returns the y coordinate adjacent to the specified grid square in the direction specified. // x : x coordinate of the original grid square // y : y coordinate of the original grid square // 8_direction : 8 directional value, 0 to 7. 0 being to the right, and 7 being down and right // //Created by : Nailog //Last Update : NULL { var curr_x, curr_y, dir; curr_x = iso_grid_x(argument0, argument1); curr_y = iso_grid_y(argument0, argument1); dir = dir_8_to_360(argument2); curr_x = curr_x + lengthdir_x(GRID_HEIGHT * 1.3, dir); curr_y = curr_y + lengthdir_y(GRID_HEIGHT * 1.3, dir); return iso_grid_y(curr_x, curr_y); } iso_grid_x // // iso_grid_x(x, y) // //Returns the x coordinate of the center of the isometric grid square that (x, y) is inside. // x : x coordinate // y : y coordinate // //Created by : Nailog //Last Update : NULL { var curr_x, curr_y, row, column, rem_x, rem_y; curr_x = argument0; curr_y = argument1; column = floor(curr_x / (GRID_WIDTH / 2)); row = floor(curr_y / (GRID_HEIGHT / 2)); rem_x = curr_x mod (GRID_WIDTH / 2); rem_y = curr_y mod (GRID_HEIGHT / 2); if (column mod 2) { if (row mod 2) { if (rem_x) { if (rem_y) { if (slope(0, (GRID_HEIGHT / 2), rem_x, rem_y) > (-1 * slope(0, 0, GRID_WIDTH, GRID_HEIGHT))) { column = column + 1; row = row + 1; } } } } else { if (rem_x) { if (rem_y) { if (slope(0, 0, rem_x, rem_y) < slope(0, 0, GRID_WIDTH, GRID_HEIGHT)) { column = column + 1; } else { row = row + 1; } } else { column = column + 1; } } else { if (rem_y) { row = row + 1; } else { row = row - 1; } } } } else { if (row mod 2) { if (rem_x) { if (rem_y) { if (slope(0, 0, rem_x, rem_y) < slope(0, 0, GRID_WIDTH, GRID_HEIGHT)) { column = column + 1; } else { row = row + 1; } } else { column = column + 1; } } else { if (rem_y) { row = row + 1; } else { row = row - 1; } } } else { if (rem_x) { if (rem_y) { if (slope(0, (GRID_HEIGHT / 2), rem_x, rem_y) > (-1 * slope(0, 0, GRID_WIDTH, GRID_HEIGHT))) { column = column + 1; row = row + 1; } } } } } curr_x = column * (GRID_WIDTH / 2); return curr_x; } iso_grid_y // // iso_grid_y(x, y) // //Returns the y coordinate of the center of the isometric grid square that (x, y) is inside. // x : x coordinate // y : y coordinate // //Created by : Nailog //Last Update : NULL { var curr_x, curr_y, row, column, rem_x, rem_y; curr_x = argument0; curr_y = argument1; column = floor(curr_x / (GRID_WIDTH / 2)); row = floor(curr_y / (GRID_HEIGHT / 2)); rem_x = curr_x mod (GRID_WIDTH / 2); rem_y = curr_y mod (GRID_HEIGHT / 2); if (column mod 2) { if (row mod 2) { if (rem_x) { if (rem_y) { if (slope(0, (GRID_HEIGHT / 2), rem_x, rem_y) > (-1 * slope(0, 0, GRID_WIDTH, GRID_HEIGHT))) { column = column + 1; row = row + 1; } } } } else { if (rem_x) { if (rem_y) { if (slope(0, 0, rem_x, rem_y) < slope(0, 0, GRID_WIDTH, GRID_HEIGHT)) { column = column + 1; } else { row = row + 1; } } else { column = column + 1; } } else { if (rem_y) { row = row + 1; } else { row = row - 1; } } } } else { if (row mod 2) { if (rem_x) { if (rem_y) { if (slope(0, 0, rem_x, rem_y) < slope(0, 0, GRID_WIDTH, GRID_HEIGHT)) { column = column + 1; } else { row = row + 1; } } else { column = column + 1; } } else { if (rem_y) { row = row + 1; } else { row = row - 1; } } } else { if (rem_x) { if (rem_y) { if (slope(0, (GRID_HEIGHT / 2), rem_x, rem_y) > (-1 * slope(0, 0, GRID_WIDTH, GRID_HEIGHT))) { column = column + 1; row = row + 1; } } } } } curr_y = row * (GRID_HEIGHT / 2); return curr_y; }slope// // slope(x1, y1, x2, y2) // //Returns the slope between the two points. // x1 : Starting x coordinate // y1 : Starting y coordinate // x2 : Ending x coordinate // y2 : Ending y coordinate // //Created by : Nailog //Last Update : NULL { var x1, y1, x2, y2, result; x1 = argument0; y1 = argument1; x2 = argument2; y2 = argument3; result = 0; if ((x2 - x1) != 0) { result = (y2 - y1) / (x2 - x1); } return result; } obj_selector  [//obj_selector's Create event { self.image_alpha = 0.5; //The selector works by going through separate modes. //For instance, when it is in Default mode, it can be moved around the grid, and pressing Enter //will select a character to move. The mode will then change to Movement Select. //When the selector is in Movement Select mode, it can still be moved around the grid, but hitting //enter when on a proper movement square will move the selected character to that movement square. //As that is happening, it changes to Off mode, which sets it to not respond to key strokes and makes //it disappear. Off mode will turn back to Default mode once the character finishes moving. // //These modes also make undoing actions easier. If you hit Backspace in Movement Select mode, the movement //squares will disappear and the mode will be changed back to Default. // //Adding new modes and behavior should be elementary. self.mode = "Default"; //This variable stores the character currently selected. Although it isn't really needed in this example, it //gives a peek in how to make this work with multiple characters. self.selected_char = noone; }0000000[//obj_selector's Step event { //While in Off mode, the selector checks the currently selected character to see if he is done //walking his path. If he is, the selector destroys all movement nodes and returns to Default behavior. if (self.mode == "Off") { if (instance_exists(self.selected_char)) { if (!self.selected_char.path_move) { with (obj_movement_node) instance_destroy(); self.mode = "Default"; } } } self.depth = -self.y; //Make the view follow the selector. view_xview[0] = self.x - floor(view_wview[0] / 2); view_yview[0] = self.y - floor(view_hview[0] / 2); }0000000[Y//obj_selector's Draw event { //If the selector is in Off mode, don't draw the sprite. if (self.mode == "Off") exit; draw_sprite_ext(self.sprite_index, self.image_index, self.x, self.y, self.image_xscale, self.image_yscale, self.image_angle, self.image_blend, self.image_alpha); }0000000([W//obj_selector's Down Key Press event. // //Pressing down should move the selector down and left. { //In Off mode, the selector is not supposed to respond to key presses. if (self.mode == "Off") exit; var temp_x, temp_y; //Find the next square in the down-left direction. temp_x = iso_adjacent_grid_x(self.x, self.y, 5); temp_y = iso_adjacent_grid_y(self.x, self.y, 5); //Only move to the new coordinates if they are inside the room. if ((temp_x > 0) && (temp_y < room_height)) { self.x = temp_x; self.y = temp_y; } }0000000'[[//obj_selector's Right Key Press event. // //Pressing right should move the selector down and right. { //Off mode stops the selector from responding to key presses. if (self.mode == "Off") exit; var temp_x, temp_y; //Find the next square in the down-right direction. temp_x = iso_adjacent_grid_x(self.x, self.y, 7); temp_y = iso_adjacent_grid_y(self.x, self.y, 7); //Only move to the new coordinates if they are inside the room. if ((temp_x < room_width) && (temp_y < room_height)) { self.x = temp_x; self.y = temp_y; } }0000000&[M//obj_selector's Up Key Press event. // //Pressing up should move the selector up and right. { //If the selector is off it shouldn't respond to keyboard commands. if (self.mode == "Off") exit; var temp_x, temp_y; //Find the next square in the up-right direction. temp_x = iso_adjacent_grid_x(self.x, self.y, 1); temp_y = iso_adjacent_grid_y(self.x, self.y, 1); //Only move to the new coordinates if they are inside the room. if ((temp_x < room_width) && (temp_y > 0)) { self.x = temp_x; self.y = temp_y; } }0000000%[M//obj_selector's Left Key Press event. // //Pressing left should make the selector move up and left to the next square. { //If the selector is Off it isn't allowed to move. if (self.mode == "Off") exit; var temp_x, temp_y; //Find the next square in the up-left direction. temp_x = iso_adjacent_grid_x(self.x, self.y, 3); temp_y = iso_adjacent_grid_y(self.x, self.y, 3); //Only move to the new coordinates if they are inside the room. if ((temp_x > 0) && (temp_y > 0)) { self.x = temp_x; self.y = temp_y; } }0000000 [b//obj_selector's Enter Key Press event. // //The Enter key is used to execute specific actions, like choosing a character to move, and then //choosing a square to move to. { //In Default mode, pressing enter will choose a character, create the movement squares, and move to //Movement Select mode, but only if the selector is on a character. if (self.mode == "Default") { var selected; selected = instance_place(self.x, self.y, obj_char); if (instance_exists(selected)) { var node; self.selected_char = selected; node = instance_create(self.x, self.y, obj_movement_node); node.movement_left = selected.stat_move; with (node) event_user(0); self.mode = "Movement Select"; } } //In Movement Select mode, pressing enter will choose a square for the character to move to, but only //if there is a movement square at that spot AND no other characters are occupying the spot. //After a square is chosen, the player is put on the path towards it and the selector moves to Off //mode. else if (self.mode == "Movement Select") { var selected; selected = instance_place(self.x, self.y, obj_movement_node); //Only execute the code if a movement square is there. if (instance_exists(selected)) { //Only execute the code if no characters are there. if (!instance_exists(instance_place(self.x, self.y, obj_char))) { var node; //This chunk of code is very important. In obj_movement_node's Create event, I discussed //the use of the parent variable and how it is key to finding a path from any specific node //to the very first node (or origin, as that is where the character is located). // //This chunk of code starts at the chosen movement node (the one we just checked to make sure //it was a legal movement square) and follows node parents until it reaches the origin. As //it does this, it adds each node found to a stack stored on the character. // //If you'll notice, we are going backwards, heading from the destination square (where the character //needs to get to) towards the origin (where the character is already). This is why we use a stack. //A stack is like a stack of papers, books, or even like a PEZ dispenser. It is also known as //Last In First Out (LIFO) or even First In Last Out (FILO). This means that if we add things to the //stack in a certain order, we'll be taking them off in the opposite order. This is what we need, as //the character has to steadily progress from the origin towards the destination. // //The movement node at the origin is not added, since the character is already there. self.selected_char.path_stack = ds_stack_create(); node = selected; //This loop executes only if the current node has a parent, which means the origin node doesn't get added //to the stack, since it has no parent. while (instance_exists(node.parent)) { ds_stack_push(self.selected_char.path_stack, node); node = node.parent; } //After the stack containing the path is created, signal the character to start his walking. self.selected_char.path_move = true; //The selector is turned Off to make sure the player can't interrupt the movement in some way. self.mode = "Off"; } } } else if (self.mode == "Off") { //Nothing happens when the selector is Off. } }0000000[//obj_selector's Backspace Key Press event. // //The backspace key is used to back out of certain actions, like choosing a square to move to. { if (self.mode == "Default") { self.selected_char = noone; } else if (self.mode == "Movement Select") { //Back out of Movement Select mode and destroy all possible movement squares. with (obj_movement_node) instance_destroy(); self.mode = "Default"; } }0000000obj_char [R//obj_char Create event. { //Set the initial direction to down-right, and don't animate. self.direction = dir_8_to_360(7); self.image_speed = 0; //These variables are used by the movement system to have the character walk a path of //movement nodes until he reaches his destination. // //This variable stores the ID number of the movement node stack created after the player //selects a square to move to. self.path_stack = -1; //This variable tells the character whether he needs to move down a path or not. self.path_move = false; //This variable stores the ID of the next movement node the character has to move to along //the path. self.path_node = noone; //These variables set how fast (pixels) the character moves, and how fast he animates. self.walk_speed = 1.5; self.walk_image_speed = 0.3; //This variable behaves like the Move stat in Final Fantasy Tactics. Defines how many squares //the character can move. self.stat_move = 5; }0000000[//obj_char Step event // //This event controls the character's movement and sprite animation. { //This code chunk controls the character while he walks his path of movement nodes. if (self.path_move) { //If the character needs to walk towards a movement node, execute the next code chunk. if (instance_exists(self.path_node)) { //This next line does two things at once. It moves the character towards his destination. //If the character has reached his destination node, the next chunk of code is executed. //Otherwise this bit of code ends, which means it'll fire again in the next Step. This Step-loop //will continue until he finally reaches his destination node. // (Note: His destination node is not always the square chosen by the player. His path goes along // many nodes until he does reach the square chosen by the player.) if (mp_linear_step(self.path_node.x, self.path_node.y, self.walk_speed, false)) { //Since he's reached a destination node, check the stack size to see if there's another node to walk to. if (ds_stack_size(self.path_stack) <= 0) { //The stack is empty, so he reached the final movement node. Destroy the stack and reset the //path following variables. ds_stack_destroy(self.path_stack); self.path_stack = -1; self.path_node = noone; self.path_move = false; } else { //The stack is not empty, so the next movement node in the path is pulled out. self.path_node = ds_stack_pop(self.path_stack); } } } //If the character doesn't have a movement node to walk towards. else { //If the stack containing the movement path is empty. if (ds_stack_size(self.path_stack) <= 0) { //Destroy the path stack. ds_stack_destroy(self.path_stack); //Reset the path following variables. self.path_stack = -1; self.path_node = noone; self.path_move = false; } //If the stack is not empty. else { //Pull the next movement node off the stack. self.path_node = ds_stack_pop(self.path_stack); } } } //This code chunk controls the animation of the character. if (self.path_move) { //The character is moving, so he needs to animate. First choose the proper sprite //based on his direction, and then set his animation speed. switch (dir_360_to_8(self.direction)) { case 0: self.sprite_index = spr_char_walk_0; break; case 1: self.sprite_index = spr_char_walk_1; break; case 2: self.sprite_index = spr_char_walk_2; break; case 3: self.sprite_index = spr_char_walk_3; break; case 4: self.sprite_index = spr_char_walk_4; break; case 5: self.sprite_index = spr_char_walk_5; break; case 6: self.sprite_index = spr_char_walk_6; break; case 7: self.sprite_index = spr_char_walk_7; break; } self.image_speed = self.walk_image_speed; } else { //The character is not moving, so he should stand still, while facing the correct direction. self.sprite_index = spr_char_standing; self.image_speed = 0; self.image_index = dir_360_to_8(self.direction); } //Adjust the character's depth. self.depth = -iso_grid_y(self.x, self.y) + 1; }0000000obj_movement_node  [y//obj_movement_node's Create event { self.image_alpha = 0.5; self.depth = -self.y + 2; //Each movement node has a parent, which is also another movement node. //The parent of a movement node is set to the movement node that created it (in the User Defined 0 event). //This allows the game to follow a path from any movement node back to the very first movement node, //which is usually the one underneath the character. The first movement node can be recognized because //his parent is never set (since he wasn't created by a movement node). // //For example ... // // PABCD // //P is where the player is standing, and is also the position of the first movement node. //A, B, C, and D are movement nodes, created in order, by their previous member. // (P created A, A created B, B created C, and C created D.) //We can choose any of these movement nodes, and checking their parents we can find P. // (D's parent is C, C's parent is B, B's parent is A, A's parent is P, and P doesn't have a parent, // so it must be the origin.) // //This interaction becomes significant in the code running in obj_selector's Enter Key Press event, as //the obj_selector builds the path of nodes for the character to follow. self.parent = noone; //This variable controls how far the movement squares can expand. As a movement node creates more //movement nodes, it lowers this value and passes it on to the new nodes. Once a movement node //hits 0, it can't create any more movement nodes. self.movement_left = 0; }0000000 [ //obj_movement_node's Event User Defined 0 // //This event starts (and continues) the movement node creation loop. { //New movement nodes can only be created if there is movement remaining. if (self.movement_left > 0) { var i; //This for loop is used to check all four directions. Since we are working in isometric directions, we only want //to check up-right, up-left, down-left, and down-right. Those correspond to 1, 3, 5, and 7. // (0 would be directly right, 2 would be up, 4 would be left, and 6 would be down.) for (i = 1; i < 8; i = i + 2) { //The next two commands were used for debugging purposes. They let you watch as the movement nodes are being created. //screen_redraw(); //sleep(200); var next_x, next_y, new_node, node; //This finds the location of the next square over in the current direction. // (The direction being controlled by the for loop.) //The two scripts I use choose the very next square in the specified direction. //They also do all the work of making sure the rules are obeyed concerning the isometric grid. next_x = iso_adjacent_grid_x(self.x, self.y, i); next_y = iso_adjacent_grid_y(self.x, self.y, i); //If the new location is outside the room, ignore. //This makes sure no movement nodes are created outside the room. if ((next_x <= 0) || (next_x >= room_width)) { continue; } if ((next_y <= 0) || (next_y >= room_height)) { continue; } //If an obstacle is at the new position, don't create a movement node. if (instance_exists(instance_place(next_x, next_y, obj_no_walk))) { continue; } //If a movement node already exists at the new position, check the movement left. //If the movement left in the old node is less than what this node would give it, //change the old node's movement left and signal it to start creating its own set //of nodes. old_node = instance_place(next_x, next_y, obj_movement_node); if (instance_exists(old_node)) { if (old_node.movement_left < (self.movement_left - 1)) { old_node.parent = self.id; old_node.movement_left = self.movement_left - 1; with (old_node) { event_user(0); } } } else { //The new location is a legal position, so create a new movement node, and then signal //it to start creating its own set of nodes. new_node = instance_create(next_x, next_y, obj_movement_node); new_node.parent = self.id; new_node.movement_left = self.movement_left - 1; with (new_node) { event_user(0); } } } } }0000000[{ draw_sprite_ext(self.sprite_index, self.image_index, self.x, self.y, self.image_xscale, self.image_yscale, self.image_angle, self.image_blend, self.image_alpha); //The next two lines were for debugging purposes. Each movement node would draw its movement_left variable. //draw_set_color(c_red); //draw_text(self.x, self.y, string(self.movement_left)); }0000000 obj_no_walk  [//obj_no_walk's Create event // //When placed in the room, this object is only used to specify a square that is unwalkable by the character. { self.depth = -self.y + 1; }0000000rm_mainMovement Example @<{ instance_create(iso_grid_x(320, 240), iso_grid_y(320, 240), obj_selector); instance_create(iso_grid_x(320, 240), iso_grid_y(320, 240), obj_char); }        @ @ P@``p@@`P`0 p`PXGame InformationX{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil Arial;}} {\colortbl ;\red0\green0\blue0;\red128\green0\blue0;\red128\green0\blue128;\red128\green0\blue255;\red128\green64\blue64;} \viewkind4\uc1\pard\cf1\fs24 This is a movement example for isometric strategy RPGs. \par \par \cf2 GM 6.1 Registered is required to run this example.\cf1 \par \par \par \par \b\fs28 Objects\b0\fs24 \par There are four main objects in this example. To understand more about each of these objects, read the comments scattered throughout their event code. \par \par \cf3 obj_selector\cf1 is the cursor the player moves around the map. It is used to select a unit, as well as select a square to move the unit to. It responds to the direction keys, as well as Enter (select) and Backspace (cancel). \par \par \cf3 obj_char\cf1 is a sample character. It can be selected and moved to a chosen square. It animates in all directions. \par \par \cf3 obj_movement_node\cf1 is the brain behind the movement pathfinding. Each of these objects will find legal squares around themselves and create new movement nodes. This process repeats until a distance limit has been reached. \par \par \cf3 obj_no_walk\cf1 is an empty object used only to mark spaces as impassable. \par \par \par \par \b\fs28 Scripts\b0\fs24 \par Eight scripts are included in this example. They were made to make certain things easier to deal with. \par \par \cf3 dir_8_to_360\cf1 (8_direction) will return a 360 degree direction based on the 8 way direction passed. GM uses 360 degrees to define direction, but in an isometric world we only really need 8 directions, being right, up-right, up, up-left, left, down-left, down, and down-right. \par \par \cf3 dir_360_to_8\cf1 (direction) is like the above, but you pass a 360 degree direction as the argument and receive an 8 way direction. \par \par \cf3 dir_opposite_360\cf1 (direction) will return the exact opposite direction to the one passed. \par \par \cf3 iso_adjacent_grid_x\cf1 (x, y, 8_direction) will return the x component of the grid square adjacent to the grid square specified by x and y, in the specified 8 way direction. Very useful for getting the "next square over". \par \par \cf3 iso_adjacent_grid_y\cf1 (x, y, 8_direction) is the same as above, but it returns the y component. Used together, they give the center coordinates of the "next square over". \par \par \cf3 iso_grid_x\cf1 (x, y) will return the center x of the grid square x and y are inside. \par \par \cf3 iso_grid_y\cf1 (x, y) is the same as above, but it returns the y component. Used together, these make programming a freely moving mouse cursor that still snaps to the isometric grid elementary. \par \par \cf3 slope\cf1 (x1, y1, x2, y2) will find and return the slope of a line segment. I should probably rewrite the above two scripts to use \cf4 point_direction\cf1 () instead, but they work perfectly using slope, so there's no immediate need. \par \par \par \par \b\fs28 Constants\b0\fs24 \par This example uses some Constants, which can be found in the Constants tab of the Global Game Settings. \par \par \cf5 GRID_HEIGHT\cf1 is the pixel height of the isometric grid squares. \par \cf5 GRID_WIDTH\cf1 is the pixel width of the isometric grid squares. \par \par \par \par \b\fs28 Questions\b0\fs24 \par If you have any questions concerning this example, you can PM \b Nailog\b0 on the GMC. \par \par \par \par \b\fs28 Credits\b0\fs24 \par \par Core_12\tab\tab : Character sprites \par Vance_Kimiyoshi\tab : Selector sprite \par Nailog\tab\tab\tab : Everything else\f1 \par } Sprites spr_char_standingspr_char_walk_0spr_char_walk_1spr_char_walk_2spr_char_walk_3spr_char_walk_4spr_char_walk_5spr_char_walk_6spr_char_walk_7 spr_selector spr_highlight_move spr_mask_tile spr_no_walkSounds BackgroundsPathsScripts dir_8_to_360 dir_360_to_8dir_opposite_360iso_adjacent_grid_xiso_adjacent_grid_y iso_grid_x iso_grid_yslope Fonts Time LinesObjects obj_selectorobj_charobj_movement_node obj_no_walkRoomsrm_main Game Information Global Game Settings