Bagaimana saya bisa membatasi pergerakan pemain ke permukaan objek 3D menggunakan Unity?

16

Saya mencoba untuk membuat efek yang mirip dengan Mario Galaxy atau Geometry Wars 3 di mana ketika pemain berjalan di sekitar "planet" gravitasi tampaknya menyesuaikan dan mereka tidak jatuh dari tepi objek seperti yang akan mereka lakukan jika gravitasi diperbaiki dalam satu arah.

masukkan deskripsi gambar di sini
(sumber: gameskinny.com )

Perang Geometri 3

Saya berhasil menerapkan sesuatu yang dekat dengan apa yang saya cari menggunakan pendekatan di mana objek yang seharusnya memiliki gravitasi menarik tubuh kaku lainnya ke arah itu, tetapi dengan menggunakan mesin fisika bawaan untuk Unity, menerapkan gerakan dengan AddForce dan sejenisnya, Aku hanya tidak bisa membuat gerakan itu terasa benar. Saya tidak bisa membuat pemain bergerak cukup cepat tanpa pemain mulai terbang dari permukaan objek dan saya tidak bisa menemukan keseimbangan yang baik antara gaya dan gravitasi yang diterapkan untuk mengakomodasi hal ini. Implementasi saya saat ini adalah adaptasi dari apa yang ditemukan di sini

Saya merasa solusinya mungkin masih akan menggunakan fisika untuk membuat pemain membumi ke objek jika mereka meninggalkan permukaan, tetapi begitu pemain telah membumi akan ada cara untuk menjepit pemain ke permukaan dan mematikan fisika dan Kontrol pemain melalui cara lain tapi saya benar-benar tidak yakin.

Pendekatan apa yang harus saya ambil untuk menjepret pemain ke permukaan benda? Perhatikan bahwa solusi harus bekerja dalam ruang 3D (tidak seperti 2D) dan harus dapat diimplementasikan menggunakan versi gratis Unity.

Spartan Donut
sumber
Saya bahkan tidak berpikir untuk mencari berjalan di dinding. Saya akan melihat dan melihat apakah ini membantu.
SpartanDonut
1
Jika pertanyaan ini secara khusus tentang melakukan ini dalam 3D in Unity, Itu harus dibuat lebih jelas dengan pengeditan. (Itu tidak akan menjadi duplikat yang tepat dari yang sudah ada saat itu.)
Anko
Itu perasaan umum saya juga - saya akan melihat apakah saya dapat mengadaptasi solusi itu ke 3D dan mempostingnya sebagai jawaban (atau jika orang lain dapat mengalahkan saya hingga saya juga setuju dengan itu). Saya akan mencoba dan memperbarui pertanyaan saya untuk lebih jelas tentang itu.
SpartanDonut
Berpotensi membantu: youtube.com/watch?v=JMWnufriQx4
ssb

Jawaban:

7

Saya berhasil mencapai apa yang saya butuhkan, terutama dengan bantuan posting blog ini untuk memecahkan teka-teki permukaan dan muncul dengan ide-ide saya sendiri untuk pergerakan pemain dan kamera.

Gertakan Pemain ke Permukaan Objek

Pengaturan dasar terdiri dari bola besar (dunia) dan bola kecil (pemain) keduanya dengan spider collider melekat padanya.

Sebagian besar pekerjaan yang dilakukan adalah dalam dua metode berikut:

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

Itu Vector3 movementDirection parameter seperti kedengarannya, arah kita akan bergerak pemain kami dalam bingkai ini, dan menghitung vektor yang, sementara berakhir relatif sederhana dalam contoh ini, agak sulit bagi saya untuk mencari tahu pada awalnya. Lebih lanjut tentang itu nanti, tetapi perlu diingat bahwa itu adalah vektor yang dinormalisasi ke arah pemain menggerakkan frame ini.

Melangkah melalui, hal pertama yang kita lakukan adalah memeriksa apakah sebuah sinar, yang berasal dari posisi masa depan hipotetis diarahkan ke para pemain vektor (-transform.up) hits dunia menggunakan WorldLayerMask yang merupakan properti publik LayerMask dari skrip. Jika Anda ingin tabrakan yang lebih kompleks atau beberapa lapisan, Anda harus membuat layer mask sendiri. Jika raycast berhasil mengenai sesuatu, hitInfo digunakan untuk mengambil titik normal dan hit untuk menghitung posisi baru dan rotasi pemain yang seharusnya tepat pada objek. Mengimbangi posisi pemain mungkin diperlukan tergantung pada ukuran dan asal objek pemain yang dimaksud.

