Legacy GM Better hitscan for the Top-Down shooter

INFERNOUX

Member
DISCLAIMER: THIS TUTORIAL ISNT TO TELL YOU HOW TO SHOOT THE PLAYER, JUST HOW TO HELP YOUR AI AIM IN A WAY THAT IS MORE FUN.

GM Version: Studio 1.4
Target Platform: ALL
Download: N/A
Links: N/A

Difficulty: Average.
Time spent: 30 - 60 min.
Useage: Shifting Gameplay towards a more aggressive playstyle.


imagine this is an enemy shooting at your player. imagine the drive it gives them to keep moving away from the bullet storm. now add more enemies. if done right it could be frightening.


The biggest issue i've seen to date with the Birds-Eye Shooter is the inability for some games to keep the player moving, instead rewarding them for using cover as often as they can and not pushing out to fight in ways that seem more active and fun. Nuclear Throne (albeit a great game) is a grand example of this. After a little bit of mastery just sticking to cover will allow you to progress much farther than you could being a kamikaze brute.

I hope to change this with a couple of scripts and a push in the right direction. The solution is to not shoot at the player, but shoot behind the player. this gives them intensive to keep moving to stay alive.

First off, you need a player. I won't tell you how to code your player. that part is up to you. Next we make an object named Obj_Delay. Obj_Delay's purpose is to track the players movement over a set number of ticks. I choose 15, the more you have the more potential for lag there is. But, at a room speed of 60 my preference is 15.

In Obj_Delay, under creation code you need to set up two variables. one being the length of time back you want to record named length. and the other an array, named playerpos. playerpos is here to hold the x/y coordinates for each step. so, playerpos[1,0] will be the x coordinate one tick ago, and playerpos[1,1] will be the y coordinate one tick ago. then you want to fill all 15 of playerpos's coordinates with the players coordinates.


Code:
playerpos = noone; //define array
length = 15; //define time recorded

for(var i = 0; i < length; i++){ //loop through the variable
    playerpos[i,0] = Obj_Player.x;//record x
    playerpos[i,1] = Obj_Player.y;//record y
}
now that we've done this go into the step end event and create a new code execution. Here we're going to update the list. every step rewriting the first number with the active coordinates, and every number after being 1 tick back. we do this in the step end event so that its updated just before the next round of step events so that it is totally up to date with everything else.


Code:
for(var i = length - 2; i > =0; i--){ //loop through and set every position to one tick later
    playerpos[i + 1,0] = playerpos[i,0];
    playerpos[i + 1,1] = playerpos[i,1];
}
//update first tick
playerpos[0,0] = Obj_Player.x;
playerpos[0,1] = Obj_Player.y;
now in our player creation code create the instance at 0,0.

Code:
 instance_create(0,0,Obj_Delay);
next comes our scripting part. we're going to make 3 scripts. one to get the x position at a given time back, one to get the y position at a given time back, and one to check for collision lines back.

firstly the x position. Make a new script and name it Scr_PX for "script previous x", set its argument[0] to be how far back to check. and define a variable that will be what you return as "noone". then, with Obj_Delay, set your return variable to playerpos[tick,0];


Code:
var tick = argument[0]; //set the tick variable to argument 0
var rtrn = noone; //define the return variable

with(Obj_Delay){
    if(tick < length){ //make sure we're not checking farther back than we have stored
        rtrn = playerpos[tick,0]; //set the variable to that stored value
    }
}

return rtrn; //return noone or the variable.
now duplicate the script, name the new one Scr_PY, and change "rtrn = playerpos[tick,0]" with "rtrn = playerpos[tick,1]" and now you have a script to return the x, and one to return the y.

now to check for collision lines.

Make one last script and name it "Scr_PLOS", for "Script player line of sight". this script is to see if you have line of sight with the position that the player was at at a given tick. set its argument[0] as the time back to check, its argument[1] as the object to see if its not in the way, its argument[2] as the x position to check from, and its argument[3] as its y position to check from. its return variable to false. and its checkx and checky variables to 0. this seems like a lot but ill explain why.

with Obj_Delay we need to get the x and y coordinates that the player was at however many ticks ago. so, we need to execute the two scripts we just made. setting the relative variable to the response.

so far we should have:


Code:
var tick = argument[0]; //get time back
var block = argument[1]; //get the object in the way
var fromx = argument[2]; //get the object checking's x coord
var fromy = argument[3]; //get the object checking's y coord
var rtrn = false;
var checkx = 0; //define the players xcoord as 0
var checky = 0;//define the players ycoord as 0

checkx = Scr_PX(tick); //get the players x
checky = Scr_PY(tick); //get the players y
next we need to make sure that the response wasnt "noone" from either. then we need to check for the collision line. and return our rtrn value. the script should now look like:


Code:
var tick = argument[0]; //get time back
var block = argument[1]; //get the object in the way
var fromx = argument[2]; //get the object checking's x coord
var fromy = argument[3]; //get the object checking's y coord
var rtrn = false;
var checkx = 0; //define the players xcoord as 0
var checky = 0;//define the players ycoord as 0

checkx = Scr_PX(tick); //get the players x
checky = Scr_PY(tick); //get the players y

if(!(checkx = noone or checky = noone)){ //if both are good
    if(!(collision_line(checkx,checky,fromx,fromy,block,1,1))){ //check the collision line between player and object
        rtrn = true; //set return to true
    }
}

return rtrn;
in use, you would use this as your hitscan target, and to see if you should or shouldnt fire. I quickly coded up an example object that is setting its direction to the player 20 ticks back to show what it would look like in game. its only code is in the step event "image_angle = point_direction(x,y,Scr_PX(14),Scr_PY(14));"



and to show it disabling its visibility when the player is behind a wall:



(in the draw event i added this:

Code:
if(Scr_PLOS(14,Obj_Block,x,y)){
    draw_self();
}

Well, best of luck, and I hope you all can find this at least a little bit useful. I view this as a rather simple process, so feel free to use it where-ever. I don't expect my name to show up in a credits sequence, but I would LOVE it if it did. And please, feel free to comment videos or gifs of it in use if you do use it.
 
Last edited by a moderator:

scorpafied

Member
sorry for bumping an old topic. but felt like this deserved a bit more attention. its an impressive way to help improve gameplay on tds games.
 
Top