Saya membuat game di Swift yang melibatkan kemunculan monster. Monster muncul, dan menghilang, berdasarkan penghitung waktu menggunakan sesuatu seperti ini:

func RunAfterDelay(_ delay: TimeInterval, block: @escaping ()->()) 
{
    let time = DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)

    DispatchQueue.main.asyncAfter(deadline: time, execute: block)
}

Dan kemudian saya akan menyebutnya seperti ini (misalnya untuk muncul setelah 2 detik):

///Spawn Monster
RunAfterDelay(2) { 
                [unowned self] in
                self.spawnMonster()
 }

Saya kemudian melakukan sesuatu yang serupa untuk bersembunyi (setelah x detik, saya menghilangkan monster itu).

Jadi saya membuat ikon pengaturan di bagian atas layar, dan ketika Anda mengetuknya, jendela persegi panjang raksasa muncul untuk mengubah pengaturan permainan, tetapi tentu saja masalahnya adalah monster masih muncul di latar belakang. Jika saya membawa pemain pergi ke layar lain, saya yakin saya akan kehilangan semua status permainan saya dan tidak dapat kembali ke sana tanpa memulai dari awal (pemain mungkin berada di tengah permainan mereka).

Apakah ada cara untuk memberi tahu semua pengatur waktu game yang saya buat di atas, mis.

DispatchQueue.main.asyncAfter(deadline: time, execute: block)

Untuk menjeda dan melanjutkan ketika saya mengatakannya? Saya kira tidak apa-apa melakukannya dengan semua penghitung waktu (jika tidak ada cara untuk memberi label dan menjeda penghitung waktu tertentu).

Terima kasih!

1
NullHypothesis 8 Desember 2016, 19:59

1 menjawab

Saya akan menunjukkan beberapa hal di sini untuk Anda, dan beberapa lagi untuk pembaca masa depan, sehingga mereka akan memiliki contoh yang bisa diterapkan hanya dengan menyalin-menempelkan kode ini. Beberapa hal berikut ini:

1. Membuat timer menggunakan SKAction

2. Menjeda tindakan

3. Menjeda simpul itu sendiri

4. Dan seperti yang saya katakan, beberapa hal lagi :)

Perhatikan bahwa semua ini dapat dilakukan dengan cara yang berbeda, bahkan lebih sederhana dari ini (ketika harus menjeda tindakan dan node) tetapi saya akan menunjukkan cara terperinci, sehingga Anda dapat memilih yang terbaik untuk Anda.

Penyiapan Awal

Kami memiliki simpul pahlawan, dan simpul musuh. Node musuh akan muncul setiap 5 detik di bagian atas layar dan akan turun ke bawah, ke arah pemain untuk meracuninya.

Seperti yang saya katakan, kita hanya akan menggunakan SKActions, tidak NSTimer, bahkan metode update:. Tindakan murni. Jadi, di sini, pemain akan statis di bagian bawah layar (kotak ungu) dan musuh (kotak merah) akan, seperti yang telah disebutkan, melakukan perjalanan ke arah pemain dan akan meracuninya.

Jadi mari kita lihat beberapa kode. Kita perlu mendefinisikan hal-hal biasa agar semua ini berfungsi, seperti menyiapkan kategori fisika, inisialisasi, dan pemosisian node. Kami juga akan mengatur hal-hal seperti penundaan pemijahan musuh (8 detik) dan durasi racun (3 detik):

//Inside of a GameScene.swift

    let hero = SKSpriteNode(color: .purple , size: CGSize(width: 50, height: 50))
    let button = SKSpriteNode(color: .yellow, size: CGSize(width: 120, height:120))
    var isGamePaused = false
    let kPoisonDuration = 3.0

    override func didMove(to view: SKView) {
        super.didMove(to: view)

        self.physicsWorld.contactDelegate = self

        hero.position = CGPoint(x: frame.midX,  y:-frame.size.height / 2.0 + hero.size.height)
        hero.name = "hero"
        hero.physicsBody = SKPhysicsBody(rectangleOf: hero.frame.size)
        hero.physicsBody?.categoryBitMask = ColliderType.Hero.rawValue
        hero.physicsBody?.collisionBitMask = 0
        hero.physicsBody?.contactTestBitMask = ColliderType.Enemy.rawValue
        hero.physicsBody?.isDynamic = false

        button.position = CGPoint(x: frame.maxX - hero.size.width, y: -frame.size.height / 2.0 + hero.size.height)
        button.name = "button"

        addChild(button)
        addChild(hero)

        startSpawningEnemies()

    }

Ada juga variabel yang disebut isGamePaused yang akan saya komentari lebih lanjut nanti, tetapi seperti yang dapat Anda bayangkan, tujuannya adalah untuk melacak apakah game dijeda dan nilainya berubah ketika pengguna mengetuk tombol kotak kuning besar.

