GameMaker RTS: Pausing and giving orders to units

G

GoDie910

Guest
First and foremost: thanks for reading this!

Second, what I want to do: I'm building an top-down RTS where you can pause time AND still being able to give orders to your units. Something like 'Kenshi' (the game, not the Mortal Kombat character). The idea is to 'freeze' time (no movement for bullets or units), but you can still select, order to move, cease fire, etc. This new orders given in freezed time will only take effect once time is resumed.

Third, what I know to do: I've been learning this past week how to pause the game and show a menu, but the solutions are either disabeling instances and redrawing it with the menu, or just having another room and going there with a key_pressed event. I don't know how to apply this to my project, or if it's useful at all for what I want to do(not saying it isn't helpful).

Lastly: I've searched in the documentation, but if there's something in there, it had eluded me.

If anyone knows anything I could use, be it a tutorial or an article, I would appreciate it a lot. And if what I'm trying to do is just not possible due to engine limitations, please let me know as well.
 

2Dcube

Member
It's easier when you implement this from the start, and can be a bit of a pain when you do it late in the project
but what I would do is either make everything move with a global.game_time variable (so if you set it to 0 everything stops moving)
or have if (global.paused) { ... } checks before each update loop (usually in the step event).
 
G

GoDie910

Guest
I think I get it.

I basically put everything that goes in the step event inside a consitional to check the global time. That way, it can recieve the input of where to go and who to attack, but the execution won't happen unless I resome time.

I'm going to implement this tomorrow and post how it went.

Either way, thanks for the idea!
 
I did wonder if you could set the room_speed to zero, but this is not possible as either: the engine is set not to be able to do this, or it literally kills off running all of it's code including the engine functions. You can set it to a lower speed, such as 1, but even at that speed you still see noticeable movement, and I'm unsure of what affect it has overall beyond what you can see visually.

I basically put everything that goes in the step event inside a consitional to check the global time. That way, it can recieve the input of where to go and who to attack, but the execution won't happen unless I resome time.
I don't think you'd want to do it quite like that. Things like an objects speed (units / bullets etc) maybe should be controlled by the "pause" so they are not moving, but you can't have the orders etc within that condition - otherwise nothing will happen.
Orders have to be given regardless of whether it's paused or not (unless you duplicate the code in both), so perhaps the only thing within the "paused" condition should purely be to do with movement / visual elements?

create event of objects:
Code:
normal_speed = whatever;
normal_anim_speed = whatever;
step event of objects:
Code:
1) do standard input i.e give commands, and set a response - regardless of whether 'paused' is true or not

if !paused
{
2) can do response if it has one, and then reset whatever conditions defined that response
3) if speed is not equal to 'normal_speed' resets speed, so that objects resume movement if "frozen" : units, bullets etc. Have a similar response for image speed with 'normal_anim_speed'
}
else
{
still receives orders, but does not respond to them whilst 'paused' is true. For example: you wouldn't want to fire and create a bullet, and have it appear.
The created bullets speed would be stopped in the following bit of code below, but it seems to me that it ought to happen after the paused event finishes - rather than during.

4) speed = 0; image_speed = 0; and whatever else that you want to stop
}
So 'paused' being true is only affecting the things you'd see, such as movement / animation etc. I will be the first to admit that I'm figuring this out off of the top of my head, so it could well be wrong :), but structuring it like this makes more sense to me in theory.
 
G

GoDie910

Guest
Okay, time to report in.

I've just finished implementing what @2Dcube said, and it works! Thank you very much!

On the other hand @the_dude_abides idea has shown me what can go wrong with my current implementation. I'll have to implement it tomorrow, since uni is calling again.

Either way, thanks both of you. I'll answer as soon as I have this done!
 
Some other things you'd maybe want to consider:

1) Applying this to physics might be problematic, as they recommend not interfering with the simulation

2) I have no clue about how you'd approach this if you use particles, as I'm unsure if you can manipulate their speed / decay life etc manually after creation. Since they would need to be "frozen" too. You'd also want any particle creation to be within the unpaused remit, as it wouldn't be good if they were still created whilst the game is paused. Could maybe end up with tons being created that aren't snuffing out, and get an fps drop or other issue (stackoverflow, or somesuch - I'm just taking laymans guesses here)

