goo blog サービス終了のお知らせ 

タブレット用プログラムの書き止め

android OS & iPadOS の記録。

【Swift P4.6.2】SpriteKit 5 (SwiftUI) 落ちパズルっぽい2

2025-06-09 20:25:48 | Swift iPadOS用

他のプログラム言語と違い、これでエラー出るのかと戸惑うばかり。
厳格化されているのか私が知らないだけなのか。

で、正攻法が分からないのでとりあえずエラーを消して動くように修正してみた。
機能ごとにまとめたいけどエラーが出るのでしょうがない。理解が及べば綺麗になるでしょう。

流れはこんなもんかな?

・落下するオブジェクトが2つ。位置の回転が有るから状態で底付き処理の順番を変える。
・落下オブジェクトの底付きと積み上がったオブジェクトの積載を調査する。積載状態は2次元配列に保存している。これも位置状態で処理の順番を変える。
・底付き、積載だったらその位置の2次元配列(オブジェクトデータ)にタイプを書き込みして、そのオブジェクトをspriteViewに追加表示させる。
・落下オブジェクトは一旦spriteViewから除いて非表示にする。単体処理。
・2つの落下オブジェクトが落ち留めになったら、落下オブジェクトを初期化して最上段に追加表示させる。
・以上を繰り返す。
・落下オブジェクトが初期化再表示の時に2次元配列上のオブジェクトと重なったらゲームオーバーとする。


今までの感覚でプログラムエラーが出て理解しにくいとなんか萎える。
単タイプ複数接触処理、連鎖処理‥面倒くさくなってきた。

あと、オブジェクトのアニメーションのスタートタイミングが合わず各個バラバラに動いている(笑)
アニメーションメソッドを作らないとダメかぁ〜。

【ContentView.swift】

import SwiftUI
import SpriteKit

var currentScene = MySKScene() // グローバルに変更

struct ContentView: View {

    init(){
        currentScene.scaleMode = .resizeFill
        currentScene.backgroundColor = .yellow
    }

    var body: some View {
        VStack {
            SpriteView(scene: currentScene)
                .frame(width: currentScene.sceneWidth, height: currentScene.sceneHeight)
            HStack {
                Button("⬅️"){
                    currentScene.moveLeft()
                }
                Button("🔄"){
                    currentScene.rotation()
                }
                Button("➡️"){
                    currentScene.moveRight()
                }
            }
        }
    }
}

【MySKScene.swift】

import SwiftUI
import SpriteKit

class MySKScene: SKScene {
    // 落下オブジェクト
    var gameObj0: GameObject = GameObject(x:4,y:0,type:1) 
    var gameObj1: GameObject = GameObject(x:5,y:0,type:2)
    // 10x15の2次元配列を初期化。
    static let gridMaxX = 10
    static let gridMaxY = 15
    // 2次元配列設定
    private var gameArray = GameArray()
    
    private var lastUpdateTime : TimeInterval = 0
    var sceneWidth: CGFloat {
        return GameObject.imageSize * CGFloat(MySKScene.gridMaxX)
    }
    var sceneHeight: CGFloat {
        return GameObject.imageSize * CGFloat(MySKScene.gridMaxY)
    } 

