防止玩家穿过地面 - Sprite Kit

3
我一直在尝试制作一个简单的Sprite Kit游戏,其中包括躲避红球。我正在使用内置的重力机制,但我无法阻止玩家穿过地面。我查找了解决方案(设置ground.physicsBody.dynamic = NO),但是玩家仍然会掉落。我需要做什么?
编辑:绿色和棕色的纹理是地面。现在,玩家被设置为不可动态,因此它“飞行”。
以下是我的MyScene.m文件中的代码:
//
//  MyScene.m
//  DodgeMan
//
//  Created by Cormac Chester on 3/8/14.
//  Copyright (c) 2014 Testman Industries. All rights reserved.
//

#import "MyScene.h"
#import "EndGameScene.h"

static const uint32_t redBallCategory =  0x1 << 0;
static const uint32_t playerCategory =  0x1 << 1;

@implementation MyScene

-(id)initWithSize:(CGSize)size
{
    if (self = [super initWithSize:size])
    {
        /* Setup your scene here */

        //Sets player location
        playerLocX = 50;
        playerLocY = 100;

        //Sets player score
        score = 0;

        //Set Background
        self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];

        //Set Ground
        SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:@"ground"];
        ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
        ground.xScale = 0.5;
        ground.yScale = 0.5;
        ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
        ground.physicsBody.dynamic = NO;

        //Player
        self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:@"character"];
        self.playerSprite.position = CGPointMake(playerLocX, playerLocY);

        //Set Player Physics
        self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
        self.playerSprite.physicsBody.dynamic = YES;
        self.playerSprite.physicsBody.categoryBitMask = playerCategory;
        self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
        self.playerSprite.physicsBody.collisionBitMask = 0;
        self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;

        //Score Label
        self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial-BoldMT"];
        self.scoreLabel.text = @"0";
        self.scoreLabel.fontSize = 40;
        self.scoreLabel.fontColor = [SKColor blackColor];
        self.scoreLabel.position = CGPointMake(50, 260);

        //Pause Button
        self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:@"pauseButton"];
        self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
        self.pauseButton.name = @"pauseButton";

        //Add nodes
        [self addChild:ground];
        [self addChild:self.playerSprite];
        [self addChild:self.scoreLabel];
        //[self addChild:self.pauseButton];

        //Sets gravity
        self.physicsWorld.gravity = CGVectorMake(0,-2);
        self.physicsWorld.contactDelegate = self;

    }
    return self;
}

-(void)addBall
{
    SKSpriteNode *redBall = [SKSpriteNode spriteNodeWithImageNamed:@"locationIndicator"];
    int minY = redBall.size.height / 2;
    int maxY = self.frame.size.height - redBall.size.height / 2;
    int rangeY = maxY - minY;
    int actualY = (arc4random() % rangeY) + minY;
    NSLog(@"Actual Y: %i", actualY);

    //Initiates red ball offscreen
    if (actualY >= 75)
    {
        //Prevents balls from spawning in the ground
        redBall.position = CGPointMake(self.frame.size.width + redBall.size.width/2, actualY);
        [self addChild:redBall];
    }
    redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2];
    redBall.physicsBody.dynamic = YES;
    redBall.physicsBody.categoryBitMask = redBallCategory;
    redBall.physicsBody.contactTestBitMask = playerCategory;
    redBall.physicsBody.collisionBitMask = 0;
    redBall.physicsBody.affectedByGravity = NO;
    redBall.physicsBody.usesPreciseCollisionDetection = YES;

    //Determine speed of red ball
    int minDuration = 3.0;
    int maxDuration = 5.0;
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;

    // Create the actions
    SKAction *actionMove = [SKAction moveTo:CGPointMake(-redBall.size.width/2, actualY) duration:actualDuration];
    SKAction *actionMoveDone = [SKAction removeFromParent];
    SKAction *ballCross = [SKAction runBlock:^{
        score++;
        self.scoreString = [NSString stringWithFormat:@"%i", score];
        self.scoreLabel.text = self.scoreString;
        NSLog(@"Score was incremented. Score is now %d", score);
    }];
    [redBall runAction:[SKAction sequence:@[actionMove, ballCross, actionMoveDone]]];
}

- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast
{
    self.lastSpawnTimeInterval += timeSinceLast;
    if (self.lastSpawnTimeInterval > 0.5) {
        self.lastSpawnTimeInterval = 0;
        [self addBall];
    }
}

