Sprite Kit设置跳跃的最小和最大值

15

我想在Y轴上移动一个名为Player的SKSpriteNode。Player没有Velocity,只有在与平台接触时才能跳跃。

每次屏幕被触摸时,我想给Player一个最小冲量或最大冲量的脉冲。

如果屏幕短暂被点击,则最小的冲量应为例如y = 50。 如果屏幕被长按,则最大值应为例如y = 100

但是,如果屏幕不是长时间按下也不是短时间按下,玩家还可以跳跃到最小和最大高度之间,此时,Player应该只获得 y = 70 的冲量。

如果屏幕被长按住,则Player应该跳到他的最大高度,然后落下,并且如果它再次接触到平台,则应该跳跃,因为你仍然按住屏幕。

我已经尝试了这个问题,使用了这个线程中建议的答案:StackOverFlow,但是这并不能使最小跳跃和按压跳跃。

为了清晰起见:冲量不应该在点击完成后发生,而是在按住时发生。你按得越久,跳跃的时间就越长。

import SpriteKit
import GameKit

struct Constants {

static let minimumJumpForce:CGFloat = 40.0
static let maximumJumpForce:CGFloat = 60.0
static let characterSideSpeed:CGFloat = 18.0
}

class GameScene: SKScene, SKPhysicsContactDelegate {

var Player: SKSpriteNode!

var Platform0: SKSpriteNode!

var World: SKNode!
var Camera: SKNode!

var force: CGFloat = 40.0

var pressed = false

var isCharacterOnGround = false

.....

func SpawnPlatforms() {

Platform0 = SKSpriteNode (color: SKColor.greenColor(), size: CGSize(width: self.frame.size.width , height: 25))
Platform0.position = CGPoint(x: self.frame.size.width / 2, y: -36)
Platform0.zPosition = 1

Platform0.physicsBody = SKPhysicsBody(rectangleOfSize:Platform0.size)
Platform0.physicsBody?.dynamic = false
Platform0.physicsBody?.allowsRotation = false
Platform0.physicsBody?.restitution = 0
Platform0.physicsBody?.usesPreciseCollisionDetection = true

Platform0.physicsBody?.categoryBitMask = Platform0Category
Platform0.physicsBody?.collisionBitMask = PlayerCategory
Platform0.physicsBody?.contactTestBitMask = PlayerCategory

World.addChild(Platform0)

}

func SpawnPlayer(){

Player = SKSpriteNode (imageNamed: "Image.png")
Player.size = CGSize(width: 64, height: 64)
Player.position = CGPoint(x: self.frame.size.width / 2, y: 0)
Player.zPosition = 2

Player.physicsBody = SKPhysicsBody(rectangleOfSize:CGSize(width: 35, height: 50))
Player.physicsBody?.dynamic = true
Player.physicsBody?.allowsRotation = false
Player.physicsBody?.restitution = 0.1
Player.physicsBody?.usesPreciseCollisionDetection = true

Player.physicsBody?.categoryBitMask = PlayerCategory
Player.physicsBody?.collisionBitMask = Platform0Category
Player.physicsBody?.contactTestBitMask = Platform0Category | Platform1Category | Platform2Category | Platform3Category | Platform4Category | Platform5Category

World.addChild(Player)

}

func jump(force : CGFloat){


    if(self.isCharacterOnGround){

        self.Player.physicsBody?.applyImpulse(CGVectorMake(0, force))
        self.isCharacterOnGround = false
    }

}

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    /* Called when a touch begins */

    for touch in (touches as! Set<UITouch>) {
        let location = touch.locationInNode(self)

        self.pressed = true

        let timerAction = SKAction.waitForDuration(0.0)

        let update = SKAction.runBlock({
            if(self.force < Constants.maximumJumpForce){
                self.force += 2.0
            }else{
                self.jump(Constants.maximumJumpForce)
                self.force = Constants.maximumJumpForce
            }
        })
        let sequence = SKAction.sequence([timerAction, update])
        let repeat = SKAction.repeatActionForever(sequence)
        self.runAction(repeat, withKey:"repeatAction")
    }
}

override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
    for touch in (touches as! Set<UITouch>) {
        let location = touch.locationInNode(self)

        self.removeActionForKey("repeatAction")

        self.jump(self.force)

        self.force = Constants.minimumJumpForce

        self.pressed = false

}
}

func didBeginContact(contact: SKPhysicsContact) {

    //this gets called automatically when two objects begin contact with each other

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

    switch(contactMask) {

    case PlayerCategory | Platform0Category:
        //either the contactMask was the bro type or the ground type
        println("Contact Made0")
        Green = true
        self.isCharacterOnGround = true

    default:
        return

    }

}