    // シーンがViewに表示されたときに実行する処理をdidMoveメソッド内に書きます
    override func didMove(to view: SKView) {
        addGameObj(obj: gameObj0)
        addGameObj(obj: gameObj1)
        gameObj0.state = 1
        gameObj1.state = 1
    }
    //一定時間毎の処理
    override func update(_ currentTime: TimeInterval) {
        if (lastUpdateTime == 0) {
            lastUpdateTime = currentTime
        }
        if (lastUpdateTime + 1.0 <= currentTime) {
            downPosition()
            lastUpdateTime = currentTime
        }
    }
    /***************************/
    // spriteView にオブジェクトを表示する
    func addGameObj(obj:GameObject) { 
        self.addChild(obj.image)
        obj.image.run(.repeatForever(
            .animate(with: MyTextures().imageTextures(type: obj.type), timePerFrame: 0.3)
        ))
    }
    // 落下オブジェクトの左移動
    func moveLeft() { 
        let x0 = gameObj0.gridPosX - 1
        let x1 = gameObj1.gridPosX - 1
        if x0 >= 0 && x1 >= 0 {
            if !gameArray.checkArrayElements(x: x0, y: gameObj0.gridPosY) &&
                !gameArray.checkArrayElements(x: x1, y: gameObj1.gridPosY)
            {
                gameObj0.moveLeft()
                gameObj1.moveLeft()
            }
        }
    }
    // 落下オブジェクトの右移動
    func moveRight() { 
        let x0 = gameObj0.gridPosX + 1
        let x1 = gameObj1.gridPosX + 1
        if x0 <  MySKScene.gridMaxX && x1 <  MySKScene.gridMaxX {
            if !gameArray.checkArrayElements(x: x0, y: gameObj0.gridPosY) &&
                !gameArray.checkArrayElements(x: x1, y: gameObj1.gridPosY)
            {
                gameObj0.moveRight()
                gameObj1.moveRight()
            }
        }
    }
    private var endFlag = false // 仮エンドフラグ
    // 落下オブジェクトの下移動
    func downPosition() { 
        if endFlag { // 仮エンド
            return
        }
        if gameObj0.gridPosY >= gameObj1.gridPosY {
            downCheckGameObj0()
            downCheckGameObj1() 
        } else {
            downCheckGameObj1() 
            downCheckGameObj0()
        }
        // 新しい落下オブジェクト設定
        if gameObj0.state == 0 && gameObj1.state == 0 {
            gameObj0.setImagePos(x: 4, y: 0)
            gameObj1.setImagePos(x: 5, y: 0)
            addGameObj(obj: gameObj0)
            addGameObj(obj: gameObj1)
            gameObj0.state = 1
            gameObj1.state = 1
            if gameArray.checkArrayElements(x: 4, y: 0) || gameArray.checkArrayElements(x: 5, y: 0) {
                // 最上部で重なったので終了
                endFlag = true
            }
        }
    }
    private func downCheckGameObj0() {
        if gameObj0.state == 0 {return}
        if gameObj0.gridPosY+1 >= MySKScene.gridMaxY || gameArray.downCheckArray(obj: gameObj0) {
            moveObjData(obj: gameObj0)
            gameObj0.removeView()
        } else{
            gameObj0.down()
        }
    }
    private func downCheckGameObj1() {
        if gameObj1.state == 0 {return}
        if gameObj1.gridPosY+1 >= MySKScene.gridMaxY || gameArray.downCheckArray(obj: gameObj1) {
            moveObjData(obj: gameObj1)
            gameObj1.removeView()
        } else{
            gameObj1.down()
        }
    }
    func rotation(){
        if rotationCheck() {return}
        if gameObj0.gridPosY == gameObj1.gridPosY {
            gameObj1.gridPosY += (gameObj0.gridPosX <  gameObj1.gridPosX ? 1: -1)
            gameObj1.gridPosX = gameObj0.gridPosX
        } else if gameObj0.gridPosX == gameObj1.gridPosX {
            gameObj1.gridPosX += (gameObj0.gridPosY <  gameObj1.gridPosY ? -1: 1)
            gameObj1.gridPosY = gameObj0.gridPosY
        }
        gameObj1.image.position = 
          getPosition(x: Double(gameObj1.gridPosX), y: Double(gameObj1.gridPosY))
    }
    private func rotationCheck()-> Bool {
        // 最上段で(1),(0)の横並び
        if gameObj0.gridPosY == 0 && 
            gameObj1.gridPosY == 0 &&
            gameObj0.gridPosX > gameObj1.gridPosX 
        {
            return true
        }
        // 最下段で(0),(1)の横並び
        if gameObj0.gridPosY == MySKScene.gridMaxY-1 && 
            gameObj1.gridPosY == MySKScene.gridMaxY-1 &&
            gameObj0.gridPosX <  gameObj1.gridPosX 
        {
            return true
        }
        // 横並び
        if gameObj0.gridPosY == gameObj1.gridPosY {
            let i = gameObj0.gridPosX <  gameObj1.gridPosX ? 1: -1
            if gameArray.checkArrayElements(x: gameObj0.gridPosX, y: gameObj0.gridPosY+i) {
                return true
            }
        }
        // 左端で (0)上 (1)下の上下並び
        if gameObj0.gridPosX == 0 &&
            gameObj1.gridPosX == 0 &&
            gameObj0.gridPosY <  gameObj1.gridPosY
        {
            return true
        }
        // 右端で (0)下 (1)上の上下並び
        if gameObj0.gridPosX == MySKScene.gridMaxX-1 &&
            gameObj1.gridPosX == MySKScene.gridMaxX-1 &&
            gameObj0.gridPosY > gameObj1.gridPosY
        {
            return true
        }
        // 縦並び
        if gameObj0.gridPosX == gameObj1.gridPosX {
            let i = gameObj0.gridPosY <  gameObj1.gridPosY ? -1: 1
            if gameArray.checkArrayElements(x: gameObj0.gridPosX+i, y: gameObj0.gridPosY)
            {
                return true
            }
        }
        return false
    }
    // 2次元配列の位置(x,y)からSpriteViewの位置に変換する
    func getPosition(x:Double, y:Double) -> CGPoint {
        let x = GameObject.imageSize * x + GameObject.imageSize/2.0
        let y = sceneHeight - GameObject.imageSize * y - GameObject.imageSize/2.0
        return CGPoint(x:x, y:y)
    }
    // 落下オブジェクトのタイプを配列の同じ位置にコピーして表示する
    func moveObjData(obj:GameObject){
        gameArray.copyObjData(obj: obj)
        addGameObj(obj: gameArray.arrayElements(obj: obj) )
    }
}

