Refactor Help> For loop mapping CGPoints, and SKSpriteNodes - ios

Hi I am new to programming and I have been learning SpriteKit. (although this is a general purpose programming question.) I have hard coded values, and reused the same image over and over. A coding no no from what I understand. Can someone help me learn this by refactoring with loops I assume.
I am creating a row of 20 dashed empty card spots and placing 4 at a time to a screen size centered at the bottom of the screen. I have added these empty spots (just one card image used over and over) as a "child" to the rack.
Side Note: Since this is a general programming question if you are not familiar with SpriteKit my CGPoints are mapped to the racks coordinate system and not to the screen coordinates... if that confused you..
I have attempted to create the refactored code at the bottom but I am stuck because the node.positions are tied to SKSpriteNodes and looping that seems to be puzzling me.
Here is what I have:
-(SKSpriteNode*)createBottomRack
{
self.rack = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(10240.0, 200)];
self.rack.position = CGPointMake(0.0,150.0);
self.rack.zPosition = 0;
self.rack.name = #"bottomRack";
// Screen 1
SKSpriteNode *number0 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number1 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number2 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number3 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
// Screen 1 positions
number0.position = CGPointMake(212, 0.0);
number1.position = CGPointMake(412, 0.0);
number2.position = CGPointMake(612, 0.0);
number3.position = CGPointMake(812, 0.0);
// Screen 2
SKSpriteNode *number4 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number5 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number6 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number7 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
// Screen 2 positions
number4.position =CGPointMake(1236.0, 0.0);
number5.position =CGPointMake(1436.0, 0.0);
number6.position =CGPointMake(1636.0, 0.0);
number7.position =CGPointMake(1836.0, 0.0);
// Screen 3
SKSpriteNode *number8 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number9 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number10 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number11 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
// Screen 3 positions
number8.position =CGPointMake(2260.0, 0.0);
number9.position =CGPointMake(2460.0, 0.0);
number10.position =CGPointMake(2660.0, 0.0);
number11.position =CGPointMake(2860.0, 0.0);
// Screen 4
SKSpriteNode *number12 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number13 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number14 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number15 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
// Screen 4 positions
number12.position =CGPointMake(3284.0, 0.0);
number13.position =CGPointMake(3484.0, 0.0);
number14.position =CGPointMake(3684.0, 0.0);
number15.position =CGPointMake(3884.0, 0.0);
// Screen 5
SKSpriteNode *number16 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number17 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number18 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
SKSpriteNode *number19 = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
// Screen 5 positions
number16.position =CGPointMake(4308.0, 0.0);
number17.position =CGPointMake(4508.0, 0.0);
number18.position =CGPointMake(4708.0, 0.0);
number19.position =CGPointMake(4908.0, 0.0);
// Add dashedCards in position on screen
[self.rack addChild:number0];
[self.rack addChild:number1];
[self.rack addChild:number2];
[self.rack addChild:number3];
[self.rack addChild:number4];
[self.rack addChild:number5];
[self.rack addChild:number6];
[self.rack addChild:number7];
[self.rack addChild:number8];
[self.rack addChild:number9];
[self.rack addChild:number10];
[self.rack addChild:number11];
[self.rack addChild:number12];
[self.rack addChild:number13];
[self.rack addChild:number14];
[self.rack addChild:number15];
[self.rack addChild:number16];
[self.rack addChild:number17];
[self.rack addChild:number18];
[self.rack addChild:number19];
return self.rack;
}
Here is where I currently am in refactoring...
NSMutableArray *dashesArray = [NSMutableArray arrayWithCapacity:20];
for (int i = 0; i<=20; i++) {
SKSpriteNode *dash = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
[dashesArray addObject:dash];
}
This is it because if I make a loop holding generically numbered SKSpriteNodes I don't see how I map those to the positions....?

