Changing the image of a SKSprite to an SKShapeNode - objective-c

Sprite Kit, Xcode.
I need to find a way to change a sprites image within the program itself. I know how to create jpg files and make them into the sprite image...
But for this program, I need to draw circles/polygons (which may change inside the program) using SKShapeNode, and then transferring this to the SKSpriteNode's image.
Let's say I have declared:
SKSpriteNode *sprite;
SKShapeNode *image;
How would I do this with these variables?
Thanks!
EDIT: I mean texture when I say image.

If I understand your question correctly, you can achieve what you're after using the textureFromNode method on SKView.
In your SKScene:
-(void)didMoveToView:(SKView *)view {
SKShapeNode *shape = [SKShapeNode shapeNodeWithCircleOfRadius:100];
shape.fillColor = [UIColor blueColor];
shape.position = CGPointMake(self.size.width * 0.25, self.size.height * 0.5);
[self addChild:shape];
SKTexture *shapeTexture = [view textureFromNode:shape];
SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture: shapeTexture];
sprite.position = CGPointMake(self.size.width * 0.75, self.size.height * 0.5);
[self addChild:sprite];
}
Hope that helps!

You cannot change a SKSpriteNode image once you assign it. To do what you want, you need to create a SKSpriteNode using a texture.
- (instancetype)initWithTexture:(SKTexture *)texture
To change a SKSpriteNode's texture you assign a new texture using its texture property. You can also do this using an image converted to a texture like this:
myNode.texture = [SKTexture textureWithImageNamed:#"imageName"];
As for a SKShapeNode, you cannot assign an image. Only a path, rect, circle, ellipse or points. Look at the SKShapeNode class docs section Creating a Shape Path for more info.

Related

How to draw curve with glow in CGContext - something like SKShapeNode with glowWidth

I am rewriting some of my graphics drawing code from SKShapeNodes to CGContext/CALayers. I am trying to draw a curve with glow in CGContext. This is what I used to have in SpriteKit:
CGPathRef path = …(some path)
SKShapeNode *node = [SKShapeNode node];
node.path = path;
node.glowWidth = 60;
After adding it to the scene with dark-grey background, the result was as follows:
Is it possible to draw line with such glow using CGContext but without using CIFilters? Normally I will be drawing over an non-blank context background, so I prefer not to use CIFilters after the line was drawn.
I have already tried the "shadow" solution, but the results are far from perfect:
UIGraphicsBeginImageContextWithOptions(frame.size, NO, 1);
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat glowWidth = 60.0;
CGContextSetShadowWithColor(context, CGSizeMake(0.0, 0.0), glowWidth, [UIColor whiteColor].CGColor);
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextStrokePath(context);
Result (the shadow is hardly visible):
Please let me know if you have some ideas.
You can create a SKEffectNode which allows you to apply Core Image filters to all of its children. In other words, you can create a SKEffectNode then add a flower sprite as a child and the SKEffectNode would apply the Core Image effect to the flower sprite.
For more detailed information, please see the SKEffectNode Class Reference.

Xcode making a pdf, trying to round corners

I am making a pdf in an iPad app. Now i can make the pdf however want to add a picture with a rounded corner border. For example to achieve the effect i want on the border on a simple view item i use the following code.
self.SaveButtonProp.layer.cornerRadius=8.0f;
self.SaveButtonProp.layer.masksToBounds=YES;
self.SaveButtonProp.layer.borderColor=[[UIColor blackColor]CGColor];
self.SaveButtonProp.layer.borderWidth= 1.0f;
With the pdf i am using the following method to add the picture with the border to the pdf.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
UIImage * demoImage = [UIImage imageWithData : Image];
UIColor *borderColor = [UIColor blackColor];
CGRect rectFrame = CGRectMake(20, 125, 200, 200);
[demoImage drawInRect:rectFrame];
CGContextSetStrokeColorWithColor(currentContext, borderColor.CGColor);
CGContextSetLineWidth(currentContext, 2);
CGContextStrokeRect(currentContext, rectFrame);
How do i round the corners?
Thanks
While drawing you can set clipping masks. For example, it's relatively easy to create a Bezier path with the shape of a rounded rectangle and apply that as clipping mask to your graphics context. Everything subsequently drawn will be clipped.
If you want remove the clipping mask later (for example because you have an image with rounded corners but follow that by other elements) you'll have to save the graphic state first, then apply your clipping mask and restore the graphics state when you're done with your rounded corners.
You can see actual code that comes pretty close to what I think you need here:
UIImage with rounded corners
You can use a method to get any UIView/UIImageView to PDF NSData:
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
NSData *data = [self makePDFfromView:imageView];
Method:
- (NSData *)makePDFfromView:(UIView *)view
{
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, view.bounds, nil);
UIGraphicsBeginPDFPage();
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
[view.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
return pdfData;
}
Maybe you can change or use this code to help you with your problem.

Is it possible to use a circle (SKShapeNode) as a mask in Sprite Kit?

I'm trying to create a circular mask in a Sprite Kit project. I create the circle like this (positioning it at the centre of the screen):
SKCropNode *cropNode = [[SKCropNode alloc] init];
SKShapeNode *circleMask = [[SKShapeNode alloc ]init];
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, NULL, CGRectGetMidX(self.frame), CGRectGetMidY(self.frame), 50, 0, M_PI*2, YES);
circleMask.path = circle;
circleMask.lineWidth = 0;
circleMask.fillColor = [SKColor blueColor];
circleMask.name=#"circleMask";
and further down the code, I set it as the mask for the cropNode:
[cropNode setMaskNode:circleMask];
... but instead of the content showing inside a circle, the mask appears as a square.
Is it possible to use a SKShapeNode as a mask, or do I need to use an image?
After much swearing, scouring the web, and experimentation in Xcode, I have a really hacky fix.
Keep in mind that this is a really nasty hack - but you can blame that on Sprite Kit's implementation of SKShapeNode. Adding a fill to a path causes the following:
adds an extra node to your scene
the path becomes unmaskable - it appears 'over' the mask
makes any non-SKSpriteNode sibling nodes unmaskable (e.g. SKLabelNodes)
Not an ideal state of affairs.
Inspired by Tony Chamblee's progress timer, the 'fix' is to dispense with the fill altogether, and just use the stroke of the path:
SKCropNode *cropNode = [[SKCropNode alloc] init];
SKShapeNode *circleMask = [[SKShapeNode alloc ]init];
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, NULL, CGRectGetMidX(self.frame), CGRectGetMidY(self.frame), 50, 0, M_PI*2, YES); // replace 50 with HALF the desired radius of the circle
circleMask.path = circle;
circleMask.lineWidth = 100; // replace 100 with DOUBLE the desired radius of the circle
circleMask.strokeColor = [SKColor whiteColor];
circleMask.name=#"circleMask";
[cropNode setMaskNode:circleMask];
As commented, you need to set the radius to half of what you'd normally have, and the line width to double the radius.
Hopefully Apple will look at this in future; for now, this hack is the best solution I've found (other than using an image, which doesn't really work if your mask needs to be dynamic).
Since I cannot use a SKShapeNode as mask I decided to convert it to a SKSpriteNode.
Here it is my Swift code:
let shape : SKShapeNode = ... // create your SKShapeNode
var view : SKView = ... // you can get this from GameViewController
let texture = view.textureFromNode(shape)
let sprite = SKSpriteNode(texture: texture)
sprite.position = ...
cropNode.mask = sprite
And it does work :)
Yes, it is impossible to use fill-colored shapenode in current Sprite-Kit realization. It's a bug, I think.
But!
You always can render the shape to texture and use it as mask!
For ex, let edge is SKShapeNode created before.
First, render it to texture before adding to view (in this case it will clean from another nodes)
SKTexture *Mask = [self.view textureFromNode:edge];
[self addChild:edge]; //if you need physics borders
Second,
SKCropNode *cropNode = [[SKCropNode alloc] init];
[cropNode setMaskNode:[SKSpriteNode spriteNodeWithTexture:Mask]];
[cropNode addChild: [SKSpriteNode spriteNodeWithTexture:rocksTiles size:CGSizeMake(w,h)]];
cropNode.position = CGPointMake(Mask.size.width/2,Mask.size.height/2);
//note, anchorpoint of shape in 0,0, but rendered texture is in 0.5,0.5, so we need to dispose it
[self addChild:cropNode];
There is a slightly awkward solution to this issue that I believe is slightly less nasty than any of the mooted hacks around this issue. Simply wrap your SKShapeNode in an SKNode. I haven't tested what kind of performance hit this might cause, but given that SKNode is non-drawing, I'm hoping it won't be too significant.
i.e.
let background = SKSpriteNode(imageNamed: "Background")
let maskNode = SKShapeNode(path: MyPath)
let wrapperNode = SKNode()
let cropNode = SKCropNode()
wrapperNode.addChild(maskNode)
cropNode.maskNode = wrapperNode
cropNode.addChild(background)
Setting alpha of mask node to .0 does the trick:
circleMask.alpha = .0;
EDIT:
Seems to work for Mac OS only.

