GMS 2.3+ Homing projectiles and boomerangs in a Top-down Twin Stick shooter

Ace Catel

Member
I've been wondering how I would go about creating 2 types of projectiles. Here's a description based on how I would like them to work if I can program them into the game.

Homing Projectile
- A gameobject's ID is used to tell the projectile of who is the target.
- The projectile will rotate to have the direction be towards the target.
- It has a variable for turning/rotating speed.

Boomerang
- This has 2 phases.
- In phase 1, it will go straight for a certain distance.
- After it reaches the maximum distance in phase 1, it goes to phase 2 and attempts to return to the user similar to the homing projectile.
- It also has a variable for turning/rotating speed.

What I have already for both of these:
- A time limit of how long this projectile will last before it deletes itself.
- Collision for said target

How would I go about coding these? Video links and suggestions are welcome.
 
Ok, so you've broken the problem down a little bit. That's good, but I think you need to go into a little more granularity. How is the target picked for the homing projectile? Is it the instance the mouse clicks on? The nearest instance to the targeting reticle (if there is a reticle)? The nearest instance to the instance launching the homing projectile? Once you know that, you'll be able to grab the id of the instance you want to target. Once you know the targeting instance, you'll need to find the direction from the homing instance to the targeted instance, I think you'll find point_direction() useful and, as for the turning/rotating speed, there's an example on the angle_difference() manual entry of exactly how to do this "turn at a certain speed".

As for the boomerang, you could use either a timer or a state machine for the "phases" (personally, I would go with the state machine). Once you've solved the problems for the homing projectile, you'll have the solutions for the second phase of the boomerang as well.
 
