1
\$\begingroup\$

I have a feeling I'm overlooking something stupid here...

By default all of my enemy objects start without a target assigned to them. As in - "target = noone;"

However, despite target being undefined I'm still erroring out from things being erroneously run inside of a code block that should only run if the enemy object has a target.

I have the if statement...

if(target != noone)

and have even tried...

if(!is_undefined(target))

... and in both cases there are lines being run that will cause my program to crash. Such as checking the distance between the target, turning to face the target, etc. All of these lines should never be getting run since they are within a block of code that should only run if the if statement check shows there is actually a target.

The entire code block is...

// If there is a target...
if(!is_undefined(target))
//if(target != noone)
    {

    // TARGET CONDITION CHECKS

    // Check the distance to target.
    var tdist = distance_to_object(target)

    // Check if it is within firing range...
    if(tdist < range_actual) then target_in_range = true else target_in_range = false;

    // Check the difference in angles between the turret and its target
    var pd = point_direction(x, y, target.x, target.y);
    var dd = angle_difference(direction, pd);

    // Check if the (absolute value of the) angle difference is within the weapons tolerance enough for the target to be considered within the firing arc of the weapon.
    if(abs(dd) < angle_tolerance) then target_in_firing_arc = true else target_in_firing_arc = false;

    //Rotate towards target using the above angle difference (dd)
    direction -= min(abs(dd), rotation_speed) * sign(dd);

    // If the target is within firing range...
    // If they are also within the firing arc, and not reloading, then tell the weapon to start firing.
    if(target_in_range = true && target_in_firing_arc && status != "reloading")
        {
            {
            status = "firing"
            }
        }

    // If the target isn't in range then start hoofing it them.
    if(!target_in_range)
        {
        speed += acceleration;
        if(speed > max_speed) then speed = max_speed;

        xPos = x + lengthdir_x(speed, direction);
        yPos = y + lengthdir_x(speed, direction);
        if(place_meeting(xPos, yPos, obj_Col_Parent) && instance_place(xPos, yPos, obj_Col_Parent) != id) then speed = 0;
        if(place_meeting(xPos, yPos, obj_player)) then speed = 0;
        }
    else    // If they are in range then start to come to a stop.
        {
        if(point_distance(x, y, target.x, target.y) < range_actual / 2)
            {
            speed -= braking;
            if(speed < 0) then speed = 0;
            }
        }
    }

When using "if(target != noone)" to start my if statement block it will error out on line 87 which is "var tdist = distance_to_object(target)" and if I instead start the if statement with "if(!is_undefined(target))" then it will get just a bit further and error out on line 93 which is "var pd = point_direction(x, y, target.x, target.y);"

Anyone out there see what exactly I'm doing wrong?

I can't figure out why any lines of code in that if statement would ever be getting run since there isn't a target set and as such everything in that if statement should be entirely skipped over. Its kinda driving me nuts.

UPDATE: Alright. As per request, here is the code relevant to targets being assigned.

In the Create Event the target is assigned to noone / undefined with...

target = noone; // The id of the enemy instance this weapon is aiming at.

In the Step Event a potential target, assuming one exists, is checked for with...

// Check if the target still exists and if not then reset target to the default 'noone'
if(target != noone && !instance_exists(target)) then target = noone;

// If there isn't currently a target being aimed at or the weapon is idle then set the target as random or nearest player piece.
if(target == noone && object_exists(obj_player))
    {
    if(targetting_method = "random")
        {
        var randtarg = irandom_range(0, ds_list_size(global.segment_list) - 1);
        target = ds_list_find_value(global.segment_list, randtarg);
        }
    if(targetting_method = "nearest")
        {
        target = instance_nearest(x, y, obj_player);
        }
    }

Using the debugger while running the program shows the enemy objects target variable as "undefined". So, at least according to the debugger, the target is correctly assigned to noone / undefined at the time the program crashes out by running the code inside that if statement block that should only be run in the event there actually is a target.

UPDATE 2: I had initially stated, just above actually, that the debugger correctly shows my target variable being assigned as "undefined" / noone. This was a mistake on my part and is incorrect.

Its true that with the specific obj_soldier that the game crashed on was showing target as holding the value "undefined". However "undefined" is NOT equal to "noone".

The Gamemaker manual lists that noone is the value of -4 so when you set a variable to hold the value of noone its simply being set to hold -4. So, my obj_soldier having a target of "undefined" means it wasn't properly set as noone.

Since "undefined" is literally not assignment when my if statement tries to check if "target != noone" it passes the if statement check because "undefined" isn't -4. Which causes my if statement to continue on and try to run its checks for when there is actually a target and the end result is that my program tries to do calculations involving "undefined", such as trying to check the obj_soldiers distance to "undefined" which results in a rather expected crash.

Through experimenting I've found that the first obj_soldier I create is correct and works fine. For them target is correctly noone (-4) and they don't crash the game when that if statement runs its check. This occurs whether I place them directly in the room or create them via code.

My second obj_soldier that is created however, doesn't have target assigned correctly and it ends up showing in the debugger as "undefined". When the if statement check occurs they erroneously pass the check and then cause the program to crash doing impossible calculations. This occurs whether the second soldier is placed in the room directly or created via code.

Interestingly, all obj_soldiers after the second work fine and have target correctly noone (-4) just like the first obj_soldier. Place directly or created via code doesn't matter.

For example, if I place or create with code 30 soldiers (which I did several times as a test) the game crashes and in the debugger the second soldier has target with a value of "undefined" while the first and third through thirtieth soldiers have their target shown as holding "-4" which is correct for an assignment of noone.