Calculating the x coordinate for the position of each node is pretty straightforward:
x starts at 212
Subsequent nodes are 200px to the right of the previous node
Every 5th node is 424px (instead of 200px) to the right of the previous node
The code for that looks like this:
- (SKSpriteNode *)createBottomRack
{
self.rack = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(10240.0, 200)];
self.rack.position = CGPointMake(0.0,150.0);
self.rack.zPosition = 0;
self.rack.name = #"bottomRack";
float x = 212.0, y = 0.0;
for (NSUInteger i = 0; i < 20; i++) {
SKSpriteNode *dash = [SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
dash.position = CGPointMake(x, y);
[self.rack addChild:dash];
// Calculate the next x coordinate
x += (i % 4 == 3) ? 424.0 : 200;
}
return self.rack;
}

Since you are new to programming, I suggest you spend some time learning how arrays work, and how they are used:
http://en.wikipedia.org/wiki/Array_data_structure
and their iOS implementation:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
For starters, you can declare two arrays, one containing the SKSpriteNodes, and the other containing the position points:
//Declare these in #interface
#property (nonatomic) NSMutableArray *sprites;
#property (nonatomic) NSMutableArray *points;
//This will contain the SKSpriteNodes
self.sprites =[[NSMutableArray alloc] init];
//This will contain the points, we initialise it with the hard-coded values.
//You have to wrap the values in an object type of NSValue of the array to accept it
self.points = [[NSMutableArray alloc] initWithObjects:[NSValue valueWithCGPoint:CGPointMake(212, 0.0)],
[NSValue valueWithCGPoint:CGPointMake(412, 0.0)],
[NSValue valueWithCGPoint:CGPointMake(612, 0.0)],
[NSValue valueWithCGPoint:CGPointMake(812, 0.0)],
//Put the rest of the values here... for the moment it will do
nil];
//Now for each declared point, we create a SKSpriteNode, insert it into the array and add it to "rack"
//for will loop over eacg point you have defined.
for (NSValue *point in self.points){
//This will be a temporary object we create to add it into the array.
SKSpriteNode *node =[SKSpriteNode spriteNodeWithImageNamed:#"dashedCard"];
node.position = [point CGPointValue];
[self.sprites addObject:node];
[self.rack addChild:node];
}
As you get more experience you will find many other ways to improve the code, but take your time studying the code and basic data structures. Good luck!

Related

SKSpriteNode resize and update SKPhysicsJointPin anchor point

I'm trying to learn SpriteKit. I have a balloon (SKSpriteNode) that after running a SKAction for resizing, the balloon will change its size. the balloon has a string attached to it. the string is just multiple SKSpriteNode attached with SKPhysicsJointPin with each other. I've inverted gravity so the balloon with attached string got upward and after 3sec timeout it gets smaller. so far it works except for the start attached point of the string is not moved to the correct position of balloon. how to solve this issue?
so i'm adding only the necessary part here otherwise the post gets too bulky. so again see balloon has skaction which resizes the balloon, and how to move the string to correct position while resizing?
Balloon is subclassed SKSpriteNode
Balloon *balloon = [Balloon spriteNodeWithImageNamed:#"balloon.png"];
[balloon setPosition:CGPointMake(200, CGRectGetMinY(scene.frame))];
[balloon setName:#"balloon"];
[balloon setUserInteractionEnabled:NO];
[balloon setPhysicsBody:[SKPhysicsBody bodyWithCircleOfRadius:55]];
balloon.physicsBody.dynamic = YES;
[balloon.physicsBody setRestitution:0.4f];
[balloon.physicsBody setFriction:0.0f];
CGPoint attachPoint = CGPointMake(200, CGRectGetMinY(scene.frame));
//create the rope
SKRopeNode *rope = [SKRopeNode new];
rope.name = #"ropeParent";
[scene addChild:rope];
[rope setAttachmentPoint:attachPoint toNode:balloon];
rope.ropeLength = 6;
[rope runAction:[SKAction sequence:#[[SKAction waitForDuration:4.5], [SKAction removeFromParent]]]];
[balloon runAction:[SKAction sequence:#[[SKAction waitForDuration:4], [SKAction scaleTo:balloon.yScale/2 duration:0.5], [SKAction removeFromParent]]]];
this how i create rope method for creating the length
SKSpriteNode *firstPart = [SKSpriteNode spriteNodeWithImageNamed:#"rope_part.png"];
firstPart.position = _positionOnStartNode;
firstPart.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:firstPart.size];
firstPart.physicsBody.allowsRotation = NO;
for (int i=1; i<ropeLength; i++) {
SKSpriteNode *ropePart = [firstPart copy];
ropePart.position = CGPointMake(firstPart.position.x, firstPart.position.y - (i*ropePart.size.height));
ropePart.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ropePart.size];
ropePart.physicsBody.allowsRotation = YES;
[self.scene addChild:ropePart];
}
and this is for adding joints for the rope before
SKNode *nodeA = balloon;
SKSpriteNode *nodeB = [_ropeParts objectAtIndex:0];
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA: nodeA.physicsBody
bodyB: nodeB.physicsBody
anchor: _positionOnStartNode];
for (int i=1; i<_ropeParts.count; i++) {
SKSpriteNode *nodeA = [_ropeParts objectAtIndex:i-1];
SKSpriteNode *nodeB = [_ropeParts objectAtIndex:i];
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA: nodeA.physicsBody
bodyB: nodeB.physicsBody
anchor: CGPointMake(CGRectGetMidX(nodeA.frame),
CGRectGetMinY(nodeA.frame))];
[self.scene.physicsWorld addJoint:joint];
}

How to make SKSpriteNode rotate in the direction of touching?

Could you please help me with the problem I have?
define CC_RADIANS_TO_DEGREES(ANGLE) ((ANGLE) * 57.29577951f) // PI
-(void)didMoveToView:(SKView )view {
/ Setup your scene here */
_skyColor = [SKColor colorWithRed:113.0/255.0 green:197.0/255.0 blue:207.0/255.0 alpha:1.0];
[self setBackgroundColor:_skyColor];
//Setup the array to hold the walking frames
NSMutableArray *padlingFrames = [NSMutableArray array];
//Load the TextureAtlas for the bear
SKTextureAtlas *kajakAnimatedAtlas = [SKTextureAtlas atlasNamed:#"KajakImages"];
//Load the animation frames from the TextureAtlas
long numImages = kajakAnimatedAtlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"kajak_0%d", i];
SKTexture *temp = [kajakAnimatedAtlas textureNamed:textureName];
[padlingFrames addObject:temp];
}
_kajakPadlingFrames = padlingFrames;
//Create kajak sprite, setup position in middle of the screen, and add to Scene
SKTexture *temp = _kajakPadlingFrames[0];
_kajak = [SKSpriteNode spriteNodeWithTexture:temp];
_kajak.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[_kajak setScale:0.2];
[self addChild:_kajak];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent )event {
/ Called when a touch begins */
CGPoint touchLocation = [[touches anyObject] locationInNode:self];
[self updateRotate:touchLocation];
}
(void)updateRotate:(CGPoint)touchLocation
{
float deltaX = touchLocation.x - _kajak.position.x;
float deltaY = touchLocation.y - _kajak.position.y;
float angle = atan2f(deltaY, deltaX);
SKAction *action = [SKAction rotateByAngle: CC_RADIANS_TO_DEGREES(angle) duration: 5.0];
[_kajak runAction:action];
}
The kayak is not rotate in the direction the I touch. It is some thing I missing? Please help me. Tank you in advance
The zero angle for SKSpriteNodes is pointing up (as opposed to standard trig functions that treat zero angle as pointing right). I had to create a special angle calculation function to take that into account in my game (see below, in Swift).
You may also need to make sure that the coordinate system you are using for the positions is the same for the sprite and the touch location. From your code (i'm not very comfortable with Obj-C) I believe they are both base on the scene's bounds but I'm not certain.
// angle between two points
// ------------------------
// SpriteKit's zero angle is pointing up
// atan2 returns an angle pointing right
// we're usung sprite kit's conventions so removing 90° from the result
//
func spriteAngleFrom(start:CGPoint, to finish:CGPoint) -> CGFloat
{
let deltaX = finish.x - start.x
let deltaY = finish.y - start.y
return atan2(deltaY,deltaX) - CGFloat.pi/2
}
You also need to use rotateToAngle, not rotateByAngle. And I would suggest you also specify shortestUnitArc : true.

How can I apply physics to a SKShapeNode in Sprite Kit?

In the following example, there are three things on the screen:
ball (a SKShapeNode)
spriteContainer (a SKSpriteNode that contains ball2, a SKShapeNode)
box (a SKSpriteNode)
Why does ball fall out of view? Does a SKShapeNode need to be inside a SKSpriteNode to have physics properly applied to it?
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKColor * warmRed = [SKColor colorWithRed:0.99 green:0.41 blue:0.25 alpha:1.0];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.backgroundColor = warmRed;
//falls out of view
SKShapeNode * ball = [[SKShapeNode alloc] init];
CGMutablePathRef ballPath = CGPathCreateMutable();
CGPathAddArc(ballPath, NULL, size.width-40, self.size.height/2, 20, 0, M_PI*2, YES);
ball.path = ballPath;
ball.lineWidth = 2;
ball.fillColor = warmRed;
ball.strokeColor = [SKColor whiteColor];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:20];
[self addChild:ball];
//lands on bottom of screen
SKShapeNode * ball2 = [[SKShapeNode alloc] init];
CGMutablePathRef ball2Path = CGPathCreateMutable();
CGPathAddArc(ball2Path, NULL, 0, 0, 20, 0, M_PI*2, YES);
ball2.path = ball2Path;
ball2.lineWidth = 2;
ball2.fillColor = warmRed;
ball2.strokeColor = [SKColor whiteColor];
CGSize spriteContainerSize = CGSizeMake(40,40);
CGPoint spriteContainerPosition = CGPointMake(size.width/2, size.height/2);
SKSpriteNode * spriteContainer = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:spriteContainerSize];
spriteContainer.position = spriteContainerPosition;
spriteContainer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteContainerSize];
[spriteContainer addChild:ball2];
[self addChild:spriteContainer];
//lands on bottom of screen
CGSize boxSize = CGSizeMake(40,40);
CGPoint boxPosition = CGPointMake(boxSize.width, size.height/2);
SKSpriteNode * box = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:boxSize];
box.position = boxPosition;
box.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:boxSize];
[self addChild:box];
}
return self;
}
Screenshot:
https://dl.dropboxusercontent.com/u/164157126/example.jpg
Note that you don't set a position for the node which falls off the screen, but set a position for the other two nodes you create.
The default position of a node is 0,0. Your ball will appear at the bottom left of the scene, and since it is over the edge body you defined, will fall off immediately.
Set the ball's position appropriately so that it does not intersect the edge of the screen, and the ball will not fall off.

