GameMaker Anything I should know before coding this?

S

SayBaconOneMoreTime

Guest
Hi there:

So I'm working on a top down game. More specifically, I'm working on a cutscene system for a top down game. Recently, I discovered one of the worst ways to handle top down game cutscenes is to run code in a with() statement that just performs an action in the instance that needs to animate for the cutscene. The problems with this are (A) you can't be sure of whether an instance of the object exists- this can be alleviated by calling instance_exists() or whatever that function is, but that's rather cumbersome, and (B) there might be more than one instance of an object in a room, meaning the code would be run in every instance, which could be desirable in some circumstances, but most of the time not.

The system I have worked out in my brain to set up to alleviate this would have a persistent object called obj_actor or something like that which contains a DS map. The map would have a height of three, zero being ID of the instance in the grid, one being whether the instance is currently active or not, and two being the ID of the script being run inside of the instance. Each x coordinate would represent one actor. I would set up an enum of values to correspond with this system of x coordinates. Since, to my knowledge, there is no real way to access the length of an enum in GMS2 (correct me if I'm wrong), I would just put a variable in the enum called "len" or something as the very last variable in the enum and pass it in for the width when creating the grid so that every actor defined in the enum has a spot. I thought about using a map with a string for different actors and the id as a value, but the more I mulled that over in my head, the more complicated it got, and I decided using an enum would just be easier, even though the idea of creating actors and deleting them as needed to save on a miniscule amount of memory pleases me greatly. I realize you can't do dynamic things like that with enums, but I can always just define "actor1, actor2, actor3" etc, and have them function as whatever as needed. The idea is, I can simply call a script that says "set this actor's ID to this instance's ID" and then have another that goes "make this actor have this script" and then somewhere in the obj_actor have it go through and say "if actor is active and actor has a script to run and there is a valid instance ID to access, run the script inside the instance whose ID is stored in the actor." I realize that seems rather complex just to do cutscenes with, but it works in my head.

So my questions are, (A) is this a good way to handle this, or is there a better way that I'm missing, and (B) as the title states, is there anything that I have left out that would make this better/more flexible/easier? I think this seems ok, but who knows.
 
S

SayBaconOneMoreTime

Guest
UPDATE: System is coded, new issue. All previous questions posed above still apply though.

So it has come to my attention that a lot of these cutscenes will involve dialogue, which obviously has to be advanced by the player to make sure they (depending on the player) have time to read it or skip through it as fast as they can. Since a script can't execute in multiple parts (i.e., have the first few lines of a script go off when it executes, then wait for the dialogue to advance, go to the next part, etc) I thought about using a timeline. However, those are built on hard coded intervals of time, while this would need to listen for the dialogue to advance, then move on. Is there a way to make a timeline advance in sequence by one every time a trigger happens? I don't need help actually setting up the trigger, I already have something to have it check for to know when it would need to do it. Perhaps have the trigger start the time line running, and then have every node in the timeline turn itself off so it only advances in increments of one frame every time dialogue advances? I'll work on it, but help is appreciated.
 
@SayBaconOneMoreTime First of all, you have one of the best user names.

Every time I read it, I have to stop, lower my voice, and in *tough guy* mode, say "Say Bacon....One....More....Time....!"

Second, I found your description a bit hard to follow, maybe break it up a bit into more paragraphs next time.

(A) you can't be sure of whether an instance of the object exists- this can be alleviated by calling instance_exists()
with() already includes a check for the existence of an instance. If there is no instance, the with() code doesn't run.

(B) there might be more than one instance of an object in a room, meaning the code would be run in every instance, which could be desirable in some circumstances, but most of the time not.
Most of the time this is useful I find.

If you need to run it on an individual instance, that's up to you to get the right id before you use it.

Since, to my knowledge, there is no real way to access the length of an enum in GMS2 (correct me if I'm wrong), I would just put a variable in the enum called "len" or something as the very last variable in the enum and pass it in for the width
This is a pretty standard *trick* for enums as far as I know. I do this with pretty much all my enums.


You've put a lot of thought into what you are doing, however I cant' decipher from your text *what* exactly kind of cutscene you are trying to make.

I think you're possibly over-thinking it.

DIALOG : Could just have all the dialog in an array/data structure, advance the index of the array to move to the next dialog. Data Structure holds string text as well as sprites to display if needed.

enum Talk
{
text,
character_name,
sprite
}

dialog[0, Talk.text] = "Hello there";
dialog[0, Talk.character_name] = "Barry";
dialog[0, Talk,sprite] = spr_barry;

etc...

For the rest - I don't know what the end result you're trying to achieve is. I know you're trying to make a cutscene system which is great, but there are so many ways to do it I feel...

...perhaps some cutscene guru will reply to you at some point :)
 
S

SayBaconOneMoreTime

Guest
@SayBaconOneMoreTime First of all, you have one of the best user names.

Every time I read it, I have to stop, lower my voice, and in *tough guy* mode, say "Say Bacon....One....More....Time....!"

Second, I found your description a bit hard to follow, maybe break it up a bit into more paragraphs next time.



with() already includes a check for the existence of an instance. If there is no instance, the with() code doesn't run.



Most of the time this is useful I find.

If you need to run it on an individual instance, that's up to you to get the right id before you use it.



This is a pretty standard *trick* for enums as far as I know. I do this with pretty much all my enums.


You've put a lot of thought into what you are doing, however I cant' decipher from your text *what* exactly kind of cutscene you are trying to make.

I think you're possibly over-thinking it.

DIALOG : Could just have all the dialog in an array/data structure, advance the index of the array to move to the next dialog. Data Structure holds string text as well as sprites to display if needed.

enum Talk
{
text,
character_name,
sprite
}

dialog[0, Talk.text] = "Hello there";
dialog[0, Talk.character_name] = "Barry";
dialog[0, Talk,sprite] = spr_barry;

etc...

For the rest - I don't know what the end result you're trying to achieve is. I know you're trying to make a cutscene system which is great, but there are so many ways to do it I feel...

...perhaps some cutscene guru will reply to you at some point :)
First of all, thank you! I played a lot of Roadhog when I first made this account, and I always loved that voice line.