Set size of UIView based on content size

Is it possible to draw something on a UIView, then, after the drawing is completed, resize the view to be the same size as the thing that was previously drawn?
For example, if I draw a circle on a UIView, I would like to crop the UIView to the dimensions of the circle that I just drew on the view.
UPDATE
I am looking into using a CAShapeLayer as a possible solution. Does anyone know how to convert a UIBezierPath to a CAShapeLayer then set the position of the CAShapeLayer?
I have tried:
shapeLayer.path = bezierPath.CGPath;
shapeLayer.position = CGPointMake(0, 0);
but this does not work.
Yes you can do that. Have a look at this example that will answer both your questions.
First of all you need to add a UIView called myView and attach it to an IBOutlet ivar.
Define this global values for demonstration purposes:
#define POSITION CGPointMake(50.0,50.0)
#define SIZE CGSizeMake(100.0,100.0)
Declare two methods, one that will draw a shape in myView and another one that will resize the view to adapt it to the drawn shape:
-(void) circle
{
CAShapeLayer *layerData = [CAShapeLayer layer];
layerData.fillColor = [UIColor greenColor].CGColor;
UIBezierPath * dataPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0, 0.0, SIZE.width, SIZE.height)];
layerData.path = dataPath.CGPath;
layerData.position = CGPointMake(POSITION.x, POSITION.y);
[myView.layer addSublayer:layerData];
}
-(void) resize
{
((CAShapeLayer *)[myView.layer.sublayers objectAtIndex:0]).position = CGPointMake(0.0, 0.0);
myView.frame = CGRectMake(POSITION.x + myView.frame.origin.x , POSITION.y + myView.frame.origin.y, SIZE.width, SIZE.height);
}
Finally, in viewWillAppear: call both methods:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self circle];
[self resize];
}
You can run the same code calling only circle and calling both methods. In both cases the drawn circle will be at the same exact position but in the second case myView has been resized to have the same size as the drawn shape.
Hope it helps.

