I have been writing a game with a helicopter for a while and now I am trying to offer the user the option between two helis. I used this code to animate the original one, with no problems whatsoever:
heliAtlas = [SKTextureAtlas atlasNamed:#"APACHE"];
NSArray *heliAtlasArray = [heliAtlas textureNames];
NSArray *heliAtlasArraySorted = [heliAtlasArray sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSMutableArray *heliTextures = [NSMutableArray array];
for (NSString *filename in heliAtlasArraySorted) {
SKTexture *texture = [heliAtlas textureNamed:filename];
[heliTextures addObject:texture];
}
SKAction *animateHeli = [SKAction animateWithTextures:heliTextures timePerFrame:.016];
SKAction *repeatAnimation = [SKAction repeatActionForever:animateHeli];
Now, I have used the exact same code for the second heli,but when I tried to use the heli, the animation looked glitched. I slowed down the animation, and I realized that it shows the first half of the animation, and then animates through the atlas style photos (with two side-by side helis cut in half or upside down, etc).
Why are my two atlases acting differently?
I found out that an atlas has an image limit of 50. I've removed images until there were 50 left and it worked just fine from there.
Related
I have 9 blocks on screen (BlockView is just a subclass of view with some properties to keep track of things), and I want to add a smoke particle emitter behind the top of each block to add some smoke rising from the tops of each block. I create a view to hold the block and the particle emitter, and bring the block in front of the subviews so the block is in front. However, this causes my device (iphone 6) to be incredibly laggy and very difficult to move the blocks with a pan gesture.
SmokeParticles.sks: birthrate of 3 (max set to 0), lifetime of 10 (100 range), position range set in code.
My code for adding a particle emitter to each view is below (I'm not very good with particle emitters so any advice is appreciated! :D)
- (void)addEffectForSingleBlock:(BlockView *)view
{
CGFloat spaceBetweenBlocksHeight = (self.SPACE_TO_WALLS * self.view.frame.size.height + self.SPACE_BETWEEN_BLOCKS*self.view.frame.size.width + self.WIDTH_OF_BLOCK*self.view.frame.size.height) - (self.HEIGHT_OF_BLOCK*self.view.frame.size.height + self.SPACE_TO_WALLS * self.view.frame.size.height);
view.alpha = 1.0;
CGRect frame2 = [view convertRect:view.bounds toView:self.view];
UIView * viewLarge = [[UIView alloc] initWithFrame:frame2];
[self.view addSubview:viewLarge];
CGRect frame1 = [view convertRect:view.bounds toView:viewLarge];
view.frame = frame1;
[viewLarge addSubview:view];
SKEmitterNode *burstNode = [self particleEmitterWithName:#"SmokeParticles"];
CGRect frame = CGRectMake(view.bounds.origin.x-self.SPACE_BETWEEN_BLOCKS*self.view.frame.size.width, view.bounds.origin.y-self.SPACE_BETWEEN_BLOCKS_HEIGHT, view.bounds.size.width+self.SPACE_BETWEEN_BLOCKS*self.view.frame.size.width, view.bounds.size.height/2);
SKView *skView = [[SKView alloc] initWithFrame:frame];
[viewLarge addSubview:skView];
SKScene *skScene = [SKScene sceneWithSize:skView.frame.size];
[skScene addChild:burstNode];
[viewLarge bringSubviewToFront:view];
[burstNode setParticlePositionRange:CGVectorMake(skView.frame.size.width/5, skView.frame.size.height/100.0)];
skView.allowsTransparency = YES;
skScene.backgroundColor = [UIColor clearColor];
skView.backgroundColor = [UIColor clearColor];
[skView presentScene:skScene];
[burstNode setPosition:CGPointMake(skView.frame.size.width/2, -skView.frame.size.height*0.25)];
}
I realize that this is an old question, but I recently learned something that could be helpful to others and decided to share it here because it is relevant (I think).
I'll assume your BlockView is a subclass of UIView (if it is not, this will not help you, sorry). A view performs a lot of unnecessary calculations each frame (for example, each view checks if someone tapped on it). When creating a game you should use as fewer UIViews as possible (that's why all other commenters recommended you to use only one SKView and make each Block a SKSpriteNode, which is not a view). But, if you need to use some other kind of object or you do not want to use SpriteKit (or SceneKit for 3D objects), then try using CALayers inside one single UIView (for example, one case where you would prefer to use CALayers instead of SpriteKit is to increase backwards compatibility with older iOS versions as SpriteKit needs iOS 7).
Mr. John Blanco explains the CALayer approach very well in his View vs. Layers (including Clock Demo).
I am very new to Sprite Kit game development. I am currently developing my first game - a simple game where the player must navigate a simple object past obstacles. If the object collides with an obstacle - GAME OVER.
I got that to work fine but I have been stuck for over a week on an animation problem. I've literally searched for an answer for days now ... without success. So basically ... when my object is being navigated past the obstacles I have one animation pattern (that works fine). Once it hits an obstacle however, I want the animation pattern to change (into an explosion). And for some reason that is NOT HAPPENING! :( It is basically completely IGNORING the two lines of code ([self stopBeeAnimation]; and [self defeatedAnimation];) I added and the game is over right away.
Any help I might get on this, is really appreciated (this is driving me nuts) :)
Thank you so much.
Here my code:
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKSpriteNode *firstSprite;
SKSpriteNode *secondSprite;
firstSprite = (SKSpriteNode *)contact.bodyA.node;
secondSprite = (SKSpriteNode *)contact.bodyB.node;
if ((contact.bodyA.categoryBitMask == beeCategory) && (contact.bodyB.categoryBitMask = obstacleCategory)) {
[self stopBeeAnimation];
[self defeatedAnimation];
[obstacleArray removeAllObjects];
[bee.physicsBody setAffectedByGravity:NO];
[timer invalidate];
scoreLabel.fontSize = 30;
scoreLabel.text = [NSString stringWithFormat:#"GAME OVER %d", score/2];
[self removeFromParent];
SKTransition *transition = [SKTransition fadeWithDuration:5];
[self.scene.view presentScene:[[NPMyScene alloc]initWithSize:self.size] transition:transition];
}}
and:
-(void) defeatedAnimation {
SKAction *defeatedAnimation;
NSMutableArray *textures2 =[NSMutableArray arrayWithCapacity:5];
for (int a = 1; a < 6; a++) {
NSString *textureName2 = [NSString stringWithFormat:#"defeated%d", a];
SKTexture *texture2 = [SKTexture textureWithImageNamed:textureName2];
[textures2 addObject:texture2];
}
defeatedAnimation = [SKAction animateWithTextures:textures2 timePerFrame:0.2];
}
Look here:
SKTransition *transition = [SKTransition fadeWithDuration:5];
[self.scene.view presentScene:[[NPMyScene alloc]initWithSize:self.size] transition:transition];
You're presenting a different scene (or maybe another instance of the same scene class?) in the view after trying to run your animations. Any animation changes in the current scene won't be visible because the new scene replaces the current scene.
But even before that...
- (void)defeatedAnimation {
// ...
defeatedAnimation = [SKAction animateWithTextures:textures2 timePerFrame:0.2];
}
You're creating an SKAction but not doing anything with it. For an action to take effect you need to run it on a node with runAction:.
I've got an app that displays photos using NSImage – specifically, -[NSImage drawInRect:fromRect:operation:fraction:]. I want to highlight areas of the photo that are completely burned out (maximum values in all components, pure white) using a color like red, as some digital cameras and image processing apps do, to help the user see whether the image is overexposed, and how badly.
I've been scratching my head as to how to do this. Options I've considered:
I could probably write a Core Image filter to do it; none of the built-in filters look up to the task. That seems like overkill, though; I've been reading through the docs, and it looks fairly complicated. Big learning curve.
I could scan through the bitmap data for the image and modify it as necessary. This is easy enough to code for one bitmap format, but the multitude of bitmap formats make it a rather annoying exercise, and speed is important here, so writing general-purpose code that renders the image up to some maximal common format and works on that bitmap would be too big a speed penalty.
As it happens, I am already scanning through images (handling all the different bitmap formats) at an earlier point in the code, to generate histogram data for the images. I could pretty easily add code at that point that would remember the burned-out pixels for later use. I'm not quite sure what the best way is to do that, though. A 1-bit-per-pixel NSBitmapImageRep? How would I draw it later, making the 1-pixels draw red and the 0-pixels draw transparent, for example? I don't want to make a 32-bit NSBitmapImageRep with an alpha channel and everything just for this purpose, as memory is not infinite and images are large. But there must be a way to draw a 1-bit mask in a given color, somehow.
Before forging ahead with one of these approaches, I thought I'd see whether anybody here has a better idea. Or maybe has implemented the CI filter in question already? Apart from the learning curve, that seems like the best approach I've thought of so far – no memory overhead, and probably faster than other options, too.
Thanks...
Ben Haller
Stick Software
OK, I implemented my own Core Image filter to do this. Wasn't as hard as I expected, although the documentation is not great for this stuff. The doc examples all assume you're using ARC, so if you're not, following those examples will give you various retain/release bugs. There was also a little weirdness with the CIFilterConstructor stuff, which did not quite go as documented. But overall pretty easy. CI is cool. My code is below, for anybody who might find it useful:
Header:
#import
#interface SSTintHighlightsFilter : CIFilter
{
CIImage *inputImage;
CIColor *highlightColor;
}
#end
Implementation file:
#import "SSTintHighlightsFilter.h"
static CIKernel *tintHighlightsFilter = nil;
#implementation SSTintHighlightsFilter
+ (void)initialize
{
[CIFilter registerFilterName:#"SSTintHighlightsFilter" constructor:(id )self
classAttributes:[NSDictionary dictionaryWithObjectsAndKeys:#"Tint Highlights", kCIAttributeFilterDisplayName, [NSArray arrayWithObjects:kCICategoryColorAdjustment, kCICategoryStillImage, nil], kCIAttributeFilterCategories, nil]];
}
+ (CIFilter *)filterWithName:(NSString *)name
{
CIFilter *filter = [[self alloc] init];
return [filter autorelease];
}
- (id)init
{
if (!tintHighlightsFilter)
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource:#"tintHighlightsAndShadows" ofType:#"cikernel"] encoding:NSASCIIStringEncoding error:NULL];
NSArray *kernels = [CIKernel kernelsWithString:code];
tintHighlightsFilter = [[kernels objectAtIndex:0] retain];
}
return [super init];
}
- (NSDictionary *)customAttributes
{
NSDictionary *attrs = #{
#"highlightColor" : #{ kCIAttributeClass : [CIColor class], kCIAttributeType : kCIAttributeTypeOpaqueColor }
};
return attrs;
}
- (CIImage *)outputImage
{
CISampler *src = [CISampler samplerWithImage:inputImage];
return [self apply:tintHighlightsFilter
arguments:[NSArray arrayWithObjects:src, highlightColor, nil]
options:[NSDictionary dictionaryWithObjectsAndKeys:[src definition], kCIApplyOptionDefinition, nil]];
}
#end
tintHighlights.cikernel:
kernel vec4 tintHighlights(sampler inputImage, __color highlightColor)
{
vec4 originalColor, tintedColor;
float sum;
// fetch the source pixel
originalColor = sample(inputImage, samplerCoord(inputImage));
// calculate the color component sum as a way of testing whether we are black or white
sum = originalColor.r + originalColor.g + originalColor.b;
// replace pixels that are white with the highlight color
tintedColor = (sum > 2.99999999999999999999999) ? highlightColor : originalColor;
// preserve alpha
tintedColor.a = originalColor.a;
return tintedColor;
}
using the filter:
+ (NSImage *)showHighlightsInImage:(NSImage *)img dstRect:(NSRect)dstRect
{
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
NSRect dstRectForCGImage = dstRect; // because the method below wants a pointer, and I don't trust it not to modify my rect...
CGImageRef cgImage = [img CGImageForProposedRect:&dstRectForCGImage context:currentContext hints:nil];
CIImage *inputImage = [[CIImage alloc] initWithCGImage:cgImage];
[SSTintHighlightsFilter class]; // get my filter initialized
CIFilter *highlightFilter = [CIFilter filterWithName:#"SSTintHighlightsFilter"];
[highlightFilter setValue:inputImage forKey:#"inputImage"];
[highlightFilter setValue:[CIColor colorWithRed:1.0 green:0.0 blue:0.0] forKey: #"highlightColor"];
[inputImage release];
CIImage *outputImage = [highlightFilter valueForKey:#"outputImage"];
NSImage *resultImage = [[NSImage alloc] initWithSize:[img size]];
[resultImage addRepresentation:[NSCIImageRep imageRepWithCIImage:outputImage]];
return [resultImage autorelease];
}
I'm not sure that I'm handling the alpha entirely robustly, with premultiplication issues and so forth, but apart from that possible glitch it is working great.
I use the following method to display the labels for my plot:
-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index{
...
CPTTextLayer *label=[[CPTTextLayer alloc] initWithText:stringValue style:textStyle];
}
which for every index should return the label
I know that it's possible to move label up or down using:
plot.labelOffset=10;
The question is: how can i move the label a bit to the right?
I tried to use
label.paddingLeft=50.0f;
but it doesn't work.
Adding padding as in your example does work, but maybe not in the way you expect. Scatter and bar plots will center the label above each data point (with a positive offset). The padding makes the whole label wider so when centered, the test appears off to the side. It's hard to control, especially if the label texts are different lengths.
There is an outstanding issue to address this (issue 266). No guarantees when it will be fixed, but it is something we're looking at.
I ran into the same problem and came up with a different solution.
What I decided to do was to create the label using the CPTAxisLabel method initWithContentLayer:
CPTTextLayer *textLayer = [[CPTTextLayer alloc] initWithText:labelStr style:axisTextStyle];
CGSize textSize = [textLayer sizeThatFits];
// Calculate the padding needed to center the label under the plot record.
textLayer.paddingLeft = barCenterLeftOffset - textSize.width/2.0;
CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithContentLayer:textLayer];
Here barCenterLeftOffset is the offset of the center of the plot record.
I wrote an article about this:
http://finalize.com/2014/09/18/horizontal-label-positioning-in-core-plot-and-other-miscellaneous-topics/
A demo project I created that uses this solution can be found at:
https://github.com/scottcarter/algorithms
You can subclass CPTTextLayer and include an offset.
#interface WPTextLayer : CPTTextLayer
#property (nonatomic) CGPoint offset;
#end
#implementation WPTextLayer
-(void)setPosition:(CGPoint)position
{
CGPoint p = CGPointMake(position.x + self.offset.x, position.y + self.offset.y);
[super setPosition:p];
}
Then Use:
WPTextLayer *tLayer = [[WPTextLayer alloc] initWithText:#"blah" style:textStyle];
tLayer.offset = CGPointMake(3, -3);
return tLayer;
There may be consequences of this that I'm not aware of, but it seems to be working so far.
I created a simple, 2 frame sprite animation based off a few tutorials that I found across the web. I took my 2 images, created a sprite sheet with plist and png file, and incorporated them into my code as seen below. This setup worked fine in Cocos2d V 1.0.1. I just upgraded my project to 2.0rc0a and now my app crashes with the following error at the point where it should switch from the first frame to the second: 'CCSprite: setTexture doesn't work when the sprite is rendered using a CCSpriteBatchNode'
I looked at this SO question, but I'm not sure if it's the same thing that I'm doing wrong, and because I'm still very new to Cocos2d, am unsure how to correctly adjust my code. Is this something that was changed in 2.0 that I didn't see in the notes, a bug that I should report, or just incorrect coding on my part? I still have a copy of the 1.0.1 project with identical code and the animation works correctly.
//CHAMELEON
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"front page/mainchameleon.plist"];
mainChameleon = [CCSpriteBatchNode batchNodeWithFile:#"front page/mainchameleon.png"];
[self addChild:mainChameleon z:7];
NSMutableArray *chameleonFrames = [NSMutableArray array];
for (int i = 1; i <= 2; ++i)
{
[chameleonFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"chameleon%d.png", i]]];
}
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.3f];
chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, chameleonDelay, nil]];
[chameleon runAction:chameleonRepeat];
[mainChameleon addChild:chameleon];
If I comment out chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"]; then the app doesn't crash, but the chameleon doesn't appear at all, as would be expected with how the code is currently written.
Or, if I comment out [chameleon runAction:chameleonRepeat]; then the chameleon appears showing the frame, chameleon1.png, but obviously doesn't go through the animation.
OK, so to make me even more confused because I'm definitely missing something, I tried changing the bottom portion of the code to this and the animation goes from frame 1, to frame 2, then stays on 2 indefinitely. However, if I make the delay anything longer than 1.0, I receive the same error I was getting before. If I re-include chameleonDelay prior to the action without the repeat forever statement, I also get the same crash. It appears that the app crashes if it has to wait longer than 1 second to perform the switch. What I need is for frame 1 to sit for a while (10 seconds) then switch to frame 2 for 0.3 seconds, then switch back to frame 1 and sit for a while again.
Attempted code #2:
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.3f]; //<--- maxes out at 1.0. Anything more causes crash
chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
[chameleon runAction:chameleonAction];
[mainChameleon addChild:chameleon];
YvesLeBorg suggested using the restoreOriginalFrame statement, but that is deprecated in ver 2.0. I tried using
CCAnimation *mouthAnim = [CCAnimation animationWithAnimationFrames:chameleonFrames delayPerUnit:0.3f loops:5];
and get the error '-[CCSpriteFrame delayUnits]: unrecognized selector sent to instance'. I'm not sure why that isn't working or what else to try from here.
EDIT: So Now It's Working...But not as efficiently coded as I'd like:
New Code:
//CHAMELEON
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"front page/mainchameleon.plist"];
mainChameleon = [CCSpriteBatchNode batchNodeWithFile:#"front page/mainchameleon.png"];
[self addChild:mainChameleon z:7];
NSMutableArray *chameleonFrames = [NSMutableArray array];
//Frame 1 - closed mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 124, 149, 122)]];
//Frame 2 - Open Mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 0, 149, 122)]];
//Frame 1 - closed mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 124, 149, 122)]];
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.9f];
chameleon = [CCSprite spriteWithTexture:mainChameleon.texture rect:CGRectMake(0,124,149,122)];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, nil]];
[chameleon runAction:chameleonRepeat];
[mainChameleon addChild:chameleon];
I really liked the way that I was doing it in 1.0.1 because if I had 2 frames or 100 frames, I only had to make a small adjustment to the if statement. This way requires coding each individual frame which seems counter-intuitive to using a plist. If no one is able to provide a better solution, or "real" answer in the next few days, I will post this as an answer and accept it to close out the question.
Here's the code I ended up with that works correctly. Not sure why I had to make these changes, but there it is. (Some of the names have changed as I started a new project to fix this on, but the differences between my original code and this are apparent). To anyone else finding this thread, my original code was based on Ray Wenderlich's sprite sheet tutorial.
CCSpriteBatchNode *chameleonBN = [CCSpriteBatchNode batchNodeWithFile:#"chameleonimages.png"];
[self addChild:chameleonBN];
//ADDED the texture part to resolve: 'CCSprite: setTexture doesn't work when the sprite is rendered using a CCSpriteBatchNode'
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"chameleonplist.plist" texture:chameleonBN.texture];
NSMutableArray *chameleonframes = [NSMutableArray array];
for (int i = 1; i <= 2 ; i++)
{
[chameleonframes addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"chameleon%d.png", i]]];
}
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonframes delay:0.9f];
//ADDED this sprite frame to resolve texture id assert error
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:chameleonBN.texture rect:CGRectMake(0, 0, 149, 122)];
CCSprite *chameleon = [CCSprite spriteWithSpriteFrame:frame];
chameleon.position = ccp(512,384);
CCAnimate *chameleonAnimate = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCDelayTime *chameleonDelay2 = [CCDelayTime actionWithDuration:0.1];//Had to use this to ge tthe mouth to close. Using restore original frame doesn't work for me.
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAnimate, chameleonDelay2, nil]];
[chameleon runAction:chameleonRepeat];
[chameleonBN addChild:chameleon];
Not certain whether this is a 2.0 issue or not, but i notice you are using twice the chameleonDelay object in the same sequence. Is this really what you were trying to accomplish ie
delay,action,delay,delay,action,delay,delay,action .... etc.
try this to see if it works :
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, nil]];