Timed Events - Respawn, Activate and more

Discussion in 'RMVX Ace Tutorials' started by Heirukichi, Mar 24, 2019.

  1. Heirukichi

    Heirukichi Veteran Veteran

    Messages:
    1,294
    Likes Received:
    546
    Location:
    Italy
    First Language:
    Italian
    Primarily Uses:
    RMVXA
    Timed Events and Complex Timed Systems
    by Heirukichi


    Version: 1.0
    Last updated: 03-26-2019 [MM-DD-YYYY]


    Requirements

    Scripts: none.

    VX Ace knowledge: as long as you know how to make basic events, how to add script calls, how to use variables, switches and conditional branches you should be fine. Everything else has its own explanation here.

    Ruby knowledge: it helps understanding script calls. However this method works by simply copy/pasting the code and changing the few things you have to adapt to your project so you should be fine even with no ruby knowledge at all. I explain what script calls do in English so ruby knowledge is not required to understand their purpose.



    Intro

    Have you ever thought of creating events capable of doing something based on a timer, but then you hit a wall after understanding that the wait command does not work when you are in a different map? Have you ever wanted to have your event encounters re-spawn after a set amount of time?

    If the answer to any of those questions - or similar ones - is "yes, I have", this tutorial is meant for you. If you ever wanted to create some kind of timed events system in your game, this tutorial might be helpful and you might be surprised knowing how many things you can do with just RPG Maker VX Ace core engine. In fact, for this tutorial I will be using no script at all.

    Since this tutorial is quite long and detailed, I decided to use spoilers to avoid huge walls of text for each paragraph. Please, keep in mind that information inside those spoilers is necessary to understand how this tutorial works. As much as you can, refrain from skipping them unless they are specifically labeled as optional - in which case their only purpose is to satisfy your curiosity.


    Table of Contents
    Here is a list of subjects I am going to cover in this tutorial:
    • FIFO approach
    • Timed event encounters re-spawn
    • Complex timed systems
    • FIFO and efficiency
    • Possible usage
    Now that you know what this tutorial is about, it is time to start going deeper into each subject.


    FIFO approach
    The first thing I want to talk about is the FIFO approach, and this is the second time I write the FIFO word - third with this. Unless you already know what I am talking about, the question "what does FIFO mean?" is quite a legitimate one.

    FIFO stands for "First In First Out". It means the first element stored is the first element that goes out. You can think of it as a queue to pay in the supermarket. The first one to enter the queue is the first one to pay (and the first one to go home). This is a real example of a FIFO.

    How can this help with timed events?

    Depending on the situation, this might be a good - not optimal but still good - implementation for your timed events. More information about FIFO efficiency in ruby can be found in the last paragraph of this tutorial.

    One of the reasons why you might want to implement timing in your game is because you want to let your event encounters re-spawn after some time. In this tutorial, I am going to use this as an example, but you should keep in mind that it does not only apply to this situation.


    Timed events re-spawn
    First of all, you should decide how many frames should pass before your event can re-spawn. I am going to use a general value called T, but you should keep in mind that in your event you have to change that to be the number of frames you need for your game.

    Once you decided how many frames have to pass, you have to decide on a self switch to trigger the blank page. In this tutorial I am going to use Self Switch A but in reality you can use any self switch in accordance to your event setup.

    There are two more things you need in order to proceed: one is a variable to save the position in the current cycle - in my example it will be variable C - while the other is a variable to save map ID, event ID and Time for your events - in my example this is going to be variable D. I hope you already know what map ID and event ID are, if you do not, or if you are uncertain about this, you might want to stop reading this tutorial and check the one in my signature instead (Andar's starting point for new users).

    The thing you might not know is what Time is. That represents the value of the counter variable at the moment when the action that triggers the blank page happens. If we are talking about event encounters, that action would be the party being victorious after fighting.

    Knowing this, there are three fundamental steps you have to follow to reach your goal and achieve a good timing system for your game:
    • set up events;
    • set up data variable at the beginning of the game;
    • set up parallel process event.

    In your event, right before triggering the blank page, you should put a script call (third page, bottom right) and use this code:
    Code:
    $game_variables[D] << [$game_map.map_id, @event_id, $game_variables[C] - 1]
    If you do that, you basically use that variable as an array to store a lot of information. Of course, this only works for arrays so you have to turn that variable into an array first. This leads to step 2 (step 1 and 2 can be completed in any order as long as the variable is successfully turned into an array before running the game).

    Turing your data variable into an array is quite simple, you only need to have an autorun event at the beginning of the game - if you already have one autorun event running at the beginning of the game it is fine, you can use that - and add the following script call in it:
    Code:
    $game_variables[D] = []
    Now your variable is an array and you can store that information without incurring in a game crash.

    NOTE: do not use that variable for other purposes later on or not only will you lose every data stored in it but it will also let your game crash - which is not nice.

    So far this is just a way to store information though, there is nothing like a timer yet. To add a timer you need a parallel process common event. You can set the switch controlling it on as soon as the game starts and let it run continuously through the whole game.

    The purpose of this common event is quite simple: update your counter variable (I remind you that in my case its ID is C but you have to change that to be a real variable ID for your game) and check if it is time to extract the first element of the data variable (in my case its ID is D but you have to change that as well).

    Theoretically speaking, you might want to extract all the elements whose time is equal to the current timer. In practice, at least if we are talking about event encounters, that is something that will not happen. I am going to show you the general way to do it anyway so that you can have a solid foundation. Those who are interested can check the notes at the end of this paragraph to learn more about the things I mentioned.

    In order to control our counter variable and prevent overflow, I am going to reset it to 0 once it reaches our set time - Time in my example is going to be called T, but change it to be a number in your code. After that, we have to check the first element of the data variable and see if it was added at the same time in the previous cycle. If that is the case, it means enough time has passed and we should use what is stored in the first element to change self switches and activate the event once again. You can repeat this last two steps as many times as you want, as long as the previous statement is true.

    This is one way to do that with a script call:
    Code:
    $game_variables[C] += 1
    $game_variables[C] = 0 if ($game_variables[C] >= T)
    i = 0
    unless $game_variables[D].empty?
      while (i < $game_variables[D].length)
       if ($game_variables[D][i][2] == $game_variables[C])
         i += 1
       else
         break
      end; end
      if (i > 0)
        elements = $game_variables[D].slice!(0, i)
        elements.each do |e|
          $game_self_switches[[e[0], e[1], 'A']] = false
    end; end; end
    With this you have events that re-spawn - or do something - after a certain amount of time has passed.

    In this paragraph, I covered everything about creating your timed system. As promised - for curious people - here is a last spoiler containing end paragraph notes. They will not change the way your timed system works, but they will be useful if you want to know how it works.

    Tips and Tricks
    In my script calls, I subtracted 1 to current counter value when adding a variable and then used a counter that is 1 frame longer than my reset time. If you are wondering why, here comes the answer.

    If you know about parallel process events, you might know that they are a little bit tricky. In particular, since parallel process events run more or less at the same time as normal events, you never know what happens. Subtracting 1 to current time ensures you that any element of the array is going to be removed in the next cycle, and not in the same cycle when it enters the array.

    No matter what happens, if the parallel process increases that counter variable each time, issues might only happen when the array is empty and you add an element whose time is equal to the current timer value. In that case there are chances its self switch is set to false as soon as it enters the array. If the code fits a single script call, nothing bad should happen, but since doing so does not decrease efficiency...better safe than sorry.

    Can you decrease the timer for each element of the array instead of increasing counter?

    Yes, you can. However, doing so is not very smart Decreasing time for each element of the array and then removing elements whose time is 0 requires a complete scan of the said array. A complete scan means you have to perform a number of operations that is equal to array length, which is much worse than performing just one or two operations. If you are trying to do something more complex - see paragraph below - this is something you want to absolutely avoid.


    Complex timed systems
    (optional paragraph)

    There are many things you might want to do with your timed system, there are so many possibilities that it is impossible for me to predict exactly what you want to do with it. However, if you are planning to do something more complex with it, I am not going to abandon you. This paragraph is meant for all those who want more freedom with their timed systems in order to achieve something less simple.

    If you are reading this paragraph, there is one thing you might want to keep in mind...
    In this situation your power comes from your level of freedom. Just because I am giving you the freedom to achieve something more complex, it does not mean you can do it however you want.

    If you do it the wrong way it will bite you later.

    Of course, checking an array of 5 elements is not going to change much, but what about an array with one million elements? Do you think checking one million elements each frame is not going to have any noticeable impact on your game? If you do, you are completely wrong. Of course, you are not going to have one million elements, or at least I hope so, but having a few hundred elements is not impossible in a complex system so you should be careful.

    The number of operations required to extract the first array element is much bigger than you think - more information in the last paragraph of this tutorial. Just because ruby is simple to understand - and to write - it does not mean it makes things simple, it just hides some information in order to make the code easier to read/write.

    This is not a problem if the number of times you have to extract each element is one or two, but be careful: as that number increases, so does the number of operations required. This might significantly slow down your game if you are not careful.

    In the previous paragraph I made a few assumptions:
    • self switch was always Self Switch A when activating it and when deactivating it;
    • frames required to reset events were the same for each event.
    This is a simplistic way of seeing things. In a complex system those assumptions might not be true at all. If that is the case, you have to change those script calls accordingly.

    This is probably the easiest thing to adapt. When storing data you can just store the self switch you want to change as well ('A', 'B', 'C' or 'D'). The most important things here are that you place it always in the same spot in your array and that you do not forget those quotation marks (you can use either 'A' or "A" but never use just A).

    If you do this, your script call should look like this:
    Code:
    $game_variables[D] << [$game_map.map_id, @event_id, $game_variables[C] - 1, self_switch]
    # change "self_switch" to be your self switch (either 'A', 'B', 'C' or 'D')
    
    This way you can use a different self switch for each event. Naturally, your parallel process event script call should be modified as well. It should deactivate the new switch instead of just deactivating Self Switch A.

    The whole thing should be more or less the same, you only have to change this line
    Code:
    $game_self_switches[[e[0], e[1], 'A']] = false
    into this one
    Code:
    $game_self_switches[[e[0], e[1], e[3]]] = false
    It goes without saying that in a complex system you might not want to just reset the switch you used to trigger the next event page. There are cases when you might want to simply activate a different switch based on your timer. Storing a self switch in your variable makes that possible. The only difference will be in the parallel process event, where instead of just setting that switch off you have to change its value.

    Instead of the previous line you have to use these two:
    Code:
    val = $game_self_switches[[elem[0], elem[1], elem[3]]]
    $game_self_switches[[elem[0], elem[1], elem[3]]] = !val
    The final script call should look like this:
    Code:
    $game_variables[C] += 1
    $game_variables[C] = 0 if ($game_variables[C] >= T)
    i = 0
    unless $game_variables[D].empty?
      while (i < $game_variables[D].length)
       if ($game_variables[D][i][2] == $game_variables[C])
         i += 1
       else
         break
      end; end
      if (i > 0)
        elements = $game_variables[D].slice!(0, i)
        elements.each do |e|
          val =$game_self_switches[[e[0], e[1], e[3]]]
          $game_self_switches[[e[0], e[1], e[3]]] = !val
    end; end; end
    
    This script call works even for less complex systems. If you want to mix complex things and normal things, feel free to use this one. It will still turn off switches that are on while turning on those that are off.

    This is easy to do, less easy to control, mostly because it means you are no longer using a FIFO approach.

    Why is that so?

    Let me explain with a simple example so that it might be easier to understand. Just consider a game where I have a garden and I am going to plant different seeds. First of all, I plant a seed that requires 600 frames to turn into a plant, after 2 seconds (120 frames) I plant another seed that requires 200 frames to turn into a plant. What happens?

    When I plant my second seed my first seed still needs 480 frames to start growing, but even if that is the first element in my array my second element needs 200 frames. Can you see the difference here? 200 is less than 480. My second element in the array is the one that is supposed to be extracted first. This goes against the rule First In First Out - thus it is no longer a FIFO approach.

    It is still a queue but it is a priority queue, where each element has a given priority that is determined when you insert it in the data variable and its position in the queue might change each time a new element is added.

    So how is it done?

    A possible solution is to set the time for each cycle to be a value greater than the biggest possible waiting time (if you have an event that has to wait 999 frames, another one that has to wait 280 and another that has to wait 650 your cycle would be 1000 frames long since 1000 is bigger than 999).

    Once you set that maximum time limit, you have to calculate the position in the current - or next - cycle for each event to trigger. That is done by adding the number of frames to wait to the current counter value, subtracting 1 from it and then subtracting the maximum time limit if the result is greater than or equal to that maximum time limit.

    The script call to calculate time should look like this:
    Code:
    time = $game_variables[C] - 1
    time += amount_of_frames_for_current_event # change this to be a number
    time -= T if (time >= T) # remember that T has to be your maximum time limit
    Of course this script call only calculates time. The next step is to insert this new element in the data variable at its right position. Luckily ruby has a method to do that so we can do it in the same script call. However, to know which position it should occupy we have to calculate if the element we are going to add to data variable is going to trigger in this cycle or in the next one. Doing so requires a few checks.

    How to check if an element is going to trigger in this cycle or in the next one?

    A new element that is going to trigger in the current cycle will have a relative time that is greater than the current counter variable value. If that is not true, the new element has to wait until the counter variable resets - thus it is going to trigger in the next cycle. Since the same rule applies to each element of the array you can check their relative time (by relative time I mean their time in relation to the current cycle). You just have to add the maximum time limit to each element time if that is less than counter variable value.

    Just add this to the previous three lines to do the trick:
    Code:
    if $game_variables[D].empty?
      $game_variables[D] << [$game_map.map_id, @event_id, time]
    else
      index = $game_variables[D].length
      cycle_time = (time >= $game_variables[C] ? time : time + 1000)
      $game_variables[D].each.with_index do |e, i|
        e_time = (e[2] >= $game_variables[C] ? e[2] : e[2] + 1000)
        if (cycle_time < e_time)
          index = i
          break
      end; end
      if index == $game_variables[D].length
        $game_variables[D] << [$game_map.map_id, @event_id, time]
      else
        $game_variables[D].insert(index, [$game_map.map_id, @event_id, time])
    end; end
    
    I used random numbers in this script call just to show you what should be a number and what not in your game.
    Code:
    time = $game_variables[8] - 1
    time += 300
    time -= 1000 if (time >= 1000)
    if $game_variables[9].empty?
      $game_variables[9] << [$game_map.map_id, @event_id, time]
    else
      index = $game_variables[9].length
      cycle_time = (time >= $game_variables[8] ? time : time + 1000)
      $game_variables[9].each.with_index do |e, i|
        e_time = (e[2] >= $game_variables[8] ? e[2] : e[2] + 1000)
        if (cycle_time < e_time)
          index = i
          break
        end
      end
      if index == $game_variables[9].length
        $game_variables[9] << [$game_map.map_id, @event_id, time]
      else
        $game_variables[9].insert(index, [$game_map.map_id, @event_id, time])
      end
    end
    This should fit in your script call box, but, since script call box size appears to be different depending on your OS, you might have to split it in multiple script calls. If that happens, you have to be a bit smart and mix conditional branches in it so that you can save space. One important thing is that you use instance variables when you split it. It should look like this (I am going to mark lines where it is safe to split it).

    Code:
    @time = $game_variables[8] - 1
    @time += 300
    @time -= 1000 if (@time >= 1000)
    # Splitting here is safe
    if $game_variables[9].empty? # You can use a conditional branch instead
      $game_variables[9] << [$game_map.map_id, @event_id, @time]
    else # Part of the previous conditional branch
      @pos_index = $game_variables[9].length
      cycle_time = (@time >= $game_variables[8] ? @time : @time + 1000)
      $game_variables[9].each.with_index do |e, i|
        e_time = (e[2] >= $game_variables[8] ? e[2] : e[2] + 1000)
        if (cycle_time < e_time)
          @pos_index = i
          break
        end
      end
      # Splitting here inside the conditional branch is safe
      if @pos_index == $game_variables[9].length # Conditional Branch
        $game_variables[9] << [$game_map.map_id, @event_id, @time]
      else # Part of the previous Conditional Branch
        $game_variables[9].insert(@pos_index, [$game_map.map_id, @event_id, @time])
      end
    end
    
    I am not sure if the last block fits, but since I cannot test it on a different OS I hope it does. If it does not fit, you can use a conditional branch: select "Script" (fourth page, last entry) and put everything in your condition there. You can repeat this step each time you find an if/else branch.
    timing_system_screenshot.png
    In the image there is a line that did not fit in the contents window. However, you should be able to get that line from the code above.

    It goes without saying that, if you have a different waiting time for each event, the situation where multiple events at the beginning of the data variable - which is an array - could be extracted at the same time is no longer something that only theoretically happens, but is something that might happen for real.

    You can combine those two things and achieve something even more complex. However, as I previously said, keep in mind that if you do not do it properly, things might get nasty later on.

    As in the previous paragraph, I am going to add some extra information here. That information includes the reason why it is better to insert each element in the right place instead of checking each time which element has to be removed. If you are not curious or if you think you do not need this extra information - you will not be needing them if you follow what I wrote step by step - feel free to skip this last spoiler. However, I strongly recommend reading it if you want to have a better understanding of how things work.

    Tips and Tricks
    In this paragraph I basically showed you how to implement a priority queue using arrays. Using a priority queue is much better than using a simple unsorted array or a normal array and sorting it. Unfortunately, arrays are not the best implementation for priority queues, but thanks to this implementation your game is unlikely to suffer performance issues because of that (not using a priority queue might lead to performance issues).

    The trick lies in how you add new elements to the queue. In a FIFO queue you just add a new element at the end of the queue and extract the first element; in a priority queue you have to insert each element in its right spot or it is not going to work. Doing so requires quite a few checks but it is definitely worth the effort.

    Is it possible to do it without so many checks?

    Yes, it is. You can just use a normal array, scan each element and remove it if it has to be removed. If you really dislike doing those checks feel free to do it, but - as I said - this is probably going to bite you later on.

    Scanning the array to insert an element means you have - well, this is obvious - to scan elements of the array. It requires a number of operations that is approximately equal to the number of elements in your array. Scanning each element to remove those that have to be removed requires the same number of operations BUT it requires that many operations each frame.

    As an example, think of an array that is long enough to let you skip one frame each time you have to check it. What happens in that situation?

    If you scan it each time you add a new element you lose 1 frame and, when that happens, your game runs at 59 FPS instead of 60. Even if you ad multiple elements in a very short amount of time you still lose X frames, where X is the number of elements you add. In the worst case scenario - where you add one element each frame - your FPS will be halved and your game runs at 30 FPS instead of 60. While this is true this is a situation that will most likely never happen. What will happen is that you add a fixed amount of elements and lose a fixed amount of frames.

    If you scan it each time you have to check if elements have to be removed you lose 1 frame each time. But that operation runs each frame so, in the best case scenario, you are going to lose 1 frame each frame - thus halving your FPS.

    Are you able to see the difference now? While one solution leads to losing a certain amount of frames only when you add new elements, the other leads to a huge frame rate drop even if you are not adding any element.

    In general you should decide which option is better based on how many times you have to scan the array and how many times you have to insert a new element. This does not apply to this situation though. That is because in the worst case scenario - adding one element each frame - scanning the array each time you want to remove one element - which happens every frame as well - is still the worst options.

    There are other options to efficiently add elements to a priority queue, but those options require scripts with proper data structures (except lists - as explained in the next section of this tutorial). If you are still having performance issues when adding elements to your data variable, you might consider a script that implements those data structures.


    FIFO and efficiency
    In this tutorial I showed you how to implement FIFO queues using arrays. I decided to show you how to implement them this way because ruby already has quite a few methods to let arrays act as lists - which are the usual way to implement FIFO queues.

    To keep it short, a list is a - well - list of elements where each element contains certain data telling you which one is the next element - and possibly which one is the previous one as well. This means adding extra elements only requires you to edit the last element field pointing to its next element.

    As you can see, this is a single operation so it is very convenient when you have to insert elements in the last spot many times. At the same time, it is very convenient because if you have to remove the first element, you can simply pick that first element, find the second element using data contained in it and change the second element so that it shows nothing before it. Done. Once again a very fast operation and your element is removed.

    Even if ruby has many methods to let arrays act as lists, they are not lists. As long as an array size increases ruby has to allocate more memory. This is not something you do manually, it is something that ruby does without you knowing about it, but it still has to do it. As far as I know, ruby starts with a size of 3 for each array and then doubles it each time you reach the previous limit. This process is time consuming.

    Of course, it is very unlikely to affect your game since resizing the array is something that only happens when you add more elements to the array itself (if you have read the end notes in the previous paragraph you know what I mean). However, if you add many elements one after another in a very short amount of time, you should keep this in mind.

    Another thing you should always remember is that removing elements from an array is not free. Each time you use the slice! method, ruby shifts each element after the sliced part to the left so that there is no gap in the remaining elements. For this reason, using slice! multiple times in your script call is not recommended and it is better to slice all the elements you want to remove at once.

    For those who really want to speed up their game and already understood everything about this tutorial, I want to add a little extra: how to achieve this using lists (maximum possible efficiency for the FIFO approach).

    As I already said, a list contains many different objects chained together. Each object points to the next object without any need for them to be stored in two consecutive memory blocks. This way whenever you interact with one of them the others are left untouched.

    The first thing to do to achieve this is turning your data variable into something that can be used as a list.

    What should the data variable contain to be used as a list?

    This is probably the most important question you should ask when thinking of implementing lists. There are many ways to do something like this. Here is a simple answer:
    • something that points to the first element of the list;
    • something that points to the last element of the list;
    • something that points to a sentry node we can use to determine if the list is empty or not.
    Please note that if you have a sentry node, whenever you find it while scanning your list it means you reached the end of the list. For this reason, knowing list size is not important if you have something like a sentry node.

    If you do not like using a sentry node and want to store list size instead - maybe because you actually need that number for something - feel free to do it. It changes how you have to handle checks but after all it is just another possible implementation that works perfectly.

    In my opinion, having a sentry node is better. This way you can skip the size update each time you add/remove one element, effectively reducing the number of operations by 1. However, a single operation on an integer value is hardly going to affect your efficiency so feel free to use list size if you want to.

    Since ruby handles everything as an object and there is no restriction on what you can put into arrays, a list could be initialized like this:
    Code:
    z = [[0, 0, T]]
    $game_variables[D] = [z, z, z]
    list_init.png

    This means you can check if the list is empty using one of the two following checks (up to you):
    Code:
    $game_variables[D][0] == $game_variables[D][2]
    # OR
    $game_variables[D][1] == $game_variables[D][2]
    
    If either list head or list tail is equal to sentry node, your list is empty.
    You might have not noticed - if you did I sincerely compliment your wits - but there are two main reasons why I initialized the sentry node that way.

    By analyzing it you can clearly see that it is an array containing a single array with three elements. I did not do it because I like matryoshka dolls, instead, I did it because this will prove to be extremely efficient later on.

    First of all, you have to check if the head element of the list has to be extracted in your parallel process. If your sentry node is an array with one element, you can write something like this without generating errors:
    Code:
    z[0] == other_ary # z is the sentry node here
    This does not generate errors because our sentry node is an array that actually contains one element. At the same time comparing it with other nodes - that have to point to their respective next node - will not be very complex.

    Ruby - as a matter of fact - checks if two arrays have the same length before start comparing their elements. When checking if your list is empty using
    Code:
    $game_variables[D][0] == $game_variables[D][2]
    # OR
    $game_variables[D][1] == $game_variables[D][2]
    
    there is no need to scan each element of the array because the sentry node only contains one element - it contains an array but it is still only one element - and thus has a different length from any other node - which contains at least two elements (see next part).

    The other good reason is that since a sentry node initialized this way contains an array with at least three elements as first element, and that array has T as third element, you can simply compare that value and your current counter to see if they match, even when the sentry node is your list head.

    It goes without saying that if you use T as third element of that array and your counter is set to 0 when greater than or equal to T, they will never be the same. This tells your system that no update is required without having to add extra checks to see if the list is empty or not.

    How to add elements to such a list?

    Adding elements to a list works in a different way than adding elements to an array - even if this list is implemented as an array. First of all, each element has to point to the next one so you cannot simply use an array containing map ID, event ID and Time.

    You can do it using once again an array where the first element contains your data array and the second element points to the next node in the list. However, if you add an element as your list tail it will have no next node so you can simply use that sentry node I mentioned above as next node for the tail node.

    Code:
    data_array = [$game_map.map_id, @event_id, $game_variables[C] - 1]
    new_list_element = [data_array, $game_variables[D][2]]
    # check the list to know if it is empty
    if ($game_variables[D][0] == $game_variables[D][2]) # list empty
      $game_variables[D][0] = new_list_element
    else # add a next element to list tail
      $game_variables[D][1][1] = new_list_element
    end
    $game_variables[D][1] = new_list_element
    list_event.png

    With this the trick is done. If the list is empty, the new element acts as both list head and tail. If the list contains other elements, it acts as list tail.

    How to remove elements from this list?

    In the same way as as adding elements to a list is different than adding elements to an array, removing elements also works in a different - more efficient - way.

    If you created your sentry node the same way I created mine, there is no need to check if the list is empty. This is because the sentry node I used contains an array of three elements and the third element of that array is the upper limit for the counter variable. That means the comparison between its third element and the current timer value is possible (because it is an array with 3 elements) but is always false.

    You can just check that in your parallel process using this:
    Code:
    $game_variables[C]+= 1
    $game_variables[C] = 0 if ($game_variables[C] >= T)
    # so far this is the same
    while ($game_variables[D][0][0][2] == $game_variables[C])
      # if you are here your list has to be updated
      c_map = $game_variables[D][0][0][0]
      c_event = $game_variables[D][0][0][1]
      $game_self_switches[[c_map, c_event, 'A']] = false
      # set list head to be current head next element
      $game_variables[D][0] = $game_variables[D][0][1]
    end # repeat from above as long as you can extract elements
    list_parallel_process.png

    If you do it this way, you get rid of all those inefficient things such as shifting the array when removing one element. This way both adding and extracting elements are operations with a complexity of T = O(1) - and thus very fast - regardless of how long your list becomes.

    NOTE: of course do not forget to change C, D and T to be real numbers. The code will not work otherwise.

    Possible usage
    In the second paragraph of this tutorial, I used event encounters as an example to explain how to use a FIFO approach in your game. However, you should keep in mind that this is not the only thing you can achieve with something like this. In this paragraph, I am going to name a couple of things you can do with the information contained in this tutorial.

    • Farming systems where you plant a seed and a plant grows after certain amount time.
    • Event encounters that re-spawn after a set amount of time.
    • Timed quests (without any need for a timer) where success and failure are determined by how fast you complete them. You can even grant players better rewards based on how long it took for them to complete the quest.
    • Timed behavior change for events. An example would be a camp of more or less hostile enemies that turn aggressive if you stay within a certain range for too long. Other examples might be aggro mechanics for real time combat.
    These and many other things can all be achieved with just what I explained in this tutorial.



    Author notes:
    This tutorial shows you how to make a complete timed system for various purposes. While credits are not required, they are very appreciated. I will not hunt you if you do not credit me, after all I am not the one who invented FIFO or priority queues.

    However, keep in mind that completing this tutorial, so that everybody could achieve something like this without using a single script and - at the same time - keeping efficiency in mind, took quite a while.

    If you want to show me your appreciation, please, add me to your credits and notify me about it. If you do not want to it is fine, you are still free to use everything written in this tutorial.



    This marks the finishing line for my tutorial. I hope everyone reading it can use this knowledge to create his or her timed system. On a side note, I really hope that this tutorial helps people understand how powerful a correct usage of RPG Maker VX Ace tools can be, even without extra scripts (script calls are just ways to use things that the engine itself already has).
     
    Last edited: Apr 4, 2019
    #1
    Cuddlebuns likes this.

Share This Page