5

I want to achieve this effect in my Spritekit game where there is a smooth trail behind the character.

See the trail behind the coin in jetpack joyride: enter image description here

And this trail behind the hero in Jupiter Jump: enter image description here

Or this super smooth trail behind the hero in Ski Safari: enter image description here

This appears to be a standard feature in other game engines maybe? I think a spritekit particle emitter will only provide a blocky/stamped trail, not a smooth trail. Should I be using some kind of sprite custom shader? Any other inventive thoughts?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Patrick Collins
  • 4,046
  • 3
  • 26
  • 29

1 Answers1

4

Your question did not include a key issue which is the type of movement to be used. My answer is based on touching the screen for a destination point but another alternative is to use core motion. Regardless of which method is used the basic code principals remain the same. Only the implementation would change.

I used a rectangle tail image in my example because I wanted for you to be able to copy and run the example code. You should replace the rect with a circle image/texture to give the tail smoother sides.

Modifying the fadeOutDuration value will result in a longer or shorter lasting tail.

Modifying the stepsDivider will result in a more or less nodes in the tail.

#import "GameScene.h"

@implementation GameScene {
    SKSpriteNode *playerNode;
    CGPoint destinationPoint;
    NSMutableArray *myArray;
    NSMutableArray *myDiscardArray;
    BOOL working;
    int numberOfSteps;
    float xIncrement;
    float yIncrement;
    float fadeOutDuration;
    int stepsDivider;
}

-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [SKColor blackColor];

    playerNode = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(30, 30)];
    playerNode.position = CGPointMake(200, 200);
    [self addChild:playerNode];

    myArray = [[NSMutableArray alloc] init];
    myDiscardArray = [[NSMutableArray alloc] init];

    working = false;
    fadeOutDuration = 0.5;
    stepsDivider = 10;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];

        if(working == false) {
            destinationPoint = location;

            if(fabsf(location.x - playerNode.position.x) > fabsf(location.y - playerNode.position.y)) {
                numberOfSteps = fabsf(location.x - playerNode.position.x) / 10;
            } else {
                numberOfSteps = fabsf(location.y - playerNode.position.y) / 10;
            }

            xIncrement = (location.x - playerNode.position.x) / numberOfSteps;
            yIncrement = (location.y - playerNode.position.y) / numberOfSteps;

            working = true;
        }
    }
}

-(void)update:(CFTimeInterval)currentTime {

    if (working == true) {

        // create trail node at current player's position
        SKSpriteNode *myNode = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(30, 30)];
        myNode.position = playerNode.position;
        [self addChild:myNode];
        [myArray addObject:myNode];
        [myNode runAction:[SKAction fadeOutWithDuration:fadeOutDuration]];

        // check array for any nodes with zero alpha
        for(SKSpriteNode *object in myArray) {
            if(object.alpha == 0) {
                [myDiscardArray addObject:object];
            }
        }

        // remove zero alpha nodes
        if([myDiscardArray count] > 0) {
            [myArray removeObjectsInArray:myDiscardArray];
            [myDiscardArray removeAllObjects];
        }

        // update player's new position
        playerNode.position = CGPointMake(playerNode.position.x+xIncrement, playerNode.position.y+yIncrement);

        // check if player has arrived at destination
        if(((int)playerNode.position.x == (int)destinationPoint.x) && ((int)playerNode.position.y == (int)destinationPoint.y)) {
            working = false;
        }
    }
}

@end
sangony
  • 11,636
  • 4
  • 39
  • 55
  • Thanks Sangony for the effort. As you said, I think it is a good approximation for the effect. As i look at the originals I wonder if it is some kind of color gradient on a bezier path, or maybe some kind of GLSL shader. – Patrick Collins Mar 22 '15 at 20:25
  • @PaddyCollins - Whatever method you end up choosing, the principals will pretty much remain the same. At some point you will have to 'draw' a shape/node/packet with an alpha fade life span. If you end up going really fine resolution, you might risk putting a heavy load on the processor for just this effect and could end up with a strained FPS count. Perhaps this effect is a standard part of other languages like Unity but I am certain SpriteKit does not have a build in feature like this. – sangony Mar 22 '15 at 22:23
  • 2
    Could anyone convert this to swift code? I'm not well versed in Obj-C. Thanks! – Noah Covey Mar 17 '16 at 02:08
  • @sangony I've adapted this excellent code to my Xamarin iOS project with great success, many thanks. Just one minor thing: is there a small memory leak? If I read this right, once the playerNode arrives at its destination, working is set to false, therefore the block inside Update wont be called anymore. That means a last "clean up" of faded nodes won't be done, leaving them hanging around. I can see that on my SKView node counter. Does Objective-C clean up here, which is why you don't have to worry about it, but I have to do a separate disposal? – Marakai May 24 '16 at 03:17