-(void)update:(CFTimeInterval)currentTime
{
    /* Called before each frame is rendered */
    // Handle time delta.
    //Prevents bad stuff happening
    CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
    self.lastUpdateTimeInterval = currentTime;
    if (timeSinceLast > 1) { // more than a second since last update
        timeSinceLast = 1.0 / 120.0;
        self.lastUpdateTimeInterval = currentTime;
    }

    [self updateWithTimeSinceLastUpdate:timeSinceLast];
}

NSDate *startTime;

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    /* Called when a touch begins */
    [super touchesBegan:touches withEvent:event];

    //Starts Timer
    startTime = [NSDate date];

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];

    //Pauses Scene
    if ([node.name isEqualToString:@"pauseButton"])
    {
        NSLog(@"Pause button pressed");
    }

}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    /* Called when a touch ends */
    [super touchesEnded:touches withEvent:event];

    NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];
    NSString *elapsedTimeString = [NSString stringWithFormat:@"Elapsed time: %f", elapsedTime];
    NSLog(@"%@", elapsedTimeString);


    for (UITouch *touch in touches)
    {
        //Gets location of touch
        CGPoint location = [touch locationInNode:self];
        NSLog(@"Touch Location X: %f \n Touch Location Y: %f", location.x, location.y);

        //Prevents destination from being in the ground
        if (location.y < 88)
        {
            location.y = 87.5;
        }

        //Moves and animates player
        //int velocity = elapsedTime * -3000;
        int velocity = 800.0/1.0;
        NSLog(@"Velocity: %i", velocity);
        float realMoveDuration = self.size.width / velocity;
        SKAction *actionMove = [SKAction moveTo:location duration:realMoveDuration];
        [self.playerSprite runAction:[SKAction sequence:@[actionMove]]];
    }

    NSLog(@"Touch ended");
}

//Collision between ball and player
- (void)redBall:(SKSpriteNode *)redBall didCollideWithPlayer:(SKSpriteNode *)playerSprite
{
    NSLog(@"Player died");
    [redBall removeFromParent];
    [playerSprite removeFromParent];

    SKTransition *reveal = [SKTransition crossFadeWithDuration:0.5];
    SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size gameEnded:YES];
    [self.view presentScene:endGameScene transition: reveal];
}

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody *firstBody, *secondBody;

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

    //Red ball collides with the player
    if ((firstBody.categoryBitMask & redBallCategory) != 0 && (secondBody.categoryBitMask & playerCategory) != 0)
    {
        [self redBall:(SKSpriteNode *) firstBody.node didCollideWithPlayer:(SKSpriteNode *) secondBody.node];
    }
}

@end

所以,实际上,你的玩家只需要左右移动吗?让玩家受重力影响,你想要实现什么目标? - AndrewShmig
1
我计划修改控件,因为目前你只需点击想要玩家移动的位置。通过让玩家受到重力影响,它将会落到地面上(希望不会穿过),然后我将实现一个跳跃控制(例如在屏幕底部点击或其他方式),允许玩家除了左右移动控制(在屏幕左右两侧点击)之外还能够向上跳跃。 - comrod
现在我明白了,之前我也遇到过同样的问题,我会尝试回忆一下我是如何解决它的。 - AndrewShmig
6个回答

1

你绝对不能将其动态设置为“无兄弟”。你需要重力效应来影响玩家(他不受物理世界的影响,所以他现在飞行着。我们需要让他掉落到地面上,不是吗?:)

所以这里是简单的解决方案。想法是在地面表面创建一个具有物理体的“隐形矩形块”,并且您需要将其动态设置为“否”以防止其掉落。

因此,此块显然是一个节点,其大小:与地面一样高,屏幕宽。您需要略微调整位置,以使其上边界正好位于地面表面。

祝好运

