In my game I'm using infinite scrolling background.
This is my code:
- (void)setupBackground {
CGSize winSize = [CCDirector sharedDirector].winSize;
// 1) Create the CCParallaxNode
_backgroundNode = [CCParallaxNode node];
[self addChild:_backgroundNode z:-2];
// 2) Create the sprites you’ll add to the
// CCParallaxNode
_spacedust1 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_spacedust2 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_planetsunrise = [CCSprite spriteWithFile:#"bg_planetsunrise.png"];
_galaxy = [CCSprite spriteWithFile:#"bg_galaxy.png"];
_spacialanomaly = [CCSprite spriteWithFile:#"bg_spacialanomaly.png"];
_spacialanomaly2 = [CCSprite spriteWithFile:#"bg_spacialanomaly2.png"];
// 3) Determine relative movement speeds for space dust
// and background
CGPoint dustSpeed = ccp(0.1, 0.1);
CGPoint bgSpeed = ccp(0.05, 0.05);
// 4) Add children to CCParallaxNode
[_backgroundNode addChild:_spacedust1 z:0
parallaxRatio:dustSpeed
positionOffset:ccp(0,winSize.height/2)];
[_backgroundNode addChild:_spacedust2 z:0
parallaxRatio:dustSpeed
positionOffset:ccp(_spacedust1.contentSize.width*
_spacedust1.scale, winSize.height/2)];
[_backgroundNode addChild:_galaxy z:-1
parallaxRatio:bgSpeed
positionOffset:ccp(0,winSize.height * 0.7)];
[_backgroundNode addChild:_planetsunrise z:-1
parallaxRatio:bgSpeed
positionOffset:ccp(600,winSize.height * 0)];
[_backgroundNode addChild:_spacialanomaly z:-1
parallaxRatio:bgSpeed
positionOffset:ccp(900,winSize.height * 0.3)];
[_backgroundNode addChild:_spacialanomaly2 z:-1
parallaxRatio:bgSpeed
positionOffset:ccp(1500,winSize.height * 0.9)];
}
It works very well.
But I want change during the game this background image with another one, for example for LV 2.
How can I do this? I tried to create another function setupBackground2, with another image, but it doesn't work.
Change the texture of the existing sprite(s) using setTexture: after loading/getting the texture from CCTextureCache addImage: method.
Related
CCSprite *sprite = [[CCSprite alloc] init];
CCDrawNode *edge = [CCDrawNode node];
edge.anchorPoint = ccp(0, 0);
edge.position = ccp(0, 0);
CGPoint verts[4] =
{
ccp(0, 0),
ccp(37, 0),
ccp(37, 37),
ccp(0, 37)
};
[edge drawPolyWithVerts:verts count:4 fillColor:nil borderWidth:1.0f borderColor:[CCColor whiteColor]];
edge.name = #"edge";
[sprite addChild:edge];
After I add edge around sprite, I can't remove them.
NSLog(#"Children: %#", self.children);
[self removeChildByName:#"edge" cleanup:YES];
NSLog(#"Children: %#", self.children);
When I use method of "removeChild", I found this method can remove the edge from sprite's children list, but the edge also is shown on the iPhone's screen.
I'll appreciate your help.
I have a regular SpriteKit scene. In fact it is the default scene created from "Sprite Kit" which draws spaceships.
I would like to draw on the background.
I have successfully added a UIImageView as a subview of the main skView. I can draw on this with lines and such.
But I can't seem to succesfully put the UIImageView behind the main skview.
How can I do this? Here is my sample code.
(In the UIImageView I'm just drawing random lines every 0.1 seconds.)
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// skView.showsFPS = YES;
// skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
//skView.ignoresSiblingOrder = YES;
// Create and configure the scene.
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
-(void)viewWillLayoutSubviews{
self.imageView = [UIImageView new];
self.imageView.frame = self.view.frame;
[self.view addSubview:self.imageView];
[self startRepeatingTimer];
}
-(void)viewDidLayoutSubviews{
// this doesn't work. was just trying it out.
int i = [[self.view subviews] count];
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:i-1];
}
///// DRAW RANDOM LINES EVERY 0.1 SECONDS ON THE UIIMAGEVIEW SUBVIEW
- (IBAction)startRepeatingTimer {
[self.repeatingTimer invalidate];
self.repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(drawOnImage:) userInfo:nil repeats:YES];
}
- (void)drawOnImage:(NSTimer*)theTimer {
//setup
float w = self.view.frame.size.width;
float h = self.view.frame.size.height;
UIGraphicsBeginImageContextWithOptions(self.view.frame.size,NO,0.0);
[self.imageView.image drawInRect:CGRectMake(0, 0, w, h)];
CGContextRef c = UIGraphicsGetCurrentContext();
//draw
CGContextMoveToPoint(c, drand48()*w,drand48()*h);
CGContextAddLineToPoint(c, drand48()*w,drand48()*h);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetLineWidth(c, 1.0f );
CGContextSetRGBStrokeColor(c, drand48(), drand48(), drand48(), 1.0);
CGContextStrokePath(c);
// set the image
self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Here's a SpriteKit-friendly way to draw random lines on a background image. The basic steps are
Create a blank image
Convert the image to an SKTexture
Create a sprite with the texture
Draw a random line on the image
Update the sprite's texture with the image
Wait for 0.1 seconds
Iterate over steps 4-6
In GameScene.m, add the following
-(void)didMoveToView:(SKView *)view {
self.scaleMode = SKSceneScaleModeResizeFill;
// 1. Create an empty UIImage
self.image = [self blankImageWithSize:view.frame.size];
// 2. Convert the image to a texture
SKTexture *texture = [SKTexture textureWithImage:_image];
// 3. Create a sprite from the texture
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:texture];
// Center the sprite in the view
sprite.position = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view..frame));
// Set zPosition such that the sprite is underneath other nodes
sprite.zPosition = -1000;
[self addChild:sprite];
self.sprite = sprite;
// 4. and 5. Create an SKAction that draw lines on the image and then update the
// sprite's texture
SKAction *drawAction = [SKAction runBlock:^{
[self drawOnImage];
_sprite.texture = [SKTexture textureWithImage:_image];
}];
// 6. create a 0.1 second delay
SKAction *wait = [SKAction waitForDuration:0.1];
SKAction *action = [SKAction sequence:#[drawAction,wait]];
// 7. Iterate
[self runAction:[SKAction repeatActionForever:action]];
// Create a label and add it to the scene to show the image is
// in the background
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 65;
myLabel.position = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view..frame));
[self addChild:myLabel];
}
This method creates a blank image
- (UIImage*) blankImageWithSize:(CGSize)size
{
UIGraphicsBeginImageContext(size);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
This method draws lines on the image
- (void)drawOnImage {
//setup
float w = _image.size.width;
float h = _image.size.height;
UIGraphicsBeginImageContextWithOptions(_image.size,NO,0.0);
[_image drawInRect:CGRectMake(0, 0, w, h)];
CGContextRef c = UIGraphicsGetCurrentContext();
//draw
CGContextMoveToPoint(c, drand48()*w,drand48()*h);
CGContextAddLineToPoint(c, drand48()*w,drand48()*h);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetLineWidth(c, 1.0f );
CGContextSetRGBStrokeColor(c, drand48(), drand48(), drand48(), 1.0);
CGContextStrokePath(c);
// set the image
_image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
I want to animate the width of my button in my Mac App and at the same time have a background image and a foreground image. For that I'm using #kgn's awesome library BBlock (https://github.com/kgn/BBlock).
Problem is that the background image seems to get drawn behind each other, so when the button scales down, you can't even see the background image animation and it seems to be cut off.
This way of "animating" the width change works if I just use setImage, but then I don't get the benefits of having a background image.
I've made a custom button that scales the button incrementally (basically animates it). It changes the size of the button by one pixel each run and changes the image, background image and alternate background image by one pixel as well:
- (void)scaleTestButtonUntilWidth:(int)width{
[NSTimer scheduledTimerRepeats:YES withTimeInterval:timeInterval andBlock:^{
if (self.width == width) {
return;
}
int newSize;
if (width > self.width) {
newSize = self.width += 1;
}else if (width < self.width){
newSize = self.width -= 1;
}
self.width = newSize;
[self setImage:self.image];
[self setAlternateBackgroundImage:[self alternateBGImage]];
[self setBackgroundImage:[self BGImage]];
}];
}
The setBackgroundImage looks like this and the implementation for setAlternateBackgroundImage is the same:
- (void)setBackgroundImage:(NSImage *)backgroundImage{
[self setImage:[self imageWithBackgroundImage:backgroundImage
andIcon:self.image]];
[self setButtonType:NSMomentaryChangeButton];
[self setBordered:NO];
}
Which calls the method that actually draws the image:
- (NSImage *)imageWithBackgroundImage:(NSImage *)background andIcon:(NSImage *)icon{
return [NSImage imageForSize:background.size withDrawingBlock:^{
NSRect bounds = NSZeroRect;
bounds.size = background.size;
NSRect iconRect = NSZeroRect;
iconRect.size = icon.size;
iconRect.origin.x = round(background.size.width*0.5f-iconRect.size.width*0.5f);
iconRect.origin.y = round(background.size.height*0.5f-iconRect.size.height*0.5f);
[background drawInRect:bounds fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
[icon drawInRect:iconRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}];
}
With the help of this method:
+ (NSImage *)imageForSize:(NSSize)size withDrawingBlock:(void(^)())drawingBlock{
if(size.width <= 0 || size.width <= 0){
return nil;
}
NSImage *image = [[NSImage alloc] initWithSize:size];
[image lockFocus];
drawingBlock();
[image unlockFocus];
#if !__has_feature(objc_arc)
return [image autorelease];
#else
return image;
#endif
}
I wonder if you would be better off subclassing NSButton and overwriting drawRect
Maybe try saving and restoring the graphics context.
return [NSImage imageForSize:background.size withDrawingBlock:^{
NSRect bounds = NSZeroRect;
bounds.size = background.size;
NSRect iconRect = NSZeroRect;
iconRect.size = icon.size;
iconRect.origin.x = round(background.size.width*0.5f-iconRect.size.width*0.5f);
iconRect.origin.y = round(background.size.height*0.5f-iconRect.size.height*0.5f);
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
[background drawInRect:bounds fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
[icon drawInRect:iconRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
CGContextRestoreGState(context);
}];
Not really sure why I am getting this error but I have looked in the .h file and I can't seem to come up with the reason xCode is throwing this error:
// on "init" you need to initialize your instance
-(id) init
{
if ((self=[super init])) {
_batchNode = [CCSpriteBatchNode batchNodeWithFile:#"Sprites.pvr.ccz"]; //1
[self addChild:_batchNode]; //2
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"Sprites.plist"]; //3
_ship = [CCSprite spriteWithSpriteFrameName:#"SpaceFlier_sm_1.png"]; //4
CGSize winSize = [CCDirector sharedDirector].winSize; //5
_ship.position = ccp(winSize.width * 0.1, winSize.height * 0.5); //6
[_batchNode addChild:_ship z:1]; //7
}
return self;
// 1) Create the CCParallaxNode
_backgroundNode = [CCParallaxNode node];
[self addChild:_backgroundNode z:-1];
// 2) Create the sprites we'll add to the CCParallaxNode
_spacedust1 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_spacedust2 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_planetsunrise = [CCSprite spriteWithFile:#"bg_planetsunrise.png"];
_galaxy = [CCSprite spriteWithFile:#"bg_galaxy.png"];
_spacialanomaly = [CCSprite spriteWithFile:#"bg_spacialanomaly.png"];
_spacialanomaly2 = [CCSprite spriteWithFile:#"bg_spacialanomaly2.png"];
// 3) Determine relative movement speeds for space dust and background
CGPoint dustSpeed = ccp(0.1, 0.1);
CGPoint bgSpeed = ccp(0.05, 0.05);
// 4) Add children to CCParallaxNode
[_backgroundNode addChild:_spacedust1 z:0 parallaxRatio:dustSpeed positionOffset:ccp(0,winSize.height/2)];
[_backgroundNode addChild:_spacedust2 z:0 parallaxRatio:dustSpeed positionOffset:ccp(_spacedust1.contentSize.width,winSize.height/2)];
[_backgroundNode addChild:_galaxy z:-1 parallaxRatio:bgSpeed positionOffset:ccp(0,winSize.height * 0.7)];
[_backgroundNode addChild:_planetsunrise z:-1 parallaxRatio:bgSpeed positionOffset:ccp(600,winSize.height * 0)];
[_backgroundNode addChild:_spacialanomaly z:-1 parallaxRatio:bgSpeed positionOffset:ccp(900,winSize.height * 0.3)];
[_backgroundNode addChild:_spacialanomaly2 z:-1 parallaxRatio:bgSpeed positionOffset:ccp(1500,winSize.height * 0.9)];
}
First off, unless C2D somehow magically changes the way return works, you proceed to do a lot of stuff after returning which can never be reached. You've just returned; you've exited the method. It's like telling the painter to leave the room he's working on and go home for the day... then screaming out to nothingness that he should paint the walls red. Actually, you wouldn't get to scream at all. Here's an example:
- (void) foo {
int x = 10;
return; //return control to caller
self->someValue = x; //never done; we've returned
}
x would be assigned ten and then you would return. Any lines afterwards are meaningless. Second of all, there's this line here.
CGSize winSize = [CCDirector sharedDirector].winSize;
It occurs within an if statement. The variable winSize can only be used within the constraints of the if statement you declared it in. It's "out of scope" of anything else. For example, the following will give the same warning.
- (void) bar {
if (0){
int x = 1;
}
int y = x;
}
Why not place all the stuff below return inside the if loop, right before it closes?
I'm building an app that has a main menu layer and a background layer (for gameplay). I am loading the exact same CCParallax node code in both places, using the same images to create an infinite scrolling background.
On the gameplay background layer, the background displays and scrolls appropriately. However, on the main menu layer, the backgrounds are scaled weird (they don't take up the entire screen), and also don't scroll appropriately (probably because the scale is off somehow, so the offset isn't working).
Here is a screenshot of what the background looks like on the main menu:
And here it is on the background layer (green lines are part of the gameplay):
It almost looks like the menu layer is adding the non-retina display version of the files, but if I load the simulator into the iPhone (non-retina) mode, the meny background is still way off:
The code that loads the two is pretty much identical.
Here is the main menu loader code:
-(id)init {
self = [super init];
if (self != nil) {
CGSize winSize = [CCDirector sharedDirector].winSize;
_backgroundNode = [CCParallaxNode node];
[self addChild:_backgroundNode z:-1];
_backgroundGrid1 = [CCSprite spriteWithFile:#"grid.png"];
_backgroundCircuits1 = [CCSprite spriteWithFile:#"bg-circuits.png"];
_backgroundGrid1.anchorPoint = CGPointMake(0,0);
_backgroundCircuits1.anchorPoint = CGPointMake(0,0);
CGPoint gridSpeed = ccp(0.05, 0.05);
CGPoint circuitSpeed = ccp(0.1, 0.1);
[_backgroundNode addChild:_backgroundGrid1 z:1 parallaxRatio:gridSpeed positionOffset:ccp(0,-winSize.height)];
[_backgroundNode addChild:_backgroundCircuits1 z:0 parallaxRatio:circuitSpeed positionOffset:ccp(0,-winSize.height)];
[self scheduleUpdate];
}
return self;
}
- (void)update:(ccTime)dt {
CGPoint backgroundScrollVel = ccp(0, 1000);
_backgroundNode.position = ccpAdd(_backgroundNode.position, ccpMult(backgroundScrollVel, dt));
NSArray *backgroundGrids = [NSArray arrayWithObjects:_backgroundGrid1, nil];
for (CCSprite *b in backgroundGrids) {
if ([_backgroundNode convertToWorldSpace:b.position].y > 0) {
[_backgroundNode incrementOffset:ccp(0,-(b.contentSize.height/3)) forChild:b];
}
}
NSArray *backgroundCircuits = [NSArray arrayWithObjects:_backgroundCircuits1, nil];
for (CCSprite *bc in backgroundCircuits) {
if ([_backgroundNode convertToWorldSpace:bc.position].y > 0) {
[_backgroundNode incrementOffset:ccp(0,-(bc.contentSize.height/3)) forChild:bc];
}
}
}
And here is the code for the background layer:
- (id)init
{
self = [super init];
if (self != nil) {
CGSize winSize = [CCDirector sharedDirector].winSize;
_backgroundNode = [CCParallaxNode node];
[self addChild:_backgroundNode z:-1];
_backgroundGrid1 = [CCSprite spriteWithFile:#"grid.png"];
_backgroundCircuits1 = [CCSprite spriteWithFile:#"bg-circuits.png"];
_backgroundGrid1.anchorPoint = CGPointMake(0,0);
_backgroundCircuits1.anchorPoint = CGPointMake(0,0);
CGPoint gridSpeed = ccp(0.05, 0.05);
CGPoint circuitSpeed = ccp(0.1, 0.1);
[_backgroundNode addChild:_backgroundGrid1 z:1 parallaxRatio:gridSpeed positionOffset:ccp(0,-winSize.height)];
[_backgroundNode addChild:_backgroundCircuits1 z:0 parallaxRatio:circuitSpeed positionOffset:ccp(0,-winSize.height)];
[self scheduleUpdate];
return self;
}
- (void)update:(ccTime)dt {
CGPoint backgroundScrollVel = ccp(0, 1000);
_backgroundNode.position = ccpAdd(_backgroundNode.position, ccpMult(backgroundScrollVel, dt));
NSArray *backgroundGrids = [NSArray arrayWithObjects:_backgroundGrid1, nil];
for (CCSprite *b in backgroundGrids) {
if ([_backgroundNode convertToWorldSpace:b.position].y > 0) {
[_backgroundNode incrementOffset:ccp(0,-(b.contentSize.height/3)) forChild:b];
}
}
NSArray *backgroundCircuits = [NSArray arrayWithObjects:_backgroundCircuits1, nil];
for (CCSprite *bc in backgroundCircuits) {
if ([_backgroundNode convertToWorldSpace:bc.position].y > 0) {
[_backgroundNode incrementOffset:ccp(0,-(bc.contentSize.height/3)) forChild:bc];
}
}
}