Metode Pembantu

Saya telah membuat beberapa metode pembantu untuk pembuatan simpul. Saya merasa ini tidak diperlukan untuk Anda secara pribadi, karena Anda tampaknya memiliki pemahaman yang baik tentang pemrograman, tetapi saya akan membuatnya untuk kelengkapan dan untuk pembaca masa depan. Jadi ini adalah tempat di mana Anda mengatur hal-hal seperti nama simpul, atau kategori fisikanya... Berikut kodenya:

 func getEnemy()->SKSpriteNode{

            let enemy = SKSpriteNode(color: .red , size: CGSize(width: 50, height: 50))
            enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.frame.size)
            enemy.physicsBody?.categoryBitMask = ColliderType.Enemy.rawValue
            enemy.physicsBody?.collisionBitMask = 0
            enemy.physicsBody?.contactTestBitMask = ColliderType.Hero.rawValue
            enemy.physicsBody?.isDynamic = true
            enemy.physicsBody?.affectedByGravity = false
            enemy.name = "enemy"

            return enemy
        }

Juga, saya memisahkan penciptaan musuh dengan pemijahan yang sebenarnya. Jadi membuat di sini berarti membuat, mengatur, dan mengembalikan sebuah simpul yang nantinya akan ditambahkan ke pohon simpul. Pemijahan berarti menggunakan simpul yang dibuat sebelumnya menambahkannya ke adegan, dan menjalankan tindakan (tindakan bergerak) ke sana, sehingga dapat bergerak ke arah pemain:

func spawnEnemy(atPoint spawnPoint:CGPoint){

        let enemy = getEnemy()

        enemy.position = spawnPoint

        addChild(enemy)

        //moving action

        let move = SKAction.move(to: hero.position, duration: 5)

        enemy.run(move, withKey: "moving")
    }

Saya pikir tidak perlu membahas metode pemijahan di sini karena sangat sederhana. Mari kita melangkah lebih jauh ke bagian pemijahan:

Pewaktu Tindakan SKA

Berikut adalah metode yang akan menelurkan musuh setiap x detik. Ini akan dijeda setiap kali kami menjeda tindakan yang terkait dengan kunci "spawning".

func startSpawningEnemies(){

        if action(forKey: "spawning") == nil {

            let spawnPoint = CGPoint(x: frame.midX, y: frame.size.height / 2.0 - hero.size.height)

            let wait = SKAction.wait(forDuration: 8)

            let spawn = SKAction.run({[unowned self] in

                self.spawnEnemy(atPoint: spawnPoint)
            })

            let sequence = SKAction.sequence([spawn,wait])

            run(SKAction.repeatForever(sequence), withKey: "spawning")
        }
    }

Setelah node di-spawn, akhirnya akan bertabrakan (lebih tepatnya akan membuat kontak) dengan hero. Dan di sinilah mesin fisika berperan...

Mendeteksi kontak

Saat musuh bepergian, pada akhirnya akan mencapai pemain, dan kami akan mendaftarkan kontak itu:

func didBegin(_ contact: SKPhysicsContact) {

        let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask

        switch contactMask {

        case ColliderType.Hero.rawValue | ColliderType.Enemy.rawValue :


            if let projectile = contact.bodyA.categoryBitMask == ColliderType.Enemy.rawValue ? contact.bodyA.node : contact.bodyB.node{

                projectile.removeAllActions()
                projectile.removeFromParent()
                addPoisionEffect(atPoint: hero.position)

            }

        // Handle more cases here

        default : break
            //Some other contact has occurred
        }
    }

Kode deteksi kontak dipinjam dari di sini (dari penulis Steve Ives).

Saya tidak akan membahas bagaimana penanganan kontak di SpriteKit bekerja, karena saya akan terlalu banyak membahas di luar topik seperti itu. Jadi ketika kontak antara pahlawan dan proyektil terdaftar, kami melakukan beberapa hal:

1. Hentikan semua tindakan pada proyektil sehingga proyektil akan berhenti bergerak. Kita bisa melakukan ini dengan menghentikan aksi bergerak secara langsung dan saya akan menunjukkan kepada Anda nanti bagaimana melakukannya.

2. Menghapus proyektil dari induknya, karena kita tidak membutuhkannya lagi.

3. Menambahkan efek keracunan dengan menambahkan node emitor (saya membuat efek itu di editor partikel menggunakan template Asap).

Berikut adalah metode yang relevan untuk langkah 3:

func addPoisionEffect(atPoint point:CGPoint){

        if let poisonEmitter = SKEmitterNode(fileNamed: "poison"){

            let wait = SKAction.wait(forDuration: kPoisonDuration)

            let remove = SKAction.removeFromParent()

            let sequence = SKAction.sequence([wait, remove])

            poisonEmitter.run(sequence, withKey: "emitAndRemove")
            poisonEmitter.name = "emitter"
            poisonEmitter.position = point

            poisonEmitter.zPosition = hero.zPosition + 1

            addChild(poisonEmitter)

        }  
    }