我实际上画了一张图片,但由于我的声望问题,无法在此发布:(


如果您还有问题,我很乐意回答。 - Dylan Yasen
我尝试过了,但理论上如果地面节点的physics.dynamic被禁用,我不应该这样做。无论如何,它没有起作用,所以我仍然不知道问题出在哪里。 - comrod
我对我的帖子感到非常抱歉。那真是太愚蠢了。我正在做flappy bird克隆项目。在这种情况下,地面是移动的,所以我制作了一个隐形的rec块以防万一。很抱歉让你困惑了。让我们回到问题上,//1:我建议您NSLog出地面节点的位置和大小。//2:为地面设置一个单独的物理类别,并设置ground.collisionbitmask //3. 顺便问一下,这是干什么用的?:“self.playerSprite.physicsBody.collisionBitMask = 0;” - Dylan Yasen
是的,我看了那个“Flappy Bird”克隆教程,甚至下载了源代码,但我无法弄清楚我的代码问题出在哪里。 - comrod
你为球和玩家指定了collisionBitmask为0,而地面不是。你认为这是否导致了问题?你有什么想法? - Dylan Yasen
显示剩余2条评论

1
你的问题与physicsBody的categoryBitMask和collisionTestBitMask有关。 你的位运算声明:
static const uint32_t redBallCategory =  0x1 << 0;
static const uint32_t playerCategory =  0x1 << 1; 

这实际上设置了以下位模式(我为示例缩短到8位): redBallCategory - 00000001 和 playerCategory - 00000010

然而,在下面的代码中,您告诉玩家仅与碰撞位掩码-00000000发生碰撞; self.playerSprite.physicsBody.collisionBitMask = 0;

所以你的第一个问题出在这里。玩家将不会与您定义的任何类别发生碰撞。

你的第二个问题是你没有给地面一个categoryBitMask或collisionBitMask。默认情况下,这意味着所有位都被设置,即地面的collisionBitMask等于11111111;

这两个物理体之间将没有碰撞。

尝试一下——我只是添加了第三个物理类别,并稍微修改了您的代码,以设置地面的categoryBitMask / collisionBitMask和玩家的collisionBitMask。

static const uint32_t redBallCategory =  0x1 << 0;
static const uint32_t playerCategory =  0x1 << 1;
static const uint32_t groundCategory = 0x1 << 2;

@implementation MyScene

-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
    /* Setup your scene here */

    //Sets player location
    playerLocX = 50;
    playerLocY = 100;

    //Sets player score
    score = 0;

    //Set Background
    self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];

    //Set Ground
    SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:@"ground"];
    ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
    ground.xScale = 0.5;
    ground.yScale = 0.5;
    ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
    ground.physicsBody.categoryBitMask=groundCategory;
    ground.physicsBody.collisionBitMask=playerCategory|redBallCategory;
    ground.physicsBody.dynamic = NO;

    //Player
    self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:@"character"];
    self.playerSprite.position = CGPointMake(playerLocX, playerLocY);

    //Set Player Physics
    self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
    self.playerSprite.physicsBody.dynamic = YES;
    self.playerSprite.physicsBody.categoryBitMask = playerCategory;
    self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
    self.playerSprite.physicsBody.collisionBitMask = groundCategory|redBallCategory;
    self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;

    //Score Label
    self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial-BoldMT"];
    self.scoreLabel.text = @"0";
    self.scoreLabel.fontSize = 40;
    self.scoreLabel.fontColor = [SKColor blackColor];
    self.scoreLabel.position = CGPointMake(50, 260);

    //Pause Button
    self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:@"pauseButton"];
    self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
    self.pauseButton.name = @"pauseButton";

    //Add nodes
    [self addChild:ground];
    [self addChild:self.playerSprite];
    [self addChild:self.scoreLabel];
    //[self addChild:self.pauseButton];

    //Sets gravity
    self.physicsWorld.gravity = CGVectorMake(0,-2);
    self.physicsWorld.contactDelegate = self;

}
return self;

}


0

只是:

self.playerSprite.physicsBody.dynamic = NO;

应该可以工作。


但是玩家不受重力影响。 - comrod
@comrod,你能截一张你的游戏屏幕截图吗?让我们更好地了解我们手头的情况。 - AndrewShmig

0
你的场景中是否有一个边缘循环物理体? 碰撞标志和类别标志设置正确,以便玩家与地面发生碰撞吗?

0
你的问题是由于缩放引起的。在Sprite Kit中,对图像进行缩放并不会改变其大小,当使用以下代码时就会出现这种情况。从你的图片来看,你地面的物理体矩形实际上比你想象的要大两倍,并且已经包围了玩家,这就是为什么没有碰撞检测的原因。这是最近与非常相似的游戏风格的经验。

-1

我曾经遇到过同样的问题,但我很简单地解决了它...

在更新方法中,我加入了一个if语句:

if(player.position.y<your_closest_value_near_ground){

player.position.y == your_Closest_value_near_ground

}

比较的结果取决于你所选择的锚点.. 希望能对某些人有所帮助


抱歉,我以为很清楚你需要将这个放在Update方法中。它会每帧检查并在需要时进行修复。 - Silviu St

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