How to make several sprite nodes each with a special path to follow - ios

I am trying to make a game like flight control where there are x amount of cars and each one has its own path to follow. My idea was to make a car class of subtype SKNode and have a CGMutablePathRef instance variable for each object. I can make the CGMutablePathRef with touchsBegins, touchsMoved, and touchesEnded and figured I could then have each object follow its specific path with the SKAction followPath. Is this a good approach? If not how would you do it? This is what I have so far for the Vehicle class:
Vehicle.h
#interface Vehicle: SKNode
#property (nonatomic, assign) CGMutablePathRef pathToFollow;
#property (nonatomic) CGFloat speed;
#property (nonatomic) int direction;
- (instancetype)initWith:(CGMutablePathRef)pathPassed;
#end
Vehicle.m
#implementation Vehicle : SKNode
#synthesize pathToFollow;
#synthesize speed;
#synthesize direction;
- (id)init
{
self = [super init];
if (self) {
SKSpriteNode *vehicle = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[self addChild:vehicle];
[self setDirection:4];
[vehicle setName:#"car"];
[self setScale:.5];
}
return self;
}
- (id)initWith:(CGMutablePathRef)pathPassed {
self = [super init];
if (self) {
SKSpriteNode *vehicle = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
[self addChild:vehicle];
[self setPathToFollow:pathPassed];
[self setDirection:4];
[vehicle setName:#"car"];
[self setScale:.1];
}
return self;
}
#end
EDIT: Adding GameScene in hopes that it will help. I am having a problem with my implementation now where the Vehicle object is following the path but only after it jumps up to the coordinates of the first point + the nodes position on the scene (ex: position = (200, 200), first point = (123, 456) it will go to (323, 656) then follow the path). It draws the line where I want it but then it follows from way up on this coordinate. Thanks again for the help!
GameScene.m
-(void)didMoveToView:(SKView *)view {
_car = [[Vehicle new] init]; // init the Vehicle object then set position
[_car setPosition:CGPointMake(self.scene.size.width/2, self.scene.size.height/2)];
[self addChild_car]; // add the child to the scene
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// get the node that was touched
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
_nodeTouchedFirst = [self nodeAtPoint:positionInScene];
if ([_nodeTouchedFirst.name isEqualToString:#"car"]) {
if (lineNode != NULL) { // remove the previous path
[lineNode removeFromParent];
[CGPathRelease(pathToDraw);
}
//Start a new path and display it on the screen
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode];
lineNode.path = pathToDraw;
lineNode.strokeColor = [SKColor redColor];
[self addChild:lineNode];
}
} // touchesBegan
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
if ([_nodeTouchedFirst.name isEqualToString:#"car"]) {
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
}
} // touchesMoved
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([_nodeTouchedFirst.name isEqualToString:#"car"]) {
SKAction *followPath = [SKAction followPath:pathToDraw asOffset:YES orientToPath:NO duration:2];
[_nodeTouchedFirst runAction:followPath];
lineNode.strokeColor = [SKColor grayColor];
}
}

Some observations that may or may not help:
You're setting the position of _car and adding it to the scene before you give it a path to follow.
You're referencing both _nodeTouchedFirst and nodeTouchedFirst, "car" and "car1".
You're following a path with asOffset:YES, orientToPath:NO, duration:2 (rather than asOffset:NO, orientToPath:YES, speed:2).
direction doesn't correspond to the SKNode zRotation property.

Related

SKNode Subclass Not Moving Children

I have a sprite that can't exist in my game without a pairing SKFieldNode so my solution was to create a subclass of SKSpriteNode and create a property for the SKFieldNode but it didn't work because the SKSpriteNode was acting weird (I don't remember exactly what happened). So my next approach was to change the subclass to SKNode and then I would make the SKSpriteNode and the SKFieldNode a property of this new SKNode. But then it turns out that touchesMoved will only move one of the properties (whichever is on top) which turns out to always be the SKSpriteNode.
What's the best approach to this problem, and how can I fix it so that I can have an SKFieldNode for every SKSpriteNode while still making sure that actions and methods still work properly.
Current code of SKNode subclass:
#interface Whirlpool : SKNode
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff;
#property (nonatomic, strong) SKFieldNode *gravityField;
#end
#import "Whirlpool.h"
#import "Categories.h"
#implementation Whirlpool
- (instancetype)initWithPosition:(CGPoint)pos region:(float)region strength:(float)strength falloff:(float)falloff {
if (self = [super init]) {
// whirlpool sprite
SKSpriteNode *whirlpoolSprite = [[SKSpriteNode alloc] initWithImageNamed:#"whirlpool"];
whirlpoolSprite.size = CGSizeMake(100, 100);
whirlpoolSprite.position = pos;
//removing physicsBody and associated attributes for now so that the boat does not collide with the whirlpool
//whirlpoolSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:whirlpoolSprite.size.width / 2];
//whirlpoolSprite.physicsBody.dynamic = NO;
whirlpoolSprite.zPosition = 1;
whirlpoolSprite.name = #"whirlpool";
[whirlpoolSprite runAction:[SKAction repeatActionForever:[self sharedRotateAction]]];
// whirlpool gravity field
_gravityField = [SKFieldNode radialGravityField];
_gravityField.position = pos;
_gravityField.strength = strength;
_gravityField.falloff = falloff;
_gravityField.region = [[SKRegion alloc] initWithRadius:region];
_gravityField.physicsBody.categoryBitMask = gravityFieldCategory;
_gravityField.zPosition = 1;
[self addChild:whirlpoolSprite];
[self addChild:_gravityField];
}
return self;
}
- (SKAction *)sharedRotateAction {
static SKAction *rotateWhirlpool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
rotateWhirlpool = [SKAction rotateByAngle:-M_PI * 2 duration:4.0];
});
return rotateWhirlpool;
}
#end
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
node.position = location;
}
}
}
I believe your issues comes down to that fact that "whirlpool" is a child of your SKNode subclass. So when you are identifying that you are indeed touching a "whirlpool" you are moving it within its parent (the SKNode subclass) and the SKFieldNode and parent stay put. This little adjustment to your original code should work...if I understand the problem correctly.
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// if the finger touches the boat or the whirlpool, update its location
if ([node.name isEqualToString:#"boat"]) {
node.position = CGPointMake(location.x, node.position.y);
} else if ([node.name isEqualToString:#"whirlpool"]) {
//move the SKNode subclass the whirlpool is a child of
node.parent.position = location;
}
}
}
Hopefully that helps.
Yikes, the problem here is the way you are grabbing nodes, you may be grabbing the wrong nodes due to all the children. Instead take this approach:
We already know that you are subclassing your sprites, so in your subclasses, add the following code:
//Whirlpool
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position = location;
}
}
Then:
//Boat
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self.parent];//Unless you are retaining the scene in the child, then use that
self.position.x = location.x;
}
}
Then for your Scene, do this:
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// we don't want the player to be able to move the whirlpools after the button is pressed
if (_isRunning) {
return;
}
//At this point it should call the children on touch events
[super.touchesMoved: touches withEvent:event];
}