Akhirnya, ini benar-benar hanya diuji dan kemungkinan hanya berfungsi dengan baik pada objek sederhana seperti bola. Sebagai posting blog saya mendasarkan solusi saya dari menyarankan, Anda mungkin ingin melakukan beberapa raycast dan rata-rata untuk posisi dan rotasi Anda untuk mendapatkan transisi yang jauh lebih baik ketika bergerak di medan yang lebih kompleks. Mungkin juga ada jebakan lain yang belum saya pikirkan saat ini.

Kamera dan Gerakan

Setelah pemain menempel ke permukaan objek, tugas selanjutnya untuk mengatasi adalah gerakan. Saya awalnya mulai dengan gerakan relatif terhadap pemain tetapi saya mulai mengalami masalah di kutub bola di mana arah tiba-tiba berubah membuat pemain saya dengan cepat mengubah arah berulang-ulang sehingga tidak membiarkan saya melewati kutub. Apa yang akhirnya saya lakukan adalah membuat gerakan pemain saya relatif terhadap kamera.

Apa yang bekerja dengan baik untuk kebutuhan saya adalah memiliki kamera yang benar-benar mengikuti pemain hanya berdasarkan posisi pemain. Akibatnya, meskipun kamera secara teknis berputar, menekan ke atas selalu menggerakkan pemain ke atas layar, turun ke bawah, dan begitu seterusnya dengan kiri dan kanan.

Untuk melakukan ini, berikut ini dilakukan pada kamera di mana objek target adalah pemain:

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

Akhirnya, untuk menggerakkan pemain, kami meningkatkan transformasi dari kamera utama sehingga dengan kendali kami naik, turun turun, dll. Dan di sinilah kami memanggil UpdatePlayerTransform yang akan membuat posisi kami tersentak ke objek dunia.

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

Untuk menerapkan kamera yang lebih menarik tetapi kontrolnya kurang lebih sama dengan apa yang kami miliki di sini, Anda dapat dengan mudah mengimplementasikan kamera yang tidak dirender atau hanya objek tiruan lainnya untuk dijadikan dasar pergerakan dan kemudian gunakan kamera yang lebih menarik untuk merender apa Anda ingin permainan terlihat seperti. Ini akan memungkinkan transisi kamera yang bagus saat Anda berkeliling objek tanpa merusak kontrol.

Spartan Donut
sumber
3

Saya pikir ide ini akan berhasil:

Menyimpan salinan sisi jala planet dari CPU. Memiliki simpul jala juga berarti Anda memiliki vektor normal untuk setiap titik di planet ini. Kemudian matikan sepenuhnya gravitasi untuk semua entitas, alih-alih berikan gaya tepat pada arah yang berlawanan dari vektor normal.

Sekarang, berdasarkan pada titik mana seharusnya vektor normal planet ini dihitung?

Jawaban termudah (yang saya yakin akan bekerja dengan baik) adalah perkiraan yang mirip dengan metode Newton : Ketika objek pertama kali bertelur, Anda tahu semua posisi awal mereka di planet ini. Gunakan posisi awal itu untuk menentukan upvektor setiap objek . Jelas gravitasi akan berada di arah yang berlawanan (menuju down). Dalam bingkai berikutnya, sebelum menerapkan gravitasi, melemparkan sinar dari posisi baru objek ke downvektor lamanya . Gunakan persimpangan sinar itu dengan planet sebagai referensi baru untuk menentukan upvektor. Kasus sinar yang jarang mengenai apa pun berarti ada yang tidak beres dan Anda harus memindahkan objek Anda kembali ke posisi sebelumnya.

Juga perhatikan bahwa menggunakan metode ini, asal pemain selanjutnya dari planet ini, semakin buruk perkiraannya. Oleh karena itu lebih baik menggunakan suatu tempat di sekitar kaki masing-masing pemain sebagai asal mereka. Saya menduga, tapi saya pikir menggunakan kaki sebagai asal juga akan menghasilkan penanganan dan navigasi pemain yang lebih mudah.


