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)
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);
}
}