I will add to that, you also want to break down in what way both kind of projectiles are similar.
For example, they both have a direction, a sprite, a maximum distance (even if it's offscreen), a speed, etc.
This will help you get into the concept of parenting, which would greatly simplify this kind of thing, and will make it much easier if you decide to add another kind of projectile down the road.

As for the rest, it's difficult to help without knowing how you are setup, what code you currently have, etc...
 

Ace Catel

Member
Ok, so you've broken the problem down a little bit. That's good, but I think you need to go into a little more granularity. How is the target picked for the homing projectile? Is it the instance the mouse clicks on? The nearest instance to the targeting reticle (if there is a reticle)? The nearest instance to the instance launching the homing projectile? Once you know that, you'll be able to grab the id of the instance you want to target. Once you know the targeting instance, you'll need to find the direction from the homing instance to the targeted instance, I think you'll find point_direction() useful and, as for the turning/rotating speed, there's an example on the angle_difference() manual entry of exactly how to do this "turn at a certain speed".

As for the boomerang, you could use either a timer or a state machine for the "phases" (personally, I would go with the state machine). Once you've solved the problems for the homing projectile, you'll have the solutions for the second phase of the boomerang as well.
Thanks! I wasn't aware of the function, angle_difference(). I'll check that out immediately.
I plan to have it check for the nearest instance, but also not lock on until a certain distance is met as well.

Yes, as I was writing this out, I realize that the boomerang is actually doable if I figure out the homing projectile.
I'll see what I can make with the information you told me.
 

TheouAegis

Member
Well technically a boomerang is all one phase. It's already turning by the time it leaves your hand. The boomerang that Link uses in Zelda games is unrealistic and, had they made it more realistic, would have been just as dangerous for Link as for the enemies he throws it at. Quick Man's boomerang in Mega Man 2 is closer to a real boomerang if you think of viewing a boomerang's trajectory from the side. I have seen boomerangs in video games that just spin out at an angle and make a circle back around to the player, which is arguably the most realistic. The deadliest boomerangs don't even return to the thrower, they're basically just throwing clubs designed to be thrown very long distances.

As for homing missiles, you basically get the id of its target however you want, then each step find the direction toward the target using point_direction(x,y,target.x,target.y). Then determine if the absolute value of the angle difference is greater than the turning speed. If it is, adjust the direction by the turning speed, otherwise adjust the direction by the angle difference.
 

Ace Catel

Member
Okay! So this is the code I plan to use. I tested the target using my mouse coordinates initially, and it worked very well. It's a code I tried after looking up angle_difference() in the manual

GML:
var dir = point_direction(0,0,x_dir,y_dir); // Find direction of x_dir & y_dir
var ang = angle_difference(dir,point_direction(x,y,target.x,target.y)); // Find angle difference of current direction and direction to target
dir -= min(abs(ang), 10) * sign(ang); // Change direction
x_dir = lengthdir_x(1,dir); y_dir = lengthdir_y(1,dir); // Apply changes
In case you're wondering what x_dir and y_dir is, it's what I use to give the direction of the bullet.
The reason why I'm using this method instead of speed & direction which is built into the game object, I wanted to do it like this so I can code in my own ricochet code.
Code:
if (strike_type != "melee") && (strike_type != "piercing")
{
    // COLLISION: Horizontal
    if (place_meeting(x+x_dir * stat_speed,y,obj_parent_wall)) // Check if "x" value is about to collide
    {
        while (!place_meeting(x+sign(x_dir * stat_speed),y,obj_parent_wall))    // While there's space between "x" and hsp...
        {x += sign(x_dir * stat_speed);}                                    // add sign * hsp
        
        if (strike_type == "ricochet")
        {
            x_dir *= -1;
            if (limit_type == "distance")
            {
                stat_limit -= consumed_limit;
                originX = x;
                originY = y;
                consumed_limit = (distance_to_point(originX,originY)); // Traveled distance increases as attack travels
            }
        }
        else {instance_destroy(self,true);} // Strike destroys self and activates final_reaction
    }

    // COLLISION: Vertical
    if (place_meeting(x,y+y_dir * stat_speed,obj_parent_wall)) // Check if "y" value is about to collide
    {
        while (!place_meeting(x,y+sign(y_dir * stat_speed),obj_parent_wall))    // While there's space between "y" and vsp...
        {y += sign(y_dir * stat_speed);}                                // add sign * vsp
        
        if (strike_type == "ricochet")
        {
            y_dir *= -1;
            if (limit_type == "distance")
            {
                stat_limit -= consumed_limit;
                originX = x;
                originY = y;
                consumed_limit = (distance_to_point(originX,originY)); // Traveled distance increases as attack travels
            }
        }
        else {instance_destroy(self,true);} // Strike destroys self and activates final_reaction
    }
}
image_angle = point_direction(0,0,x_dir,y_dir);
x += x_dir * stat_speed; // Apply movement on x value
y += y_dir * stat_speed; // Apply movement on y value
Eh heh, I don't want to talk about that ricochet code though, I wanted to show it in case you're wondering why I'm not using a traditional direction variable.

Regarding this important question
How is the target picked for the homing projectile? Is it the instance the mouse clicks on? The nearest instance to the targeting reticle (if there is a reticle)? The nearest instance to the instance launching the homing projectile?
I want it to look for the closest enemy possible.

Since the AI can shoot these homing bullets as well at other enemies (multiple hostile factions) I was wondering if it's possible to have the bullet check for the closest enemy that ALSO has a string that isn't the same as it's own.

- The bullet has a string called "team". They will inherit it from the enemy or player that fires it. The enemies can vary since there may be 2 hostile factions at once.
- Enemies have a string called "team" too.Yoyo Games Forum attachment.png
Ideally, I want to see if it's possible to check available targets, pick one that isn't on the same team, and let the rest of the code work its magic. It only needs to run this code once, and after it is close enough to said available targets using distance_to_object();
 

TheouAegis

Member
 

Ace Catel

Member
Whoa, never knew you could do it like this
I have yet to dabble enough with the "with (){}" code before.

I'm gonna try one that fits the following conditions
1. Close enough to the bullet
2. It's the closest one amongst the others
3. the target object has team tag different from the bullet

I'm concerned that condition 2 may conflict with condition 3, because if the closest object has the same team tag, I don't know if it will even check if the second closest object with a different team tag will be applicable.
 

TheouAegis

Member
Check the tag before the distance. The distance checking is going to be a lot slower than the other two checks anyway.
 
Top