Você pode usar o ruído 1D Perlin para isso. Aqui está uma imagem do ruído 2D. Tirei amostras onde a linha vermelha está e multipliquei-as com alguma constante para obter a linha verde.

A constante com a qual você multiplica os valores determina a altura do ruído. Então você pode facilmente aumentar a dificuldade. Outro benefício do ruído Perlin é que ele pode gerar ruído inclinável, para que você possa construir um nível aparentemente interminável.
Se você deseja criar um fundo e um teto, é importante que você não gere nenhuma área intransitável. A maneira mais fácil seria simplesmente usar a parte "superior" do ruído (por exemplo 1.0 - noiseValue) como teto e a parte inferior ( noiseValue) na parte inferior (com algum deslocamento entre, é claro). Se isso parecer muito chato, você também pode considerar duas amostras próximas, por exemplo. imagine outra linha de amostra vermelha de um ou dois pixels para cima ou para baixo. Isso dará uma onda semelhante, mas não idêntica.