如何通过swiftUI制作2048游戏[关闭]

问题描述 投票:0回答:1

我尝试通过SwiftUI制作2048游戏,我的游戏逻辑基本上是正确的,但是过渡/动画是错误的,我不知道问题出在哪里以及如何解决!游戏现在看起来像这样:the game acting gif

我有几个问题:

  1. [当磁贴移动到左侧或顶部时,过渡隐藏在灰色背景视图的后面,我尝试将ZIndex添加到磁贴和背景,但它不起作用。
  2. 如何进行序列化的过渡,首先应将其从原始单元格移出,然后再将其从较大的位置缩放到正常的位置
  3. 合并的过渡每次都无法按我预期的那样工作,实际上它只会发生几次

我的查看代码如下:

struct GameView: View {
    @ObservedObject var viewModel:GameViewModel

    var body: some View {
        let tap = DragGesture().onEnded({value in self.move(by: value)})

        return GeometryReader{geo in
            ZStack{
                RoundedRectangle(cornerRadius: 10.0).fill(Color(#colorLiteral(red: 0.719702065, green: 0.6819230318, blue: 0.6252140403, alpha: 1)))
                VStack{
                    ForEach(0..<self.viewModel.gridSize){rowIndex in
                        HStack {
                            ForEach(self.viewModel.tiles[rowIndex]){tile in
                                    TileView(tile:tile)
                            }
                        }
                    }
                }
                .padding()
            }
            .gesture(tap)
            .frame(width: geo.size.width, height: geo.size.width, alignment: .center)

        }



    }

    func move(by value:DragGesture.Value){

        withAnimation(Animation.easeInOut.speed(2)){
            if abs(value.translation.height) > abs(value.translation.width){
                if value.translation.height > 30 {
                    self.viewModel.move(by:GameModel.Direction.down)
                }else if value.translation.height < -30{
                    self.viewModel.move(by:GameModel.Direction.up)
                }
            }else{
                if value.translation.width > 30 {
                    self.viewModel.move(by:GameModel.Direction.right)
                }else if value.translation.width < -30{
                    self.viewModel.move(by:GameModel.Direction.left)
                }
            }
        }

    }

}


struct TileView: View {
    var tile:GameModel.Tile

    var body: some View {
        GeometryReader { geometry in
            self.body(for: geometry.size)
        }
    }

    @ViewBuilder
    private func body(for size:CGSize) -> some View {
        ZStack{
            RoundedRectangle(cornerRadius: 10.0).fill(Color(#colorLiteral(red: 0.794301331, green: 0.7563138604, blue: 0.7084676027, alpha: 1)))
            if(tile.value != 0){
                ZStack{
                    RoundedRectangle(cornerRadius: 10.0).fill(tile.bgColor)
                    Text(String(tile.value)).font(Font.system(size: fontSize(for: size,in: tile.value))).foregroundColor(tile.fontColor)
                }
                .transition(transition(for: size))
                .zIndex(100)
            }
        }.zIndex(98)

    }


    private let cornerRadius:CGFloat = 10.0
    private let edgeLineWidth:CGFloat = 3

    private func fontSize(for size: CGSize, in value:Int) -> CGFloat{
        size.width * 0.7 / CGFloat(String(value).count)
    }

    private func transition(for size:CGSize) -> AnyTransition{
        var transition:AnyTransition
        if let mergedFrom = tile.mergedFrom{
            let perviousTile = mergedFrom[0]
            let offset = CGSize(width: CGFloat(perviousTile.x - tile.x )*size.width, height: CGFloat(perviousTile.y - tile.y )*size.height)
            transition = AnyTransition.offset(offset).combined(with: AnyTransition.scale(scale: 2))
        }else{
            if let previousPosition = tile.previousPosition {
                let offset = CGSize(width: CGFloat(previousPosition.x - tile.x )*size.width, height: CGFloat(previousPosition.y - tile.y )*size.height)
                transition = AnyTransition.offset(offset)
            }else{
                transition = AnyTransition.scale
            }
        }
        return AnyTransition.asymmetric(insertion: transition.animation(.easeInOut), removal: .identity)
    }

}

和型号代码:

struct GameModel {
    private(set) var tiles: Array<Array<Tile>>
    private(set) var gridSize:Int



    init(gridSize:Int){
        self.gridSize = gridSize
        tiles = Array<Array<Tile>>()
        for i in 0..<gridSize{
            var row = Array<Tile>()
            for j in 0..<gridSize{
                row.append(Tile(x: j, y: i))
            }
            tiles.append(row)
        }
        generateTile()
        generateTile()
    }

    private mutating func generateTile(){
        var emptyPositionArr:Array<(x:Int,y:Int)> = []
        for rowIndex in 0..<gridSize{
            for colIndex in 0..<gridSize{
                if(tiles[rowIndex][colIndex].value == 0){
                    emptyPositionArr.append((x:colIndex,y:rowIndex))
                }
            }
        }
        if let randomPos = emptyPositionArr.randomElement() {
            let randomValue = Bool.random() ? 2 : 4
            tiles[randomPos.y][randomPos.x].value = randomValue
        }else{
            print("No remaining spaces!")
        }
    }

    mutating func prepareTiles(){
        for y in 0..<gridSize{
            for x in 0..<gridSize{
                if(tiles[y][x].value != 0){
                    tiles[y][x].mergedFrom = nil
//                    tiles[y][x].savePosition()
                }
            }
        }
    }


    mutating func move(by direction:Direction){
        let vector:(x:Int,y:Int) = getVector(by: direction)
        var moved = false
        prepareTiles()

        var col:Array<Int> = []
        var row:Array<Int> = []
        for i in 0...3{
            col.append(i)
            row.append(i)
        }
        if(vector.x == 1){
            col = col.reversed()
        }
        if(vector.y == 1){
            row = row.reversed()
        }
        for y in row{
            for x in col{
                let cell = Cell(x: x, y: y)
                var newCell:Cell
                let tile = tiles[y][x]
                if(tile.value != 0){
                    let positions = findFarthestPosition(cell: cell, vector: vector)

                    if let next = cellCotent(at: positions.next), next.value == tile.value, next.mergedFrom == nil{
                        let merged = Tile(x: next.x, y: next.y, value: tile.value * 2, mergedFrom: [tile,next])
                        insertTile(tile: merged)
                        removeTile(tile: tile)
                        newCell = Cell(x: next.x, y: next.y)
                    }else{
                        removeTile(tile: tile)
                        insertTile(tile: Tile(x: positions.farthest.x, y: positions.farthest.y, value: tile.value,previousPosition: cell))

                        newCell = Cell(x: positions.farthest.x, y: positions.farthest.y)
                    }

                    if(newCell.x != cell.x  || newCell.y != cell.y){
                        moved = true
                    }

                }
            }
        }

        if(moved){
            generateTile()
        }

    }

    func findFarthestPosition(cell: Cell,vector:(x:Int,y:Int)) -> (farthest:Cell,next:Cell){
        var previous:Cell
        var currentcell = cell
        repeat{
            previous = currentcell
            currentcell = Cell(x: previous.x + vector.x, y: previous.y + vector.y)
        }while(withinBounds(cell: currentcell) && cellAvailable(cell: currentcell) )
        return (previous,currentcell)
    }

    func getVector(by direction:Direction) -> (x:Int,y:Int){
        var x = 0;
        var y = 0;
        switch direction {
        case .down:
            y =  1
        case .up:
            y = -1
        case .left:
            x = -1
        case .right:
            x = 1
        }
        return (x:x,y:y)
    }

    //MARK: - Cell

    struct Cell {
        var x:Int
        var y:Int
    }

    func cellCotent(at cell:Cell) -> Tile? {
        if(withinBounds(cell: cell)){
            return tiles[cell.y][cell.x]
        }else{
            return nil
        }
    }

    func cellAvailable (cell:Cell) -> Bool {
        if(withinBounds(cell: cell)){
            return tiles[cell.y][cell.x].value == 0
        }else{
            return false
        }
    }

    func withinBounds(cell:Cell) -> Bool {
        return cell.x >= 0 && cell.x < self.gridSize && cell.y >= 0 && cell.y < gridSize
    }

    mutating func insertTile(tile:Tile){
        tiles[tile.y][tile.x] = tile
    }

    mutating func removeTile(tile:Tile){
        tiles[tile.y][tile.x].value = 0
        tiles[tile.y][tile.x].mergedFrom = nil
        tiles[tile.y][tile.x].previousPosition = nil
    }

    mutating func moveTile(tile:Tile,cell:Cell){
        tiles[tile.y][tile.x].value = 0
        tiles[tile.y][tile.x].mergedFrom = nil
        tiles[cell.y][cell.x] = tile

    }

    //MARK: - Tile

    struct Tile: Identifiable{
        var x:Int
        var y:Int
        var value:Int = 0
        var mergedFrom:Array<Tile>?
        var previousPosition:Cell?
        var bgColor:Color{
            get{
                if(value > 2048){
                    return Color(.black)
                }else{
                    return  GameModel.ColorMap[value]!}
            }
        }
        var fontColor:Color{
            get{
                if(value <= 4){
                    return Color(.black)
                }else{
                    return Color(.white)
                }
            }
        }

        var id: Int{
            get{
                (y*10) + x
            }
        }

        mutating func savePosition(){
            self.previousPosition = Cell(x: x, y: y)
        }

    }

    static let ColorMap =  [
        0:Color(#colorLiteral(red: 0.8036968112, green: 0.7560353875, blue: 0.7039339542, alpha: 1)),
        2: Color(#colorLiteral(red: 0.9316522479, green: 0.8934505582, blue: 0.8544340134, alpha: 1)),
        4: Color(#colorLiteral(red: 0.9296537042, green: 0.8780228496, blue: 0.7861451507, alpha: 1)),
        8: Color(#colorLiteral(red: 0.9504186511, green: 0.6943461895, blue: 0.4723204374, alpha: 1)),
        16: Color(#colorLiteral(red: 0.9621869922, green: 0.6018956304, blue: 0.3936881721, alpha: 1)),
        32:Color(#colorLiteral(red: 0.9640850425, green: 0.49890697, blue: 0.3777080476, alpha: 1)),
        64: Color(#colorLiteral(red: 0.9669782519, green: 0.406899184, blue: 0.2450104952, alpha: 1)),
        128: Color(#colorLiteral(red: 0.9315031767, green: 0.8115276694, blue: 0.4460085034, alpha: 1)),
        256: Color(#colorLiteral(red: 0.9288312197, green: 0.7997121811, blue: 0.3823960423, alpha: 1)),
        512: Color(#colorLiteral(red: 0.9315162301, green: 0.783490479, blue: 0.3152971864, alpha: 1)),
        1024: Color(#colorLiteral(red: 0.9308142066, green: 0.7592952847, blue: 0.179728806, alpha: 1)),
        2048: Color(#colorLiteral(red: 0.9308142066, green: 0.7592952847, blue: 0.179728806, alpha: 1)),
    ]

    enum Direction {
        case up
        case down
        case left
        case right
    }

}

viewmodel:

class GameViewModel: ObservableObject {
    @Published private(set) var model: GameModel =  GameViewModel.createGame()

    private static func createGame () -> GameModel {
        return GameModel(gridSize: 4)
    }

    //MARK: - Access to the model
    var tiles:Array<Array<GameModel.Tile>> {
        model.tiles
    }

    var gridSize:Int {
        model.gridSize
    }

    //MARK: - Intent(s)

    func move(by direction:GameModel.Direction){
        model.move(by: direction)
    }

}

我的源代码已上传到github,请帮助我

animation swiftui transition
1个回答
0
投票

之所以发生这种情况,是因为这些图块正被移动到其Z位置比“行中的方式”低的位置。

您可以将背景与图块分开,因此请确保图块始终位于顶部:

var body: some View {
    let tap = DragGesture().onEnded({value in self.move(by: value)})

    return GeometryReader{geo in
        ZStack{
            RoundedRectangle(cornerRadius: 10.0).fill(Color(#colorLiteral(red: 0.719702065, green: 0.6819230318, blue: 0.6252140403, alpha: 1)))
            VStack{
                ForEach(0..<self.viewModel.gridSize){ rowIndex in
                    HStack {
                        ForEach(0..<self.viewModel.gridSize){ columnIndex in
                            RoundedRectangle(cornerRadius: 10.0).fill(Color(#colorLiteral(red: 0.794301331, green: 0.7563138604, blue: 0.7084676027, alpha: 1))).transition(.identity)
                        }
                    }
                }
            }
            .padding()
            VStack{
                ForEach(0..<self.viewModel.gridSize){rowIndex in
                    HStack {
                        ForEach(self.viewModel.tiles[rowIndex]){tile in
                            TileView(tile:tile)
                        }
                    }
                }
            }
            .padding()
        }
        .gesture(tap)
        .frame(width: geo.size.width, height: geo.size.width, alignment: .center)
    }
}
    
© www.soinside.com 2019 - 2024. All rights reserved.