Cocos2d: Solid color rectangular sprite with border

Is there a way one could add a border around a plain color sprite without having to create another sprite?
Here the code to create the plain colored rectangular sprite. Thanks goes to Mat Cegiela for sharing it on SO (https://stackoverflow.com/a/14609459/1097106).:
-(CCSprite*)blankSpriteWithSize:(CGSize)size
{
CCSprite *sprite = [CCSprite node];
GLubyte *buffer = malloc(sizeof(GLubyte)*4);
for (int i=0;i<4;i++) {buffer[i]=255;}
CCTexture2D *tex = [[CCTexture2D alloc] initWithData:buffer pixelFormat:kCCTexture2DPixelFormat_RGB5A1 pixelsWide:1 pixelsHigh:1 contentSize:size];
[sprite setTexture:tex];
[sprite setTextureRect:CGRectMake(0, 0, size.width, size.height)];
free(buffer);
return sprite;
}
This works great as it doesn't use any image files.
To add a border one could create two plain colored sprites using the above method, one slightly smaller and add the smaller one as a child to the parent sprite, but is there a way one could do it more elegantly?
I tried (https://stackoverflow.com/a/10903183/1097106) -- thanks Morion for sharing it:
CGSize selfSize = [self contentSize];
float selfHeight = selfSize.height;
float selfWidth = selfSize.width;
CGPoint vertices[4] = {ccp(0.f, 0.f), ccp(0.f, selfHeight), ccp(selfWidth, selfHeight), ccp(selfWidth, 0.f)};
ccDrawPoly(vertices, 4, YES);
in the sprite's -(void)draw without any positive result. It doesn't show the sprite at all when adding this code to -(void)draw.
Edit:
Turns out the problem was a missing [super draw]; inside -(void)draw. Adding it will make it work.
Here the implementation of -(void)draw that will allow for borders around a plain color rectangular sprite (it also defines a custom color):
-(void)draw
{
[super draw];
CGSize selfSize = [self contentSize];
float selfHeight = selfSize.height;
float selfWidth = selfSize.width;
CGPoint vertices[4] = {ccp(0.f, 0.f), ccp(0.f, selfHeight), ccp(selfWidth, selfHeight), ccp(selfWidth, 0.f)};
ccDrawColor4B(120, 120, 120, 255);
ccDrawPoly(vertices, 4, YES);
}