Okay, I have had a few questions floating around stackoverflow now, and most of them are related to this one question. I've been trying to get it myself, and I have a couple of times but the result is never quite what I expected. I am trying to develop a chain that my sprite can throw.
So far what I have done is thrown the lead of the hook from the centre of the sprite outward. Once it travels 10 pixels away from the sprites position it generates another link in the chain. The chain is then rotated to match the lead chains rotation and attached to it using a joint pin. It actually works fairly well. The only problem is it only works when I set the physics world speed to 0.01. If I return it to normal physics it throws the lead link in the chain but basically skips over everything else. Before this I tried containing the lead link in a physics body and calling a didEndContact to attach the other links but that didn't work nearly as well.
Does anyone have any ideas on how I can accomplish this? I just need the chain to extend from the sprites position, to a maximum length, and then afterwards retract. I had no idea it was going to be this difficult. Thank you in advance for all of your help, if you would like me to post my code I would be glad to, but considering I don't think it will work I haven't added it yet. Once again thank you very much in advance I have been racking my brain over this for weeks and it seems like I'm getting nowhere, although I have learnt many invaluable concepts which I am deeply appreciative to the stackoverflow community for.
This is a simple example of how to draw a line that is updated continuously...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
startingPoint = positionInScene;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
// Remove temporary line if it exist
[lineNode removeFromParent];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, startingPoint.x, startingPoint.y);
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
CGPathRelease(pathToDraw);
lineNode.strokeColor = [SKColor whiteColor];
lineNode.lineWidth = 1;
[self addChild:lineNode];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
// Remove temporary line
[lineNode removeFromParent];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, startingPoint.x, startingPoint.y);
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
SKShapeNode *finalLineNode = [SKShapeNode node];
finalLineNode.path = pathToDraw;
CGPathRelease(pathToDraw);
finalLineNode.strokeColor = [SKColor redColor];
finalLineNode.lineWidth = 1;
[self addChild:finalLineNode];
}
EDIT: This method detects when a line, defined by points start and end, intersects with one or more physics bodies.
- (void) rotateNodesAlongRayStart:(CGPoint)start end:(CGPoint)end
{
[self.physicsWorld enumerateBodiesAlongRayStart:start end:end
usingBlock:^(SKPhysicsBody *body, CGPoint point,
CGVector normal, BOOL *stop)
{
SKNode *node = body.node;
[node runAction:[SKAction rotateByAngle:M_PI*2 duration:3]];
}];
}
Related
I have a certain point at the bottom of the screen . . . when I touch the screen somewhere, I'd like a dotted line to appear between the point, and the point my finger is at. The length and rotation of the line will change based on where my finger is, or moves to.
I'm assuming I'd make the dotted line with a repetition of a small line image, but I guess that's why I need your help!
Note that all this can be organized better, and I personally don't like SKShapeNode in any shape :) or form, but this is the one way to do it:
#import "GameScene.h"
#implementation GameScene{
SKShapeNode *line;
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
line = [SKShapeNode node];
[self addChild:line];
[line setStrokeColor:[UIColor redColor]];
}
-(void)drawLine:(CGPoint)endingPoint{
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
CGPathAddLineToPoint(pathToDraw, NULL, endingPoint.x,endingPoint.y);
CGFloat pattern[2];
pattern[0] = 20.0;
pattern[1] = 20.0;
CGPathRef dashed =
CGPathCreateCopyByDashingPath(pathToDraw,NULL,0,pattern,2);
line.path = dashed;
CGPathRelease(dashed);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
[self drawLine:location];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
[self drawLine:location];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
line.path = nil;
}
The result is:
Also I don't know how much performant this is, but you can test it, tweak it and improve it. Or even use SKSpriteNode like you said. Happy coding!
EDIT:
I just noticed that you said dotted (not dashed) :)
You have to change pattern to something like:
pattern[0] = 3.0;
pattern[1] = 3.0;
I am attempting to add a particle effect in the touchesBegan method, so when the user touches the drawn sprite(SKSpriteNode), it draws the particle effect. However, I receive an error Attempted to add a SKNode which already has a parent: SKEmitterNode. To add some context... The game is of the bejeweled/candy crush style where blocks(deleteNode) are deleted based upon neighboring colors. In the touch event I iterate recursively, checking neighboring blocks and add them to an array to later be removed. Prior to each block(deleteNode) being removed I would like the particle event to occur. They both inherit from SKNode (right?), so I don't understand the conflict...
#interface
{
NSString *blockParticlePath;
SKEmitterNode *blockParticle;
}
in the initialization method...
blockParticlePath = [[NSBundle mainBundle] pathForResource:#"blockParticle" ofType:#"sks";
blockParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:blockParticlePath];
in touchesBegan...
blockParticle.position = deleteNode.position;
blockParticle.particleColor = deleteNode.color;
[self addChild:blockParticle];
To make sure I wasn't crazy, I checked other forums and have seen this same logic for adding particle effects to a scene. Thanks in advance. If you need any more info let me know.
#whfissler, you explanations helped a lot to pinpoint this solution.
This error occurred only when I had many SKSpriteNodes active (ballon game). On each ballon click it pops and a SKEmitterNode (explosion) is added. It seems that when the particles created by the explosion touch each other I recieve the same error like you. I changed my code from:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
SKEmitterNode *explosion = [SKEmitterNode orb_emitterNamed:#"ballonEksplosion"];
CGPoint positionInScene = [touch locationInNode:self];
explosion.position = positionInScene;
SKSpriteNode *touchedSprite;
for ( int i = 0; i < [[self nodesAtPoint:positionInScene] count]; i++)
{
touchedSprite = (SKSpriteNode *)[[self nodesAtPoint:positionInScene] objectAtIndex:i];
if ([touchedSprite.name isEqualToString:#"BALLON"])
{
[(MBDBallon *)touchedSprite popAndRemoveWithSoundEffect];
[self addChild:explosion];
}
}
}
to:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKSpriteNode *touchedSprite;
for ( int i = 0; i < [[self nodesAtPoint:positionInScene] count]; i++)
{
touchedSprite = (SKSpriteNode *)[[self nodesAtPoint:positionInScene] objectAtIndex:i];
if ([touchedSprite.name isEqualToString:#"BALLON"])
{
SKEmitterNode *explosion = [SKEmitterNode orb_emitterNamed:#"ballonEksplosion"];
explosion.position = positionInScene;
[(MBDBallon *)touchedSprite popAndRemoveWithSoundEffect];
[self addChild:explosion];
}
}
}
and it worked. To me it seems that my explosion SKEmitterNode had been kept somehow to long on the SKScene and therefore adding another SKEmitterNode for the currentPosition lead to problems with the:
self nodesAtPoint:positionInScene
nodesAtPoint stack.
I donĀ“t understand it fully, maybe this helps you in further understanding.
I would like to draw a line in sprite kit along the points collected in touchesmoved.
Whats the most efficient way of doing this? I've tried a few times and my line is either wrong on the y axis, or takes up a lot of processing power that the fps goes down to 10 a second.
Any ideas?
You could define a CGpath and modify it by adding lines or arcs in your touch moved function. After that, you can create a SKShapeNode from your path and configure it as you prefer.
If you want to draw the line while the finger is moving on the screen you can create the shape node when the touch begins with an empty path and then modify it.
Edit: I wrote some code, it works for me, draws a simple red line.
In your MyScene.m:
#interface MyScene()
{
CGMutablePathRef pathToDraw;
SKShapeNode *lineNode;
}
#end
#implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode = [SKShapeNode node];
lineNode.path = pathToDraw;
lineNode.strokeColor = [SKColor redColor];
[self addChild:lineNode];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(pathToDraw, NULL, positionInScene.x, positionInScene.y);
lineNode.path = pathToDraw;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// delete the following line if you want the line to remain on screen.
[lineNode removeFromParent];
CGPathRelease(pathToDraw);
}
#end
I'm making a drawing app, and I'm having the users draw with CCRenderTexture. It basically keeps rendering a picture of a black circle to simulate drawing. When I move my finger slowly, it works really well since the circles come together to form a line. However, when I move my finger quickly, it ends up just being a bunch of circles that aren't connected (http://postimage.org/image/wvj3w632n/). My question is how I get the render texture to render the image faster or have it fill in the blanks for me.
Also, I'm not completely sold on this method, but it's what I've found while looking around. Feel free to suggest whatever you think would be better. I was originally using ccdrawline but it really killed my performance. Thanks!
The gaps between start point and the end points need to be sorted out. I am pasting code that might help you to resolve the situation you showed in the link.
in .h file
CCRenderTexture *target;
CCSprite* brush;
in the init method of .m file
target = [[CCRenderTexture renderTextureWithWidth:size.width height:size.height] retain];
[target setPosition:ccp(size.width/2, size.height/2)];
[self addChild:target z:1];
brush = [[CCSprite spriteWithFile:#"brush_i3.png"] retain];
add the touches method I am showing the touchesMoved code.
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint start = [touch locationInView: [touch view]];
start = [[CCDirector sharedDirector] convertToGL: start];
CGPoint end = [touch previousLocationInView:[touch view]];
end = [[CCDirector sharedDirector] convertToGL:end];
printf("\n x= %f \t y= %f",start.x,start.y);
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[target begin];
[brush setColor:ccc3(0, 255, 0)];
brush.opacity = 5;
[brush visit];
[target end];
}
}
}
Hopefully it would work for you.
Its not that CCRenderTexture draws too slow its that the event only fires so often. You do need to fill in the gaps between the touch points you receive.
There is a great tutorial here about it which you may have already seen, http://www.learn-cocos2d.com/2011/12/how-to-use-ccrendertexture-motion-blur-screenshots-drawing-sketches/#sketching
I'm developing a game that will use the force of the swipe as a variable user input.
I read from the documentation that on the touchesEnded event, I can get the allTouches array which is a list of the user touches collected from touchesBegan. From this I plan to get the last two touches to get the direction of the swipe. I will also get the time interval between touchesBegan and touchesEnded, from which I will get the speed of the swipe. I will use the direction and the speed to calculate the force of the swipe.
What I'd like to know is: is there a better way to do this? Is this already encapsulated in a library call somewhere?
Thanks in advance.
Solve like this
- (void)rotateAccordingToAngle:(float)angle
{
[spinWheel setTransform:CGAffineTransformRotate(spinWheel.transform, angle)];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[spinWheel.layer removeAllAnimations];
previousTimestamp = event.timestamp;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.view==spinWheel)
{
UITouch *touch = [touches anyObject];
CGPoint center = CGPointMake(CGRectGetMidX([spinWheel bounds]), CGRectGetMidY([spinWheel bounds]));
CGPoint currentTouchPoint = [touch locationInView:spinWheel];
CGPoint previousTouchPoint = [touch previousLocationInView:spinWheel];
CGFloat angleInRadians = atan2f(currentTouchPoint.y - center.y, currentTouchPoint.x - center.x) - atan2f(previousTouchPoint.y - center.y, previousTouchPoint.x - center.x);
[self rotateAccordingToAngle:angleInRadians];
CGFloat angleInDegree = RADIANS_TO_DEGREES(angleInRadians);
revolutions+= (angleInDegree/360.0f);
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.view==spinWheel)
{
NSTimeInterval timeSincePrevious = event.timestamp - previousTimestamp;
CGFloat revolutionsPerSecond = revolutions/timeSincePrevious;
NSLog(#"%.3f",revolutionsPerSecond);
[self startAnimationWithRevolutions:revolutionsPerSecond forTime:5.0f];
}
revolutions = 0;
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
spinWheel.userInteractionEnabled = TRUE;
if (timerUpdate) {
[timerUpdate invalidate];
timerUpdate = nil;
}
}
-(void)updateTransform{
spinWheel.transform = [[spinWheel.layer presentationLayer] affineTransform];
}
-(void)startAnimationWithRevolutions:(float)revPerSecond forTime:(float)time
{
spinWheel.userInteractionEnabled = FALSE;
float totalRevolutions = revPerSecond * time;
timerUpdate = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(updateTransform) userInfo:nil repeats:YES];
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:time] forKey:kCATransactionAnimationDuration];
CABasicAnimation* spinAnimation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation"];
CGAffineTransform transform = spinWheel.transform;
float fromAngle = atan2(transform.b, transform.a);
float toAngle = fromAngle + (totalRevolutions*4*M_PI);
spinAnimation.fromValue = [NSNumber numberWithFloat:fromAngle];
spinAnimation.toValue = [NSNumber numberWithFloat:toAngle];
spinAnimation.repeatCount = 0;
spinAnimation.removedOnCompletion = NO;
spinAnimation.delegate = self;
spinAnimation.timingFunction = [CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseOut];
[spinWheel.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
[CATransaction commit];
}
I've accomplished something along these lines rather simply by just comparing [touch locationInView:self] to [touch previousLocationInView:self] in touchesEnded of the Class (subclass of UIView) of the object that will be moved. This will give you a vector with location, direction & rough sense of velocity at the moment user released finger from the iPhone.
You could use a UIPanGestureRecognizer which has function velocityInView. See https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPanGestureRecognizer_Class/
Rather than using allTouches on the UIEvent, you should use the touches set that you get in the call to your UIView's (or other UIResponder's) touchesEnded:withEvent: method. This ensures that you don't get touches that belong to other views.
Since the iPhone is a multi-touch device, the set contains all the touches that are associated with that event. In other words, if the user is touching the screen with two fingers, there should be two UITouch objects in the set, etc.
This means that the set does not contain all the points traversed since a touch began until it ended. To track that, you have to save the start point and time in touchesBegan:withEvent:, and then when the touch ends you calculate the speed based on that.
Note that if the set contains several points (which means the user is touching the screen with several fingers), you have to try to keep track of which UITouch object corresponds to which finger. You will want to do this in touchesMoved:withEvent:.
Since you get the touches in a set, you can't use an index or some key value to keep track of the touches you are interested in. I believe that the recommended way of doing this is to just assume that the touch that is closest to the point you saved on the previous event comes from the same finger. If you want to be more exact you can also use the UITouch's previousLocationInView: method.
If you're lazy, you can also simply do [[touches allObjects] objectAtIndex:0] and hope that this will give you the right touch (ie the one originating from the same finger) on each event. This actually often works, but I don't think you should use it in production code, especially not if you're trying to create an app with multi-touch functionality.