【GameObject.swift】

import SwiftUI
import SpriteKit

struct GameObject {
    var image: SKSpriteNode!
    static let imageSize:CGFloat = 32.0
    var gridPosX: Int = 0 // マス目位置
    var gridPosY: Int = 0
    var type: Int = 0 // オブジェクト、アニメーション
    var state: Int = 0 // 0:停止非表示、1:稼働中

    private var sceneHeight: CGFloat {
        return GameObject.imageSize * CGFloat(MySKScene.gridMaxY)
    } 
    init(){
        //
    }
    init(x:Int,y:Int,type:Int){
        self.type = type
        initImage(x:x,y:y)
    }
    mutating func initImage(x:Int,y:Int){
        self.image = SKSpriteNode()
        self.image.size = CGSize(width: GameObject.imageSize, height: GameObject.imageSize)
        setImagePos(x:x,y:y)
    }
    mutating func setImagePos(x:Int,y:Int){
        self.gridPosX = x
        self.gridPosY = y
        self.image.position = getPosition(x: Double(x), y: Double(y))
    }
    mutating func moveLeft(){
        self.gridPosX -= 1
        self.image.position = getPosition(x: Double(self.gridPosX), y: Double(self.gridPosY))
    }
    mutating func moveRight() {
        self.gridPosX += 1
        self.image.position = getPosition(x: Double(self.gridPosX), y: Double(self.gridPosY))
    }
    mutating func down(){
        self.gridPosY += 1
        self.image.position = getPosition(x: Double(self.gridPosX), y: Double(self.gridPosY))
    }  
    // 2次元配列の位置(x,y)からSpriteViewの位置に変換する
    private func getPosition(x:Double, y:Double) -> CGPoint {
        let pos = GameObject.imageSize/2.0
        return CGPoint(x:GameObject.imageSize * x + pos, y:self.sceneHeight - GameObject.imageSize * y - pos)
    } 
    // spriteView から外す
    mutating func removeView(){
        self.image.removeAllActions()
        self.image.removeFromParent()
        self.state = 0
    }
    mutating func clear(){
        self.image.removeAllActions()
        self.image.removeFromParent()
        self.image = nil
        self.gridPosX = 0
        self.gridPosY = 0
        self.type = 0
        self.state = 0
    }
}