Spritekit scene anchorpoint affecting child node positioning

I have created my SKScene subclass which sets the anchorpoint and then adds one SKSpriteNode for the world, the world has multiple SKSpriteNodes for the obstacles, player etc. I am also centering on the
The problem I am having is that as I have set the anchorpoint of the scene to (0.5, 0.5), the position of any child node that I add to the world starts at the center of the world. How do I fix the postion of the nodes so that position = (0,0) will be at the bottom left of the world node and any child nodes added to it, instead of the center?
#implementation LevelScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
NSLog(#"width:%f height:%f", self.view.bounds.size.width, self.view.bounds.size.height);
// set the physics body
self.physicsWorld.gravity = CGVectorMake(0,-5);
self.physicsWorld.contactDelegate = self;
self.anchorPoint = CGPointMake(0.5, 0.5);
NSMutableDictionary *plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"LevelScene" ofType:#"plist"]];
NSString *backgroundImage = [plistDict objectForKey:#"background"];
// add a node that holds the background
background = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:backgroundImage] size:CGSizeMake(1024, 768)];
background.position = CGPointMake(0, 0);
[self addChild:background];
world = [SKSpriteNode spriteNodeWithColor:[SKColor brownColor] size:CGSizeMake(1024, 768)];
world.position = CGPointMake(0, 0); // this should be bottom-left
world.size = CGSizeMake(1024, 768);
world.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:world.frame];
world.physicsBody.categoryBitMask = worldCategory;
[self addChild:world];
// load in the game tiles (these are non-dynamic tiles the player can use)
[self loadInTiles];
// add in game object to the world skspritenode - this just creates a subclass of skspritenode and sets position to 0,0
[self addGameObject:CGPointMake(0, 0)];
...
}
// ...setup functions, input handling, etc
-(void)didSimulatePhysics {
// setup the player to move depending on their direction
[player updatePosition];
[self centreOnNode:player];
}
-(void)centreOnNode: (SKSpriteNode *)node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
CGFloat x = node.parent.position.x - cameraPositionInScene.x;
CGFloat y = node.parent.position.y - cameraPositionInScene.y;
NSLog(#"camera x:%f y:%f", x, y);
NSLog(#"world frame origin x:%f y:%f", world.frame.origin.x, world.frame.origin.y);
node.parent.position = CGPointMake(x, y);
}
If you want to set the world sprite's origin to the bottom left side, just set it's anchor point.
world.anchorPoint = CGPointMake(0,0);
With this, the world sprite's coordinate system will be just like that of the scene's default.
Make sure to remove the line:
self.anchorPoint = CGPointMake(0.5, 0.5);
Replace this row:
world.position = CGPointMake(0, 0);
by this:
world.position = CGPointMake(-CGRectGetMidX(self.frame), -CGRectGetMidY(self.frame));
(0,0) is the center of the scene, since you set anchor point of the SKScene to (0.5,0.5)

How do you scale SKSpirteNode without anti aliasing

I am trying to scale an SKSpriteNode object without smoothing/anti-aliasing (I'm using pixel-art so it looks better pixelated).
Is there a property I need to set to do this? This is the code I am using to render the sprite:
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"objects"];
SKTexture *f1 = [atlas textureNamed:#"hero_1.png"];
SKSpriteNode *hero = [SKSpriteNode spriteNodeWithTexture:f1];
[self addChild: hero];
hero.scale = 6.0f;
The image is scaled correctly but blurry/smoothed out. This is hero_1.png .
Try,
SKTexture *texture = [SKTexture textureWithImageNamed:#"Fak4o"];
texture.filteringMode = SKTextureFilteringNearest;
SKSpriteNode *newHero = [SKSpriteNode spriteNodeWithTexture:texture];
newHero.position = CGPointMake(200, 200);
[newHero setScale:50];
[self addChild:newHero];
SKTextureFilteringNearest
Each pixel is drawn using the nearest point in the texture. This mode
is faster, but the results are often pixelated.
Swift:
sprite.texture!.filteringMode = .Nearest
Just a note, if you create a SKSpriteNode instead of SKTexture, you can set the SKTextureFilteringNearest like in the following example:
SKSpriteNode *asteroid = [SKSpriteNode spriteNodeWithImageNamed:#"o1"];
asteroid.size = CGSizeMake(36, 24);
asteroid.position = CGPointMake(startX, startY);
asteroid.name = #"obstacle";
// >>>
asteroid.texture.filteringMode = SKTextureFilteringNearest;
// <<<
[self addChild:asteroid];
try this:
hero.xscale = 6.0f;
hero.yscale = 6.0f;

Resources