- Home /
How can I make movement on a sphere?
Hello. I want to make a game like Super Stardust HD.
Super Stardust HD
The game takes place on a large sphere that the player travels around. But the trick is, the player doesnt actually move, they spaceship is always in the same place, everything else just rotates. I have scripts for things like the movement, firing, etc. but I cannot figure out how to create the illusion of moving around the sphere (currently the movement is just on a 2D plane like geometry wars, etc., and it cannot be on a sphere for obvious physics reasons).
How would I create (or at least create the illusion of) a sphere that the player can move around?
Thanks.
can you just create a sphere? or your world is large while the sphere is obviously smaller?
sounds like the bonus level in the 3rd sonic game on the genesis. Something like that?
I'm not sure what obvious reason that it cannot be on a sphere. I guess you are referring to the fact that gravity only pulls down in Physx, but it is much easier than it sounds to add spherical gravity. The trickier part is aligning the object with the spheres normal, or if you are on a perfectly rounds sphere, you can do some trig to get the angle of rotation.
Answer by Statement · Jan 08, 2011 at 12:25 PM
I thought it was a fun thing to attempt to create from scratch. Here's my stab at it.
In my scene, I set up a sphere and a cube such that the cube is parented to the sphere and that the cube has 0.1 scale. Then I apply the script to the cube. Now you can control it running along the sphere.
It works like having a 2d coordinate system but instead of dealing with vectors, we're dealing with quaternions. I use a float angle to determine which way the player is facing on the sphere and then rotate the player around the midpoint of the planet in the direction expressed by cos angle and sin angle. Sideways movement is achieved by taking the perpendicular vector and rotating with respect to that axis.
It can feel a bit daunting at first but if you just look at the functions one at a time, you'll see there isn't too much wierd stuff going on. My hardest problem was figuring out the proper up and forward vector so I can rotate the object properly (without up vector, the object would flip 180 degrees at some places). :)
Script in action with bullets.
Bullets with trail particles.
Here is my game object hierarchy with annotations:
- Sphere [Scale 1]
- Cube [Scale 0.1] [SphericController]
Camera (Looking down at Cube)
- Cube [Scale 0.1] [SphericController]
List of controls:
Up - Go forward.
Down - Go backward.
Left - Rotate left.
Right - Rotate right.
A - Go left.
D - Go right.
Left Control - Shoot.
Controller
using UnityEngine;
public class SphericController : MonoBehaviour
{
public SphericProjectile prefab;
public float radius = 0.6f;
public float translateSpeed = 180.0f;
public float rotateSpeed = 360.0f;
public float fireInterval = 0.05f;
float angle = 0.0f;
Vector3 direction = Vector3.one;
Quaternion rotation = Quaternion.identity;
void Fire()
{
var clone = (SphericProjectile)Instantiate(prefab);
clone.transform.position = transform.position;
clone.transform.rotation = transform.rotation;
clone.transform.parent = transform.parent;
clone.angle = angle;
clone.rotation = rotation;
}
void Update()
{
direction = new Vector3(Mathf.Sin(angle), Mathf.Cos(angle));
// Fire bullet code
if (Input.GetKeyDown(KeyCode.LeftControl))
InvokeRepeating("Fire", float.Epsilon, fireInterval);
if (Input.GetKeyUp(KeyCode.LeftControl))
CancelInvoke("Fire");
// Rotate with left/right arrows
if (Input.GetKey(KeyCode.LeftArrow)) Rotate( rotateSpeed);
if (Input.GetKey(KeyCode.RightArrow)) Rotate(-rotateSpeed);
// Translate forward/backward with up/down arrows
if (Input.GetKey(KeyCode.UpArrow)) Translate(0, translateSpeed);
if (Input.GetKey(KeyCode.DownArrow)) Translate(0, -translateSpeed);
// Translate left/right with A/D. Bad keys but quick test.
if (Input.GetKey(KeyCode.A)) Translate( translateSpeed, 0);
if (Input.GetKey(KeyCode.D)) Translate(-translateSpeed, 0);
UpdatePositionRotation();
}
void Rotate(float amount)
{
angle += amount * Mathf.Deg2Rad * Time.deltaTime;
}
void Translate(float x, float y)
{
var perpendicular = new Vector3(-direction.y, direction.x);
var verticalRotation = Quaternion.AngleAxis(y * Time.deltaTime, perpendicular);
var horizontalRotation = Quaternion.AngleAxis(x * Time.deltaTime, direction);
rotation *= horizontalRotation * verticalRotation;
}
void UpdatePositionRotation()
{
transform.localPosition = rotation * Vector3.forward * radius;
transform.rotation = rotation * Quaternion.LookRotation(direction, Vector3.forward);
}
}
Projectile
using UnityEngine;
public class SphericProjectile : MonoBehaviour
{
public float radius = 0.6f;
public float translateSpeed = 270.0f;
public float angle = 0.0f;
public Quaternion rotation = Quaternion.identity;
Vector3 direction = Vector3.one;
void Start()
{
Destroy(gameObject, 1.0f);
}
void Update()
{
direction = new Vector3(Mathf.Sin(angle), Mathf.Cos(angle));
Translate(0, translateSpeed);
UpdatePositionRotation();
}
void Translate(float x, float y)
{
var perpendicular = new Vector3(-direction.y, direction.x);
var verticalRotation = Quaternion.AngleAxis(y * Time.deltaTime, perpendicular);
var horizontalRotation = Quaternion.AngleAxis(x * Time.deltaTime, direction);
rotation *= horizontalRotation * verticalRotation;
}
void UpdatePositionRotation()
{
transform.localPosition = rotation * Vector3.forward * radius;
transform.rotation = rotation * Quaternion.LookRotation(direction, Vector3.forward);
}
}
Apparently I checked "community wiki" and so no rep is awarded. sob sob :)
Hey so, I updated your script to work for a first person camera walking on a planet. $$anonymous$$ove and Rotate speed are variable, and I've included jumping with variable "gravity".
The planet must be its own transform with a collider, and the Camera must be the child of an empty game object that is in the same location as the planet. I made this for fairly large planets (I tested it on a standard sphere with a scale of 1500).
Hope someone finds this useful. I'll probably post it on the unify wiki as well.
using UnityEngine;
using System.Collections;
public class SphereNavigator : $$anonymous$$onoBehaviour
{
#region vars
public float rotSpeed;
public float moveSpeed;
public float rotDamp;
public float moveDamp;
public float height;
public float jumpHeight;
[Range(.1f, 10f)]
public float gravity;
public float radius;
public Transform planet;
protected Transform trans;
protected Transform parent;
protected float angle = 90f;
public float curJumpHeight = 0;
protected float jumpTimer;
protected bool jumping;
protected Vector3 direction;
protected Quaternion rotation = Quaternion.identity;
#endregion
#region Unity methods
void Start ()
{
trans = transform;
parent = transform.parent;
}
void Update ()
{
//parent.position = planet.position; // If you want to have a moving planet
direction = new Vector3($$anonymous$$athf.Sin(angle), $$anonymous$$athf.Cos(angle));
if(Input.Get$$anonymous$$ey($$anonymous$$eyCode.LeftShift))
Position(Input.GetAxis("Horizontal") * -moveSpeed, 0);
else
Rotation(Input.GetAxis("Horizontal") * -rotSpeed);
if(Input.GetButtonDown("Jump") && !jumping)
{
jumping = true;
jumpTimer = Time.time;
}
if(jumping)
{
curJumpHeight = $$anonymous$$athf.Sin((Time.time - jumpTimer) * gravity) * jumpHeight;
if(curJumpHeight <= -.01f)
{
curJumpHeight = 0;
jumping = false;
}
}
Position (0, Input.GetAxis("Vertical") * moveSpeed);
$$anonymous$$ovement();
}
#endregion
#region Actions
protected void Rotation(float amt)
{
angle += amt * $$anonymous$$athf.Deg2Rad * Time.fixedDeltaTime;
}
protected void Position(float x, float y)
{
Vector2 perpendicular = new Vector2(-direction.y, direction.x);
Quaternion vRot = Quaternion.AngleAxis(y, perpendicular);
Quaternion hRot = Quaternion.AngleAxis(x, direction);
rotation *= hRot * vRot;
}
protected void $$anonymous$$ovement()
{
trans.localPosition = Vector3.Lerp(trans.localPosition, rotation * Vector3.forward * GetHeight(), Time.fixedDeltaTime * moveDamp);
trans.rotation = Quaternion.Lerp(trans.rotation, rotation * Quaternion.LookRotation(direction, Vector3.forward), Time.fixedDeltaTime * rotDamp);
}
protected float GetHeight()
{
Ray ray = new Ray(trans.position, planet.position - trans.position);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
radius = Vector3.Distance(planet.position, hit.point) + height + curJumpHeight;
return radius;
}
#endregion
}
oh, also i included raycasting, so it can work on irregular shaped planets as well. for example, you may have small hills or a low-poly style planet. large mountains won't work well, however, because the camera always orients to the center of the planet rather than approximating the normal of the ground beneath your feet.
Answer by Jesse Anders · Jan 03, 2011 at 09:00 AM
Why do you think the player doesn't move? That seems unlikely to me. I'd be willing to bet the player does move, and that the camera simply adjusts so as to keep the player at the center of the screen.
Also, I doubt it's an illusion per se. I think it's more likely that the player and other game entities actually do move around the surface of the sphere in a more or less physically correct manner. (Keep in mind though that I'm just speculating - I haven't played the game, and I don't know for sure how it's implemented.)
As for moving on the surface of a sphere, this is discussed fairly frequently on the forums. The basic idea is:
Translate the player the desired direction and distance (tangent to the sphere)
Move the player to the closest point on the sphere to the player
Correctively align the player with the normal to the sphere at that point
Another option would be to actually move the player 'correctly' along an arc on the sphere's surface (I haven't tried that method myself though).
Yeah I doubt the scene moves according to the player but hey! We live in a relativistic world! :) I managed to solve the problem without having to toss around the scene. I did parent the camera to the player, so it appears as if the world revolves around the player. (History repeats itself).
I have a question regarding the script mentioned above. I tried it out and it worked quit nice, however if I spawn a character using Unitys "Random.onUnitSphere" the player was reset to position 0,0. I figured out that the reason for that was the rotation variable.
Random.onUnitSphere seems to give me a Vector3, is there a way I can use that Vector3 in order to calculate the right Quaternion for the rotation variable ?
Answer by DaveA · Jan 08, 2011 at 07:21 PM
What if you put all the world (except the ship or character) under one game object, call it 'world' and rotate that? The ship or character would not be moving but everything else would.
I think what it comes down to is, what's the point in doing that? What does it gain you over simply moving the ship/character itself and letting stationary objects be stationary?
I started my game doing this as well (rotating the world and leaving the character stationary), but you lose the ability to mark any of that stuff as static, which is a big loss for performance.
I ended up doing the opposite. Everything is marked static in the environment that isn't animating and I have the game camera following the character.
Answer by deathripper · Mar 27, 2015 at 10:20 AM
I have a question regarding the script mentioned above. I tried it out and it worked quit nice, however if I spawn a character using Unitys "Random.onUnitSphere" the player was reset to position 0,0. I figured out that the reason for that was the rotation variable.
Random.onUnitSphere seems to give me a Vector3, and rotation needs a Quaternion. Is there a way I can use that Vector3 in order to calculate the right Quaternion for the rotation variable ?
Your answer
Follow this Question
Related Questions
Make one GameObject look at, move toward another across a sphere 1 Answer
"Chasing" object around a sphere 0 Answers
The name 'Joystick' does not denote a valid type ('not found') 2 Answers
Making a bubble level (not a game but work tool) 1 Answer
Why does my character only dash forward and not in the moving direction? 1 Answer