Search code examples
opengl-essprite-kitglslskemitternode

Spritekit and OpenGL: smooth smoke trail


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?


Solution

  • 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