Why the texture in my SKSpriteNode gets removed when it's done animating?

I have a SKSpriteNode, I'm Trying to animate it in my touchesbegan and the animation works fine but when it's done animating, the default texture for my SKSpriteNode is gone. My R.atlas contains images named R1,R2..R7 ,What am I doing wrong? Thanks a lot.
#interface MyScene()
{
SKSpriteNode * rightTubeNode;
}
#property NSArray* rightTubeAnimationArray;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKTextureAtlas *rightTubeAtlas = [SKTextureAtlas atlasNamed:#"R"];
rightTubeTexture = [rightTubeAtlas textureNamed:#"R1"];
NSMutableArray *rightAnimFrames = [NSMutableArray array];
for (int i = 1; i <= rightTubeAtlas.textureNames.count; ++i){
NSString *texture =[NSString stringWithFormat:#"R%d",i];
[rightAnimFrames addObject:[rightTubeAtlas textureNamed:texture]];
}
self.rightTubeAnimationArray = rightAnimFrames;
rightTubeNode = [self createRightTubeNode];
rightTubeNode.position = CGPointMake(self.frame.size.width/2+rightTubeNode.frame.size.width/2,50);
rightTubeNode.zPosition = 0.1;
[self addChild:rightTubeNode];
}
return self;
}
- (SKSpriteNode *)createRightTubeNode
{
SKSpriteNode *rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube = [SKSpriteNode spriteNodeWithTexture:rightTubeTexture];
rightTube.name = #"rightTubeNode";
return rightTube;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
CGPoint nodesLocation = CGPointMake(location.x,self.frame.size.height/2);
if (nodesLocation.x>self.frame.size.width/2) {
SKNode *archerNode = [self childNodeWithName:#"rightTubeNode"];
if (archerNode != nil){
SKAction *animate = [SKAction animateWithTextures:self.rightTubeAnimationArray
timePerFrame: 0.02];
[archerNode runAction:animate];
}
}
}
}
Re-adding the R.atlas (with checking the Create groups options box) without any further changes did the trick, Thanks to #Whirlwind

