21581


早速質問なのですが、今現在簡単な間違い探しの様なゲームを作成しようとしています。  
内容としては、正方形を同じ形で色を付けて４つ表示し、１つだけ違う色で表示するというものです。

そこでいくつもの問題を作り（赤色、黄色、青など）ランダムに表示させた後に  
１回選ばれた問題を削除する（配列から削除し、次回から表示させない）用にしたいのですが  
うまくその問題が削除されずに、また表示されてしまいます。  
コードは以下になります。

コードの問題があると思う所は、「問題をランダムに選んでその後削除する」になります。  
よろしくお願いします。

[![画像の説明をここに入力](https://i.stack.imgur.com/E9U2H.png)](https://i.stack.imgur.com/E9U2H.png)  
[![画像の説明をここに入力](https://i.stack.imgur.com/tVjlh.png)](https://i.stack.imgur.com/tVjlh.png)

[code]

    import SpriteKit
    import GameplayKit
    
    class GameScene: SKScene {
    
        //問題設定
        let randomSource = GKARC4RandomSource()
        var questionMax:Int = 6
        var questionList:[SKShapeNode] = []
        let correct = ["■"]
        let mistake = ["■"]
        var questionNo:Int = 0
        var misutakeNo:Int = 0
    
        override func didMoveToView(view: SKView) {
            //問題実行
            questioning()
        }
    
        //問題をランダムで選んでその後削除する
        func questioning(){
            var items = [newQuestion, newQuestion2]
            let q1 = Int(arc4random()) % items.count
            items[q1]()
            let _ = items.removeAtIndex(q1)
        }
    
        //問題設定１青
        func newQuestion(){
            questionMax = 4
            questionNo = randomSource.nextIntWithUpperBound(correct.count)
            misutakeNo = randomSource.nextIntWithUpperBound(questionMax)
            questionList = []
    
            for LoopID in 0..<questionMax {
                let question = SKShapeNode(rectOfSize:CGSizeMake(308.0,308.0))
                let x = (LoopID % 2) * 315 + 165
                let y = (LoopID / 2) * 315 + 470
                question.position = CGPoint(x: x, y: y)
                self.addChild(question)
                questionList.append(question)
    
                let sikisai = SKLabelNode()
    
                if LoopID != misutakeNo {
                    sikisai.text = correct[questionNo]
                    sikisai.fontColor = UIColor(red: 0/255, green: 0/255, blue: 255/255, alpha: 1.0)
                }else {
                    sikisai.text = mistake[questionNo]
                    sikisai.fontColor = UIColor(red: 0/255, green: 0/255, blue: 100/255, alpha: 1.0)
                }
    
                sikisai.fontSize = 405
                sikisai.position = CGPoint(x:0, y:-155)
                question.addChild(sikisai)
            }
        }
    
        //問題設定2赤
        func newQuestion2(){
            questionMax = 4
            questionNo = randomSource.nextIntWithUpperBound(correct.count)
            misutakeNo = randomSource.nextIntWithUpperBound(questionMax)
            questionList = []
    
            for LoopID in 0..<questionMax {
                let question = SKShapeNode(rectOfSize:CGSizeMake(308.0,308.0))
                let x = (LoopID % 2) * 315 + 165
                let y = (LoopID / 2) * 315 + 470
                question.position = CGPoint(x: x, y: y)
                self.addChild(question)
                questionList.append(question)
    
                let sikisai = SKLabelNode()
    
                if LoopID != misutakeNo {
                    sikisai.text = correct[questionNo]
                    sikisai.fontColor = UIColor(red: 255/255, green: 0/255, blue: 0/255, alpha: 1.0)
                }else {
                    sikisai.text = mistake[questionNo]
                    sikisai.fontColor = UIColor(red: 100/255, green: 0/255, blue: 0/255, alpha: 1.0)
                }
    
                sikisai.fontSize = 405
                sikisai.position = CGPoint(x:0, y:-155)
                question.addChild(sikisai)
            }
        }
    
        //タップを調べる
        func answerCheck(NO:Int) {
            if NO == misutakeNo {
                //正解
                questioning()
            } else {
                //不正解
                questioning()
            }
    
            for LoopID in 0..<questionMax {
                questionList[LoopID].removeFromParent()
                questioning()}
            }
    
        //タップ処理
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            for touch in touches {
                let location = touch.locationInNode(self)
                let touchNodes = self.nodesAtPoint(location)
    
                for tNode in touchNodes {
                    for LoopID in 0..<questionMax {
                        if tNode == questionList[LoopID]{
                            answerCheck(LoopID)
                        }
                    }
                }
            }
        }
    }
    
[/code]


CC BY-SA 4.0
2016-01-31T15:06:58.653
21545
2019-06-06T01:05:09.933
2019-06-06T01:05:09.933
3060
14204
question
swift swift2 spritekit
Swift2. 配列を削除するremoveAtIndex()について質問です


いただいたコメントと修正後のご質問に合わせてコードを修正しました。(なかなか難しいことをやろうとしているようなので、正直あなたの意図を理解できている自信はないのですが。)

あなたは、questioning()メソッドの中で

[code]

    var items = [newQuestion,newQuestion2]
    
[/code]

という宣言をしています。つまりquestioning()メソッドを呼ぶたびに毎回毎回全部の問題が入った配列をitemsに設定しなおしているわけです。残りの行でitemsの中の問題を乱数で均一に(厳密に言うと均一ではないのですが、クイズアプリを作るなら全く問題にならない誤差です)選んでいるのですから、同じ問題が何度も出題されることがあっても当然と言うことになります。

もしメソッドが終了した後も状態を覚えさせておきたいのだとしたら、メソッドのローカル変数にそのような状態を入れても意味がありません。コメントにも書きましたようにメソッドのローカル変数というのは、メソッドの実行が終了したら消えてしまいます。(あなたのコードの場合、毎回そのローカル変数に値を入れ直しているので、消えてしまうどころか、「何も削除されていない」ように見えるわけです。)

いつ元の状態にリセットするのか、という点はよく考えないといけませんが、そのような状態を表すための変数は、通常インスタンス変数(Swiftならインスタンスプロパティ)にします。  
ただし、Swiftではインスタンス変数の初期化に使う式は特殊なコンテキストで解釈されるので、変数の宣言と変数への値の設定は分けるという(直感からすると)変則的な書き方になります。

[code]

    class GameScene: SKScene {
        //インスタンスプロパティはclassの内側、すべてのメソッドの外側に宣言する
        var items: [()->Void] = []
        override func didMoveToView(view: SKView) {
            //メソッドをクロージャとして使うのは、インスタンスのコンテキストでないといけない
            items = [newQuestion, newQuestion2]
            //問題実行
            questioning()
        }
        func questioning(){
            let q1 = Int(arc4random()) % items.count
    
            //問題を出す
            items[q1]()
            //選ばれた問題を削除
            items.removeAtIndex(q1)
            //↑このitemsはインスタンスプロパティなので、「削除」された状態をインスタンスが存在する限り覚えている
        }
    
        func newQuestion(){
            //...
        }
    
        func newQuestion2(){
            //...
        }
    }
    
[/code]

ただし、このコードには問題があります。それはSwiftのメソッドをクロージャーとして使った場合、必ずselfが強参照としてクロージャー内に保持されることで、それをさらにselfのプロパティに放り込んでしまうと、循環参照が生じて、メモリリークの原因になります。

少し回りくどく、「本当にやりたいこと」が見えにくくなってしまうのですが、例えば次のようなコードに書き直す必要があるでしょう。

[code]

    enum QuestionItem {
        case Q1, Q2, Q3
    }
    extension QuestionItem {
        func question(object: GameScene) {
            switch self {
            case Q1:
                object.newQuestion1()
            case Q2:
                object.newQuestion2()
            case Q3:
                object.newQuestion3()
            }
        }
    }
    class GameScene: SKScene {
        //インスタンスプロパティはclassの内側、すべてのメソッドの外側に宣言する
        var items: [QuestionItem] = [.Q1, .Q2, .Q3]
        override func didMoveToView(view: SKView) {
            //問題実行
            questioning()
        }
        func questioning(){
            if items.count == 0 {
                /* itemsが空の時の処理 */
            }
            let q1 = Int(arc4random()) % items.count
    
            //問題を出す
            items[q1].question(self)
            //選ばれた問題を削除
            items.removeAtIndex(q1)
            //↑このitemsはインスタンスプロパティなので、「削除」された状態をインスタンスが存在する限り覚えている
        }
    
        func newQuestion1(){
            //...
        }
    
        func newQuestion2(){
            //...
        }
    
        func newQuestion3(){
            //...
        }
    }
    
[/code]

少々厄介な問題が絡んでしまいましたが、いかがでしょうか。何かお気付きの点がありましたら、またコメント等でお知らせください。


CC BY-SA 3.0
2016-02-01T12:39:21.487
21581
2016-02-04T14:49:28.233
2016-02-04T14:49:28.233
13972
13972
21545
answer