all.
I am now developing an ios game using sprite kit.The game mechanics include: infinitely scroll sprites from top to bottom.
My game scene holds just 3 sprites,top,middle,bottom.
I have dozens of sprite images,so my solution is dynamic create 3 sprites ( so the sprites can fill the whole screen),when the bottom sprite is off screen,then destroy it;when the top sprite
goes down the top frame,create a new sprite node,then exchange the bottom,middle,top sprite.
The pseudo code:
in the interface file:
- GameScene:SKScene
{
...
SKSpriteNode *_topSprite;
SKSpriteNode *_middleSprite;
SKSpriteNode *_bottomSprite;
...
}
in the implementation file:
- (void)update:(CFTimeInterval)currentTime
{
// 1 compute time interval
// 2 update sprite node
_topSprite.position move down 100*timeInterval
_middleSprite.position move down 100*timeInterval
_bottomSprite.position move down 100*timeInterval
// 3 crop the off-screen bottom sprite node
if (_bottomSprite is offScreen)
{
[_bottomSprite removeFromParent]
}
// 4 check the highest sprite position visible
// if the highest position is below the frame top,then create new sprite
_bottomSprite = _middleSprite;
_middleSprite = _topSprite;
_topSprite = [self createNewNode]; // random a sprite image
}
the game fps is 60,and the nodes counts is not increasing.
All seems good,but i found sometimes the game just suddenly has a very shot choke causing the moving frame not continuous.
In my instinct,I thought the main reason is caused by step 4.
But I don't have any idea to solve it.
Any suggestions will be appreciated.
Here is the createNewNode method:
- (SKSpriteNode *)createNewNode {
NSString *blockName = [self randomImageName];
SKSpriteNode *sprite = [[SKSpriteNode alloc] initWithImageNamed:blockName];
sprite.position = _highestPosition;
sprite.anchorPoint = CGPointMake(0.5, 0);
NSLog(#"putAnotherNodeOnTop %#", blockName);
[self addChild:sprite];
return sprite;
}
A couple of suggestions.
Since you are using the same three images, you don't need to re-create the sprites each time. It should be less expensive to set the hidden value to YES, move the sprite somewhere, set the texture and then show it again with hidden to NO. SpriteKit doesn't draw hidden nodes, or ones off screen.
Your code basically asks for a PNG on disk every frame in update when createNewNode: is called. It should be more performant to load the textures in memory and assign them to sprites as needed. If you have just a few, making them SKTexture instance variables would be an easy way to do it.
Use spritesheets (texture atlases), it is much faster to draw an image from memory at mapped coordinates than to load it from disk each time. For example with three images in a spritesheet this can be a single draw call. From disk this is three separate draw calls.
You are returning a sprite and adding it to the scene in the createNewNode: method. Maybe that is what you want for some reason. It stood out as possible duplicate effort.
Related
I am writing a game in which the user must prevent falling objects (rocks) to pass the button of the screen using some plates, each click make a plate at that position but he can only have 5 plates at each time and clicking on sixth point vanished the first plate while creating a new one.
That was a brief on how my game mechanics work.
Here is the part of my code that counts falling objects(sprites) using physics and if they became more than 10 a game over message will be shown and the code restarts the game, a new game. to have a new game I need to
-(void)didSimulatePhysics
{
[self enumerateChildNodesWithName:#"rock" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
{
ScoreNum++;
[node removeFromParent];
score.text = [NSString stringWithFormat:#"%# %li",ScoreMSG,(long)ScoreNum];
if (ScoreNum > 10)
{
score.fontSize = 40;
score.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
score.text = #"Game Over!";
SKScene *spaceshipScene = [[GameScene alloc] initWithSize:CGRectMake(0, 0,CGRectGetMaxY(self.frame),CGRectGetMaxX(self.frame)).size];
printf("%f %f",CGRectGetMidX(self.frame),CGRectGetMidX(self.frame));
SKTransition *doors = [SKTransition doorsOpenVerticalWithDuration:0.25];
[self.view presentScene:spaceshipScene transition:doors];
ScoreNum = 0;
}
}
}];
}
GameScene is subclassed from SKScene like this
#import <SpriteKit/SpriteKit.h>
#interface GameScene : SKScene
#end
The code works find but the plates (all of the sprites on the screen) gets squeezed from sides
The following picture shows the results, first time, second time and third time, I needed 10 reputation to include 3 pictures so I had to merge them all into one picture, Here it is.
https://www.imageupload.co.uk/images/2015/08/30/123.png
Sorry, looks like I can not embed an image since I have not enough reputation!
Why each my scene squeezes?
Looks like you set a scaleMode in your viewController.viewDidLoad that handles your game scene, nothing wrong with your physics code (in your question).
look for something like this scene.scaleMode = SKSceneScaleModeAspectFill usually SKScenes are automatically scaled based on the screen size of the device and it is upto SKScene to provide you a scene.
Your scenes in your pictures shows that scenes have a complete and full cover but they are scaled.
It is kind of hard to handle sprite size while do not controling scale. I suggest to get ride of every Scale Mode and remark them all and control the scale your self.
if you are not going to zoom in/out as I believe it is the case in your game you just need to reset the scenes scale to 1 each time after each scene transition.
I am using Quartz 2D to make a simple multi-touch drawing iPad game. The game requires me to draw a new stroke at the finger position every 1/30th of a second.
As far as I know, there is basically no way to get drawRect() to not clear the context every time it is called (self.clearsContextBeforeDrawing = NO; does not work), so my solution was to create a back buffer bitmap (or layer, I can use both), draw every new small stroke into that back buffer every iteration for each finger, and then copy the buffer into the screen every call to drawRect(). In other words:
backlayer = CGLayerCreateWithContext(context, CGSizeMake(W, H), NULL);
offctx = CGLayerGetContext (backlayer);
and then
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
//code here to draw small strokes from old finger position to new one
CGContextDrawLayerInRect(context, [self bounds], backlayer);
}
This worked without problems while I was testing on my iPad 2, but yesterday I noticed that this same code runs much slower on the new iPad 3. The performance is abysmal, slowing my game down from 30FPS all the way to about 5 or so, probably due to the larger, retina display. I have the same problem if I use a separate CGBitmapContext that I create, and then every iteration I create an ImageRef from it and paint it with CGContextDrawImage.
What approach could I take to address this? It seems like I must redraw everything every iteration since it's not good enough to even pass a small rectange to drawRect of what has changed (since every iteration there would need to be several rectangles for each finger)
Thank you
I managed to resolve this as follows:
I create a new UIView subclass header and implementation files:
#interface fingerView : UIView {
}
Then in my main view, in header I declare 5 of these views:
fingerView* fview[5];
and in my main view implementation I create 5 views of this instance, one for each finger separately. Also, must make sure to make them, enable multitouch for each of them, and make sure that clearsContextBeforeDrawing is set to NO, as we will be updating tiny rects in each of them at a time, and we dont want the system to clear our work.
for(int i=0;i<5;i++) {
fview[i] = [[pView alloc] initWithFrame:topFrame];
[self addSubview: fview[i]];
[self sendSubviewToBack: fview[i]];
fview[i].opaque= NO;
fview[i].clearsContextBeforeDrawing = NO;
fview[i].multipleTouchEnabled = YES;
}
Now inside every finger view keep a large array (i use a simple array, say 10,000 long) of x and y positions that the finger had drawn on. Whenever a finger moves, the main view detects it, and calls a [fview[i] updatePos(newx, newy)], and crucially, we will command the view to only update a tiny potion of itself around these coordinates:
[fview[i] setNeedsDisplayInRect: fingerRect];
where fingerRect is a small rect centered at (newx, newy). Inside the drawRect method for every finger view,
- (void)drawRect:(CGRect)rect
{
if (movep==0) return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, r, g, b, 1);
CGContextSetLineWidth(context, linewidth);
//paint finger
CGContextBeginPath(context);
CGFloat slack= 15;
CGFloat minx= CGRectGetMinX(rect)-slack;
CGFloat maxx= CGRectGetMaxX(rect)+slack;
CGFloat miny= CGRectGetMinY(rect)-slack;
CGFloat maxy= CGRectGetMaxY(rect)+slack;
bool drawing = NO;
for(int i=0;i<movep;i++) {
CGFloat xx= x[i];
CGFloat yy= y[i];
if(xx>minx && xx<maxx && yy>miny && yy<maxy) {
if(drawing) {
// continue line
CGContextAddLineToPoint(context, xx, yy);
CGContextMoveToPoint(context, xx, yy);
} else {
// start drawing
CGContextMoveToPoint(context, xx, yy);
drawing= YES;
}
} else {
drawing= NO;
}
}
CGContextStrokePath(context);
and also, as I mentioned
- (void)updatePos: (CGFloat)xnew: (CGFloat) ynew
{
x[movep]= xnew;
y[movep]= ynew;
movep= movep+1;
Hopefully you can figure out how this works. Every view will look into this rectangle that has been modified, and checks all finger positions that went around that rect, and only draws those. This will come down to very few strokes, and so the entire code runs very fast.
The lesson overall is that UIViews are extremely optimized. As much as possible, try to make a whole bunch of them, update them only locally if at all, and let Apple's magic blend it all together.
I'm making a 2d Game, in which I need instances of a sprite to fly randomly across the screen. They will spawn in randomly just beyond the boundaries of the iPhone screen, then move within the screen. When they hit the edges, they will appear back on the other side. All I need to know is how to get the sprite to move randomly.
Add this method to your layer class - it takes in a sprite and then moves it randomly around the screen for ever:
-(void)moveRandom:(CCSprite*)s
{
CGPoint randomPoint = ccp(arc4random()%480, arc4random()%320);
NSLog(#"%#", NSStringFromCGPoint(randomPoint));
[s runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:arc4random()%5+1 position: randomPoint],
[CCCallBlock actionWithBlock:^{
[self performSelector:#selector(moveRandom:) withObject:s afterDelay:0.5];
}],
nil]
];
}
As you can see it's pretty easy - generate a random point on the screen and then run move action on the sprite to that point. When that's done - just repeat.
To add a sprite on the screen and start the process, put this (probably) in your scene init method or wherever you do the scene initialization:
CCSprite* s = [CCSprite spriteWithFile:#"yourImage.png"];
[self addChild: s];
[self moveRandom:s];
Let's say that I got 120 image files in my project. And I want to animate a sprite with all of them.
I was thinking about this method:
CCSprite *temp = [CCSprite spriteWithFile:#"TheNextSprite.png"];
[sprite setTexture:[temp texture]];
The above code will run like once every 0.03 seconds.
sprite is my animated CCSprite.
temp is simply a temporary CCSprite initialized with the image file for the next animation frame, so I can take its texture later.
Is that way efficient? I heard that I could use CCAnimate with a CCSpriteBatchNode, but this is only for one single sprite. Plus I got 120 (big) frames - they won't fit in a 2048x2048 texture canvas.
Have a look at the CCAnimation, CCAnimate and CCSpriteFrame classes.
Sprite frames are what you are trying to mimic, a 'piece' of a texture to which a sprite is mapped. They are best used when combined with spritesheets, not all of the sprites in the animation need to be in the same sprite sheet.
CCAnimation allows you to create an animation out of a sequence of such frames.
And CCAnimate allows you to run that animation as an action:
[node runAction:[CCAnimate actionWithAnimation:animationInstance restoreOriginal:NO]];
I have an application where, in one window, there is an NSImageView. The user should be able to drag and drop ANY FILE/FOLDER (not only images) into the image view, so I subclassed NSImageView class to add support for those types.
The reason why I chose an NSImageView instead of a normal view is because I also wanted to display an animation (say an arrow pointing downwards and going up and down) when the user hovers over with files ready to drop. My question is this: what would be the best way (most efficient, quickest, least CPU usage, etc) to do this?
In fact, I have already done it, but what made me ask this question is the fact that when I set the images to change at a rate below 0.02 sec it starts to lag. Here is how I did it:
In the NSImageView subclass:
have an ivar: NSTimer* animTimer;
override awakeFromNib, calling [super awakeFromNib] and loading the images into an array (about 45 images) using NSImage
whenever user enters with files, start animTimer with frequency = 0.025 (less and it lags), and a selector that sets the next image in the array (called drawNextImage)
whenever the user exits or ends the drag and drop, call [animTimer invalidate] to stop updating images
Here is how I set the image in the subclass:
- (void)drawNextImage
{
currentImageIndex++; // ivar / kNumberDNDImages is a constant defined as 46
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[super setImage: [imagesArray objectAtIndex: currentImageIndex]]; // imagesArray is ivar
}
So, how would I do this quick enough? I'd like the frequency to be about 0.01 secs, but less than 0.025 lags, so that is what I have set for the moment. Oh, and my images are the correct size (+ or - one pixel or something) and they are in .png (I need the transparency - jpegs, for example, won't do it).
EDIT:
I have tried to follow NSResponder's suggestion, and have updated my method to this:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, [self.bigDNDImage size].height); // Up left corner - ??
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left corner - ??
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
I have also moved this method and the other drag and drop methods from the NSImageView subclass to an NSView subclass I already had. Everything is exactly the same, except for the superclass and this method. I also modified some constants.
In my early testing of this, I got some error/warning messages that didn't stop execution talking abou NSGraphicsContext or something. These have disappeared now, but just so you know. I have absolutely no idea why they were appearing and what they mean. If they ever appear again I'll worry about them, not now :)
EDIT 2:
This is what I'm doing now:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[self drawCurrentImage];
}
- (void)drawCurrentImage
{
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, 0); // Bottom left, for sure
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left as well
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
And the catch here is to call drawCurrentImage when drawRect is called (see, it actually was easier to solve than I thought).
Now, I must say I haven't tried this with the composite image, because I couldn't find a good and quick way to merge 40+ images the way I wanted (one next to the other). But for the ones ineterested, I modified this to do the same thing as my NSImageView subclass (reading 40+ images from an array and displaying them) and I found no speed bump: NSView is as laggy below 0.025 as NSImageView. Also I found some problems when using core animation (the image is drawn in weird places instead of the place I tell her to) and some warnings talking about NSGraphicsContext, which I don't know how to solve at all (I'm a complete noob when it comes to drawing and such with Objective-C's tools). So for the time being I'm using NSImageView, unless I find a way to merge all those images and try it with NSView.
Core Animation would probably be quickest, since it'll do everything on the GPU. Create a layer for each image, setting each layer's contents to the CGImage you can make from each image, add them all as sublayers of a single top-level layer, host the top-level layer in a plain NSView, and then just toggle each image layer's hidden property in turn.
I'd probably draw all of the component images into one long image, and draw segments into a view using -drawAtPoint:fromRect:operation:fraction:. I'm sure you could make it faster than that by resorting to OpenGL, though.