【GameArray.swift】

import SwiftUI
import SpriteKit

struct GameArray {
    private var grid: [[GameObject]] = []
    private var aFlag: [[Int]] = []

    init(){
        grid = [[GameObject]](repeating: [GameObject](repeating: GameObject(), count: MySKScene.gridMaxY), count: MySKScene.gridMaxX)
        for i in 0..<  MySKScene.gridMaxX {
            for j in 0..<  MySKScene.gridMaxY {
                grid[i][j].initImage(x: i, y: j)
            }
        }
        aFlag = [[Int]](repeating: [Int](repeating: 0, count: MySKScene.gridMaxY), count: MySKScene.gridMaxX)
    }
    mutating func copyObjData(obj:GameObject){
        grid[obj.gridPosX][obj.gridPosY].type = obj.type
    }
    func arrayElements(obj:GameObject)-> GameObject {
        return grid[obj.gridPosX][obj.gridPosY]
    }
    mutating func checkArrayElements(x:Int,y:Int)-> Bool {
        if x < 0 || x >= MySKScene.gridMaxX {
            return true
        }
        if y < 0 || y >= MySKScene.gridMaxY {
            return true
        }
        return grid[x][y].type != 0
    }
    func downCheckArray(obj:GameObject)-> Bool {
        if grid[obj.gridPosX][obj.gridPosY+1].type > 0 {
            return true
        }
        return false
    }
    mutating func removeAll(){
        for i in 0..<  MySKScene.gridMaxX {
            for j in 0..<  MySKScene.gridMaxY {
                grid[i][j].clear()
            }
        }
    }
    mutating func resetAll(){
        for i in 0..<  MySKScene.gridMaxX {
            for j in 0..<  MySKScene.gridMaxY {
                grid[i][j].removeView()
                grid[i][j].type = 0
            }
        }
    }
}

【MyTextures.swift】

import SwiftUI
import SpriteKit

class MyTextures {
    private var _imageTextures:[SKTexture] = []
    
    func imageTextures(type:Int) -> [SKTexture] {
        switch (type) {
        case 1:
            if let uii = UIImage(systemName: "circle.grid.cross.up.filled" ) {
                _imageTextures.append(SKTexture(image: uii))
            }
            if let uii = UIImage(systemName: "circle.grid.cross.right.filled" ) {
                _imageTextures.append(SKTexture(image: uii))
            } 
            if let uii = UIImage(systemName: "circle.grid.cross.down.filled" ) {
                _imageTextures.append(SKTexture(image: uii))
            } 
            if let uii = UIImage(systemName: "circle.grid.cross.left.filled" ) {
                _imageTextures.append(SKTexture(image: uii))
            }
        case 2:
            if let uii = UIImage(systemName: "poweroutlet.type.b" ) {
                _imageTextures.append(SKTexture(image: uii))
            }
            if let uii = UIImage(systemName: "poweroutlet.type.i" ) {
                _imageTextures.append(SKTexture(image: uii))
            } 
            if let uii = UIImage(systemName: "poweroutlet.type.b" ) {
                _imageTextures.append(SKTexture(image: uii))
            } 
            if let uii = UIImage(systemName: "poweroutlet.type.b" ) {
                _imageTextures.append(SKTexture(image: uii))
            }
        default: break
            //
        }
        
        return _imageTextures
    }
    func removeAll(){
        _imageTextures.removeAll()
    }
}