The assumption here is that the particle system would have to be manually done, and entirely under your control (and observation) to be able to be "paused"

3) Do collision detection manually, and have it only running when unpaused. Don't want excessive collision checks whilst paused, as they are unnecessary and will not be responded to anyway. By not doing this in the collision event you can cut out those periods (being paused) where it would be running. As even if you did the code in the event it would still have to do a check for a collision, before getting to the code block that tells it to ignore reaction. Done under 'unpaused' only, it reduces the load, and ensures action only happens under the right conditions.

collision event in objects:
Code:
if !paused
{
can respond
}
still has to actually check there was a collision, and will still be active regardless of the state of 'paused'

alternative under end step event
Code:
if !paused
{
do collision check
if collision
{
do whatever
}
}
else
{
does nothing? does limited response?
regardless - all checks for collisions stop
}
But on the other hand your mouse collisions for troop selection will have to be outside of the 'paused' conditions, as will any code for selecting units.

So some thought will be required as to where you put code / events that are always active, and how they are ordered. Then consider what being paused manipulates - though if you do have particles I'd be curious to know if there is a solution for them. Without actually having a clue I'd say that would be a....tough....challenge......:)

PS:
Doing the "frozen" image as a screenshot: you can't deactivate instances if you want any collision detection working, and you can't make them invisible either. As either way you need a sprite / mask for any collision type that uses the sprite index.

You could use point in rectangle instead (when invisible), but precise collision checking will not be possible. The trade off would be being able to snapshot the screen, and save on draw calls by stopping objects being visible.

I think....

PPS:
I have an idea on how you might deactivate instances, and still have pixel perfect accuracy for selecting troops, using an mp grid. I'm wondering if by deactivating instances, and by extension their drawing, you could gain a boost for more complex actions undertaken during the pause - such as pathfinding.

Kill two birds with one stone: object responses are paused (by code anyway, but at this point - deactivation), and the surface screenshot maintains the visual element as drawing / activity is all turned off.

In the meantime you can also do some of the heftier computational elements, such as checking whether bullets will hit / AI responses / pathfinding, whilst you have more resources available.

If you want to know about this idea, then I'd be happy to post it here. Otherwise I'll leave you to it :)
 
Last edited:
G

GoDie910

Guest
okay, I think I get what you're sayin @the_dude_abides :
- do collisions manually
- look how to handle particles when it's (un)paused
- think carefully the order of each stuff in the step event
- no object deactivation / 'frozen' image

For the moment, I've tried to restructured the Step Event to look like this:
Code:
if !paused
{
do collision check
if collision
{
do whatever
}
}
else
{
does nothing? does limited response?
regardless - all checks for collisions stop
}
the only problem I have rn is with the collision: I'm trying for my different units to remain at a fixed distance from one another while moving and idle. This is to prevent them from overlapping one another. It works, but not in a... nice way. I think it's best to show you.

Code:
/// @description Insert description here
// You can write your code in this editor

//find closest unit
aux_x = x; aux_y = y; x = -1000; y = -1000;
closest_char = instance_nearest(aux_x,aux_y,obj_pc);
x = aux_x; y = aux_y;

//recieve orders
if(global.time_speed != 0)
{
    
    image_speed = aux_image_speed;
    sprite_index = spr_ally_move;
    
    
    //the next 4 if check the movement andcollision with other units - this is what I'm trying to divide
    if(moving == true and distance_to_object(closest_char) > 1)
    {
        if(distance_to_point(go_to_x, go_to_y) < 10)
        {
            speed = 0;
            moving = false;
        }else
        {
            move_towards_point(go_to_x,go_to_y,my_speed*global.time_speed);
            image_angle = point_direction(x,y,go_to_x,go_to_y);
        }
    }
    
    if(moving == true and distance_to_object(closest_char) <= 1)
    {
        move_towards_point(go_to_x,go_to_y,my_speed*global.time_speed);
        motion_add(point_direction(closest_char.x,closest_char.y,x,y),my_speed*global.time_speed);
    }
    
    if(moving == false and distance_to_object(closest_char) <= 1)
    {
        motion_add(point_direction(closest_char.x,closest_char.y,x,y),my_speed*global.time_speed);
    }
    
    if(moving == false and distance_to_object(closest_char) > 1)
    {
        motion_set(direction,0);
        sprite_index = spr_ally
    }
    
}
else
{
    speed = 0;
    image_speed    = 0;
}

