Consegui realizar o que eu precisava, principalmente com a ajuda deste post do blog para a peça de quebra-cabeça da superfície e tive minhas próprias idéias para o movimento do jogador e a câmera.
Como encaixar o player na superfície de um objeto
A configuração básica consiste em uma esfera grande (o mundo) e uma esfera menor (o jogador), ambas com coletores de esferas ligados a eles.
A maior parte do trabalho que está sendo realizado foi nos dois métodos a seguir:
private void UpdatePlayerTransform(Vector3 movementDirection)
{
RaycastHit hitInfo;
if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
{
Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);
transform.rotation = finalRotation;
transform.position = hitInfo.point + hitInfo.normal * .5f;
}
}
private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
Vector3 newPosition = transform.position;
Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);
if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
{
return true;
}
return false;
}
o Vector3 movementDirection
parâmetro é exatamente o que parece, a direção que vamos mover nosso player nesse quadro, e calcular esse vetor, embora tenha sido relativamente simples neste exemplo, foi um pouco complicado para eu descobrir no começo. Mais adiante, mas lembre-se de que é um vetor normalizado na direção em que o jogador está movendo esse quadro.
Avançando, a primeira coisa que fazemos é verificar se um raio, originário da posição hipotética futura direcionada aos vetores inativos dos jogadores (-transform.up), atinge o mundo usando o WorldLayerMask, que é uma propriedade pública do script LayerMask. Se você quiser colisões mais complexas ou várias camadas, precisará criar sua própria máscara de camada. Se o raycast atingir com sucesso algo, o hitInfo é usado para recuperar o normal e o ponto de impacto para calcular a nova posição e rotação do player, que deve estar no objeto. Pode ser necessário compensar a posição do jogador, dependendo do tamanho e da origem do objeto do jogador em questão.
Finalmente, isso realmente só foi testado e provavelmente só funciona bem em objetos simples, como esferas. Como a postagem do blog em que baseei minha solução, sugere, você provavelmente desejará executar vários raycasts e calculá-los como média para sua posição e rotação para obter uma transição muito mais agradável ao se deslocar em terrenos mais complexos. Também pode haver outras armadilhas em que não pensei neste momento.
Câmera e Movimento
Uma vez que o jogador estava grudado na superfície do objeto, a próxima tarefa a ser abordada era o movimento. Inicialmente, eu comecei com um movimento em relação ao jogador, mas comecei a encontrar problemas nos polos da esfera, onde as direções mudavam repentinamente, fazendo com que meu jogador mudasse rapidamente de direção várias vezes, sem me deixar passar pelos polos. O que acabei fazendo foi fazer meus jogadores se moverem em relação à câmera.
O que funcionou bem para minhas necessidades foi ter uma câmera que seguisse estritamente o jogador com base apenas na posição do jogador. Como resultado, mesmo que a câmera estivesse tecnicamente girando, pressionar para cima sempre movia o player em direção à parte superior da tela, para baixo em direção à parte inferior e assim por diante com a esquerda e a direita.
Para fazer isso, o seguinte foi executado na câmera em que o objeto alvo era o jogador:
private void FixedUpdate()
{
// Calculate and set camera position
Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);
// Calculate and set camera rotation
Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}
Finalmente, para mover o player, alavancamos a transformação da câmera principal para que, com nossos controles para cima, para cima, para baixo, para baixo, etc. E é aqui que chamamos UpdatePlayerTransform, que obterá nossa posição ajustada ao objeto mundial.
void Update ()
{
Vector3 movementDirection = Vector3.zero;
if (Input.GetAxisRaw("Vertical") > 0)
{
movementDirection += cameraTransform.up;
}
else if (Input.GetAxisRaw("Vertical") < 0)
{
movementDirection += -cameraTransform.up;
}
if (Input.GetAxisRaw("Horizontal") > 0)
{
movementDirection += cameraTransform.right;
}
else if (Input.GetAxisRaw("Horizontal") < 0)
{
movementDirection += -cameraTransform.right;
}
movementDirection.Normalize();
UpdatePlayerTransform(movementDirection);
}
Para implementar uma câmera mais interessante, mas com os controles semelhantes aos que temos aqui, você pode implementar facilmente uma câmera que não é renderizada ou apenas outro objeto fictício para basear o movimento e, em seguida, usar a câmera mais interessante para renderizar o que você quer que o jogo pareça. Isso permitirá boas transições de câmera à medida que você percorre os objetos sem interromper os controles.