我喜欢你发布链接中的第二个答案(使用SKAction)。你试过那个吗?关于持续跳跃而手指仍在屏幕上的问题... 我想你应该添加一个变量来指示用户仍然按住屏幕,如果是这种情况,当平台和玩家之间发生接触时,你将为下一次跳跃施加冲量... - Whirlwind
我也试了第二个,但还不是我想要的。是的,在接触之后我应该为下一次跳跃施加一个冲量,但仍然需要设置最小和最大值。 - Nimbahus
看看我的答案。只需复制并粘贴代码即可查看其工作原理... - Whirlwind
1个回答

12

这里是一个关于如何实现以下功能的示例:

  • 基于按压时间实现长按跳跃
  • 短按一次实现跳跃
  • 在空中时限制角色跳跃
  • 当手指触碰屏幕时使角色不停跳跃

代码(Swift 4.x)

import SpriteKit

struct Constants {
    static let minimumJumpForce:CGFloat = 15.0
    static let maximumJumpForce:CGFloat = 30.0
    static let characterSideSpeed:CGFloat = 18.0
}

class GameScene: SKScene,SKPhysicsContactDelegate
{
    let CharacterCategory   : UInt32 = 0x1 << 1
    let PlatformCategory    : UInt32 = 0x1 << 2
    let WallCategory        : UInt32 = 0x1 << 3

    var force: CGFloat = 16.0 //Initial force
    var pressed = false
    var isCharacterOnGround = false // Use this to prevent jumping while in the air
    let character = SKSpriteNode(color: .green, size: CGSize(width: 30, height:30))
    let debugLabel = SKLabelNode(fontNamed: "Geneva")

    override func didMove(to view: SKView)
    {
        //Setup contact delegate so we can use didBeginContact and didEndContact methods
        physicsWorld.contactDelegate = self
        physicsWorld.speed = 0.5
        //Setup borders so character can't escape from us :-)
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        self.physicsBody?.categoryBitMask = WallCategory
        self.physicsBody?.collisionBitMask = CharacterCategory

        //Setup character
        character.position = CGPoint(x: 150, y: 150)
        character.physicsBody = SKPhysicsBody(rectangleOf: character.size)
        character.physicsBody?.categoryBitMask = CharacterCategory
        character.physicsBody?.contactTestBitMask = PlatformCategory
        character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
        character.physicsBody?.allowsRotation = false
        character.physicsBody?.isDynamic = true
        character.physicsBody?.restitution = 0.1

        self.addChild(character)

        generatePlatforms()

        debugLabel.text = " DEBUG: "
        debugLabel.fontColor = .white
        debugLabel.fontSize = 12.0
        debugLabel.position = CGPoint(x: frame.midX, y: frame.midY+100)
        self.addChild(debugLabel)
    }

    func generatePlatforms(){
        for i in 1...4
        {
            let position = CGPoint(x: frame.midX, y: CGFloat(i)*140.0 - 100)
            let platform = createPlatformAtPosition(position: position)
            self.addChild(platform)
        }
    }


    func createPlatformAtPosition(position : CGPoint)->SKSpriteNode{

        let platform = SKSpriteNode(color: .green, size: CGSize(width: frame.size.width, height:20))

        platform.position = position

        platform.physicsBody = SKPhysicsBody(
            edgeFrom: CGPoint(x: -platform.size.width/2.0, y:platform.size.height/2.0),
            to:CGPoint(x: platform.size.width/2.0, y: platform.size.height/2.0))

        platform.physicsBody?.categoryBitMask       = PlatformCategory
        platform.physicsBody?.contactTestBitMask    = CharacterCategory
        platform.physicsBody?.collisionBitMask      = CharacterCategory
        platform.physicsBody?.allowsRotation        = false
        platform.name                               = "platform"
        platform.physicsBody?.isDynamic             = false
        platform.physicsBody?.restitution           = 0.0

        return platform
    }

    func jump(force : CGFloat){
        if(self.isCharacterOnGround){
            self.character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: force))
            self.character.physicsBody?.collisionBitMask = WallCategory
            self.isCharacterOnGround = false
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.pressed = true

        let timerAction = SKAction.wait(forDuration: 0.05)

        let update = SKAction.run({
            if(self.force < Constants.maximumJumpForce){
                self.force += 2.0
            }else{
                self.jump(force: Constants.maximumJumpForce)
                self.force = Constants.maximumJumpForce
            }
        })