Catatan terakhir: Untuk hasil yang lebih baik, Anda bahkan dapat melakukan hal berikut: melacak pergerakan pemain di setiap frame (misalnya menggunakan current_position - last_position). Kemudian penjepit itu movement_vectorsehingga panjang menuju objek upadalah nol. Sebut saja vektor baru ini reference_movement. Pindahkan sebelumnya reference_pointdengan reference_movementdan gunakan titik baru ini sebagai asal tracing ray. Setelah (dan jika) sinar itu mengenai planet ini, pindahlah reference_pointke titik hit itu. Akhirnya, hitung upvektor baru , dari yang baru ini reference_point.

Beberapa kode semu untuk meringkasnya:

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}
Ali1S232
sumber
2

Posting ini bisa membantu. Intinya adalah, Anda tidak menggunakan pengendali karakter, tetapi buat sendiri menggunakan mesin fisika. Kemudian Anda menggunakan normals yang terdeteksi di bawah pemain untuk mengarahkan mereka ke permukaan mesh.

Berikut adalah ikhtisar teknik yang bagus. Ada lebih banyak sumber daya dengan istilah pencarian web seperti "unity walk on 3d objects mario galaxy".

Ada juga proyek demo dari unity 3.x yang memiliki mesin berjalan, dengan seorang prajurit dan seekor anjing dan di salah satu adegan. Ini menunjukkan berjalan di objek 3d Galaxy- style. Ini disebut sistem penggerak oleh runevision.

SpidermanSpidermanDWASC
sumber
1
Sekilas demo itu tidak berfungsi dengan baik dan paket Unity mengalami kesalahan. Saya akan melihat apakah saya dapat membuat ini bekerja tetapi jawaban yang lebih lengkap akan dihargai.
SpartanDonut
2

Rotasi

Lihat jawaban atas pertanyaan ini di answer.unity3d.com (sebenarnya ditanyakan oleh saya sendiri). Mengutip:

Tugas pertama adalah mendapatkan vektor yang mendefinisikan. Dari gambar Anda, Anda dapat melakukannya dengan salah satu dari dua cara. Anda dapat memperlakukan planet ini sebagai bola dan menggunakan (object.position - planet.position). Cara kedua adalah menggunakan Collider.Raycast () dan menggunakan 'hit.normal' yang dikembalikan.

Berikut adalah kode yang dia sarankan kepada saya:

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Sebut itu setiap pembaruan, dan Anda harus membuatnya berfungsi. (perhatikan bahwa kodenya dalam UnityScript ).
Kode yang sama dalam C # :

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Gravitasi

Untuk gravitasi, Anda dapat menggunakan "teknik fisika planet" saya, yang saya jelaskan dalam pertanyaan saya, tetapi tidak benar-benar dioptimalkan. Itu hanya sesuatu yang ada dalam pikiran saya.
Saya sarankan Anda membuat sistem Anda sendiri untuk gravitasi.

Berikut ini adalah tutorial Youtube oleh Sebastian Lague . Itu solusi yang sangat bagus.

EDIT: Dalam kesatuan, pergi ke Edit > Pengaturan Proyek > Fisika dan atur semua nilai untuk Gravity ke 0 (Atau hapus Rigidbody dari semua objek), untuk mencegah gravitasi bawaan (yang hanya menarik pemain ke bawah) untuk berkonflik dengan solusi khusus. (matikan)

Daniel Kvist
sumber
Memiringkan pemain ke pusat planet (dan memutarnya sesuai) bekerja dengan baik untuk planet yang hampir bulat, tetapi saya bisa membayangkan itu benar-benar salah untuk benda-benda yang kurang bulat, seperti yang dilompati Mario dalam bentuk roket di GIF pertama.
Anko
Anda bisa membuat kubus yang dibatasi hanya bergerak di sekitar sumbu-X misalnya, di dalam objek non-bola, dan memindahkannya saat pemain bergerak, sehingga selalu lurus di bawah pemain, lalu tarik pemain turun ke kubus. Cobalah.
Daniel Kvist