I want create a slingshot like Angry Bird and my question is how can I create a elastic which follow my sprite (a bird for example) when I load the launched and which hang to my slingshot ?
How can I do that with SpriteKit ?
Thank you for your help.
This kind of logic coding you really need to figure out on your own. I will give you something to work with. This is from my personal prototype:
-(void)fireRocket
{
if (timesRan <= 0) {
timesRan++;
}
else
{
[self enumerateChildNodesWithName:#"rocket" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *rocket = (SKSpriteNode *) node;
[rocket removeFromParent];
[rocket removeAllActions];
}];
}
SKSpriteNode * rocket = [SKSpriteNode spriteNodeWithImageNamed:#"rocket-310663_150"];
rocket.size = CGSizeMake(30, 32);
rocket.name = #"rocket";
rocket.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
rocket.position = CGPointMake(50, 185);
//CGFloat angle = M_PI_4;
CGFloat magnitude = 26;
CGFloat angle = [self angleOfRocketFire];
//CGFloat angle = M_PI_4;
[self addChild:rocket];
[rocket.physicsBody applyImpulse:CGVectorMake(magnitude*cos(angle),
magnitude*sin(angle))];
}
-(CGFloat)angleOfRocketFire
{
__block CGFloat angle;
__block CGPoint handlePosition;
[self enumerateChildNodesWithName:#"handle" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *handle = (SKSpriteNode *) node;
CGFloat xCoord = handle.position.x + (handle.size.width / 2);
CGFloat yCoord = handle.position.y + (handle.size.height / 2);
handlePosition = CGPointMake(xCoord, yCoord);
}];
[self enumerateChildNodesWithName:#"rocketLauncher" usingBlock: ^(SKNode *node, BOOL *stop) {
SKSpriteNode *rocketLauncher = (SKSpriteNode *) node;
CGFloat xCoord = rocketLauncher.position.x + (rocketLauncher.size.width / 2);
CGFloat yCoord = rocketLauncher.position.y + (rocketLauncher.size.height / 2);
CGPoint launcherPosition = CGPointMake(xCoord, yCoord);
CGFloat slope = (handlePosition.y - launcherPosition.y) / (handlePosition.x - launcherPosition.x);
//angle = atan(slope);
angle = atan(slope);
NSLog(#"%f", slope);
}];
return angle;
}
Related
I'm not much of an Objective-C guy I primarily use swift and I need some help from this error
Variable 'indexOfLargestGap' may be uninitialized when used here
and
Variable 'indexOfSmallestGap' may be uninitialized when used here
I have imported the code from Github and created a bridging header and fixed a few errors but I don't know how to fix this one.
The original file from Github https://github.com/jberlana/JBCroppableView
#import "JBCroppableImageView.h"
#import "JBCroppableLayer.h"
#implementation JBCroppableImageView{
JBCroppableLayer *_pointsView;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.userInteractionEnabled = YES;
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPoints:4];
[self addSubview:_pointsView];
UIPanGestureRecognizer *singlePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(singlePan:)];
singlePan.maximumNumberOfTouches = 1;
[self addGestureRecognizer:singlePan];
UIPanGestureRecognizer *doublePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(doublePan:)];
doublePan.minimumNumberOfTouches = 2;
[self addGestureRecognizer:doublePan];
}
return self;
}
-(void)singlePan:(UIPanGestureRecognizer *)gesture{
CGPoint posInStretch = [gesture locationInView:_pointsView];
if(gesture.state==UIGestureRecognizerStateBegan){
[_pointsView findPointAtLocation:posInStretch];
}
if(gesture.state==UIGestureRecognizerStateEnded){
_pointsView.activePoint.backgroundColor = _pointsView.pointColor;
_pointsView.activePoint = nil;
}
[_pointsView moveActivePointToLocation:posInStretch];
// CGPoint translation = [gesture translationInView:self.imageView];
// [gesture setTranslation:CGPointZero inView:self.imageView];
}
-(void)doublePan:(UIPanGestureRecognizer *)gesture{
NSLog(#"double pan");
CGPoint translation = [gesture translationInView:self];
CGPoint newCenter = CGPointMake(self.center.x+translation.x, self.center.y+translation.y);
[self setCenter:newCenter];
[gesture setTranslation:CGPointZero inView:self];
}
-(void)crop{
[_pointsView maskImageView:self];
[_pointsView removeFromSuperview];
}
-(void)reverseCrop{
self.layer.mask = nil;
[self addSubview:_pointsView];
}
-(UIImage *)getCroppedImage{
return [_pointsView getCroppedImageForView:self withTransparentBorders:NO];
}
-(UIImage *)getCroppedImageWithTransparentBorders:(BOOL)transparent{
return [_pointsView getCroppedImageForView:self withTransparentBorders:transparent];
}
- (void)addPoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
// CGPoint new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);
NSInteger indexOfLargestGap; //points to here for first warning
CGFloat largestGap = 0;
for(int i=0; i< oldPoints.count-1; i++){
CGPoint first = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:i+1] CGPointValue];
CGFloat distance = [self distanceBetween:first And:last];
if(distance>largestGap){
indexOfLargestGap = i+1;
largestGap = distance;
}
}
CGPoint veryFirst = [[oldPoints firstObject] CGPointValue];
CGPoint veryLast = [[oldPoints lastObject] CGPointValue];
CGPoint new;
if([self distanceBetween:veryFirst And:veryLast]>largestGap){
indexOfLargestGap = oldPoints.count;
new = CGPointMake((veryFirst.x+veryLast.x)/2.0f, (veryFirst.y+veryLast.y)/2.0f);
} else {
CGPoint first = [[oldPoints objectAtIndex:indexOfLargestGap-1] CGPointValue];//Variable 'indexOfLargestGap' may be uninitialized when used here
CGPoint last = [[oldPoints objectAtIndex:indexOfLargestGap] CGPointValue];
new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);
}
[oldPoints insertObject:[NSValue valueWithCGPoint:new] atIndex:indexOfLargestGap];
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:oldPoints];
[self addSubview:_pointsView];
}
- (void)removePoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
if(oldPoints.count==3) return;
NSInteger indexOfSmallestGap;// points to here for second warning
CGFloat smallestGap = INFINITY;
for(int i=0; i< oldPoints.count; i++){
int firstIndex = i-1;
int lastIndex = i +1;
if(firstIndex<0){
firstIndex = (int)oldPoints.count-1+firstIndex;
} else if(firstIndex>=oldPoints.count){
firstIndex = firstIndex-(int)oldPoints.count;
}
if(lastIndex<0){
lastIndex = (int)oldPoints.count-1+lastIndex;
} else if(lastIndex>=oldPoints.count){
lastIndex = lastIndex-(int)oldPoints.count;
}
CGPoint first = [[oldPoints objectAtIndex:firstIndex] CGPointValue];
CGPoint mid = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:lastIndex] CGPointValue];
CGFloat distance = [self distanceFrom:first to:last throuh:mid];
if(distance<smallestGap){
indexOfSmallestGap = i;
smallestGap = distance;
}
}
[oldPoints removeObjectAtIndex:indexOfSmallestGap]; //Variable 'indexOfSmallestGap' may be uninitialized when used here
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:[NSArray arrayWithArray:oldPoints]];
[self addSubview:_pointsView];
}
-(CGFloat)distanceBetween:(CGPoint)first And:(CGPoint)last{
CGFloat xDist = (last.x - first.x);
if(xDist<0) xDist=xDist*-1;
CGFloat yDist = (last.y - first.y);
if(yDist<0) yDist=yDist*-1;
return sqrt((xDist * xDist) + (yDist * yDist));
}
-(CGFloat)distanceFrom:(CGPoint)first to:(CGPoint)last throuh:(CGPoint)middle{
CGFloat firstToMid = [self distanceBetween:first And:middle];
CGFloat lastToMid = [self distanceBetween:middle And:last];
return firstToMid + lastToMid;
}
-(void)setFrame:(CGRect)frame{
[super setFrame:frame];
[_pointsView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[_pointsView setNeedsDisplay];
}
#end
And I'm getting this from the terminal
index 0 beyond bounds for empty array
Question:
How to achieve this animation with Spritekit?
What I've done:
Problem:
1) I can draw all four petals,but once I lift my finger to draw the circle, it will still create a line from the previous point where I lift my finger to the new touches begin point. refer to gif below:
2) How to remove the solid orange line from the view incrementally (mine is too abrupt)?
3) Need to tune the .sks file properties.
4) https://stackoverflow.com/questions/29792443/set-the-initial-state-of-skemitternode
This is my code:
#import "GameScene.h"
#interface GameScene()
#property (nonatomic) SKEmitterNode* fireEmmitter;
#property (nonatomic) SKEmitterNode* fireEmmitter2;
#end
#implementation GameScene
NSMutableArray *_wayPoints;
NSTimer* myTimer;
-(void)didMoveToView:(SKView *)view {
_wayPoints = [NSMutableArray array];
//Setup a background
self.backgroundColor = [UIColor blackColor];
//setup a fire emitter
NSString *fireEmmitterPath = [[NSBundle mainBundle] pathForResource:#"magic" ofType:#"sks"];
_fireEmmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:fireEmmitterPath];
_fireEmmitter.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2 - 200);
_fireEmmitter.name = #"fireEmmitter";
_fireEmmitter.zPosition = 1;
_fireEmmitter.targetNode = self;
_fireEmmitter.particleBirthRate = 0;
[self addChild: _fireEmmitter];
//setup another fire emitter
NSString *fireEmmitterPath2 = [[NSBundle mainBundle] pathForResource:#"fireflies" ofType:#"sks"];
_fireEmmitter2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fireEmmitterPath2];
_fireEmmitter2.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
_fireEmmitter2.name = #"fireEmmitter";
_fireEmmitter2.zPosition = 1;
_fireEmmitter2.targetNode = self;
_fireEmmitter2.particleBirthRate = 0;
[self addChild: _fireEmmitter2];
//Setup a LightNode
SKLightNode* light = [[SKLightNode alloc] init];
light.categoryBitMask = 1;
light.falloff = 1;
light.ambientColor = [UIColor whiteColor];
light.lightColor = [[UIColor alloc] initWithRed:1.0 green:1.0 blue:0.0 alpha:0.5];
light.shadowColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.3];
[_fireEmmitter addChild:light];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
CGMutablePathRef ref = CGPathCreateMutable();
CGPoint p = touchPoint;
p = [self.scene convertPointToView:p];
CGPathMoveToPoint(ref, NULL, p.x, p.y);
_fireEmmitter.position = CGPointMake(touchPoint.x, touchPoint.y);
_fireEmmitter.particleBirthRate = 2000;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
//On Dragging make the emitter with the attached light follow the position
for (UITouch *touch in touches) {
[self addPointToMove:touchPoint];
CGPoint location = [touch locationInNode:self];
[self childNodeWithName:#"fireEmmitter"].position = CGPointMake(location.x, location.y);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
_fireEmmitter.particleBirthRate = 0;
[self performSelector:#selector(userHasCompletedTheDrawing) withObject:nil afterDelay:3];
}
- (void)userHasCompletedTheDrawing{
CGMutablePathRef path = CGPathCreateMutable();
if (_wayPoints && _wayPoints.count > 0) {
CGPoint p = [(NSValue *)[_wayPoints objectAtIndex:0] CGPointValue];
//p = [self.scene convertPointToView:p];
CGPathMoveToPoint(path, nil, p.x, p.y);
_fireEmmitter2.position = CGPointMake(p.x,p.y);
_fireEmmitter2.particleBirthRate = 1000;
for (int i = 0; i < _wayPoints.count; ++i) {
p = [(NSValue *)[_wayPoints objectAtIndex:i] CGPointValue];
CGPathAddLineToPoint(path, nil, p.x, p.y);
}
SKAction *followTrack = [SKAction followPath:path asOffset:NO orientToPath:YES duration:1];
[_fireEmmitter2 runAction:followTrack completion:^{
_fireEmmitter2.particleBirthRate = 0;
[_fireEmmitter2 runAction:[SKAction waitForDuration:1] completion:^{
//_fireEmmitter2.particleBirthRate = 0;
}];
}];
}
//myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: #selector(removePointToMove) userInfo: nil repeats: YES];
[self performSelector:#selector(removeAllPointToMove) withObject:nil afterDelay:1];
}
- (void)addPointToMove:(CGPoint)point {
[_wayPoints addObject:[NSValue valueWithCGPoint:point]];
}
- (void)removeAllPointToMove{
[_wayPoints removeAllObjects];
}
- (void)removePointToMove{
if ([_wayPoints count]>0) {
[_wayPoints removeObjectAtIndex:0];
}
}
- (void)drawLines {
//1
NSMutableArray *temp = [NSMutableArray array];
for(CALayer *layer in self.view.layer.sublayers) {
if([layer.name isEqualToString:#"line"]) {
[temp addObject:layer];
}
}
[temp makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
//3
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.name = #"line";
lineLayer.strokeColor = [UIColor orangeColor].CGColor;
lineLayer.fillColor = nil;
lineLayer.lineWidth = 3;
lineLayer.lineJoin = kCALineJoinRound; /* The join style used when stroking the path. Options are `miter', `round'
* and `bevel'. Defaults to `miter'. */
lineLayer.zPosition = -1;
//4
CGPathRef path = [self createPathToMove];
lineLayer.path = path;
CGPathRelease(path);
[self.view.layer addSublayer:lineLayer];
}
- (CGPathRef)createPathToMove {
//1
CGMutablePathRef ref = CGPathCreateMutable();
//2
for(int i = 0; i < [_wayPoints count]; ++i) {
CGPoint p = [_wayPoints[i] CGPointValue];
p = [self.scene convertPointToView:p];
//3
if(i == 0 ) {
CGPathMoveToPoint(ref, NULL, p.x, p.y);
} else {
CGPathAddLineToPoint(ref, NULL, p.x, p.y);
}
}
return ref;
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self drawLines];
if ([_wayPoints count]==0) {
[myTimer invalidate];
}
}
#end
This is my .sks files properties:
Concerning your first question, you need to split your CGPathRef into multiple subpaths so that no line gets drawn between the petals and the center. Use the CGPathCloseSubpath function when you are done drawing the petals so that you can call CGPathMoveToPoint and CGPathAddLineToPoint afterwards.
This question continues from a previous answer.
I have the following CAShapeLayer:
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.lineCap = kCALineCapRound;
_gaugeCircleLayer.path = [self insideCirclePath].CGPath;
CAShapeLayer *cap = [CAShapeLayer layer];
cap.shadowColor = [UIColor blackColor].CGColor;
cap.shadowRadius = 8.0;
cap.shadowOpacity = 0.9;
cap.shadowOffset = CGSizeMake(0, 0);
cap.fillColor = [UIColor grayColor].CGColor;
[_gaugeCircleLayer addSublayer:cap];
}
return _gaugeCircleLayer;
}
Then I have the following UIBezierPath:
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
This produces something like the following:
What I am trying to produce with the cap sublayer is the drop shadow at the end and I'd also be interested to know how to get a GradientLayer working similar to the image below:
The problem is that the sublayer is not appearing anywhere and I'm not quite sure why. I'm also not 100% sure on whether or not this is the best way to produce the desired effect.
UPDATE:
The following bit of code creates a cap, though I'm not quite sure how to add it to my UIBezierPath properly:
let cap = CAShapeLayer()
cap.shadowColor = UIColor.blackColor().CGColor
cap.shadowRadius = 8.0
cap.shadowOpacity = 0.9
cap.shadowOffset = CGSize(width: 0, height: 0)
cap.path = UIBezierPath(ovalInRect: CGRectMake(0, 40, 20, 20)).CGPath
cap.fillColor = UIColor.grayColor().CGColor
layer.addSublayer(cap)
I don't know if this will be useful to you, since it doesn't use the CHCircleGaugeView. I was working on several projects related to this question, so I mashed them together and made some changes to produce a progress view that has a color gradient background with a tip that overlaps the end at 100%. I haven't gotten to the point where I make the rounded tip disappear at 0%, but I'll get there eventually. Here are a couple of views of it at 2 different progress levels,
The view is created with a polar gradient drawn in drawRect, masked by an annulus. The rounded tip is a separate layer that's a half circle on the end of a line connected to the center of the circle that's revolved around its center with a transform based on the progress level. Here's the code,
#implementation AnnulusProgressView{ // subclass of UIView
int slices;
CGFloat circleRadius;
CAShapeLayer *maskLayer;
CGFloat segmentAngle;
CGFloat startAngle;
CAShapeLayer *tipView;
NSMutableArray *colors;
int sign;
}
-(instancetype)initWithFrame:(CGRect)frame wantsBackgroundRing:(BOOL)wantsBackground backgroundRingColor:(UIColor *)ringColor {
if (self = [super initWithFrame:frame]) {
slices = 360;
_ringThickness = 12;
circleRadius = self.bounds.size.width/2;
_startColor = [UIColor colorWithHue:0.24 saturation:1 brightness:0.8 alpha:1];
_endColor = [UIColor colorWithHue:0.03 saturation:1 brightness:1 alpha:1];
[self setupColorArray];
_backgroundRing = wantsBackground? [self createBackgroundRingWithColor:ringColor] : nil;
self.layer.mask = [self createAnnulusMask];
}
return self;
}
-(void)setStartColor:(UIColor *)startColor {
_startColor = startColor;
[self setupColorArray];
}
-(void)setEndColor:(UIColor *)endColor {
_endColor = endColor;
[self setupColorArray];
}
-(void)setupColorArray {
colors = [NSMutableArray new];
CGFloat startHue, startSaturation, startBrightness, startAlpha, endHue, endSaturation, endBrightness, endAlpha;
[self.startColor getHue:&startHue saturation:&startSaturation brightness:&startBrightness alpha:&startAlpha];
[self.endColor getHue:&endHue saturation:&endSaturation brightness:&endBrightness alpha:&endAlpha];
for(int i=0;i<slices;i++){
CGFloat hue = startHue + (endHue - startHue)*i/slices;
CGFloat brightness = startBrightness + (endBrightness - startBrightness)*i/slices;
CGFloat saturation = startSaturation + (endSaturation - startSaturation)*i/slices;
CGFloat alpha = startAlpha + (endAlpha - startAlpha)*i/slices;
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
[colors addObject:color];
}
self.progress = _progress;
}
-(UIView *)createBackgroundRingWithColor:(UIColor *) color {
UIView *bgRing = [[UIView alloc] initWithFrame:self.frame];
bgRing.backgroundColor = color;
bgRing.layer.mask = [self createAnnulusMask];
[(CAShapeLayer *)bgRing.layer.mask setStrokeEnd:startAngle + 2*M_PI ];
return bgRing;
}
-(void)didMoveToSuperview {
if (self.backgroundRing) [self.superview insertSubview:self.backgroundRing belowSubview:self];
tipView = [self tipView];
[self.superview.layer addSublayer:tipView];
}
-(CAShapeLayer *)tipView {
CAShapeLayer *tip = [CAShapeLayer layer];
tip.frame = self.frame;
tip.fillColor = [UIColor redColor].CGColor;
UIBezierPath *shape = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[shape moveToPoint:center];
CGPoint bottomPoint = CGPointMake(center.x, center.y + circleRadius - self.ringThickness*2);
CGFloat tipCenterY = bottomPoint.y + self.ringThickness - 1;
[shape addLineToPoint: bottomPoint];
[shape addLineToPoint:CGPointMake(bottomPoint.x+2, bottomPoint.y)];
double fractionAlongTangent = self.ringThickness;
[shape addCurveToPoint:CGPointMake(center.x, center.y + circleRadius) controlPoint1:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY - fractionAlongTangent) controlPoint2:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY + fractionAlongTangent)];
[shape closePath];
tip.path = shape.CGPath;
tip.shadowColor = [UIColor darkGrayColor].CGColor;
tip.shadowOpacity = 0.8;
tip.shadowOffset = CGSizeMake(-6, 0);
tip.shadowRadius = 2;
return tip;
}
- (void)setProgress:(CGFloat)progress{
sign = (progress >= _progress)? 1 : -1;
[self animateProgressTo:#(progress)];
}
-(void)animateProgressTo:(NSNumber *) newValueNumber{
float newValue = newValueNumber.floatValue;
_progress = (_progress + (sign * 0.1) > 1)? 1 : _progress + (sign * 0.1);
if ((_progress > newValue && sign == 1) || (_progress < newValue && sign == -1)) {
_progress = newValue;
}
maskLayer.strokeEnd = _progress;
tipView.transform = CATransform3DMakeRotation(-(1 - _progress + 0.002) * M_PI*2, 0, 0, 1); //the 0.002 closes a small gap between the tip and the annulus strokeEnd
int i = (int)(_progress*(slices - 1));
tipView.fillColor = ((UIColor *)colors[i]).CGColor;
if (sign == 1) {
if (_progress < newValue) {
[self performSelector:#selector(animateProgressTo:) withObject:#(newValue) afterDelay:0.05];
}
}else{
if (_progress > newValue) {
[self performSelector:#selector(animateProgressTo:) withObject:#(newValue) afterDelay:0.05];
}
}
NSLog(#"%f",_progress);
}
- (CAShapeLayer *)createAnnulusMask {
maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
segmentAngle = 2*M_PI/(slices);
startAngle = M_PI_2;
CGFloat endAngle = startAngle + 2*M_PI;
maskLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)) radius:circleRadius - self.ringThickness startAngle:startAngle endAngle:endAngle clockwise:YES].CGPath;
maskLayer.fillColor = [UIColor clearColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.lineWidth = self.ringThickness * 2;
maskLayer.strokeEnd = self.progress;
return maskLayer;
}
-(void)drawRect:(CGRect)rect{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(ctx, NO);
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
for(int i=0;i<slices;i++){
CGContextSaveGState(ctx);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addArcWithCenter:center radius:circleRadius startAngle:startAngle endAngle:startAngle+segmentAngle clockwise:YES];
[path addClip];
[colors[i] setFill];
[path fill];
CGContextRestoreGState(ctx);
startAngle += segmentAngle;
}
}
This should resolve the problem.
Only remaining issue is the animation, whereby the cap is not animated.
The trick was to add the cap to the end of the gauge, and update it when the value of the gauge changed. To calculate the location, a little math magic was used. It needs to be under the gauge, so the cap is added in the trackCircleLayer
//
// CHCircleGaugeView.m
//
// Copyright (c) 2014 ChaiOne <http://www.chaione.com/>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "CHCircleGaugeView.h"
#import "CHCircleGaugeViewDebugMacros.h"
#import <CoreText/CoreText.h>
#import <QuartzCore/QuartzCore.h>
static CGFloat const CHKeyAnimationDuration = 0.5f;
static CGFloat const CHKeyDefaultValue = 0.0f;
static CGFloat const CHKeyDefaultFontSize = 32.0f;
static CGFloat const CHKeyDefaultTrackWidth = 0.5f;
static CGFloat const CHKeyDefaultGaugeWidth = 2.0f;
static NSString * const CHKeyDefaultNAText = #"n/a";
static NSString * const CHKeyDefaultNoAnswersText = #"%";
#define CHKeyDefaultTrackTintColor [UIColor blackColor]
#define CHKeyDefaultGaugeTintColor [UIColor blackColor]
#define CHKeyDefaultTextColor [UIColor blackColor]
#interface CHCircleGaugeView ()
#property (nonatomic, strong) CAShapeLayer *trackCircleLayer;
#property (nonatomic, strong) CAShapeLayer *gaugeCircleLayer;
// ADDED
#property (nonatomic, strong) CAShapeLayer *capLayer;
// END ADDED
#property (nonatomic, strong) UILabel *valueTextLabel;
#end
#implementation CHCircleGaugeView
#pragma mark - View Initialization
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initSetup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initSetup];
}
return self;
}
- (void)initSetup {
_state = CHCircleGaugeViewStateNA;
_value = CHKeyDefaultValue;
_trackTintColor = CHKeyDefaultTrackTintColor;
_gaugeTintColor = CHKeyDefaultGaugeTintColor;
_textColor = CHKeyDefaultTextColor;
_font = [UIFont systemFontOfSize:CHKeyDefaultFontSize];
_trackWidth = CHKeyDefaultTrackWidth;
_gaugeWidth = CHKeyDefaultGaugeWidth;
_notApplicableString = CHKeyDefaultNAText;
_noDataString = CHKeyDefaultNoAnswersText;
[self createGauge];
}
- (void)createGauge {
[self.layer addSublayer:self.trackCircleLayer];
[self.layer addSublayer:self.gaugeCircleLayer];
[self addSubview:self.valueTextLabel];
[self setupConstraints];
}
- (void)setupConstraints {
NSDictionary *viewDictionary = #{#"valueText" : self.valueTextLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
}
#pragma mark - Property Setters
- (void)setState:(CHCircleGaugeViewState)state {
if (_state != state) {
_state = state;
switch (state) {
case CHCircleGaugeViewStateNA: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStatePercentSign: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStateScore: {
[self updateGaugeWithValue:self.value animated:NO];
break;
}
default: {
ALog(#"Missing gauge state.");
break;
}
}
}
}
- (void)setValue:(CGFloat)value {
[self setValue:value animated:NO];
}
- (void)setValue:(CGFloat)value animated:(BOOL)animated {
self.state = CHCircleGaugeViewStateScore;
if (value != _value) {
[self willChangeValueForKey:NSStringFromSelector(#selector(value))];
value = MIN(1.0f, MAX(0.0f, value));
[self updateGaugeWithValue:value animated:animated];
_value = value;
[self didChangeValueForKey:NSStringFromSelector(#selector(value))];
}
}
- (void)setUnitsString:(NSString *)unitsString {
if ([_unitsString isEqualToString:unitsString] == NO) {
_unitsString = [unitsString copy];
self.valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
}
- (void)updateGaugeWithValue:(CGFloat)value animated:(BOOL)animated {
self.valueTextLabel.attributedText = [self formattedStringForValue:value];
BOOL previousDisableActionsValue = [CATransaction disableActions];
[CATransaction setDisableActions:YES];
self.gaugeCircleLayer.strokeEnd = value;
// ADDED
_capLayer.path = [self capPathForValue:value].CGPath;
// END ADDED
if (animated) {
self.gaugeCircleLayer.strokeEnd = value;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = CHKeyAnimationDuration;
pathAnimation.fromValue = [NSNumber numberWithFloat:self.value];
pathAnimation.toValue = [NSNumber numberWithFloat:value];
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.gaugeCircleLayer addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
}
[CATransaction setDisableActions:previousDisableActionsValue];
}
- (void)setTrackTintColor:(UIColor *)trackTintColor {
if (_trackTintColor != trackTintColor) {
_trackTintColor = trackTintColor;
self.trackCircleLayer.strokeColor = trackTintColor.CGColor;
}
}
- (void)setGaugeTintColor:(UIColor *)gaugeTintColor {
if (_gaugeTintColor != gaugeTintColor) {
_gaugeTintColor = gaugeTintColor;
self.gaugeCircleLayer.strokeColor = gaugeTintColor.CGColor;
// ADDED
self.capLayer.fillColor = gaugeTintColor.CGColor;
// END ADDED
}
}
- (void)setTrackWidth:(CGFloat)trackWidth {
if (_trackWidth != trackWidth) {
_trackWidth = trackWidth;
self.trackCircleLayer.lineWidth = trackWidth;
[self.layer layoutSublayers];
}
}
- (void)setGaugeWidth:(CGFloat)gaugeWidth {
if (_gaugeWidth != gaugeWidth) {
_gaugeWidth = gaugeWidth;
self.gaugeCircleLayer.lineWidth = gaugeWidth;
[self.layer layoutSublayers];
}
}
- (void)setTextColor:(UIColor *)textColor {
if (_textColor != textColor) {
_textColor = textColor;
self.valueTextLabel.textColor = textColor;
}
}
- (void)setFont:(UIFont *)font {
if (_font != font) {
_font = font;
self.valueTextLabel.font = font;
}
}
- (void)setGaugeStyle:(CHCircleGaugeStyle)gaugeStyle {
if (_gaugeStyle != gaugeStyle) {
_gaugeStyle = gaugeStyle;
[self.layer layoutSublayers];
}
}
#pragma mark - Circle Shapes
- (CAShapeLayer *)trackCircleLayer {
if (_trackCircleLayer == nil) {
_trackCircleLayer = [CAShapeLayer layer];
_trackCircleLayer.lineWidth = self.trackWidth;
_trackCircleLayer.fillColor = [UIColor clearColor].CGColor;
_trackCircleLayer.strokeColor = self.trackTintColor.CGColor;
_trackCircleLayer.path = [self insideCirclePath].CGPath;
// ADDED
_capLayer = [CAShapeLayer layer];
_capLayer.shadowColor = [UIColor blackColor].CGColor;
_capLayer.shadowRadius = 8.0;
_capLayer.shadowOpacity = 0.9;
_capLayer.shadowOffset = CGSizeMake(0, 0);
_capLayer.fillColor = self.gaugeTintColor.CGColor;
_capLayer.path = [self capPathForValue:self.value].CGPath;
[_trackCircleLayer addSublayer:_capLayer];
// END ADDED
}
return _trackCircleLayer;
}
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
return _gaugeCircleLayer;
}
// ADDED
- (UIBezierPath *)capPathForValue:(float)value {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = CGRectGetWidth(self.bounds) / 2.0f;
float angle = value * 360.0;
float x = radius * sin(angle*M_PI/180.0);
float y = radius * cos(angle*M_PI/180.0);
CGPoint capArcCenter = CGPointMake(arcCenter.x + x, arcCenter.y - y);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:capArcCenter
radius:self.gaugeWidth*_capLayer.shadowRadius / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
// END ADDED
- (UIBezierPath *)circlPathForCurrentGaugeStyle {
switch (self.gaugeStyle) {
case CHCircleGaugeStyleInside: {
return [self insideCirclePath];
}
case CHCircleGaugeStyleOutside: {
return [self outsideCirclePath];
}
default: {
return nil;
}
}
}
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
- (UIBezierPath *)outsideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = (CGRectGetWidth(self.bounds) / 2.0f) + (self.trackWidth / 2.0f) + (self.gaugeWidth / 2.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
#pragma mark - Text Label
- (UILabel *)valueTextLabel {
if (_valueTextLabel == nil) {
_valueTextLabel = [[UILabel alloc] init];
[_valueTextLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_valueTextLabel.textAlignment = NSTextAlignmentCenter;
_valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
return _valueTextLabel;
}
- (NSAttributedString *)formattedStringForValue:(CGFloat)value {
NSAttributedString *valueString;
NSDictionary *stringAttributes = #{
NSForegroundColorAttributeName : self.textColor,
NSFontAttributeName : self.font
};
switch (self.state) {
case CHCircleGaugeViewStateNA: {
valueString = [[NSAttributedString alloc] initWithString:self.notApplicableString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStatePercentSign: {
valueString = [[NSAttributedString alloc] initWithString:self.noDataString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStateScore: {
NSString *suffix = self.unitsString ? self.unitsString : #"";
valueString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%.0f %#", value * 100.0f, suffix]
attributes:stringAttributes];
break;
}
default: {
ALog(#"Missing gauge state.");
break;
}
}
return valueString;
}
#pragma mark - KVO
// Handling KVO notifications for the value property, since
// we're proxying with the setValue:animated: method.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:NSStringFromSelector(#selector(value))]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
#pragma mark - CALayerDelegate
- (void)layoutSublayersOfLayer:(CALayer *)layer {
[super layoutSublayersOfLayer:layer];
if (layer == self.layer) {
self.trackCircleLayer.path = [self insideCirclePath].CGPath;
self.gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
}
#end
So, I have an object composed of several physics bodies with 10 or so joints holding it together (pin and fixed). It works well but I am surprised at how wobbly the joints are. I'd imagine "fixed" to be just as fixed as if I merged them into one physics body, but it's substantially wobbly to the point that it is unusable. Also with pins, the points on each physics body seem to have quite some play apart from each other when force is applied to connecting objects. This is the case all the time, and both bodies involved are not restricted in motion in any way.
I would love to have the joints more rigid and less elastic. Has anyone been able to find a solution or reason for this? adding several more joints does not seem to make it any more rigid.
I faced a similar elasticity bug with a rope simulation and could finally come up with a workaround.
Here's my rope interface:
#import <SpriteKit/SpriteKit.h>
#interface ALRope : NSObject
#property(nonatomic, readonly) NSArray *ropeRings;
#property(nonatomic) int ringCount;
#property(nonatomic) CGFloat ringScale;
#property(nonatomic) CGFloat ringsDistance;
#property(nonatomic) CGFloat jointsFrictionTorque;
#property(nonatomic) CGFloat ringsZPosition;
#property(nonatomic) CGPoint startRingPosition;
#property(nonatomic) CGFloat ringFriction;
#property(nonatomic) CGFloat ringRestitution;
#property(nonatomic) CGFloat ringMass;
#property(nonatomic) BOOL shouldEnableJointsAngleLimits;
#property(nonatomic) CGFloat jointsLowerAngleLimit;
#property(nonatomic) CGFloat jointsUpperAngleLimit;
-(instancetype)initWithRingTexture:(SKTexture *)ringTexture;
-(void)buildRopeWithScene:(SKScene *)scene;
-(void)adjustRingPositions;
-(SKSpriteNode *)startRing;
-(SKSpriteNode *)lastRing;
#end
Rope Implementation:
#import "ALRope.h"
#implementation ALRope
{
SKTexture *_ringTexture;
NSMutableArray *_ropeRings;
}
static CGFloat const RINGS_DISTANCE_DEFAULT = 0;
static CGFloat const JOINTS_FRICTION_TORQUE_DEFAULT = 0;
static CGFloat const RING_SCALE_DEFAULT = 1;
static int const RING_COUNT_DEFAULT = 30;
static CGFloat const RINGS_Z_POSITION_DEFAULT = 1;
static BOOL const SHOULD_ENABLE_JOINTS_ANGLE_LIMITS_DEFAULT = NO;
static CGFloat const JOINT_LOWER_ANGLE_LIMIT_DEFAULT = -M_PI / 3;
static CGFloat const JOINT_UPPER_ANGLE_LIMIT_DEFAULT = M_PI / 3;
static CGFloat const RING_FRICTION_DEFAULT = 0;
static CGFloat const RING_RESTITUTION_DEFAULT = 0;
static CGFloat const RING_MASS_DEFAULT = -1;
-(instancetype)initWithRingTexture:(SKTexture *)ringTexture
{
if(self = [super init]) {
_ringTexture = ringTexture;
//apply defaults
_startRingPosition = CGPointMake(0, 0);
_ringsDistance = RINGS_DISTANCE_DEFAULT;
_jointsFrictionTorque = JOINTS_FRICTION_TORQUE_DEFAULT;
_ringScale = RING_SCALE_DEFAULT;
_ringCount = RING_COUNT_DEFAULT;
_ringsZPosition = RINGS_Z_POSITION_DEFAULT;
_shouldEnableJointsAngleLimits = SHOULD_ENABLE_JOINTS_ANGLE_LIMITS_DEFAULT;
_jointsLowerAngleLimit = JOINT_LOWER_ANGLE_LIMIT_DEFAULT;
_jointsUpperAngleLimit = JOINT_UPPER_ANGLE_LIMIT_DEFAULT;
_ringFriction = RING_FRICTION_DEFAULT;
_ringRestitution = RING_RESTITUTION_DEFAULT;
_ringMass = RING_MASS_DEFAULT;
}
return self;
}
-(void)buildRopeWithScene:(SKScene *)scene
{
_ropeRings = [NSMutableArray new];
SKSpriteNode *firstRing = [self addRopeRingWithPosition:_startRingPosition underScene:scene];
SKSpriteNode *lastRing = firstRing;
CGPoint position;
for (int i = 1; i < _ringCount; i++) {
position = CGPointMake(lastRing.position.x, lastRing.position.y - lastRing.size.height - _ringsDistance);
lastRing = [self addRopeRingWithPosition:position underScene:scene];
}
[self addJointsWithScene:scene];
}
-(SKSpriteNode *)addRopeRingWithPosition:(CGPoint)position underScene:(SKScene *)scene
{
SKSpriteNode *ring = [SKSpriteNode spriteNodeWithTexture:_ringTexture];
ring.xScale = ring.yScale = _ringScale;
ring.position = position;
ring.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ring.size.height / 2];
ring.physicsBody.allowsRotation = YES;
ring.physicsBody.friction = _ringFriction;
ring.physicsBody.restitution = _ringRestitution;
if(_ringMass > 0) {
ring.physicsBody.mass = _ringMass;
}
[scene addChild:ring];
[_ropeRings addObject:ring];
return ring;
}
-(void)addJointsWithScene:(SKScene *)scene
{
for (int i = 1; i < _ropeRings.count; i++) {
SKSpriteNode *nodeA = [_ropeRings objectAtIndex:i-1];
SKSpriteNode *nodeB = [_ropeRings objectAtIndex:i];
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:nodeA.physicsBody
bodyB:nodeB.physicsBody
anchor:CGPointMake(nodeA.position.x,
nodeA.position.y - (nodeA.size.height + _ringsDistance) / 2)];
joint.frictionTorque = _jointsFrictionTorque;
joint.shouldEnableLimits = _shouldEnableJointsAngleLimits;
if(_shouldEnableJointsAngleLimits) {
joint.lowerAngleLimit = _jointsLowerAngleLimit;
joint.upperAngleLimit = _jointsUpperAngleLimit;
}
[scene.physicsWorld addJoint:joint];
}
}
//workaround for elastic effect should be called from didSimulatePhysics
-(void)adjustRingPositions
{
//based on zRotations of all rings and the position of start ring adjust the rest of the rings positions starting from top to bottom
for (int i = 1; i < _ropeRings.count; i++) {
SKSpriteNode *nodeA = [_ropeRings objectAtIndex:i-1];
SKSpriteNode *nodeB = [_ropeRings objectAtIndex:i];
CGFloat thetaA = nodeA.zRotation - M_PI / 2,
thetaB = nodeB.zRotation + M_PI / 2,
jointRadius = (_ringsDistance + nodeA.size.height) / 2,
xJoint = jointRadius * cosf(thetaA) + nodeA.position.x,
yJoint = jointRadius * sinf(thetaA) + nodeA.position.y,
theta = thetaB - M_PI,
xB = jointRadius * cosf(theta) + xJoint,
yB = jointRadius * sinf(theta) + yJoint;
nodeB.position = CGPointMake(xB, yB);
}
}
-(SKSpriteNode *)startRing
{
return _ropeRings[0];
}
-(SKSpriteNode *)lastRing
{
return [_ropeRings lastObject];
}
#end
Scene code to showcase how to use the Rope:
#import "ALRopeDemoScene.h"
#import "ALRope.h"
#implementation ALRopeDemoScene
{
__weak SKSpriteNode *_branch;
CGPoint _touchLastPosition;
BOOL _branchIsMoving;
ALRope *_rope;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.2 green:0.5 blue:0.6 alpha:1.0];
[self buildScene];
}
return self;
}
-(void) buildScene {
SKSpriteNode *branch = [SKSpriteNode spriteNodeWithImageNamed:#"Branch"];
_branch = branch;
branch.position = CGPointMake(CGRectGetMaxX(self.frame) - branch.size.width / 2,
CGRectGetMidY(self.frame) + 200);
branch.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(2, 10)];
branch.physicsBody.dynamic = NO;
[self addChild:branch];
_rope = [[ALRope alloc] initWithRingTexture:[SKTexture textureWithImageNamed:#"rope_ring"]];
//configure rope params if needed
// _rope.ringCount = ...;//default is 30
// _rope.ringScale = ...;//default is 1
// _rope.ringsDistance = ...;//default is 0
// _rope.jointsFrictionTorque = ...;//default is 0
// _rope.ringsZPosition = ...;//default is 1
// _rope.ringFriction = ...;//default is 0
// _rope.ringRestitution = ...;//default is 0
// _rope.ringMass = ...;//ignored unless mass > 0; default -1
// _rope.shouldEnableJointsAngleLimits = ...;//default is NO
// _rope.jointsLowerAngleLimit = ...;//default is -M_PI/3
// _rope.jointsUpperAngleLimit = ...;//default is M_PI/3
_rope.startRingPosition = CGPointMake(branch.position.x - 100, branch.position.y);//default is (0, 0)
[_rope buildRopeWithScene:self];
//attach rope to branch
SKSpriteNode *startRing = [_rope startRing];
CGPoint jointAnchor = CGPointMake(startRing.position.x, startRing.position.y + startRing.size.height / 2);
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:branch.physicsBody bodyB:startRing.physicsBody anchor:jointAnchor];
[self.physicsWorld addJoint:joint];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if(CGRectContainsPoint(_branch.frame, location)) {
_branchIsMoving = YES;
_touchLastPosition = location;
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_branchIsMoving = NO;
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if(_branchIsMoving) {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self],
branchCurrentPosition = _branch.position;
CGFloat dx = location.x - _touchLastPosition.x,
dy = location.y - _touchLastPosition.y;
_branch.position = CGPointMake(branchCurrentPosition.x + dx, branchCurrentPosition.y + dy);
_touchLastPosition = location;
}
}
-(void)didSimulatePhysics
{
//workaround for elastic effect
[_rope adjustRingPositions];
}
#end
Notice the [rope adjustRingPositions] call from [scene didSimulatePhysics]. That was the actual workaround for the elastic bug.
Complete demo code is here. I hope this helps!
After transferring a large image from a REST endpoint, I need to divide the image into a number of smaller image tiles.
The initial image is (for instance) 1024x1024, stored in an NSData; I need to create sub-image of size 256x256 (In this case, there will be 16 sub-images).
How would this be done? (I haven't found any articles which even come close, but I assume it must be possible since most image editing software supports image cropping.)
Thanks.
This is the function I use to crop images in some of my project.
- (UIImage *)cropImage:(UIImage *) image{
CGRect rect = CGRectMake(0, 0, 256, 256);
CGImageRef subImageRef = CGImageCreateWithImageInRect(image.CGImage, rect);
CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef));
UIGraphicsBeginImageContext(smallBounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, smallBounds, subImageRef);
UIImage* smallImg = [UIImage imageWithCGImage:subImageRef];
UIGraphicsEndImageContext();
return smallImg;
}
I think you can fine a way from there to call it multiple times to crop your pictures 16 times .
Hope this helps
originalImageView is a IBOutlet ImageView. This image will be cropped.
#import <QuartzCore/QuartzCore.h>
This is needed for the border around each slice for better understanding.
-(UIImage*)getCropImage:(CGRect)cropRect
{
CGImageRef image = CGImageCreateWithImageInRect([originalImageView.image CGImage],cropRect);
UIImage *cropedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
return cropedImage;
}
-(void)prepareSlices:(uint)row:(uint)col
{
float flagX = originalImageView.image.size.width / originalImageView.frame.size.width;
float flagY = originalImageView.image.size.height / originalImageView.frame.size.height;
float _width = originalImageView.frame.size.width / col;
float _height = originalImageView.frame.size.height / row;
float _posX = 0.0;
float _posY = 0.0;
for (int i = 1; i <= row * col; i++) {
UIImageView *croppedImageVeiw = [[UIImageView alloc] initWithFrame:CGRectMake(_posX, _posY, _width, _height)];
UIImage *img = [self getCropImage:CGRectMake(_posX * flagX,_posY * flagY, _width * flagX, _height * flagY)];
croppedImageVeiw.image = img;
croppedImageVeiw.layer.borderColor = [[UIColor whiteColor] CGColor];
croppedImageVeiw.layer.borderWidth = 1.0f;
[self.view addSubview:croppedImageVeiw];
[croppedImageVeiw release];
_posX += _width;
if (i % col == 0) {
_posX = 0;
_posY += _height;
}
}
originalImageView.alpha = 0.0;
}
Call it like this:
[self prepareSlices:16 :16];
#interface UIImage (Sprite)
- (NSArray *)spritesWithSpriteSheetImage:(UIImage *)image spriteSize:(CGSize)size;
- (NSArray *)spritesWithSpriteSheetImage:(UIImage *)image inRange:(NSRange)range spriteSize:(CGSize)size;
#end
#implementation UIImage (Sprite)
-(NSArray *)spritesWithSpriteSheetImage:(UIImage *)image spriteSize:(CGSize)size {
return [self spritesWithSpriteSheetImage:self inRange:NSMakeRange(0, lroundf(MAXFLOAT))
spriteSize:size];
}
-(NSArray *)spritesWithSpriteSheetImage:(UIImage *)image
inRange:(NSRange)range
spriteSize:(CGSize)size {
if (!image || CGSizeEqualToSize(size, CGSizeZero) || range.length == 0)
return nil;
NSLog(#"%i %i", range.location, range.length);
CGImageRef spriteSheet = [image CGImage];
NSMutableArray *tempArray = [[[NSMutableArray alloc] init] autorelease];
int width = CGImageGetWidth(spriteSheet);
int height = CGImageGetHeight(spriteSheet);
int maxI = width / size.width;
int startI = 0;
int startJ = 0;
int length = 0;
int startPosition = range.location;
// Extracting initial I & J values from range info
//
if (startPosition != 0) {
for (int k=1; k<=maxI; k++) {
int d = k * maxI;
if (d/startPosition == 1) {
startI = maxI - (d % startPosition);
break;
}
else if (d/startPosition > 1) {
startI = startPosition;
break;
}
startJ++;
}
}
int positionX = startI * size.width;
int positionY = startJ * size.height;
BOOL isReady = NO;
while (positionY < height) {
while (positionX < width) {
CGImageRef sprite = CGImageCreateWithImageInRect(spriteSheet, CGRectMake(positionX, positionY, size.width, size.height));
[tempArray addObject:[UIImage imageWithCGImage:sprite]];
CGImageRelease(sprite);
length++;
if (length == range.length) {
isReady = YES;
break;
}
positionX += size.width;
}
if (isReady)
break;
positionX = 0;
positionY += size.height;
}
return [NSArray arrayWithArray:tempArray];
}
#end