        let sequence = SKAction.sequence([timerAction, update])
        let repeat_seq = SKAction.repeatForever(sequence)
        self.run(repeat_seq, withKey:"repeatAction")
    }


    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        self.removeAction(forKey: "repeatAction")
        self.jump(force: self.force)
        self.force = Constants.minimumJumpForce
        self.pressed = false

    }

    override func update(_ currentTime: TimeInterval) {

        debugLabel.text = "DEBUG: onTheGround : \(isCharacterOnGround), force \(force)"

        if(character.position.x <= character.size.width/2.0 + 5.0 && character.physicsBody!.velocity.dx < 0.0 ){
            character.physicsBody?.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
        }else if((character.position.x >= self.frame.size.width - character.size.width/2.0 - 5.0) && character.physicsBody!.velocity.dx >= 0.0){
            character.physicsBody?.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
        }else if(character.physicsBody!.velocity.dx > 0.0){
            character.physicsBody!.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
        }else{
            character.physicsBody!.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
        }
    }

    func didBegin(_ contact: SKPhysicsContact) {

        var firstBody, secondBody: SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

        if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
            (secondBody.categoryBitMask & PlatformCategory != 0)) {


            let platform = secondBody.node! as! SKSpriteNode
            //  platform.color = UIColor.redColor()
            let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

            let player = contact.bodyB.node! as! SKSpriteNode
            let playerLegsYPos = player.position.y - player.size.height/2.0

            if((platformSurfaceYPos <= playerLegsYPos)){
                character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
                self.isCharacterOnGround = true

                if(self.pressed){
                    let characterDx = character.physicsBody?.velocity.dx
                    character.physicsBody?.velocity = CGVector(dx: characterDx!, dy: 0.0)
                    self.jump(force: Constants.maximumJumpForce)
                }
            }
        }
    }

    func didEnd(_ contact: SKPhysicsContact) {

        var firstBody, secondBody: SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

        if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
            (secondBody.categoryBitMask & PlatformCategory != 0)) {

            let platform = secondBody.node as! SKSpriteNode
            let platformSurfaceYPos = platform.position.y + platform.size.height/2.0

            let player = contact.bodyB.node as! SKSpriteNode
            let playerLegsYPos = player.position.y - player.size.height/2.0

            if((platformSurfaceYPos <= playerLegsYPos) && ((character.physicsBody?.velocity.dy)! > CGFloat(0.0))){
                character.physicsBody?.collisionBitMask = WallCategory
                self.isCharacterOnGround = false
            }
        }
    }
}

请注意,这只是一个简单的例子,在实际应用中,您可能需要以不同的方式处理类似“isOnTheGround”之类的状态。目前,为了确定角色是否在地面上,您只需在角色与平台接触时设置isOnTheGround = true,并在didEndContact中将其设置为false...但在空中时,角色可能会与平台接触(例如侧向接触)...

编辑:

我改变了代码,使玩家可以一直按下跳跃。以下是结果:

enter image description here

重要提示:

实际平台实现和接触处理取决于您,这没有经过测试。此示例的唯一目的是向您展示如何在长时间按下按钮后进行跳跃。目前,physicsWorld.speed设置为0.5,以使动画速度变慢,因为这样更容易调试,但您可以将其更改为默认值(1.0)。

如您从图像中所见,玩家在第一平台上时,通过简单地点击或短暂地按压,进行了一些小跳跃。然后(玩家仍在第一个平台上),进行了长时间按压,玩家跳到了第二个平台上。之后,再次进行长时间按压,但这次没有释放,玩家开始使用最大力量从一个平台跳到另一个平台。

这需要大量调整和适当的平台和接触检测,但它可以让您了解有关所要求的跳跃的实现方式。


谢谢您的建议!虽然不完全是我想要的,但“当按下时与平台接触时跳跃”的代码对我有用。我更新了我的帖子,并附上了一个使用相同逻辑的游戏视频。 - Nimbahus
@Alber。你好... 你有哪部分不工作?你说:“我已经尝试了这个线程中建议的答案:https://dev59.com/DYrda4cB1Zd3GeqPRdNO!但是这没有提供最小弹跳,也没有按跳。”我的示例与你的陈述有关,并旨在向你展示如何根据按下持续时间更改跳跃力,并具有“最小跳跃”和“按跳跃”(短按=小跳跃,长按=大跳跃等)。 - Whirlwind
通过这段代码,我们设定了一个最小值。如果“力量”小于100,你长按屏幕的时间越久,它就会增加2,直到达到100。如果我理解正确的话,然而跳跃只会在你完成长按后出现,而不是在长按过程中。我希望在屏幕被触摸时就能够实现跳跃。 - Nimbahus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接