if(speed > my_speed) speed = my_speed;

if(selected == true) selected_angle += 2;
And about that concept, I'll really like to see it, since it's best to start thinking on optimization from the get go.
 
Alright - the idea is to use a grid instead of any precise collision checking. Basically every cell is one pixel, and instances will be placed into it.

I would need to check how intensive the ways are to do this, and also if it's practical - since you'd need to know what cell relates to which instance. So you couldn't just add the instances, as it wouldn't store coordinates.

Maybe doing a for loop and checking the pixels of the sprite for transparency, and when a pixel is not transparent you translate it's position into a room or view coordinate. The grid covers the room / view and you set the appropriate cell.

This is not likely to be cheap, but it is a one time only step once pause is active. It would mean you could then do the surface snapshot and deactivate all functions / drawing of the instances. When you want to know whether the mouse is over an area, or has an area to check, you get those coordinates (maybe stored in a ds map) and find the id / ids of the instance / s.

Reactivate that instance, set it's activities and then deactivate it. Once pause is not active, all the instances are reactivated.

When all is said and done it may be more expensive than just leaving them active - I'd have to test it.

Either:

1) Leave them all active, with certain collision tests still available for selecting units. Then have a less detailed mp grid for proper pathfinding

or

2) Big cost of first step to fill in detailed precise grid, offset by being able to turn all instances off afterwards. Still requires a less detailed grid for pathfinding, but maybe has more resources for doing so.

It may work, it may not :)

EDIT:
Here is my first attempt at doing id checking without using collision checks (so can turn off all instances) Didn't go with a grid in the end.....:
create event of control object:
Code:
is_map = ds_map_create();
get_id = noone;
script to enter instances, run in control object:
Code:
var do_map = is_map;
var is_left, is_top, start_top, horiz_length, vert_length, is_id;
ds_map_clear(do_map);

with (object2) // this is my object. You would probably want a parent object for all of those to include
{is_left = bbox_left;
is_top = bbox_top;
start_top = is_top
horiz_length = bbox_right - is_left;
vert_length = bbox_bottom - is_top;
is_id = id;
repeat (horiz_length)
{
repeat(vert_length)
{
if position_meeting(is_left, is_top, id)
{
ds_map_add(do_map, string(is_left) + "/" + string(is_top), is_id);
}
is_top += 1;
}
is_top = start_top;
is_left += 1;
}
}
step event of control object:
Code:
get_id = ds_map_find_value(is_map, string(mouse_x) + "/" + string(mouse_y));
destroy event control object:
Code:
ds_map_destroy(is_map);
A BREAKDOWN OF THIS:
I realised that an mp grid was pointless, as this still requires a ds map to store coordinates and ids. So it just uses a ds map, and puts coordinates as keys, and ids as a value.

1) Objects have precise collision checking set for sprites

2) They test themselves (across their bounding box coordinates) for collision

3) Points found are stored as a key in a ds map, and the value is their id

4) Surface would be captured (not included here)

5) Instances can be deactivated, and so no drawing done. Image is instead a fixed surface being held.

6) In the step event the mouse x / y are converted into a key, and checked to see if same key exists. If it does it returns the id of the instance at that position. That instance can then be reactivated with visible set to false.

I have this working, so it is doable. Putting the keys into the map isn't cheap, but not unexpected and is only one time cost per pause event. Then you can deactivate the instances.

Looking up the keys doesn't seem very costly, but I'd have to test that and shouldn't be taken as gospel.....
 
Last edited:
Top