Ikeyana - Procedural Sprite Shape Levels
Updated a month ago
Created by: Jonathan Miller and Camden Cecrle
About Ikeyana
Ikeyana is a demonstration of creating modular, procedurally-generated, spline-based levels using Unity’s new 2D tools. The level in this demonstration is constructed entirely of 3 sprite shape platforms (or cells). These cells are not grid based, nor do they have a set connection position. They are spawned in at runtime as the player runs to the right. Then, the splines of the cells are modified to join the end of the existing cells. There can be any number of cells, of any size or shape, and Ikeyana will merge them seamlessly. The colliders of the platforms are also merged, to allow for smooth movement across a continuous level.
There are still improvements to be made to the system, and this is obviously not yet a fully fleshed-out game. This is just a demonstration of the new Unity 2D tools. The next improvements in this system we are planning are:
  • The creation/adjustment of additional points along the level spline for smoothing when the angle formed is too steep
  • Injecting another spline between the spawned platforms in order to support smooth transitions between sprites
  • Dynamically altering the sprite index of the sprite shape to allow for procedural, natural variations in a sprite shape
  • Solving an issue where the movement of one of a spline’s points causes the sprite of the entire spline to move
These improvements are all relatively simple, and made possible using Unity’s new 2D Sprite Shape system.
The current code used for this is free to use for anyone, and is pasted below.

using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.U2D; using System.Linq; public class LevelSpawner : MonoBehaviour { public GameObject[] cells; private GameObject player; private Vector3 rightSideOfContinousPlatform; private GameObject currentPlatform; private GameObject nextPlatform; private Spline currentPlatformSpline; private Spline nextPlatformSpline; private EdgeCollider2D currentPlatformEdgeCollider; private EdgeCollider2D nextPlatformEdgeCollider; private int platNumber = 1; void Awake() { //Find the pre-existing spawn platform, and initialize current platform variables with respect to it. player = GameObject.FindGameObjectWithTag("Player"); currentPlatform = GameObject.Find("Platform"); currentPlatformSpline = currentPlatform.GetComponent<SpriteShapeController>().spline; currentPlatformEdgeCollider = currentPlatform.GetComponent<EdgeCollider2D>(); rightSideOfContinousPlatform = currentPlatform.transform.TransformPoint(currentPlatformSpline.GetPosition(currentPlatformSpline.GetPointCount() - 1)); } void Update() { //If the player is approaching the right end of the level, spawn a new platform. if(Vector3.Distance(player.transform.position, rightSideOfContinousPlatform) < 200f) { spawnRightPlatform(); } //TODO: If the player is approaching the left end of the level, spawn a new platform. } void spawnRightPlatform() { //Decide what platform template to use randomly. int randomCell = Random.Range(0, cells.Length); //Pick point far to the right to place the platform, this will be adjusted in the same frame. Vector3 placeLocation = rightSideOfContinousPlatform + new Vector3(90f, 0, 0); //Spawn the platform at placeLocation nextPlatform = Instantiate(cells[randomCell], placeLocation, Quaternion.identity); //Setup the GameObjects & Components needed for this function, based off the spawned platform GameObject nextPlatformSS = nextPlatform.transform.Find("SpriteShape").gameObject; SpriteShapeController nextPlatformSSC = nextPlatformSS.GetComponent<SpriteShapeController>(); nextPlatformEdgeCollider = nextPlatformSS.GetComponent<EdgeCollider2D>(); nextPlatformSpline = nextPlatformSSC.spline; //Retrieve the width of the new platform, and adjust the positioning accordingly. 10 is added to the x spacing to prevent sharp turns. (adjustable) float platWidth = nextPlatformSS.GetComponent<InformationHolder>().width; Vector3 correctLocation = rightSideOfContinousPlatform + new Vector3((platWidth/2)+10, 0, 0); nextPlatform.transform.position = correctLocation; //Find the ends of the platforms. Vector 3rightSideOfCurrentPlatform is already specified from previous iteration. Vector3 leftSideOfNextPlatform = nextPlatformSpline.GetPosition(0); Vector3 left = nextPlatform.transform.TransformPoint(leftSideOfNextPlatform); Vector3 right = rightSideOfContinousPlatform; //Find the midpoint between the two ends. Vector3 midpoint = new Vector3((left.x + right.x) / 2f, (left.y + right.y) / 2f, 0); //Brings the two end points on the colliders to the midpoint. Vector2[] currentColliderPoints = currentPlatformEdgeCollider.points; currentColliderPoints[currentColliderPoints.Length - 1] = currentPlatform.transform.InverseTransformPoint(midpoint); currentPlatformEdgeCollider.points = currentColliderPoints; Vector2[] nextColliderPoints = nextPlatformEdgeCollider.points; nextColliderPoints[0] = nextPlatform.transform.InverseTransformPoint(midpoint); nextPlatformEdgeCollider.points = nextColliderPoints; //Brings the two end points of the spline together. Either side is visually adjusted to overlap .25, to avoid gaps. (adjustable) currentPlatformSpline.SetPosition(currentPlatformSpline.GetPointCount() - 1, currentPlatform.transform.InverseTransformPoint(midpoint + new Vector3(.25f, 0, 0))); nextPlatformSpline.SetPosition(0, nextPlatform.transform.InverseTransformPoint(midpoint + new Vector3(-.25f, 0, 0))); //Renames the new platform, and moves the newly generated platform into the 'Current Platform' variables = "Plat" + platNumber; rightSideOfContinousPlatform = nextPlatform.transform.TransformPoint(nextPlatformSpline.GetPosition(nextPlatformSpline.GetPointCount() - 1)); currentPlatformEdgeCollider = nextPlatformEdgeCollider; currentPlatformSpline = nextPlatformSpline; currentPlatform = nextPlatform; platNumber++; nextPlatform = null; } }

Camden Cecrle