Building Palm Run's Spawning System
Working on Palm Run, one of my main challenges has been building a spawning system for obstacles and collectibles. Endless runners rely on variety and unpredictability to keep players engaged, so creating a system that feels dynamic but balanced is critical. For me, as a beginner, this process has been equal parts rewarding and frustrating.
In this post, I’ll share my current progress with the spawning system, some of the challenges I’m still facing, and how I’ve been iterating to refine it. I’ll also walk through the code and decisions that shaped the system as it exists today.
The Vision for the Spawning System
The idea was simple on paper: obstacles and collectibles should spawn dynamically as the player progresses, creating a unique experience every time. However, achieving this required balancing three key aspects:
Patterned Spawning: I wanted to define specific patterns for obstacles and collectibles directly in the Unity editor for more control.
Procedural Spawning: Adding randomness for variety, so no two runs feel exactly the same.
Avoiding Chaos: Ensuring that obstacles don’t overlap or create impossible scenarios—a problem I’m still tackling.
This vision led me to a hybrid approach: predefined patterns with the option for procedural randomization. Let’s break it down.
How It Works
Patterned Spawning
One of the first things I implemented was the ability to define obstacle and collectible patterns in the Unity editor. This allowed me to create configurations like:
A row of logs blocking multiple lanes.
Coins forming a zigzag pattern.
A mix of stationary and moving obstacles.
I also added an "associated collectible pattern" to each obstacle pattern, which makes spawning collectibles more cohesive and streamlined. Initially, I had separate spawners for obstacles and collectibles, but it felt clunky and prone to issues. Merging them into a single system simplified everything.
Here’s the core structure of the pattern system:
[System.Serializable] public class ObstaclePattern { public string patternName; public float minimumDistance; public float maximumDistance; public ObstacleType[] obstacleTypes; public LaneType[] laneTypes; public SpawnPointPosition[] spawnPositions; public List<CollectablePattern> associatedCollectablePatterns; }
This setup lets me specify details like lane types, spawn positions, and even associated collectibles for each pattern. It’s made the system far more user-friendly compared to my earlier iterations, where I manually specified spawn point IDs and lane positions.
Procedural Spawning
To add variety, I implemented an option for procedural spawning. This mode selects from predefined patterns or places obstacles and collectibles randomly, based on rules. While it’s great for creating unpredictability, it’s also introduced some persistent issues:
Overlapping Obstacles: Moving obstacles sometimes intersect with static ones, creating unplayable situations.
Difficulty Balance: Randomization occasionally makes some sections far harder (or easier) than intended.
Here’s an example of the procedural spawning logic:
private void SpawnRandomObstacles(GameObject segment) { Transform spawnPointsParent = segment.transform.Find("ObstacleSpawnPoints"); if (spawnPointsParent == null) { Debug.LogWarning("ObstacleSpawnPoints parent not found in the segment."); return; } List<Transform> availableSpawnPoints = new List<Transform>(); foreach (Transform lane in spawnPointsParent) { foreach (Transform spawnPoint in lane) { availableSpawnPoints.Add(spawnPoint); } } int obstacleCount = Random.Range(1, Mathf.Min(maxObstaclesPerSegment, availableSpawnPoints.Count) + 1); for (int i = 0; i < obstacleCount; i++) { if (availableSpawnPoints.Count == 0) break; int randomIndex = Random.Range(0, availableSpawnPoints.Count); Transform randomSpawnPoint = availableSpawnPoints[randomIndex]; availableSpawnPoints.RemoveAt(randomIndex); GameObject randomObstacle = GetRandomObstacle(); if (randomObstacle == null) continue; GameObject newObstacle = Instantiate(randomObstacle, randomSpawnPoint.position, randomSpawnPoint.rotation); float yOffset = obstacleYOffsets[randomObstacle.name]; Vector3 obstaclePosition = newObstacle.transform.position; obstaclePosition.y += yOffset; newObstacle.transform.position = obstaclePosition; newObstacle.transform.SetParent(segment.transform); } }
Hybrid Approach
Currently, I’m using a mix of both approaches. The system prioritizes predefined patterns to maintain structure and balance but also includes procedural elements to keep runs fresh. This hybrid method is a work in progress, but it’s getting closer to what I envisioned.
Challenges I’m Facing
Spawning systems are deceptively complex. Here are some issues I’m still working through:
Overlaps in Procedural Mode: Moving obstacles and static obstacles occasionally spawn in ways that intersect. This is a priority for me to fix.
Dialing in Patterns: While the framework is in place, I need to create more patterns to ensure the game feels complete.
Editor Workflow: I’ve iterated on the editor setup multiple times to make it user-friendly, but there’s always room for improvement. For example, adding easier ways to preview patterns directly in the editor would be helpful.
Here’s what the editor currently looks like when defining patterns:
What I’ve Learned
This process has been an incredible learning experience. Some of the biggest lessons include:
Iterate Often: My first few attempts at the spawning system were overly complicated. Simplifying the logic and iterating frequently made it much more manageable.
Cohesion Matters: Combining obstacle and collectible spawning into a single system made everything feel more polished and less prone to weird bugs.
Balance Structure and Randomness: Predefined patterns provide consistency, while procedural spawning keeps things interesting.
What’s Next
The spawning system is functional but far from perfect. My immediate goals are:
Fixing overlapping issues between moving and static obstacles.
Expanding the pattern library to ensure variety and fun gameplay.
Refining the procedural logic to create smoother difficulty progression.
With these improvements, I’m confident the system will evolve into something that feels polished and complete.
Reflecting on the Process
Building the spawning system for Palm Run has been both challenging and rewarding. It’s taught me so much about game design, iteration, and problem-solving. While there’s still work to be done, I’m excited about where it’s headed.
If you’re a beginner like me, don’t be afraid to experiment and embrace the process. Every iteration brings you closer to your vision, and every mistake is an opportunity to learn.
Let me know if you’d like to see more details or have questions about the system!