After making adjustments to my code as suggested, my animation now works. Now, I'd like to ask how to reverse the movements of the sprite. I am trying to make the sprite move as if the wind is blowing in it. Here's my code now:
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self.isTouchEnabled = YES;
CCSprite *black = [CCSprite spriteWithFile:#"b.png"];
black.position = ccp(100, 160);
black.scaleX = 100 / black.contentSize.width;
black.anchorPoint = ccp(0.03, 0);
CCSpriteFrameCache *frame = [CCSpriteFrameCache sharedSpriteFrameCache];
[frame addSpriteFramesWithFile:#"bLongAnimation.plist"];
CCSpriteBatchNode *bHair = [CCSpriteBatchNode batchNodeWithFile:#"bLongAnimation.png"];
[self addChild:bHair];
[self addChild:black z:1 tag:1];
//Animation
NSMutableArray *animateBlackHair = [NSMutableArray arrayWithCapacity:10];
for (int i = 1; i < 10; i++)
{
NSString *animBlackHair = [NSString stringWithFormat:#"b%i.png", i];
CCSpriteFrame *blackFrame = [frame spriteFrameByName:animBlackHair];
[animateBlackHair addObject:blackFrame];
//I added this code block thinking it might work
for(i = 10; i > 1; i--)
{
NSString *revAnimation = [NSString stringWithFormat:#"bRightLong%i.png", i];
CCSpriteFrame *revFrame = [frame spriteFrameByName:revAnimation];
[animateBlackHair1 addObject:revFrame];
}
}
CCAnimation *blowHair = [CCAnimation animationWithSpriteFrames:animateBlackHair delay:0.1];
CCAction *blowingHair = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:blowHair]];
[black runAction:blowingHair];
}
return self;}
My guess didn't work so I was wondering how I can reverse the movements as soon as it finishes the first one?
UPDATE: Never mind, I figured it out. I just moved the for loop for reversing the action outside of the other loop. Thank you for the help.
Try setting the delay on the animation to something other than 0. Try 0.1 or 0.4 or something. If you set it to zero I don't think the animation runs, and if it does, it runs too fast to be visible.
Related
I am attempting to recreate the spring behavior that you see in the iOS Messages app in my UICollectionView. Like Messages it will have various cell sizes based on the text size. I have created a custom UICollectionViewFlowLayout which does add the behavior to the UICollectionView however the message bubbles continue to oscillate slightly after the user has stopped scrolling. I have tried any number of combinations in the length, damping and spring values but the oscillation never goes away.
After some reading of other stack questions I did find this comment
In order to prevent oscillation it's necessary to dynamically increase the damping factor on a quadratic scale as the attached views get closer and closer to their attachment points. <
But I am not really sure where to get started with implementing something like that on what I currently have. Any help or guidance would be appreciated.
Below is my code on the UICollectionViewFlowLayout that is creating the current effect.
- (void) prepareLayout {
[super prepareLayout];
CGRect originalRect = (CGRect){.origin = self.collectionView.bounds.origin, .size = self.collectionView.frame.size};
CGRect visibleRect = CGRectInset(originalRect, -50, -50);
NSArray *itemsInVisibleRectArray = [super layoutAttributesForElementsInRect:visibleRect];
NSSet *itemsIndexPathsInVisibleRectSet = [NSSet setWithArray:[itemsInVisibleRectArray valueForKey:#"indexPath"]];
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIAttachmentBehavior *behaviour, NSDictionary *bindings) {
BOOL currentlyVisible = [itemsIndexPathsInVisibleRectSet member:[[[behaviour items] firstObject] indexPath]] != nil;
return !currentlyVisible;
}];
NSArray *noLongerVisibleBehaviours = [self.animator.behaviors filteredArrayUsingPredicate:predicate];
[noLongerVisibleBehaviours enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
[self.animator removeBehavior:obj];
[self.visibleIndexPathsSet removeObject:[[[obj items] firstObject] indexPath]];
}];
NSPredicate *newPredicate = [NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *item, NSDictionary *bindings) {
BOOL currentlyVisible = [self.visibleIndexPathsSet member:item.indexPath] != nil;
return !currentlyVisible;
}];
NSArray *newlyVisibleItems = [itemsInVisibleRectArray filteredArrayUsingPredicate:newPredicate];
CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView];
[newlyVisibleItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *item, NSUInteger idx, BOOL *stop) {
CGPoint center = item.center;
UIAttachmentBehavior *springBehaviour = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:center];
springBehaviour.length = 0.1f;
springBehaviour.damping = 3.0f;
springBehaviour.frequency = 2.8f;
if (!CGPointEqualToPoint(CGPointZero, touchLocation)) {
CGFloat yDistanceFromTouch = fabs(touchLocation.y - springBehaviour.anchorPoint.y);
CGFloat xDistanceFromTouch = fabs(touchLocation.x - springBehaviour.anchorPoint.x);
CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f;
if (self.latestDelta < 0) {
center.y += MAX(self.latestDelta, self.latestDelta*scrollResistance);
}
else {
center.y += MIN(self.latestDelta, self.latestDelta*scrollResistance);
}
item.center = center;
}
[self.animator addBehavior:springBehaviour];
[self.visibleIndexPathsSet addObject:item.indexPath];
}];
}
You can fix it with 2 steps.
1. add action for the behavior when initials to make sure the center of the cell doesn't change during the animation
springBehaviour.action = ^{
CGPoint itemCenter = item.center;
itemCenter.x = center.x;
item.center = itemCenter;
};
remove/re-add the behaviors when collectionview stops scrolling. To do this, you need implement a scrollview delegate method and in this method to remove/re-add behaviors.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
ZZHCollectionFlowLayout *flowLayout = self.collectionView.collectionViewLayout;
if ([flowLayout isKindOfClass:[ZZHCollectionFlowLayout class]])
{
[flowLayout removeAnimationBehavior];
}
else
{
// Your NSAssertionHandler
}
}
- (void)removeAnimationBehavior
{
NSArray *behaviors = self.dynamicAnimator.behaviors;
[self.dynamicAnimator removeAllBehaviors];
for (UIDynamicBehavior *obj in behaviors)
{
[self.dynamicAnimator addBehavior:obj];
}
}
BTW, if there is way of changing damping to fix, would like to hear it!
The following code doesn't work as expected. According to the SPRITE KIT PROGRAMMING GUIDE, pages 61 and 62 one may perform "advanced searches" by using regular expression like syntax, so, unless I'm misunderstanding the implementation, this should work?
SKShapeNode *myCircle = [self getCircle];
myCircle.name = [NSString stringWithFormat:#"CIRCLE_%d_%d", x, y];
myCircle.position = CGPointMake(10,10);
[self addChild:myCircle];
// Lets remove ALL SKNodes that begin with the name "CIRCLE_"
[self enumerateChildNodesWithName:#"CIRCLE_*" usingBlock:^(SKNode *node, BOOL *stop) {
[node removeFromParent];
}];
But alas, the nodes do not go away. If I specify an exact name (like #"CIRCLE_10_10") it works, but the wildcard expression * doesn't seem to, nor does something like this #"CIRCLE_[0-9]+_[0-9]+" -- not even if I use slashes #"/CIRCLE_[0-9]+_[0-9]+".
What am I doing wrong?
EDIT:
THIS WORKS and I could implement regular expression matching instead of substring'ing, but hoping to get the Sprite Kit implementation working (ideally).
[[self children] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
SKNode *node = (SKNode *)obj;
if ([[node.name substringWithRange:NSMakeRange(0, 7)] isEqual: #"CIRCLE_"]) {
[node removeFromParent];
}
}];
I tried the following variation of your code in both the current and beta versions of Xcode on both iOS and OS-X.
SKScene *scene = [[SKScene alloc] initWithSize:self.gameView.frame.size];
for (int x = 0; x < 20; x++)
{
for (int y = 0; y < 20; y++)
{
CGPathRef myPath = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 20, 20), NULL);
SKShapeNode *myCircle = [[SKShapeNode alloc] init];
myCircle.path = myPath;
#if TARGET_OS_IPHONE
myCircle.fillColor = [UIColor redColor];
#else
myCircle.fillColor = [NSColor redColor];
#endif
myCircle.name = [NSString stringWithFormat:#"CIRCLE_%d_%d", x, y];
myCircle.position = CGPointMake(20*x,20*y);
[scene addChild:myCircle];
CGPathRelease(myPath);
}
}
[self.gameView presentScene:scene];
All of your sample expressions work on the Mac in both versions of the SDK. On iOS, however, they only worked in the Developer Preview.
I'm trying to add sprites to a NSMutableArray but it's not adding them. This is what I have:
NSMutableArray *tail;
CCSprite *block;
int j;
-(void)handleTail:(CCSprite*)pos{
CGPoint point= pos.position;
block = [CCSprite spriteWithFile:#"Icon-Small-50.png"];
//Adding the tail blocks
block.scale = .8;
block.color = ccGREEN;
block.position = point;
NSLog(#"Block Pos (%f,%f)",block.position.x,block.position.y);
//CGPoint playerPos = piece.position;
originalPos = point;
if ([tail count] < maxLength) {
[tileMap addChild:block];
[tail addObject:block];
NSLog(#"Tail length:%i",tail.count);
j+=1;
}
if (j == 3) {
NSLog(#"J called");
[tail removeObjectAtIndex:0];
}
}
I don't understand why this isn't working?
You have not alloc+inited the tail.
In awakeFromNib or init or viewDidLoad ( which ever is applicable for your class) use
tail=[[NSMutableArray alloc] init];
Suggestion NOTE : Try to follow naming convention.
As tail is an array (plural) you should use tails.
You forget to alloc init NSMuttableArray
tail = [[NSMuttableArray alloc]init];
without alloc and init your array you cannot add object to it
when you try to access its member it returns nil
try NSLog (#"%#",tail); it returns
You need to instantiate your NSMutableArray.
tail = [NSMutableArray array];
You need to allocate and initialize the array.
Something like:
tail = [NSMutableArray array];
I am trying to check collisions of objects from a NSMutableArray against another object (using a CGRect) but it keeps saying the method requires a scalar type?!
Here is the method throwing the error:
-(void) checkSquareToCircleCollisions{
NSMutableArray *array = [squares getSquares];
for(int i = 0; i < [squares getCount]; i++){
Square *s = [array objectAtIndex: i];
CGRect rect1 = [player getRect];
CGRect rect2 = [s getRect];
//if(CGRectIntersection(rect1, rect2)){
//[player setAlive: NO];
// }
}
}
Use CGRectIntersectsRect, not CGRectIntersection.
CGRectIntersectsRect returns a boolean: YES if the rectangles intersect. CGRectIntersection returns the CGRect that is the overlap (if any) between the two rectangles.
if (CGRectIntersectsRect(playerRect, squareRect)) {
player.alive = NO;
}
I am a beginner in cocos2d and im facing a problem with detecting collision for my coins.
Sometimes it works sometimes it doesn't.
So basically, im creating a game which the user (ship) have to avoid the obstacles and collect coins on the way. The collision of the obstacle works well but not for the coins.
I was thinking maybe the loops for creating many coins is the problem but im not sure.
Can anyone help?
My codes:
- (void)update:(ccTime)dt{
double curTime = CACurrentMediaTime();
if (curTime > _nextBridgeSpawn) {
float randSecs = [self randomValueBetween:3.0 andValue:5.0];
_nextBridgeSpawn = randSecs + curTime;
float randX = [self randomValueBetween:50 andValue:500];
float randDuration = [self randomValueBetween:8.0 andValue:10.0];
CCSprite *bridge = [_bridge objectAtIndex:_nextBridge];
_nextBridge++;
if (_nextBridge >= _bridge.count) _nextBridge = 0;
[bridge stopAllActions];
bridge.position = ccp(winSize.width/2, winSize.height);
bridge.visible = YES;
[bridge runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:randDuration position:ccp(0, -winSize.height)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
this is where i declare my coins (continued from the update method)
int randCoin = [self randomValueBetween:0 andValue:5];
_coin = [[CCArray alloc] initWithCapacity:randCoin];
for(int i = 0; i < randCoin; ++i) {
coin = [CCSprite spriteWithFile:#"coin.png"];
coin.visible = NO;
[self addChild:coin];
[_coin addObject:coin];
}
float randCoinX = [self randomValueBetween:winSize.width/5 andValue:winSize.width - (border.contentSize.width *2)];
float randCoinY = [self randomValueBetween:100 andValue:700];
float randCoinPlace = [self randomValueBetween:30 andValue:60];
for (int i = 0; i < _coin.count; ++i) {
CCSprite *coin2 = [_coin objectAtIndex:i];
coin2.position = ccp(randCoinX, (bridge.position.y + randCoinY) + (randCoinPlace *i));
coin2.visible = YES;
[coin2 runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:randDuration position:ccp(0, -winSize.height-2000)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
}
}
this is to check for collision (also in the update method)
for (CCSprite *bridge in _bridge) {
if (!bridge.visible) continue;
if (CGRectIntersectsRect(ship.boundingBox, bridge.boundingBox)){
bridge.visible = NO;
[ship runAction:[CCBlink actionWithDuration:1.0 blinks:5]];
}
}
}
//this is the collision for coins which only work at times
for (CCSprite *coin2 in _coin) {
if (!coin2.visible) continue;
if (CGRectIntersectsRect(ship.boundingBox, coin2.boundingBox)) {
NSLog(#"Coin collected");
coin2.visible = NO;
}
}
}
Thank you.
Why you just don't check distances?
float dist = ccpDistance(ship.position, coin2.position);
float allowedDist = ship.contentSize.width*0.5 + coin2.contentSize.width*0.5;
if (dist<allowedDist){
NSLog(#"Collision detected");
}
If your sprites are not on the same layer, these layers should have at least same positions.
what is phone in the last piece of code? you check there if it's boundingBox intersects coins one, but I cannot see it anywhere else. Another one, boundingBox will work only if your objects have the same parent, because it returns "local" rect, relatieve to it's parent
another one point is that in case of too fast movement speed of your objects it is possible, that they will pass throw each other during tick time. I mean, that one update will be called before collision, so it will not detect it and the next one will be called after collision was finished, so it will not detect it either.
I've solved my problem by creating a timer in init and call it every 10 secs to create my coins.
My codes:
-(void) createCoins:(ccTime)delta{
CGSize winSize = [CCDirector sharedDirector].winSize;
int randCoin = [self randomValueBetween:0 andValue:10];
_coin = [[CCArray alloc] initWithCapacity:randCoin];
for(int i = 0; i < randCoin; i++) {
coin = [CCSprite spriteWithFile:#"coin30p.png"];
coin.visible = NO;
[self addChild:coin];
[_coin addObject:coin];
}
float randCoinX = [self randomValueBetween:winSize.width/5 andValue:winSize.width - (border.contentSize.width *2)];
float randCoinY = [self randomValueBetween:100 andValue:700];
float randCoinPlace = [self randomValueBetween:30 andValue:60];
for (int i = 0; i < _coin.count; i++) {
CCSprite *coin2 = [_coin objectAtIndex:i];
coin2.position = ccp(randCoinX, (winSize.height + randCoinY) + (randCoinPlace *i));
coin2.visible = YES;
[coin2 runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:8 position:ccp(0, -winSize.height-2000)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
}
}
in the update method:
for (CCSprite *coins in _coin){
if (!coins.visible) continue;
if (CGRectIntersectsRect(phone.boundingBox, coins.boundingBox)) {
NSLog(#"Coin collected");
coins.visible = NO;
}
}
Thank you everyone for the help(: