- Home /
How do I proceduraly generate an environment around a sphere?
Hi
I'm creating an endless running game with an interesting perspective. The world is a sphere and i'm not sure how to go about generating a random environment (off the path and on the path within the 3 lanes) on the surface of the sphere.
Thanks
Jared
Answer by aldonaletto · Aug 07, 2013 at 02:13 PM
For something like shown in the picture, you should create tileable meshes in a 3D editor. As the player moves, randomly select the next mesh and replace the older one as a child of the planet object. The trees and other details could also be randomly selected and childed to the meshes.
You could have segments corresponding to spherical wedges of about 90 degrees, for instance, like below:
Each new wedge could be a game object containing the mesh and with the details childed to it. The hierarchy could be as follows:
Planet
Wedge1
Tree1
Tree2
Rock1
Bush1
Wedge2
// wedge2 details
Wedge3
// wedge3 details
Wedge4
// wedge4 details
EDITED: If you want to use a simple sphere as the planet, things are easier. Paint a sphere with the path and the grass (you must create a suitable texture in an image editor), then rotate it when the player walks. Items like trees, rocks etc. may be created at Start and populate the planet near to the player. As the planet rotates and items pass the player, they may be "transplanted" to a new location ahead enough of the player.
The script below is an example on how to do this. The path region is delimited by the angle (-pathAngle..pathAngle) from the vertical, and the grass and items populate the region from (-grassAngle..grassAngle) from the vertical (path region excluded). When the up key is pressed, the planet rotates about X to simulate that the player is walking; as the planet rotates, items that pass the angle killAngle from the forward direction are moved to a new random position at bornAngle from the horizontal plane.
In order to use this script you must attach it to the player, then drag the planet object to planet, set radius to the radius of your planet and fill the prefabs array with the prefabs.
var planet: Transform; // drag the planet here
var radius: float = 20; // planet radius
var vel: float = 6; // player speed - degrees per second
var prefabs: Transform[]; // drag the item prefabs here
var qntItems = 30; // how many items populate the scene
var bornAngle: float = 0; // items born at this X angle
var killAngle: float = 90; // items disappear after this angle
var pathAngle: float = 10; // path angle from vertical
var grassAngle: float = 45; // end of grass angle from vertical
private var items: Transform[];
function Start () {
items = new Transform[qntItems];
// populate planet
for (var i=0; i<qntItems; i++){
// create a random item
var item = Instantiate(prefabs[Random.Range(0, prefabs.Length)]);
MoveItem(item, Random.Range(bornAngle, killAngle)); // move it to a random position
item.parent = planet; // child item to the planet
items[i] = item;
}
}
function Update () {
// rotate planet according to up/down arrow keys
var vAxis: float = Input.GetAxis("Vertical");
planet.Rotate(-vAxis*vel*Time.deltaTime, 0, 0);
if (vAxis > 0.1){ // if moving...
animation.CrossFade("walk"); // play "walk" animation
} else {
animation.CrossFade("idle"); // else play "idle"
}
for (var i = 0; i < qntItems; i++){
// if item passed the kill angle from Z axis...
if (Vector3.Angle(items[i].up, Vector3.forward) > killAngle){
// replant it at the born angle, at random position
MoveItem(items[i], bornAngle);
}
}
}
// Move item to a random position in the grass, at elevation angleX
function MoveItem(item: Transform, angleX: float) {
var angleY: float;
if (Random.value < 0.5){ // randomly select left or right sides
angleY = Random.Range(-grassAngle, -pathAngle);
} else {
angleY = Random.Range(pathAngle, grassAngle);
}
// calculate new item up direction
var dir = Quaternion.Euler(0, angleY, 0) * Vector3.forward;
dir = Quaternion.Euler(-angleX, 0, 0) * dir;
// set item position and rotation
item.position = planet.position + radius * dir;
item.rotation = Quaternion.FromToRotation(Vector3.up, dir);
}
EDITED2: This version includes random size, rotation and color for the world assets, and continuous pickup items generation. The pickups are generated at a 0 born angle, and with a separation of pickupAngle degrees: when the planet rotates pickupAngle from the born angle, a new pickup is generated. The pickups must be collected by the player, since the code doesn't takes care of them after creation - if not collected and destroyed, they will crowd the path after a few turns:
var planet: Transform; // drag the planet here
var radius: float = 20; // planet radius
var vel: float = 6; // player speed - degrees per second
var prefabs: Transform[]; // drag the item prefabs here
var qntItems = 30; // how many items populate the scene
var bornAngle: float = 0; // items born at this X angle
var killAngle: float = 90; // items disappear after this angle
var pathAngle: float = 10; // path angle from vertical
var grassAngle: float = 45; // end of grass angle from vertical
var pickupPrefabs: Transform[]; // drag the pickup prefabs here
var pickupAngle: float = 7; // degrees between pickups
var pickupHeight: float = 1; // pickup height above ground
private var items: Transform[];
private var lastPickup: Transform; // last pickup created
function Start () {
items = new Transform[qntItems];
// populate planet
for (var i=0; i<qntItems; i++){
// create a random item
var item = Instantiate(prefabs[Random.Range(0, prefabs.Length)]);
MoveItem(item, Random.Range(bornAngle, killAngle)); // move it to a random position
item.parent = planet; // child item to the planet
items[i] = item;
}
lastPickup = CreatePickup();
}
function Update () {
// rotate planet according to up/down arrow keys
var vAxis: float = Input.GetAxis("Vertical");
if (vAxis > 0.1){ // if moving forward...
planet.Rotate(-vAxis*vel*Time.deltaTime, 0, 0); // rotate planet
animation.CrossFade("walk"); // play "walk" animation
} else {
animation.CrossFade("idle"); // else play "idle"
}
for (var i = 0; i < qntItems; i++){
// if item passed the kill angle from Z axis...
if (Vector3.Angle(items[i].up, Vector3.forward) > killAngle){
// replant it at the born angle, at random position
MoveItem(items[i], bornAngle);
}
}
if (Vector3.Angle(lastPickup.up, Vector3.forward) > pickupAngle){
lastPickup = CreatePickup();
}
}
// Move item to a random position in the grass, at elevation angleX
function MoveItem(item: Transform, angleX: float) {
var angleY: float;
if (Random.value < 0.5){ // randomly select left or right sides
angleY = Random.Range(-grassAngle, -pathAngle);
} else {
angleY = Random.Range(pathAngle, grassAngle);
}
// calculate new item up direction
var dir = Quaternion.Euler(0, angleY, 0) * Vector3.forward;
dir = Quaternion.Euler(-angleX, 0, 0) * dir;
// set item position and rotation
item.position = planet.position + radius * dir;
item.rotation = Quaternion.FromToRotation(Vector3.up, dir);
// set random size and rotation about Y
item.Rotate(0, Random.Range(0, 360), 0);
item.localScale = Random.Range(0.4, 1.5)*Vector3.one;
// set a random color
var rndColor: Vector4 = Random.insideUnitSphere;
var rndr = item.GetComponentInChildren(Renderer);
rndr.material.color = rndColor;
}
function CreatePickup(){ // create pickup in front of the planet
var pickup = Instantiate(pickupPrefabs[Random.Range(0, pickupPrefabs.Length)]);
pickup.parent = planet;
pickup.up = Vector3.forward;
pickup.position = planet.position + Vector3.forward * (radius + pickupHeight);
return pickup;
}
If some pickups may not be collected by the player, all pickups should have a script attached that would suicide them after passing back the player - something like this:
function Update(){ // destroy pickup when at more than 90 degrees from born angle:
if (Vector3.Angle(transform.up, Vector3.forward) > 90) Destroy(gameObject);
}
If one wants irregular spacing between pickups, a possible solution is to include a number of "empty pickups" in the pickupPrefabs array - empty objects with the suicide script above: they would do nothing but occupy the space of real pickups.
Thanks very much for the speedy response. So going back to my question: how would I randomly spawn and despawn assets on the surface, whether it be the entire sphere or segments? And is there something like a weight painting function that I an use maybe to separate the spawn area for the environment and for the track.
@Xtro, hope you're right: it would be much easier this way!
I know it's the most advanced method but it's the easiest to start with. If you prove your game design, then maybe you can build an advanced system.
@aldonaletto, you my friend are a genius! The script works beautifully, the only issue is scale and rotation. On my trees, the scale needs to be at 2.5 rather than 1 and for my little plants, the rotation puts them 90 deg on the x making them face sideways. Also how would I go about, varying the spatial distance between the elements? And how would I make the z rotation random? Thanks a lot for all your help!!
Answer by meat5000 · Aug 10, 2013 at 02:09 PM
At the start() store the initial position of the character in Vector3. Keep track of the angle difference between the current position and start position. Store this angle as a Quaternion and you can use it to modify a Vector3 directly.
UpdatedVector = Quaternion * OldVector
This will allow you to spawn anything using instantiate relative to the player using that Quaternion, provided the initial position on the object is similar. Add a Random.range to it to make an object spawn randomly within the area. Use the same angle to rotate the clone in the instantiate() function to keep its feet on the floor upon spawn. Parent the spawned object in script to the world to allow it to follow the transform of the rotating world. When objects drop off camera view behind the player, destroy them.
Here is something I've used for instantiating a cube around a circle. A separate "Governor" script decides the next position. Maybe you can draw some inspiration from it.
private var guv : GameObject;
public var original : GameObject;
static var cloneCount : int = 0;
var turnAngle : float = 18.0;
var nextPos : int = 0;
var timeKeeper : float = 0.0;
var spawnTime : float = 3.0;
var placement : Vector3 = Vector3(0,88,0);
var newPlacement : Vector3;
@HideInInspector
public var clone : GameObject;
function Start ()
{
guv = GameObject.Find("Governor");
//original = GameObject.Find("Cube");
original = GameObject.Find("CrazyCube");
turnAngle = guv.GetComponent(Governor).stepAngle;
spawnTime = guv.GetComponent(Governor).spawnTiming;
nextPos = guv.GetComponent(Governor).nextPlace;
}
function FixedUpdate ()
{
turnAngle = guv.GetComponent(Governor).stepAngle;
nextPos = guv.GetComponent(Governor).nextPlace;
spawnTime = guv.GetComponent(Governor).spawnTiming;
timeKeeper += Time.fixedDeltaTime;
if (timeKeeper >= spawnTime)
{
newPlacement = Quaternion.Euler(0,0,turnAngle*nextPos) * placement;
clone = Instantiate(original, newPlacement, Quaternion.Euler(0,0,turnAngle*nextPos));
cloneCount += 1;
clone.name = ("clone"+cloneCount.ToString());
timeKeeper = 0;
}
}
Answer by sacredgeometry · Aug 10, 2013 at 01:31 PM
The easiest way that doesn't involve any maths is to use a parented objects rotational pivot. To explain I will give you a little stepped procedure that you can replicate programatically.
Create two cubes and offset one from the other.
Parent one to the other
Rotate the parent cube so that the child follows its rotation
unparent the child
You will notice that it is still in the same position, you could do this with a center pivot to a sphere ... the only real problem is uneaven terrain but I guess you could fix this with ray casting.
This is a simple non-mathematic solution :)
Other than that (sorry i had the wrong link in my copy/paste buffer ) ...
http://entitycrisis.blogspot.co.uk/2011/02/uniform-points-on-sphere.html
Answer by glitchy · Aug 07, 2013 at 02:15 PM
You could split your map up into pre-made segments then randomly spawn and connect them together. What i would do is think of the world like a pie that you are running around on the edge of. As you pass a piece of the pie and can no longer see it, replace it with another random piece.
Answer by Xtro · Aug 07, 2013 at 01:56 PM
I think easiest way is to set a lot of preset locations via empty gameobjects. Than you can crete the obstacles or bonuses on those locations.
Your answer
Follow this Question
Related Questions
Endless "Falling" Game - Background generation 2 Answers
Creating random seeds and saving them (procedural generation)Universe 1 Answer
How to make a race track similar to audiosurf? 0 Answers
How to create endless terrain... 0 Answers
Generating a tiled world with roads, possibly using a 2D array? (C#) 0 Answers