Circular (Inside cylinder) World for 2d Platformer

I’m wondering what would be the best way to have a 2.5D side-scrolling platformer play out on the inside of a cylinder. So in a simple example, the character would be able to run all the way around the inside back to where they started.

I’ve thought about possibly having the character not move left or right but rather the cylinder rotate around them when pressing left/right, but wouldn’t this cause problems with the camera?

Basically, is there some efficient way to restrict the character’s left/right movement to a circular motion around the inside face of a cylinder world?

I just built a looping 2D/3D world recently, and I considered using a scheme like you describe with the inside of a cylinder, but I dropped that approach; it’s too messy and involves too much computation to have things move in arcs. Instead, split your scenery into two or more pieces that are at least wider than the screen. Two pieces —each half the size of the world— is ideal. Attach a script to these scenery groups called “LoopingScenery.” Then have all your game objects inherit from a class named “LoopingWorldObject” (or just attach it as a component). Then your scenery and game objects will move as needed according to the position of the main camera. Here’s what these classes look like for my game, which uses a world 10,000 units wide and two scenery chunks that are each half the size of the world:

public class LoopingScenery : MonoBehaviour {

  Transform T, mainT;

  // Manual Initialization, if needed...
  public void Init() { gameObject.SetActive(true); }

  // Initialization once in the world
  public virtual void Awake() {
    T = transform;
    mainT = Camera.main.transform;
  }
  
  // Move this object to the other end of the world as-needed
  void Update() {
    float diff = mainT.position.x - (T.position.x + 2500f); // 2500 = center of the chunk
    if (diff > 5000f)
      T.position += Vector3.right * 10000f;
    else if (diff < -5000f)
      T.position += Vector3.left * 10000f;
  }
}

public class LoopingWorldObject : MonoBehaviour {

  protected Transform T, mainT;

  // Manual Initialization, if needed...
  public virtual void Init() { gameObject.SetActive(true); }

  public virtual void Awake() {
    T = transform;
    mainT = Camera.main.transform;
    while (CorrectForCamera()) { /* do nothing */ }
  }
  
  // Move this object to the other end of the world as-needed
  public virtual void FixedUpdate() { CorrectForCamera(); }

  bool CorrectForCamera() {
    bool didCorrect = false;
    float diff = mainT.position.x - T.position.x;
    if (diff > 5000f) {
      T.position += Vector3.right * 10000f;
      didCorrect = true;
    }
    else if (diff < -5000f) {
      T.position += Vector3.left * 10000f;
      didCorrect = true;
    }
    return didCorrect;
  }
}

NOTE: Since your gameObjects probably have to collide with the scenery, your particular solution will therefore need to involve one extra copy of the scenery chunks to ensure nothing falls through when the scenery is moved. Or, to avoid the extra scenery copies, you could devise a solution similar to this one which keeps each gameObject inside the same group as the scenery that it’s closest to, changing its parent as it moves from one to the other. In that case, you just need to make sure the gameObjects have access to the scenery groups and check them against their positions rather than the camera.