How to slice/cut sprites in iOS (Core Graphics) - objective-c

I am working on a game and I would like to add a proper slicing feature in it.. so when a sprite sliced, 2 new sprites should be created.. please check here
At the moment, I am just reducing the size and duplicating the sprites.. Something like this.. Thanks in advance..
- (BOOL) sliceSprite: (Sprite *) sprite withPath: (UIBezierPath *) slicePath
{
CGSize size = sprite.size;
size.width /= 2;
size.height /=2;
sprite.size = size;
sprite.sliced = YES;
Sprite *newSprite = [[Sprite alloc] initWithImage: sprite.image];
newSprite.position = sprite.position;
newSprite.size = size;
newSprite.sliced = YES;
newSprite.inView = YES;
newSprite.xVelocity = SLICE_SPEEDUP * sprite.yVelocity;
newSprite.yVelocity = SLICE_SPEEDUP * sprite.xVelocity;
newSprite.angularVelocity = -SLICE_REVUP * sprite.angularVelocity;
[sprites addObject: newSprite];
[newSprite release];
sprite.angularVelocity = SLICE_REVUP * sprite.angularVelocity;
sprite.xVelocity = -SLICE_SPEEDUP * sprite.xVelocity;
sprite.yVelocity = -SLICE_SPEEDUP * sprite.yVelocity;
return YES;
}
- (void) sliceSpritesInSwipePath
{
CGRect swipeRect = [swipePath bounds];
for (NSUInteger i = 0; i < [sprites count]; i++)
{
Sprite *sprite = [sprites objectAtIndex: i];
if ([sprite intersectsWithPathInArray: swipePoints
inRect: swipeRect])
if ([self sliceSprite: sprite withPath: swipePath])
{
[self resetSwipe];
if (![sliceSound isPlaying])
[sliceSound play];
break;
}
}
}

Is the specific line of splitting required? Fruit Ninja just spawns two halves of the fruit, as if it was split down the middle, this would be quite easy to do:
Create two sprites which are half the width of the original sprite
Position them 1/4 and 3/4 of the way along the original sprite's horizontal centre line
Add rotation/acceleration etc.
Modify texture coordinates so that the left sprite has the left half of the texture and the right sprite has the right half of the texture.

Since you're using CoreGraphics here, why not simply use a clipping path when drawing the sprite(s)?
Duplicate the sprite to be sliced, then apply simple polygons masking the two halves as their respective clipping paths. The function you need is called CGContextClip and a short tutorial can be found here.
Edit: The tutorial lists this example:
CGContextBeginPath (context);
CGContextAddArc (context, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*PI, 0);
CGContextClosePath (context);
CGContextClip (context);
This sets the current path to a circle, then applies the current path as the clipping region.

Related

Moving SKNodes and SKSpriteNodes

I have the following setup in my game:
An SKNode with the name _backgroundLayer
A repeating texture which I have added to the _backgroundLayer 9 times to make a larger background
An SKSprite with the name levelButton, which is added to the _backgroundLayer ([_backgroundLayer addChild:levelButton];).
I use levelButton.anchorPoint = CGPointMake(0.5, 0.5); to make the levelButton have an anchor point in the middle.
Now, when I make levelButton.position = CGPointMake(0, 0); and _backgroundLayer.position = CGPointMake(0, 0); the levelButton's middle is correctly situated in (0, 0) with its middle being the in lower left corner of the screen. So that's fine.
However, if I move the levelButton to be levelButton.position = CGPointMake(100, 0); and _backgroundLayer.position = CGPointMake(-100, 0);, as I show below, the levelButton should still have its middle in (0,0) aka the lower left corner of the screen. However, that is not the case, and the levelButton is more to the right, its something like 50 pixels to the right. But it shouldn't be, since I am moving the _backgroundLayer 100 to the left (-100) and _levelButton 100 to the right (100). It should have stayed in place.
These are basic stuff which I do not understand why they are not working as they should. I am probably doing something wrong but I can not find it even though I've read through the iOS Games by Tutorials and a bunch of tutorials and tips.
Please help.
Now, my code is the following:
#implementation LevelSelectScene
{
SKNode *_backgroundLayer;
}
-(id)initWithSize:(CGSize)size {
/* Setup your scene here */
_backgroundLayer = [SKNode node];
_backgroundLayer.name = #"backgroundLayer";
SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:#"levelSelect"];
int textureID = 0;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture];
background.anchorPoint = CGPointZero;
background.position = CGPointMake((background.size.width)*i,
(background.size.height)*j);
background.zPosition = 0;
background.name = [NSString stringWithFormat:#"background%d", textureID];
textureID++;
[_backgroundLayer addChild:background];
}
}
[self addChild:_backgroundLayer];
SKSpriteNode * levelButton = [SKSpriteNode spriteNodeWithImageNamed:#"lock"];
levelButton.anchorPoint = CGPointMake(0.5, 0.5);
levelButton.position = CGPointMake(100, 0); //IMPORTANT
levelButton.zPosition = 150;
levelButton.name = #"test";
[_backgroundLayer addChild:levelButton];
_backgroundLayer.position = CGPointMake(-100, 0); //IMPORTANT
}
return self;
}
Found it!
I was changing the scale of the _backgroundLayer SKnode so the coordinates were different.

