Draw smooth circle in iOS sprite kit - ios

I try to draw a single circle in my iOS SpriteKit project but the edges of the circle are not smooth. I would like to have a nicely drawn circle as I would draw it with Photoshop (anti-aliasing). I found several similar questions but the my problem remains.
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor whiteColor];
self.scaleMode = SKSceneScaleModeAspectFill;
self.size = CGSizeMake(640, 1136);
CGRect circle = CGRectMake(100.0, 100.0, 80.0, 80.0);
SKShapeNode *shapeNode = [[SKShapeNode alloc] init];
shapeNode.path = [UIBezierPath bezierPathWithOvalInRect:circle].CGPath;
shapeNode.fillColor = [SKColor redColor];
shapeNode.lineWidth = 0;
[self addChild:shapeNode];
}
return self;
}
What is the trick here?

The antialiasing is activated by default, but It only applies to the lines, not the fill.
Set the lineWidth to a number greater than 0 and try again.

Set the antialised property to YES.
shapeNode.antialiased = YES;
The rough edges are not visible when you use fill color.
//shapeNode.fillColor = [SKColor redColor];
shapeNode.strokeColor =[SKColor redColor];
shapeNode.antialiased = YES;
shapeNode.lineWidth = 1;
Now switch between YES and NO for antialisaed property.
You will notice the difference.

Related

ios Setting the RoundCorner does not work after override drawRect,but normal UIView is ok

I wrote an example about Wave Animation.The animation is ok,but I don't understand why the custom UIView needs to add "self.layer.masksToBounds = YES" to have the round Corner.
This is a custom UIView. I have rewritten its drawRect. If i don't set "masksToBounds" to YES, the round corner disappear.
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.frame = CGRectMake(10, 40, 300, 300);
self.layer.cornerRadius = 150;
self.backgroundColor = [UIColor greenColor];
self.layer.borderColor = [UIColor blueColor].CGColor;
self.layer.borderWidth = 2;
// self.layer.masksToBounds = YES; //if write this line,the round corner appear
self.x_move = 0;
self.y_move = 300;
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, 1);
CGContextSetFillColorWithColor(context, [UIColor grayColor].CGColor);
CGPathMoveToPoint(path, NULL, 0, 0);
for(float i=0; i<=300; i++){
float x=i;
float y = 5 * sin( 0.05 * x+ self.x_move) + self.y_move;
CGPathAddLineToPoint(path, nil, x, y);
}
CGPathAddLineToPoint(path, nil, 300, 0);
CGPathAddLineToPoint(path, nil, 0, 0);
CGContextAddPath(context, path);
CGContextFillPath(context);
CGContextDrawPath(context, kCGPathStroke);
CGPathRelease(path);
}
- (void)startAnimation {
if (self.waveTimer == nil) {
self.waveTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(refresh) userInfo:nil repeats:YES];
}
}
- (void)refresh {
self.x_move += 0.3;
self.y_move -= 0.2;
if(self.y_move - 100 < 0.00001){
[self.waveTimer invalidate];
}else{
[self setNeedsDisplay];
}
}
ViewController:
self.wave = [[waveAnimation alloc] init];
[self.wave startAnimation];
[self.view addSubview:self.wave];
This is a normal UIView. Its "masksToBunds" is NO, but its round corner shows normal. Compared with the examples above, why one should add, one does not need.
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 60, 100, 100)];
view.backgroundColor = [UIColor greenColor];
view.layer.cornerRadius = 50;
view.layer.borderWidth = 2;
view.layer.borderColor = [UIColor blackColor].CGColor;
[self.view addSubview:view];
The reason for this is from your override of drawRect:(CGRect)frame.
You are attempting to set the corner radius of the layer that is not the element you assigned the fill color to. The reason setting masksToBounds achieves the desired rounded corner is because here you are telling the view to mask all of it's layers to the bounds from your frame.
In your second example with the UIView the corner radius appears as expected because you have not adjusted it's layers.
I suggest either adopting setting masksToBounds or redraw the view's bounds a different way if you need masksToBounds = NO. Since you have the "squiggle" sin curve and you want rounded corners, perhaps just create a generic rounded rect (one with the desired corner radius) then merge that with the path you have already created with the sin wave.
Merge multiple CGPaths together to make one path
That link may help you merge your rounded view with your custom view to achieve the desired end result.
Otherwise.. probably best bet to just set this to yes.