Secondly, my apologies, that post was honestly as much me getting my head together about my own system as it was me asking for advice. I have a little better handle on it now, and I'm in the process of coding it, so let me try to explain better:

I start by creating a persistent object. In that object, I set up that enum and a grid structure with a height of three and width of the size of said enum. Each column in the grid represents one "actor" and has a corresponding variable in the enum, and each row represents one bit of information about that actor. First row is the instance ID associated with that actor, second row is whether that actor is active or not, and third row is what script needs to be run with the instance ID associated with the actor. This is basically the most complicated part of the whole system, everything else would seem to be fairly standard.

Whenever I want to make a new cutscene in my game, I will start by creating a timeline. Each moment in the timeline represents one event within the cutscene. Whenever the cutscene is triggered, I will make the associated timeline run in the persistent object containing the grid. In the first moment of the timeline, I will also use a script to register IDs for all the "actors" I will need for the script- i.e., if this cutscene only involves the player, I will register the player's ID into the slot in the enum specifically for the player. I have several vanilla "actor1, actor2, actor3", etc set up in the enum in case there is a character I need to include in the cutscene but I do not want to make a specific variable in the enum for. However, if there is a character who appears across multiple cutscenes in multiple rooms, I will have both a specific variable in the enum with their name on it and a script that finds an instance of that character in the room and associates its ID with the appropriate spot in the grid. The idea behind this is that I then have the specific IDs of the instances required for the cutscene on call from a grid structure whenever I need them.

After all the setup within the first moment has been completed, I can then call a different script and associate another script (yes I know, this is a lot of scripts) with the specific "actor" I want to do something. This may be something as simple as panning the camera over to them, or it could be as elaborate as having them move along a specific sequence of points while animating. The script will also set the actor as "active." At this point, it should be clear what all the grid stuff is about.

