I wouldn't exactly say "easier", but the proper way to do this is through a process called "shuffling", which is primarily used in card games to ensure the same card won't appear multiple times, while still giving you a random card.
The problem with your current approach is that there's a chance the player will need to wait a long amount of time if the random number generator keeps returning a number that has already been given before. In the worst case, the player may even have to wait forever until a new number appears. This may not be noticeable in your example, since you only have 4 options, but the more options you have, the more likely it is you'll run into this problem (and the longer it'll take to get out).
Also, your event appears to be missing the case when the player has already been transported to all 4 locations. In this case the event will remain stuck in an infinite loop.
The simplest, most popular shuffling algorithm is the Fisher-Yates algorithm. It works like this:
- Take a random number between 1 and the number of cards you have.
- Swap the first card with the card given by the random number.
- Take a random number between 2 and the number of cards you have.
- Swap the second card with the card given by the random number.
- Repeat until you reach the last card.
We could in theory implement this with events only, but shuffling is best performed with an array (a collection of values), and we don't want to waste too many variables on that. Fortunately, we won't need a plugin for that. Instead, we'll be using the
Script event command, which gives us access to JavaScript, but without needing a plugin.
First we want an array with our options. In your example, our options can be represented by numbers, so an array with numbers is good enough:
JavaScript:const options = [...Array(4).keys()];
This will give you an array containing the number 0 - 3 (4 numbers in total).
Note: I'm not sure if MV supports this, but since this is about MZ, I'll just leave this code here, as it's the shortest (and easiest) way to create an enumerated array.
Next, we shuffle our array:
JavaScript:for (let i = options.length - 1; i > 0; i--) {
const index = Math.randomInt(i + 1);
const tmp = options[i];
options[i] = options[index];
options[index] = tmp;
}
This is what is happening:
- The
for keyword allows us to loop while a certain condition is met.
let i = options.length - 1 creates a new variable named i (this is a JavaScript variable, and is separate from RPG Maker variables). The number is initialized with the number of cards we have (you'll understand the -1 part later). This variable only exists within the for loop.
i > 0 this is the condition for the for loop. As soon as i > 0 is no longer true (e.g. i === 0), the loop will stop.
i-- ensures that our i variable is decreased by 1 every time we're about to repeat the loop (this happens before the condition is checked).
const index = Math.randomInt(i + 1); gives us a random number between 0 and max - 1 (in this case, max is i + 1). Note: Math.randomInt() is a function that only exists in RPG Maker.
const tmp = options[i]; allows us to save the value of options[i] in a variable named tmp. We need this, because we're about to replace the value of options[i] with something else. Since we want to swap numbers, we need to remember what the old number was before we overwrite it. By the way, arrays start with the number 0, so if you want the first element of an array, you need [0] instead of [1]. This is why we originally set i to be options.length - 1 instead of simply options.length; otherwise we'd try and access a value that doesn't exist in our options array.
options[i] = options[index]; is the first step of our swap.
options[index] = tmp; is the second step of our swap.
As you can see, in this implementation of the Fisher-Yates algorithm, we shuffled from the last to first card (as opposed to shuffling from the first to last card). The reason is to simplify the math around getting the random number (it's easier to get a random number between 0 and n, than to get one between m and n).
Now that we have shuffled our
options array, how do we use it? Simple:
JavaScript:options.pop();
will give us the last element of our
options array and remove it from the array. Thus, we ensure that we will never receive the same number twice.
Now to combine this with RPG Maker events:
- Use a
Control Variables event command to create a variable that will contain our array. Operation: Set, Operand: Script. For the script, write [...Array(4).keys()] (change the 4 to the number of options you want to support). Also, remember the ID of the variable you're using. We'll need this so our shuffling algorithm can access the array.
- Use a
Script event command and write the following code inside:
JavaScript:const arr = $gameVariables.value(13);
for (let i = arr.length - 1; i > 0; i--) {
const index = Math.randomInt(i + 1);
const tmp = arr[i];
arr[i] = arr[index];
arr[index] = tmp;
}
Replace the 13 with the ID of the variable from step 1 (if the ID is #0001, then use 1; do not add zeroes in front of the number, as this can lead to unexpected results).
The above two steps must be performed only once. They must not be repeated (unless you want to create a new array and shuffle it).
Once you've shuffled the array, every time you want to get your next random number, use a
Control Variables event command to save the number in your D4Roll variable. Operation: Set, Operand: Script. For the script, write
$gameVariables.value(13).pop(); Remember to replace the number 13 with the ID of your array variable.
As you keep popping numbers from your array, you'll eventually run out of numbers (and then you'll only get 0). To avoid that, you'll need a
Conditional Branch event command with the Script:
$gameVariables.value(13).length > 0. Again, remember to replace the number 13 with the ID of your array variable. If you enter that conditional branch, it means you still have numbers in your array. If you land in the Else branch, then you ran out of numbers to use. What to do in that case is yours to decide. Maybe you want to create a new array and shuffle it whenever your current array runs out?
The rest is very similar to your existing code:
Code:If: D4Roll = 0
Transfer Player: Normal Town
End
If: D4Roll = 1
Transfer Player: Abandoned Town
End
If: D4Roll = 2
Transfer Player: Oasis
End
If: D4Roll = 3
Transfer Player: Mining City
End
Remember that our array had numbers from 0 to 3, so you need to adapt your event to use 0-3 instead of 1-4.
The advantages of this approach:
- You don't need any Switches.
- There are no unpredictable lags from when your game has to reroll a random number multiple times if the player is unlucky.
- Your teleport event becomes much shorter and easier to read.
The disadvantages of this approach:
- It might be harder to read and understand, depending on your programming skills.
- You need to prepare an additional event that will create the array and shuffle it, and make sure it can only run once per playthrough (and always before the teleport event can run).
- Your game can crash if you made any mistake writing (or copy-pasting) the script. If it does, finding and fixing the mistake may be harder.