CATiledLayers on OS X

This has been driving me crazy.. I have a large image, and need to have a view that is both zoomable, and scrollable (ideally it should also be able to rotate, but I've given up on that part). Since the image is very large, I plan on using CATiledLayer, but I simply can't get it to work.
My requirements are:
I need to be able to zoom (on mouse center) and pan
The image should not change its width:height ratio (shouldn't resize, only zoom).
This should run on Mac OS 10.9 (NOT iOS!)
Memory use shouldn't be huge (although up to like 100 MB should be ok).
I have the necessary image both complete in one file, and also tiled into many (even have it for different zoom levels). I prefer using the tiles, as that should be easier on memory, but both options are available.
Most of the examples online refer to iOS, and thus use UIScrollView for the zoom/pan, but I can't get to copy that behaviour for NSScrollView. The only example for Mac OS X I found is this, but his zoom always goes to the lower left corner, not the middle, and when I adapt the code to use png files instead of pdf, the memory use gets around 400 MB...
This is my best try so far:
#implementation MyView{
CATiledLayer *tiledLayer;
}
-(void)awakeFromNib{
NSLog(#"Es geht los");
tiledLayer = [CATiledLayer layer];
// set up this view & its layer
self.wantsLayer = YES;
self.layer = [CALayer layer];
self.layer.masksToBounds = YES;
self.layer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
// set up the tiled layer
tiledLayer.delegate = self;
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 5;
tiledLayer.anchorPoint = CGPointZero;
tiledLayer.bounds = CGRectMake(0.0f, 0.0f, 41*256, 22*256);
tiledLayer.autoresizingMask = kCALayerNotSizable;
tiledLayer.tileSize = CGSizeMake(256, 256);
self.frame = CGRectMake(0.0f, 0.0f, 41*256, 22*256);
self.layer = tiledLayer;
//[self.layer addSublayer:tiledLayer];
[tiledLayer setNeedsDisplay];
}
-(void)drawRect:(NSRect)dirtyRect{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGFloat scale = CGContextGetCTM(context).a;
CGSize tileSize = tiledLayer.tileSize;
tileSize.width /= scale;
tileSize.height /= scale;
// calculate the rows and columns of tiles that intersect the rect we have been asked to draw
int firstCol = floorf(CGRectGetMinX(dirtyRect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(dirtyRect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(dirtyRect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(dirtyRect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) {
for (int col = firstCol; col <= lastCol; col++) {
NSImage *tile = [self tileForScale:scale row:row col:col];
CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
tileSize.width, tileSize.height);
// if the tile would stick outside of our bounds, we need to truncate it so as
// to avoid stretching out the partial tiles at the right and bottom edges
tileRect = CGRectIntersection(self.bounds, tileRect);
[tile drawInRect:tileRect];
}
}
}
-(BOOL)isFlipped{
return YES;
}
But this deforms the image, and doesn't zoom or pan correctly (but at least the tile selection works)...
I can't believe this is so hard, any help would be greatly appreciated. Thanks :)
After a lot of research and tries, I finally managed to get this to work using this example. Decided to post it for future reference. Open the ZIP > CoreAnimationLayers> TiledLayers, there's a good example there. That's how CATiledLayer works with OS X, and since the example there doesn't handle zoom very well, I leave here my zoom code
-(void)magnifyWithEvent:(NSEvent *)event{
[super magnifyWithEvent:event];
if (!isZooming) {
isZooming = YES;
BOOL zoomOut = (event.magnification > 0) ? NO : YES;
if (zoomOut) {
[self zoomOutFromPoint:event.locationInWindow];
} else {
[self zoomInFromPoint:event.locationInWindow];;
}
}
}
-(void)zoomInFromPoint:(CGPoint)mouseLocationInWindow{
if(zoomLevel < pow(2, tiledLayer.levelsOfDetailBias)) {
zoomLevel *= 2.0f;
tiledLayer.transform = CATransform3DMakeScale(zoomLevel, zoomLevel, 1.0f);
tiledLayer.position = CGPointMake((tiledLayer.position.x*2) - mouseLocationInWindow.x, (tiledLayer.position.y*2) - mouseLocationInWindow.y);
}
}
-(void)zoomOutFromPoint:(CGPoint)mouseLocationInWindow{
NSInteger power = tiledLayer.levelsOfDetail - tiledLayer.levelsOfDetailBias;
if(zoomLevel > pow(2, -power)) {
zoomLevel *= 0.5f;
tiledLayer.transform = CATransform3DMakeScale(zoomLevel, zoomLevel, 1.0f);
tiledLayer.position = CGPointMake((tiledLayer.position.x + mouseLocationInWindow.x)/2, (tiledLayer.position.y + mouseLocationInWindow.y)/2);
}
}

Positioning Multiple UIImage's - Objective C

In the app I'm building, a square image is imported, it is broken into 16 tiles, and those tiles are placed in a square on the screen, starting from the top. However, I'm adding an extra bar of info at the top above the tiles, so I need to move the set of tiles further down the screen. I've played around with the for loop and the coordinates that the code makes, but nothing is giving me the desired respect.
-(void) initPuzzle:(NSString *) imagePath{
UIImage *orgImage = [UIImage imageNamed:imagePath];
if( orgImage == nil ){
return;
}
[self.tiles removeAllObjects];
tileWidth = orgImage.size.width/NUM_HORIZONTAL_PIECES;
tileHeight = orgImage.size.height/NUM_VERTICAL_PIECES;
blankPosition = CGPointMake( NUM_HORIZONTAL_PIECES-1, NUM_VERTICAL_PIECES-1 );
for( int x=0; x<NUM_HORIZONTAL_PIECES; x++ ){
for( int y=0; y<NUM_VERTICAL_PIECES; y++ ){
// *** The coordinates need to be altered - ?
CGPoint orgPosition = CGPointMake(x,y);
if( blankPosition.x == orgPosition.x && blankPosition.y == orgPosition.y ){
continue;
}
CGRect frame = CGRectMake(tileWidth*x, tileHeight*y,
tileWidth, tileHeight );
CGImageRef tileImageRef = CGImageCreateWithImageInRect( orgImage.CGImage, frame );
UIImage *tileImage = [UIImage imageWithCGImage:tileImageRef];
CGRect tileFrame = CGRectMake((tileWidth+TILE_SPACING)*x, (tileHeight+TILE_SPACING)*y,
tileWidth, tileHeight );
tileImageView = [[Tile alloc] initWithImage:tileImage];
tileImageView.frame = tileFrame;
tileImageView.originalPosition = orgPosition;
tileImageView.currentPosition = orgPosition;
CGImageRelease( tileImageRef );
[tiles addObject:tileImageView];
// now add to view
[self.view insertSubview:tileImageView atIndex:0];
}
}
[self shuffle];
}
int topBarWidth = 100; //100 based on the example in your comment
Then at the point in your code where you're setting the frames of each tile, add topBarWidth to whatever y value you were previously setting.
Another option is to create a subview just for your tiles and then reposition that instead. If you then wanted to animate all the tiles moving up and down you'd only have to animate the tiles view, instead of recalculating the position of all the images.

Tile based map movement sluggish with UIImage

I have the following code which draws a bunch of uiimage tiles on screen each time the screen is touched (its a character exploring a tile dungeon). But the movement is very jumpy if I click quickly. Why is it acting sluggishly.
Thanks.
- (void) updateView {
// lets remove all subviews and start fresh
for (UIView *subview in self.view.subviews)
[subview removeFromSuperview];
[self drawVisibleDungeon];
[self displayMonsters];
[self drawPlayer];
[self.view setNeedsDisplay];
[self.view addSubview:messageDisplay];
[self.view addSubview:miniMap];
[miniMap setNeedsDisplay];
}
- (void) processTouchAtX: (int)x AndY: (int)y
{
int tempx = 0;
int tempy = 0;
if (x > 4)
tempx++;
if (x < 4)
tempx--;
if (y > 6)
tempy++;
if (y < 6)
tempy--;
[[[self _accessHook] dungeonReference]processOneTurnWithX:tempx AndY:tempy];
[self updateView];
}
- (void) displayMonsters
{
UIImage *aImg;
int x, y;
NSString *aString;
int monCount = [[[_accessHook dungeonReference]aDungeonLevel]monsterCount];
NSMutableArray *monArray = [[[_accessHook dungeonReference]aDungeonLevel]mArray];
int player_x_loc = [[[self _accessHook] dungeonReference] thePlayer].x_location;
int player_y_loc = [[[self _accessHook] dungeonReference] thePlayer].y_location;
for (int i=0; i<monCount; i++)
{
x = [[monArray objectAtIndex: i] x_loc];
y = [[monArray objectAtIndex: i] y_loc];
if ([self LOSFrom:CGPointMake(x, y)])
if ((x >= player_x_loc-4) && (x < (player_x_loc+5)) && (y >= player_y_loc-6) && (y < (player_y_loc+6)))
{
UIImageView *imgView=[[UIImageView alloc]init];
aString = [[monArray objectAtIndex:i]monsterPicture];
aImg = [UIImage imageNamed:aString];
imgView.frame=CGRectMake((x - (player_x_loc-4))*32, (y - (player_y_loc-6))*32, 32, 32);
imgView.image = aImg;
[self.view addSubview:imgView];
[self.view bringSubviewToFront:imgView];
[imgView release];
}
}
}
Your idea of using cocos2d is fine, and it's a very good framework and probably well suited to other problems you may face. But... why don't we also discuss why you're having so much trouble.
You're using a separate UIImageView for each tile. UIImage is fine, and pretty efficient for most purposes. But a full view for every tile is pretty expensive. Worse, every time you call updateView, you take everything and throw it away. That's very, very expensive.
Just as step one, because it's easy for you to fix, set up all the image views at the beginning and store them in an array. Then in displayMonsters, just change their locations and images rather than deleting them and creating new ones. That by itself will dramatically improve your performance I suspect.
If you still need more performance, you can switch to using CALayer rather than UIImageView. You can very easily create a CALayer and set its contents to aImg.CGImage. Again, you'd add a bunch of CALayer objects with [view.layer addSublayer:layer], just like adding views. Layers are just much cheaper than views because they don't handle things like touch events. They just draw.
But if you anticipate needing fancier 2D graphics, there's nothing wrong with Cocos2D.
It's slow because using UIImage for a bunch of map tiles has a lot of overhead. It would be best to draw everything using OpenGL and vertex buffers.

Erasing Cocoa Drawing done by NSRectFill?

I have an NSBox, inside of which I am drawing small rectangles, with NSRectFill(). My code for this looks like this:
for (int i = 0; i <= 100; i++){
int x = (rand() % 640) + 20;
int y = (rand() % 315) + 196;
array[i] = NSMakeRect(x, y, 4, 4);
NSRectFill(array[i]);
}
This for loop creates 100 randomly placed rectangles within the grid. What I have been trying to do is create a sort of animation, created by this code running over and over, creating an animation of randomly appearing rectangles, with this code:
for (int i = 0; i <= 10; i++) {
[self performSelector:#selector(executeFrame) withObject:nil afterDelay:(.05*i)];
}
The first for loop is the only thing inside the executeFrame function, by the way. So, what I need to do is to erase all the rectangles between frames, so the number of them stays the same and they look like they are moving. I tried doing this by just drawing the background again, by calling [myNsBox display]; before calling executeFrame, but that made it seem as though no rectangles were being drawn. Calling it after did the same thing, so did switching in setNeedsDisplay instead of display. I cannot figure this one out, any help would be appreciated.
By the way, an additional thing is that when I try to run my code for executing the frames, without trying to erase the rectangles in between, all that happens is that 100 more rectangles are drawn. Even if I have requested that 1000 be drawn, or 10,000. Then though, if I leave the window and come back to it (immediately, time is not a factor here), the page updates and the rectangles are there. I attempted to overcome that by with [box setNeedsDisplayInRect:array[i]]; which worked in a strange way, causing it to update every frame, but erasing portions of the rectangles. Any help in this would also be appreciated.
It sounds like you're drawing outside drawRect: . If that's the case, move your drawing code into a view's (the box's or some subview's) drawRect: method. Otherwise your drawing will get stomped on by the Cocoa drawing system like you're seeing. You'll also want to use timers or animations rather than loops to do the repeated drawing.
I recently wrote an example program for someone trying to do something similar with circles. The approach I took was to create an array of circle specifications and to draw them in drawRect. It works pretty well. Maybe it will help. If you want the whole project, you can download it from here
#implementation CircleView
#synthesize maxCircles, circleSize;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
maxCircles = 1000;
circles = [[NSMutableArray alloc] initWithCapacity:maxCircles];
}
return self;
}
- (void)dealloc {
[circles release];
[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect {
NSArray *myCircles;
#synchronized(circles) {
myCircles = [circles copy];
}
NSRect bounds = [self bounds];
NSRect circleBounds;
for (NSDictionary *circleSpecs in myCircles) {
NSColor *color = [circleSpecs objectForKey:colorKey];
float size = [[circleSpecs objectForKey:sizeKey] floatValue];
NSPoint origin = NSPointFromString([circleSpecs objectForKey:originKey]);
circleBounds.size.width = size * bounds.size.width;
circleBounds.size.height = size * bounds.size.height;
circleBounds.origin.x = origin.x * bounds.size.width - (circleBounds.size.width / 2);
circleBounds.origin.y = origin.y * bounds.size.height - (circleBounds.size.height / 2);
NSBezierPath *drawingPath = [NSBezierPath bezierPath];
[color set];
[drawingPath appendBezierPathWithOvalInRect:circleBounds];
[drawingPath fill];
}
[myCircles release];
}
#pragma mark Public Methods
-(void)makeMoreCircles:(BOOL)flag {
if (flag) {
circleTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(makeACircle:) userInfo:nil repeats:YES];
}
else {
[circleTimer invalidate];
}
}
-(void)makeACircle:(NSTimer*)theTimer {
// Calculate a random color
NSColor *color;
color = [NSColor colorWithCalibratedRed:(arc4random() % 255) / 255.0
green:(arc4random() % 255) / 255.0
blue:(arc4random() % 255) / 255.0
alpha:(arc4random() % 255) / 255.0];
//Calculate a random origin from 0 to 1
NSPoint origin;
origin.x = (double)arc4random() / (double)0xFFFFFFFF;
origin.y = (double)arc4random() / (double)0xFFFFFFFF;
NSDictionary *circleSpecs = [NSDictionary dictionaryWithObjectsAndKeys:color, colorKey,
[NSNumber numberWithFloat:circleSize], sizeKey,
NSStringFromPoint(origin), originKey,
nil];
#synchronized(circles) {
[circles addObject:circleSpecs];
if ([circles count] > maxCircles) {
[circles removeObjectsInRange:NSMakeRange(0, [circles count] - maxCircles)];
}
}
[self setNeedsDisplay:YES];
}
#end