Now that the actual events of this moment in the cutscene have been defined, I need to make a decision. Obviously, one thing that almost always goes with cutscenes is dialogue, and since I have a nonexistent budget, there is no way I will ever have voice acting for this game. Therefore, as much as I dislike it for this game, I'm using text box dialogue. However, these systems seem to interfere with each other, as with a text box dialogue, one wants the player to advance them at their own pace, whether that is to carefully pour over every word or to skip through it as fast as possible. This conflicts with the prescripted timing of a timeline. In addition, not every cutscene and not every moment in every cutscene might ned dialogue. There might need to be something as simple as a screenshake and brief denial of movement, or there might need to be a scene in which there is a bit of dialogue, then a pan over to a different location in the room, and then pan back to the characters for more dialogue. Here's what I've come up with to deal with that.

While coding the cutscene, at the end of every moment in the timeline, I need to look at whether there is a bit of dialogue in the moment I have just written. If there is, I need to do two things: (A) pause the timeline and (B) make the next moment in the timeline exactly one frame after the current one. Within the text box processor, there will be a trigger for whenever dialogue is advanced. When this trigger happens, the timeline will unpause and run for one frame right into the next moment. If there is no dialogue, simply set up the timeline regularly so that the next event will happen x frames after this one runs. Easy! The only other thing to remember is that, at the end of every cutscene, the grid needs to be reset back to being totally blank so the next cutscene has a plain canvas to work with.

Now, for the step event of the persistent grid object. It will loop through every x index of the grid, and check (A) if it is active, (B) if there is a defined script, and (C) whether there is a valid instance ID. If all of this is true, it will execute the script inside the instance ID, and reset all of these variables to false/noone and move right along to the next x coordinate. Thaaat's where all the actor stuff actually happens. All of the script association and whatnot is actually executed on here.

Alrighty, I think that about covers it. I realize this is a rather large wall of text, but I tried to break it up so it is easier to read. Thank you!
 
Sounds good to me. :)

I made a timeline / event system before, and basically followed a very similar logic, just without timelines. (This was using a different engine)

- I defined a bunch of events and put each one in a function / script.

- I then had an array of all these events.

- Each event also had a time assigned to it for when it needed to trigger.

- I had a global timer running.

- If I needed to do some dialog, I would pause the timer if needed. Resume the global timer at the end of the dialog.

- Otherwise, I would just check if the global timer has reached the next event, then execute the event.

Very close to what you have, plus I think TimeLines are designed for you to be able to stop and start them if needed.

It sounds like you are using a separate Moment for every line of dialog?

Why not have the whole dialog triggered off of one Moment? Then you may be able to avoid the whole, advance one timeline moment check thing.

Then just resume the timeline after dialog is finished.
 
S

SayBaconOneMoreTime

Guest
Sounds good to me. :)

I made a timeline / event system before, and basically followed a very similar logic, just without timelines. (This was using a different engine)

- I defined a bunch of events and put each one in a function / script.

- I then had an array of all these events.

- Each event also had a time assigned to it for when it needed to trigger.

- I had a global timer running.

- If I needed to do some dialog, I would pause the timer if needed. Resume the global timer at the end of the dialog.

- Otherwise, I would just check if the global timer has reached the next event, then execute the event.

Very close to what you have, plus I think TimeLines are designed for you to be able to stop and start them if needed.

It sounds like you are using a separate Moment for every line of dialog?

Why not have the whole dialog triggered off of one Moment? Then you may be able to avoid the whole, advance one timeline moment check thing.

Then just resume the timeline after dialog is finished.
Probably something I should consider doing. Most of the time whenever I am triggering dialogue, I also have the camera pan to whoever is talking, which means in most cases that wouldn't be that useful, but certainly couldn't hurt.
 
S

SayBaconOneMoreTime

Guest
Update: System is working wonderfully. Now to go through and convert all my old ramshackle cutscenes to this sleek new system. I'd highly recommend something like this to whoever wants to make in-engine cutscenes, but take my advice with a grain of salt because I'm new to this thing.
 

GMWolf

aka fel666
I would suggest using an actor action system.

All your characters should be actors, with minimal logic. Actions then dictate what they do.

This will allow you to transition between different cutscenes and game play very easily.
 
Top