Where Did This Method Come From in SpriteKit

I have been following a tutorial on SpriteKit, and for the life of me, I don't know where this one method comes from. In a StartGameLayer, I have this code in its entirety:
#import "StartGameLayer.h"
#interface StartGameLayer()
#property (nonatomic, retain) SKSpriteNode* playButton;
#end
#implementation StartGameLayer
- (id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size])
{
SKSpriteNode* startGameText = [SKSpriteNode spriteNodeWithImageNamed:#"FlappyBirdText"];
startGameText.position = CGPointMake(size.width * 0.5f, size.height * 0.8f);
[self addChild:startGameText];
SKSpriteNode* playButton = [SKSpriteNode spriteNodeWithImageNamed:#"PlayButton"];
playButton.position = CGPointMake(size.width * 0.5f, size.height * 0.30f);
[self addChild:playButton];
[self setPlayButton:playButton];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if ([_playButton containsPoint:location])
{
if([self.delegate respondsToSelector:#selector(startGameLayer:tapRecognizedOnButton:)])
{
[self.delegate startGameLayer:self tapRecognizedOnButton:StartGameLayerPlayButton];
}
}
}
#end
Where does the setPlayButton:(SKSpriteNode* ) method get set? In the help file it says it is declared in this file, but this is the only instance I see of it, and it is just driving me the tiniest bit nuts how this method is created.

How to remove a child SKSpritenode from SKNode?

i will explain a part of my code. I have Spritenodes (images) who are moving down on the screen.
SKTexture* Squaretexture = [SKTexture textureWithImageNamed:#"squaregreen"];
SquareTexture.filteringMode = SKTextureFilteringNearest;
Square = [SKSpriteNode spriteNodeWithTexture:SquareTexture];
Square.name = #"square";
.
.
.
[_objects addChild:Square];
_objects is a SKNode and Square is a SKSpriteNode. Now there is my code: every one second there is one square, who came from "over the screen" and is moving to the bottom. (Also there are more then one squares on the screen).
Now I want this: When I touch a square it should be "deleted" or hidden, but only the one who i touch. With my code, when i touch all squares are deleted or nothing. I tried with removefromparent and removechild, but i couldn't solve it.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"Point in myView: (%f,%f)", location.x, location.y);
if ([node.name isEqualToString:#"Square"]) {
[Square removeFromParent];
[Square removeAllChildren];
}
}
Do you have a suggestion how can I do it?
Thanks for Answers.
Mehmet
You almost had it right. The trick is that you need to have a unique identifier for each object (sprite) that you create and then store those objects in an array for later use.
The code below creates 5 sprites and gives them unique names: Sprite-1, Sprite-2, etc...
Whenever a touch is registered, it extracts the touched node's name, searches the array for the matching object, removes the object from the view and lastly removes the object from the array.
Note that my sample code is based on landscape view.
#import "MyScene.h"
#implementation MyScene
{
NSMutableArray *spriteArray;
int nextObjectID;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
spriteArray = [[NSMutableArray alloc] init];
nextObjectID = 0;
// create 5 sprites
for (int i=0; i<5; i++)
{
SKSpriteNode *mySprite = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(30, 30)];
nextObjectID ++; // increase counter by 1
mySprite.name = [NSString stringWithFormat:#"Sprite-%i",nextObjectID]; // add unique name to new sprite
mySprite.position = CGPointMake(50+(i*70), 200);
[spriteArray addObject:mySprite];
[self addChild:mySprite];
}
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
NSLog(#"touched node name: %#",node.name);
NSLog(#"objects in spriteArray: %lu",(unsigned long)[spriteArray count]);
NSMutableArray *discardedItems = [NSMutableArray array];
for(SKNode *object in spriteArray)
{
if([object.name isEqualToString:node.name])
{
[object removeFromParent];
[discardedItems addObject:object];
}
}
[spriteArray removeObjectsInArray:discardedItems];
NSLog(#"objects in spriteArray: %lu",(unsigned long)[spriteArray count]);
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
#end

Cocos2d Drawing App Making Lines

I am making a very simple drawing application. I got the lines to draw using the ccTouchMoved event. I am putting all the touch moved moved points into an array and then using a for loop to draw a line between all the points. Now, I do not want to join the points when I have lifted my finger and started new line drawing. I got that part working too but now whenever I begin a new drawing the whole screen flicker.
//
// HelloWorldLayer.mm
// DrawPuppets
//
// Created by Mohammad Azam on 12/11/12.
// Copyright __MyCompanyName__ 2012. All rights reserved.
//
// Import the interfaces
#import "DrawPuppetLayer.h"
#import "AppDelegate.h"
#import "PhysicsSprite.h"
enum {
kTagParentNode = 1,
};
#pragma mark - HelloWorldLayer
#interface DrawPuppetLayer()
-(void) initPhysics;
-(void) addNewSpriteAtPosition:(CGPoint)p;
-(void) createMenu;
#end
#implementation DrawPuppetLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
DrawPuppetLayer *layer = [DrawPuppetLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init])) {
// enable events
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
index = -1;
canvas = [[NSMutableArray alloc] init];
// init physics
[self initPhysics];
[self scheduleUpdate];
}
return self;
}
-(void) draw
{
if([lineDrawing.points count] > 1)
{
for(int i = 0; i<([canvas count]) ;i++)
{
LineDrawing *drawing = (LineDrawing *) [canvas objectAtIndex:i];
for(int j=0;j<[drawing.points count] - 1;j++)
{
LinePoint *firstPoint = (LinePoint *) drawing.points[j];
LinePoint *secondPoint = (LinePoint *) drawing.points[j + 1];
CGPoint point1 = [[CCDirector sharedDirector] convertToGL:CGPointMake(firstPoint.x, firstPoint.y)];
CGPoint point2 = [[CCDirector sharedDirector] convertToGL:CGPointMake(secondPoint.x, secondPoint.y)];
ccDrawLine(point1, point2);
}
}
}
//
// IMPORTANT:
// This is only for debug purposes
// It is recommend to disable it
//
[super draw];
ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );
kmGLPushMatrix();
world->DrawDebugData();
kmGLPopMatrix();
}
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
lineDrawing = [[LineDrawing alloc] init];
lineDrawing.points = [[NSMutableArray alloc] init];
[canvas addObject:lineDrawing];
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView: [touch view]];
LinePoint *linePoint = [[LinePoint alloc] init];
linePoint.x = point.x;
linePoint.y = point.y;
[lineDrawing.points addObject:linePoint];
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
}
}
#end
Can anyone spot my mistake?
Try visiting it all down to a texture, there's going to be a time when your points array gets too large to be drawn nicely
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// get the node space location of our touch
CGPoint location = [self getNodeSpaceTouchLocationFromUIEvent:event];
// draw with our current location and a random colour
[_canvas begin]; // our rendertexture instance
// do your drawing here
[_pen drawPenWithPosition:location andColour:_colour];
// end capturing the current pen state
[_canvas end];
}
Here's a simple example project written for iOSDevUK 2012 it uses GL_POINTS in Cocos2d v1 and is based on the approach we took when developing SketchShare

Resources