Aside from the possibility of this issue being a problem with Gamemaker itself, which I've contacted YoYo Games about and am awaiting a response, I can't really track down a reason for it to be occurring.

All of my obj_soldiers should be identical. They are call just instances of that one object and there should be no exceptions to setting "target = noone" for any of them.

I've even gone so far as to try forcibly reseting target to noone at the time of creation by storing obj_soldier in a variable at the time of creation and using "with" to set "target = noone" for each one.

The only workaround I've found is to immediately before my if statement of "if(target != noone)" is run to have this line "if(is_undefined(target)) then target = noone" to ensure that if the value of target is "undefined" it is forced to being noone just before running the check.

While that workaround avoids the crash it doesn't solve the issue of why setting to noone can sometimes result in a value of "undefined" instead of the correct -4 though.

\$\endgroup\$
  • \$\begingroup\$ Show the code where target is being declared, and how it gets a value \$\endgroup\$ – DH. Aug 1 '16 at 23:42
  • \$\begingroup\$ Use the edit option, there's a small edit button and I know it's hard to see, but don't post more relevant code as an answer. \$\endgroup\$ – DH. Aug 1 '16 at 23:54
  • \$\begingroup\$ Please register and use this page to have your accounts merged. This will allow you to edit your own posts and comment on it and on answers to your questions. \$\endgroup\$ – Alexandre Vaillancourt Aug 2 '16 at 1:34
  • \$\begingroup\$ Do you understand that undefined in debugeer may mean that you didn't define/initialize variable? undefined can be only in very few specific cases, in all other cases that means an error - you done something wrong. Share your project then I can see, because there may be lot of reasons of the error. \$\endgroup\$ – Dmi7ry Aug 5 '16 at 6:06
1
\$\begingroup\$

is_undefined() is wrong for this case. It can be used, as example, for ds_map functions, but not for an undefined variable (or varibale which equal noone). There must be used if target != noone and instance_exists(target) { actions with target }.

If your if(target != noone && !instance_exists(target)) then target = noone; and actions with target is placed in different events then you need to add check for sure that instance still exists. Because instance can be destroyed between these events.

// If there is a target...
if target != noone and instance_exists(target)
{
    // TARGET CONDITION CHECKS

    // Check the distance to target.
    var tdist = distance_to_object(target);

    // Check if it is within firing range...
    var target_in_range = (tdist < range_actual);

    // Check the difference in angles between the turret and its target
    var pd = point_direction(x, y, target.x, target.y);
    var dd = angle_difference(direction, pd);

    // Check if the (absolute value of the) angle difference is within the weapons tolerance enough for the target to be considered within the firing arc of the weapon.
    var target_in_firing_arc = (abs(dd) < angle_tolerance);

    //Rotate towards target using the above angle difference (dd)
    direction -= min(abs(dd), rotation_speed) * sign(dd);

    // If the target is within firing range...
    // If they are also within the firing arc, and not reloading, then tell the weapon to start firing.
    if target_in_range and target_in_firing_arc and status != "reloading"
    {
        status = "firing";
    }

    // If the target isn't in range then start hoofing it them.
    if !target_in_range
    {
        speed += acceleration;
        speed = min(speed, max_speed);

        xPos = x + lengthdir_x(speed, direction);
        yPos = y + lengthdir_x(speed, direction);
        if (place_meeting(xPos, yPos, obj_Col_Parent) and instance_place(xPos, yPos, obj_Col_Parent) != id)
        or (place_meeting(xPos, yPos, obj_player))
            speed = 0;
    }
    else    // If they are in range then start to come to a stop.
    {
        if point_distance(x, y, target.x, target.y) < range_actual / 2
        {
            speed -= braking;
            speed = max(0, speed);
        }
    }
}
else if target != noone
    target = noone;

And for targetting_method better will be use switch statement:

// Check if the target still exists and if not then reset target to the default 'noone'
if target != noone and !instance_exists(target)) target = noone;

// If there isn't currently a target being aimed at or the weapon is idle then set the target as random or nearest player piece.
if target == noone and object_exists(obj_player)
{
    switch targetting_method
    {
        case "random":
            var randtarg = irandom_range(0, ds_list_size(global.segment_list) - 1);
            target = ds_list_find_value(global.segment_list, randtarg);
            break;
        case "nearest":
            target = instance_nearest(x, y, obj_player);
            break;
    }
}

Instead strings ("firing", "reloading", etc) use macros. For example, define macros: STATE_FIRING as 0, STATE_RELOADING as 1 and TARGETTING_RANDOM as 0, TARGETTING_NEAREST as 1, then use it in code:

switch targetting_method
{
    case TARGETTING_RANDOM:
        var randtarg = irandom_range(0, ds_list_size(global.segment_list) - 1);
        target = ds_list_find_value(global.segment_list, randtarg);
        break;
    case TARGETTING_NEAREST:
        target = instance_nearest(x, y, obj_player);
        break;
}

When you use strings, you may easy get typo, etc, and code will work wrong (but it won't show any error messages in most cases). Also autocomplete will be available for all macros - you don't need remember all exact names, etc. You will write more fast and without errors.

\$\endgroup\$
  • \$\begingroup\$ Good suggestions with the switch and macro I'll have to look into them. Your if statement changes though don't work. There actually isn't anything wrong with it though as it SHOULD work. However, while testing that and experimenting further I've found that my issue doesn't seem to have anything to do with my problem if statement in the first place. Assigning target to noone isn't actually working properly and consistently and may actually be a problem with Gamemaker itself. I'll update (Update 2) my initial question to reflect what I've found. \$\endgroup\$ – Kol Aug 4 '16 at 1:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.