adding a SpriteKit particle to existing app

Ive added a SpriteKit particle to my app - The skView has been added to an existing image view (myImageView). All works well but the skScenne cannot be set to clear colour to show myImage behind the emitter. You can change the colour but not to clear colour ?? `
SKView *skView = [[SKView alloc] initWithFrame:CGRectMake(0.0, 0.0, 768, 1024)];
[myImageView addSubview:skView];
SKScene *skScene = [SKScene sceneWithSize:skView.frame.size];
skScene.scaleMode = SKSceneScaleModeAspectFill;
skScene.backgroundColor = [UIColor clearColor];
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"MyParticle" ofType:#"sks"]];
emitter.position = CGPointMake(400,0);
[skScene addChild:emitter];
[skView presentScene:skScene];
[self.view addSubview:myImageView];
Unfortunately you can't make an SKView transparent in iOS 7. In iOS 8 it works by using:
skView.allowsTransparency = YES;
skScene.backgroundColor = [UIColor clearColor];
In iOS 7 you can add a large spriteView that is as big as your skView as background and set your myImage as texture.
I suggest you avoid setting the color of your SKScene to [UIColor clearColor] for performance reasons (use opaque views whenever possible). Instead, you can create an SKSpriteNode and add it to your scene as the background...
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"image"];
background.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
// Bottom/left of your image
background.anchorPoint = CGPointZero;
background.position = CGPointZero;
// Make sure the background is behind other nodes in the scene
background.zPosition = -100;
[self addChild:background];
Since the background is added to the SKScene, it is automatically removed/released when you transition to a new scene.

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.

Issues adding SKSpriteNode in SKScene initWithSize method in landscape orientation

When trying to add a SKSpriteNode at position 0,0 in my scenes initWithSize method, it is offset on the x axis. This is due to the positions still holding values for "portrait" orientation.
I've followed the advice in the following article by adding my scene in the "viewWillLayoutSubviews" method of the view controller, but I'm still experiencing an offset issue.
Here is the initWithSize method:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.userInteractionEnabled = YES;
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
self.physicsWorld.gravity = CGVectorMake(0.0f, -1.0f);
self.physicsWorld.contactDelegate = self;
SKSpriteNode *n1 = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(size.width,20)];
n1.position = CGPointMake(0, 0);
n1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:n1.size];
n1.physicsBody.contactTestBitMask = catCategory;
n1.physicsBody.dynamic = NO;
n1.physicsBody.categoryBitMask = floorCategory;
[self addChild:n1];
}
return self;
}
Following Ray Wanderlich's article I was able to achieve success by using an offset, and applying it to the position when adding the sprite, but there HAS to be a better way, right?
-(id)initWithSize:(CGSize)size {
float center = 240.0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
center = 284.0;
}
if (self = [super initWithSize:size]) {
self.userInteractionEnabled = YES;
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
self.physicsWorld.gravity = CGVectorMake(0.0f, -1.0f);
self.physicsWorld.contactDelegate = self;
SKSpriteNode *n1 = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(size.width,20)];
n1.position = CGPointMake(center, 0);
n1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:n1.size];
n1.physicsBody.contactTestBitMask = catCategory;
n1.physicsBody.dynamic = NO;
n1.physicsBody.categoryBitMask = floorCategory;
[self addChild:n1];
}
return self;
}
I'd like to find a comprehensive solution that doesn't rely on a hard-coded offset, and will work with landscape orientation regardless of if its an iPad or iPhone.
Thanks.

How to make something like this?

I'm not sure if I'm correct in wording it "modal sprite-kit scene" but what I'm trying to do is have a smaller sized scene appear over a scene when the game is complete. Attached is a screenshot to show what I mean:
The screenshot is from Flappy Bird where when the player dies, the small game-over scene pops up almost like a modal effect, and displays the user's final results. I was wondering how to go about creating this.
I tried calling this when the game was done:
[self.player runAction:death completion:^{
[self removeAllActions];
GameOverNode *gameOverNode = [[GameOverNode alloc] initWithScore:self.size];
gameOverNode.gameScene = self;
gameOverNode.position = CGPointMake(self.scene.size.width/2, -150);
[self addChild:gameOverNode];
[gameOverNode runAction:[SKAction moveToY:self.scene.size.height/2 duration:0.6]];
And this was the code for the gameOverNode in the gameOverScene.m's file:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.userInteractionEnabled = YES;
self.zPosition = 20;
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithColor:[UIColor blackColor] size:CGSizeMake(280*DoubleIfIpad, 300*DoubleIfIpad)];
bg.alpha = 0.55;
But the node only shows up in the bottom left hand corner of the screen, as opposed to in the middle like I want it too.
What am I doing wrong? How can I make the small gameover node pop up and sit in the middle of the scene.
You simply can't. According to Apple Docs and multiple StackOverflow posts you cannot be running two SKScene's at the same time. To recreate the effect you want, you can create an SKShapeNode with border and line color similar to those you want, and place objects like score inside, or you can create an image for the screen that pops up and just add SKLabelNodes to the scene that show the high score and current score. I'm attaching a picture of how I created an end "view" for my app, Average Guy.
I made the popup screen using an SKShapeNode like this:
SKShapeNode *optionsMenu = [SKShapeNode node];
CGRect tempRect = CGRectMake(SELF_WIDTH/2, SELF_HEIGHT/2, SELF_WIDTH-20, (finalScoreLabel.position.y+(finalScoreLabel.frame.size.height/2)) - (mainMenuButton.position.y-(mainMenuButton.frame.size.height/2)) + 20);
CGPathRef tempPath = CGPathCreateWithRoundedRect(CGRectMake(tempRect.origin.x - tempRect.size.width/2, tempRect.origin.y - tempRect.size.height/2, tempRect.size.width, tempRect.size.height), 5, 5, Nil);
optionsMenu.path = tempPath;
optionsMenu.fillColor = [SKColor grayColor];
optionsMenu.strokeColor = [SKColor blueColor];
optionsMenu.zPosition = 30;
optionsMenu.glowWidth = 2.0f;
CGPathRelease(tempPath);
Of course, don't mind the hectic CGRect implementation, I wanted to make everything relative to fit all of the screen sizes so I didn't hardcode any of the positions. As for adding the score and the buttons, I did this:
SKLabelNode *restartButton = [SKLabelNode labelNodeWithFontNamed:#"00 Starmap Truetype"];
restartButton.text = #"Play Again";
restartButton.zPosition = 31;
restartButton.fontColor = [SKColor blueColor];
restartButton.fontSize = 30;
restartButton.position = CGPointMake(HALF_WIDTH, HALF_HEIGHT-50);
restartButton.name = #"restart_button";
SKLabelNode *restartButtonShadow = restartButton.copy;
restartButtonShadow.position = CGPointMake(restartButton.position.x+1, restartButton.position.y-1);
restartButtonShadow.color = [SKColor colorWithRed:0 green:0 blue:.5 alpha:.1];
SKLabelNode *mainMenuButton = [SKLabelNode labelNodeWithFontNamed:#"00 Starmap Truetype"];
mainMenuButton.text = #"Main Menu";
mainMenuButton.zPosition = 31;
mainMenuButton.fontColor = [SKColor blueColor];
mainMenuButton.fontSize = 30;
mainMenuButton.position = CGPointMake(HALF_WIDTH, HALF_HEIGHT - 85);
mainMenuButton.name = #"main_menu_button";

Resources