Seperti yang saya katakan, saya akan menyebutkan beberapa hal yang tidak penting untuk pertanyaan Anda, tetapi sangat penting ketika melakukan semua ini di SpriteKit. SKEmitterNode tidak dihapus saat pemancaran selesai. Itu tetap di pohon simpul dan memakan sumber daya (pada beberapa persen). Itu sebabnya Anda harus menghapusnya sendiri. Anda melakukan ini dengan menentukan urutan tindakan dari dua item. Pertama adalah SKAction yang menunggu waktu tertentu (sampai emisi selesai) dan item kedua adalah tindakan yang akan menghapus emitor dari induknya ketika saatnya tiba.

Akhirnya - Menjeda :)

Metode yang bertanggung jawab untuk menjeda disebut togglePaused() dan mengubah status jeda game berdasarkan variabel isGamePaused saat tombol kuning diketuk:

func togglePaused(){

        let newSpeed:CGFloat = isGamePaused ? 1.0 : 0.0

        isGamePaused = !isGamePaused

        //pause spawning action
        if let spawningAction = action(forKey: "spawning"){

            spawningAction.speed = newSpeed
        }

        //pause moving enemy action
        enumerateChildNodes(withName: "enemy") {
            node, stop in
            if let movingAction = node.action(forKey: "moving"){

                movingAction.speed = newSpeed
            }

        }

        //pause emitters by pausing the emitter node itself
        enumerateChildNodes(withName: "emitter") {
            node, stop in

            node.isPaused = newSpeed > 0.0 ? false : true

        }
    }

Apa yang terjadi di sini sebenarnya sederhana: kami menghentikan tindakan spawning dengan mengambilnya menggunakan kunci yang ditentukan sebelumnya (spawning), dan untuk menghentikannya kami mengatur kecepatan tindakan ke nol. Untuk membatalkan jeda, kita akan melakukan yang sebaliknya - setel kecepatan aksi ke 1.0. Ini berlaku untuk aksi bergerak juga, tetapi karena banyak simpul dapat dipindahkan, kami menghitung melalui semua simpul dalam sebuah adegan.

Untuk menunjukkan perbedaannya, saya menjeda SKEmitterNode secara langsung, jadi ada satu cara lagi bagi Anda untuk menjeda sesuatu di SpriteKit. Ketika node dijeda, semua tindakan dan tindakan anak-anaknya juga dijeda.

Apa yang tersisa untuk disebutkan adalah bahwa saya mendeteksi di touchesBegan jika tombol ditekan, dan menjalankan metode togglePaused() setiap saat, tetapi saya pikir kode itu tidak terlalu diperlukan.

Contoh video

Untuk membuat contoh yang lebih baik, saya telah merekam semuanya. Jadi ketika saya menekan tombol kuning, semua tindakan akan dihentikan. Berarti efek spawning, moving dan poison jika ada akan dibekukan. Dengan mengetuk lagi, saya akan membatalkan jeda semuanya. Jadi inilah hasilnya:

video

Di sini Anda dapat (jelas?) melihat bahwa ketika musuh memukul pemain, saya menghentikan semuanya, katakan 1-1,5 detik setelah pukulan terjadi. Kemudian saya menunggu sekitar 5 detik atau lebih, dan saya membatalkan jeda semuanya. Anda dapat melihat bahwa emitor terus memancarkan selama satu atau dua detik, dan kemudian menghilang.

Perhatikan bahwa ketika emitor tidak dijeda, sepertinya itu tidak benar-benar tidak dijeda :), tetapi sepertinya partikel itu memancarkan bahkan emitor dijeda (yang sebenarnya benar). Ini adalah bug di iOS 9.1 dan saya masih menggunakan iOS 9.1 di perangkat ini :) Jadi di iOS 10, ini tetap.

Kesimpulan

Anda tidak perlu NSTimer untuk hal semacam ini di SpriteKit karena SKActions dimaksudkan untuk ini. Seperti yang Anda lihat, saat Anda menjeda aksi, semuanya akan berhenti. Pemijahan dihentikan, bergerak dihentikan, seperti yang Anda minta ... Saya telah menyebutkan bahwa ada cara yang lebih mudah untuk melakukan semua ini. Artinya, menggunakan node kontainer. Jadi jika semua node Anda berada dalam satu wadah, semua node, tindakan, dan semuanya akan dihentikan hanya dengan menjeda node wadah. Sederhana seperti itu. Tapi saya hanya ingin menunjukkan kepada Anda bagaimana Anda dapat mengambil tindakan dengan kunci, atau menjeda simpul, atau mengubah kecepatannya... Semoga ini membantu dan masuk akal!

1
Community 23 Mei 2017, 10:30