That's good to hear about the simple solution. The distance checks would've introduced a decent amount of overhead.
Anyway, what you want to do is create a big database of information that tracks spawn related data: Type of spawn(s), Amount of spawn(s), Interval of spawn(s). Then use Timers to manage when things should happen.
So first you create a database:
-
Events
-

Time - Elapsed game time is 0.00 seconds
-
Conditions
-
Actions
-

-------- Round [1] --------
-

Set Variable Spawn_Type[1] = Weak Zombie (Lvl 1)
-

Set Variable Spawn_Total[1] = 30
-

Set Variable Spawn_Interval[1] = 1.00
-

-------- Round [2] --------
-

Set Variable Spawn_Type[2] = Dumb Zombie (Lvl 2)
-

Set Variable Spawn_Total[2] = 26
-

Set Variable Spawn_Interval[2] = 1.25
-

-------- Round [3] --------
-

Set Variable Spawn_Type[3] = Big Zombie (Lvl 3)
-

Set Variable Spawn_Total[3] = 20
-

Set Variable Spawn_Interval[3] = 2.00
The [index] here is meant to represent the current round a player is on (or if rounds are shared, the current round in general). You can reference this data at any given time to determine what should happen next. Of course, if you want things like Spawn_Interval to be random then you would simply remove that variable from the database and use your own (Random interval number) logic.
Then you create a trigger that handles preparing for the next round:
-
Events
-

Countdown Timer - Next_Round_Timer expires
-
Conditions
-
Actions
-

Set Variable Current_Round = (Current_Round + 1)
-

Set Variable Spawns_Remaining = (Spawn_Total[Current_Round] x (Number of players in PG_Users_Playing))
-

Player Group - Pick every player in PG_Users_Playing and do (Actions)
-


Loop - Actions
-



Set Variable Player_ID = (Player number of (Picked player))
-



Countdown Timer - Start Spawn_Timer[Player_ID] as a One-shot timer that expires in (Random real between 6.00 and 10.00) seconds
So whenever you start the Next_Round_Timer and it expires, it'll start a new round. You can do this anywhere at any time.
Then you create trigger(s) that handle the creation of the zombies:
-
Events
-

Countdown Timer - Spawn_Timer[1] expires
-
Conditions
-
Actions
-

Set Variable Player_ID = 1
-

Set Variable Spawns_Remaining = (Spawns_Remaining - 1)
-

Set Variable Random_Lane_Number = (Random number between Lane_Min_Range[Player_ID] and Lane_Max_Range[Player_ID])
-

Unit - Create 1 Spawn_Type[Current_Round] for Player 9 (Gray) at Spawn_Start_Points[Random_Lane_Number] facing 180.00 degrees
-

Set Variable Last_Spawn = (Last created unit)
-

Unit - Order Last_Spawn to Attack-Move To Spawn_End_Points[Random_Lane_Number]
-

If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-


If - Conditions
-



Spawns_Remaining Greater than 0
-


Then - Actions
-



Countdown Timer - Start Spawn_Timer[Player_ID] as a One-shot timer that expires in (Random real between 6.00 and 10.00) seconds
-


Else - Actions
-



Countdown Timer - Start Next_Round_Timer as a One-shot timer that expires in 30.00 seconds
In my demo map I handle each player's Timer in one central trigger, but if that's too confusing for you then you can break it up into multiple Triggers for multiple Players. In this example I kept it simple and dedicated this trigger to Player 1, but as you may be able to tell by the lack of Player-specifics, the trigger is very close to being generic. You would just need to determine which Timer expired and get the appropriate Player_ID that matches it. (Refer to my demo map to see how)
Anyway, this trigger is doing the following:
1) Gets the ID of the Player whose Timer expired so that we can get their other personal data.
2) Uses that Player's personal data to determine the Position where the zombie is created and where it's told to Attack-Move to. You would have setup Point variables at the start and end of their Lane Regions beforehand. The same is true for their Lane number ranges (1-5, 6-10, 11-15).
3) Updates how many Spawns remain. This way we know when they have all been created so that we can move on to the next round.
4) Creates a Zombie based on the current Round, referencing our spawn database that we created earlier.
5) Starts the timer again to spawn a new zombie OR if all zombies have spawned then prepare for the next Round.
Some comments about your current triggers:
1) If you're typing "P1" in your variable name then you're likely doing something wrong. Most of these variables can either be Arrays where you simply plug a Player's Number into the [index] to get their associated data OR you don't need an Array at all and one variable will suffice. For example, my Last_Spawn variable does NOT need to be player specific or an Array. It's simply a shortcut to the (Last created unit), with the benefit of being more efficient and less prone to error.
2) Stop repeating yourself. If you're copying and pasting the same chunk of Actions multiple times then you're likely doing something wrong. You can use Arrays, For Loops, Timers, Run Trigger, and other methods to make a single trigger work for everyone and to be very clean with only ~10 Actions instead of ~1000 of the same Actions copied over and over again (like your P1Wave1 trigger).
3) Waits generally suck and are the quick and dirty solution. Use them only when precision isn't needed since they will be inconsistent when playing online. For a crucial system like this one you will want predictability and control which comes with using Timers.