this code loads a map1.tmx tileMap when player reaches the "door". When the player enters the door it loads a new map2.tmx file. Problem is when the new map2.tmx file is loaded, the old map1.tmx is running behind map.2.tmx and using all of map.1.tmx wall collisions AND ignoring map2.tmx wall collisions. Is there a way to do an opposite of addObject like removeObject and add map2.tmx as the new map? I would like to make map.2 run when the players enters the door.
I have tried removeAllActions, removeAllChildren, removeTileAtCoord: and other approaches but I lack of SpriteKit experience. Any help is appreciated.
- (void)handleDoorCollisions:(Player *)player
{
if (self.doorOver) return;
NSInteger indices[8] = {7, 1, 3, 5, 0, 2, 6, 8};
for (NSUInteger i = 0; i < 8; i++) {
NSInteger tileIndex = indices[i];
CGRect playerRect = [player collisionBoundingBox];
CGPoint playerCoord = [self.doors coordForPoint:player.desiredPosition];
NSInteger tileColumn = tileIndex % 3;
NSInteger tileRow = tileIndex / 3;
CGPoint tileCoord = CGPointMake(playerCoord.x + (tileColumn - 1), playerCoord.y + (tileRow - 1));
NSInteger gid = [self tileGIDAtTileCoord:tileCoord forLayer:self.doors];
if (gid != 0) {
CGRect tileRect = [self tileRectFromTileCoords:tileCoord];
if (CGRectIntersectsRect(playerRect, tileRect)) {
[self.doors removeTileAtCoord:tileCoord];
// add new map2.tmx
self.map = [JSTileMap mapNamed:#"map2.tmx"];
[self addChild:self.map];
[self removeObject:????]
}
}
}
}
I had the same issue. My solution was/is on map load I collect all collision data in a List<> and check any collisions against this list.
When the player enters a different map the list is beeing cleared and reinitialized with the data from the new map.
I use this approach for some other things as well like objects the player can interact with or areas that trigger some actions.
I wanted to create a space background so I make a for loop to create the stars. Here is the code:
for (int i = 0; i<100; i++) {
SKShapeNode *star= [SKShapeNode shapeNodeWithPath:Path.CGPath];
star.fillColor = [UIColor whiteColor];
star.physicsBody = nil;
int xposition = arc4random()%960;
int yposition = arc4random()%640;
star.position = CGPointMake(xposition, yposition);
float size = (arc4random()%3 + 1)/10.0;
star.xScale = size;
star.yScale = size;
star.alpha = (arc4random()%10 + 1 )/ 10.0;
star.zPosition = -2;
[self addChild:star];
}
But it takes a lot from my cpu. when the code is activated the cpu at top 78%.(I check the code in the iPhone simulator);
Somebody know how to fix it? thanks.
Your physics bodies continue to calculate even when off of the screen. You will need to remove them once they go out of the frame, otherwise everything will slow to a crawl. (And to echo what others have stated you will eventually need a real device).
From this document: Jumping Into Sprite Kit
You can implement the "Did Simulate Physics" method to get rid of the stars that fell from the bottom of the screen like so:
-(void)didSimulatePhysics
{
[self enumerateChildNodesWithName:#"star" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
[node removeFromParent];
}];
}
Note that you will first need to set the name of your star shapes by using the name property like so:
star.name = "star"
I have this problem:
I have node A and node B.
What i want is when collision between them happens (node A collides with the top of B), node B won't be pushing node A a bit up. Because as it is now, when A collides with B, its being pushed back a bit, and collision instantly ends(didEndContact is called). So when im like colliding with that B, collision instantly ends, then starts again, then ends, then starts again.... What i want is that if A collided with B, A will lay on this B. I noticed i could achieve that by setting A.dynamic = NO, but i need to apply velocity to A, and if its not dynamic, velocity wont work.
This is the code:
typedef NS_OPTIONS(uint32_t, CollisionCategory)
{
CollisionCategoryPlayer = 1 << 0,
CollisionCategoryDiggable = 1 << 1,
};
-(void)didMoveToView:(SKView *)view
{
self.playerNode = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
self.playerNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerNode.size];
self.playerNode.physicsBody.categoryBitMask = CollisionCategoryPlayer;
self.playerNode.physicsBody.collisionBitMask = CollisionCategoryDiggable;
self.playerNode.physicsBody.contactTestBitMask = CollisionCategoryDiggable;
self.playerNode.position = CGPointMake(160, 520);
self.playerNode.name = #"player";
self.playerNode.physicsBody.allowsRotation = NO;
self.playerNode.physicsBody.friction = 0;
[self addChild:self.playerNode];
SKSpriteNode* spriteNode = [SKSpriteNode spriteNodeWithTexture:textToUse];
spriteNode.name = keyFromCoordinate(coord);
spriteNode.position = CGPointMake(160, 400);
spriteNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteNode.size];
spriteNode.physicsBody.dynamic = NO;
spriteNode.physicsBody.categoryBitMask = CollisionCategoryDiggable;
spriteNode.physicsBody.collisionBitMask = CollisionCategoryPlayer;
spriteNode.physicsBody.contactTestBitMask = CollisionCategoryPlayer;
spriteNode.physicsBody.resting = YES;
[self addChild:spriteNode];
}
-(void)update:(NSTimeInterval)currentTime
{
self.playerNode.physicsBody.velocity = CGVectorMake(0, -200);
}
And again the problem is that didBeginContact is called, then didEndContact, then didBegin and so on, what i want is - did begin is only called once.
Setting the restitution = 0 for both physicsBodies helped
In my game I have 4 bitmasks and everything is setup, yet didBeginContact only gets called when the first bitmask (playerCategory) collides with something. if 3 collides with 4, nothing happens, even though I have the contactTestBitMask set for them to collide.
myscene.h
self.physicsWorld.gravity = CGVectorMake(0.0, -2.45);
self.physicsWorld.contactDelegate = self;
self.player = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size: CGSizeMake(10.0, 20.0)];
self.player.position = CGPointMake(20.0, self.frame.size.height);
self.player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.player.size];
self.player.physicsBody.affectedByGravity = YES;
self.player.physicsBody.dynamic = YES;
self.player.physicsBody.categoryBitMask = playerCategory;
self.player.physicsBody.contactTestBitMask = enemyCategory;
self.player.physicsBody.collisionBitMask = tileCategory;
[self.gameNode addChild:self.player];
[...]
- (void) swipeRightHandler:(UISwipeGestureRecognizer *) recognizer {
NSLog(#"swipe right");
SKSpriteNode *attackRect = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(40, 5)];
attackRect.position = CGPointMake(self.player.position.x + 10, self.player.position.y);
attackRect.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:attackRect.size];
attackRect.physicsBody.categoryBitMask = attackCategory;
attackRect.physicsBody.contactTestBitMask = 255;
attackRect.physicsBody.collisionBitMask = enemyCategory;
attackRect.physicsBody.affectedByGravity = NO;
attackRect.physicsBody.dynamic = NO;
[self.gameNode addChild:attackRect];
[attackRect runAction:[SKAction moveBy:CGVectorMake(250, 0) duration:1.0] completion:^{
[attackRect removeFromParent];
}];
}
RandomLevelGenerator.m:
SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(20, 20)];
enemy.position = CGPointMake(visual_x, FLOOR_X + arc4random_uniform(MAX_Y - FLOOR_X));
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = playerCategory | attackCategory;
enemy.physicsBody.collisionBitMask = attackCategory;
enemy.physicsBody.affectedByGravity = NO;
enemy.physicsBody.dynamic = NO;
[self.scene.gameNode addChild:enemy];
LevelGenerator.h:
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t tileCategory = 0x1 << 1;
static const uint32_t enemyCategory = 0x1 << 2;
static const uint32_t attackCategory = 0x1 << 3;
MyScene.m again:
SKPhysicsBody *firstBody, *secondBody;
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
NSLog(#"%d - %d", firstBody.categoryBitMask, secondBody.categoryBitMask);
That last NSLog, only ever prints 1 - x, never 3 - x. I've also tried allowing other collisions and I can't get any of them to collide.
If the categoryBitMask for two bodies ANDed together is zero, they will not generate contact events. For instance this happens with playerCategory (1) and enemyCategory (4): 1 & 4 = 0 <== no contact events are generated, ever, between players and enemies. In that case it won't even get to check the contactBitMask flags.
If you want two bodies to generate contact events, they both have to have the same category bitmask flag set.
The default for categoryBitMask is 0xFFFFFFFF so that all bodies can contact with each other. Same for collisionBitMask because generally you want all bodies to contact and collide by default.
The only bit mask you really need to modify in most cases is the contactBitMask, whose default is 0, meaning no contact events are generated by default (to improve performance).
Only change the other bitmasks where changing contactBitMask alone does not suffice.
For example when you want contact events, but no collision feedback (= change in velocity/position of colliding bodies when they come in contact), then make sure that those body's collisionBitMask ANDed together is 0. This allows bodies to generate contact events, but pass through each other. Useful to create triggers, ie when the player enters an area you can let him pass but also trigger a game event.
Or when you want to use the same contact bitmasks for two different categories of bodies who should only collide/contact with other bodies sharing the same category - ie regular and "ghost" characters who should have the same contact behavior with everything else in the game, except they shouldn't contact/collide with their corporeal (non-ghost) counterparts. In that case, use the same contactBitMask and collisionBitMask for both bodies. Then set all flags in categoryBitMask for all other categories, but don't set the flags for the two categories representing the corporeal and ghost entities. Ie if corporeal category is 2 and ghost category is 8, the categoryBitMask should be: 0xFFFFFFFF - 2 - 8 = 0xFFFFFFF5 (lower 8 bits would be: 11110101)
Long story short: categoryBitMask flags should be cleared from 0xFFFFFFFF (rather than set to a specific bit) so that individual bodies in specific categories don't contact with each other but still contact with all other bodies.
I have a question about the NSStatusItem for cocoa in mac osx. If you look at the mac app called snippets (see the movie at http://snippetsapp.com/). you will see that once you clicked your statusbar icon that a perfectly aligned view / panel or maybe even windows appears just below the icon.
My question is ... How to calculate the position to where to place your NSWindow just like this app does?
I have tried the following:
Subclass NSMenu
Set the view popery for the first item of the menu (Worked but enough)
Using addSubview instead of icon to NSStatusItem this worked but could not get higher then 20px
Give the NSStatusItem a view, then get the frame of that view's window. This technically counts as UndocumentedGoodness, so don't be surprised if it breaks someday (e.g., if they start keeping the window offscreen instead).
I don't know what you mean by “could not get heigher then 20px”.
To do this without the hassle of a custom view, I tried the following (that works). In the method that is set as the action for the status item i.e. the method that is called when the user clicks the status item, the frame of the status item can be retrieved by:
[[[NSApp currentEvent] window] frame]
Works a treat for me
Given an NSMenuItem and an NSWindow, you can get the point that centers your window right below the menu item like this:
fileprivate var centerBelowMenuItem: CGPoint {
guard let window = window, let barButton = statusItem.button else { return .zero }
let rectInWindow = barButton.convert(barButton.bounds, to: nil)
let screenRect = barButton.window?.convertToScreen(rectInWindow) ?? .zero
// We now have the menu item rect on the screen.
// Let's do some basic math to center our window to this point.
let centerX = screenRect.origin.x-(window.frame.size.width-barButton.bounds.width)/2
return CGPoint(x: centerX, y: screenRect.origin.y)
}
No need for undocumented API's.
Maybe another solution which works for me (swift 4.1) :
let yourStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let frameOrigin = yourStatusItem.button?.window?.frame.origin
let yourPoint = CGPoint(x: (frameOrigin?.x)!, y: (frameOrigin?.y)! - 22)
yourWindow?.setFrameOrigin(yourPoint)
It seems that this app uses Matt's MAAttachedWindow. There's an sample application with the same layout & position.
NOTE: PLEASE DO NOT USE THIS, at least not for the purpose of locating an NSStatusItem.
Back when I posted this, this crazy image matching technique was the only way to solve this problem without undocumented API. Now, you should use Oskar's solution.
If you're willing to use image analysis to find the status item on a menu bar, here's a category for NSScreen which does exactly that.
It might seem crazy to do it this way, but it's fast, relatively small, and it's the only way of finding a status item without undocumented API.
If you pass in the current image for the status item, this method should find it.
#implementation NSScreen (LTStatusItemLocator)
// Find the location of IMG on the screen's status bar.
// If the image is not found, returns NSZeroPoint
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
{
CGColorSpaceRef csK = CGColorSpaceCreateDeviceGray();
NSPoint ret = NSZeroPoint;
CGDirectDisplayID screenID = 0;
CGImageRef displayImg = NULL;
CGImageRef compareImg = NULL;
CGRect screenRect = CGRectZero;
CGRect barRect = CGRectZero;
uint8_t *bm_bar = NULL;
uint8_t *bm_bar_ptr;
uint8_t *bm_compare = NULL;
uint8_t *bm_compare_ptr;
size_t bm_compare_w, bm_compare_h;
BOOL inverted = NO;
int numberOfScanLines = 0;
CGFloat *meanValues = NULL;
int presumptiveMatchIdx = -1;
CGFloat presumptiveMatchMeanVal = 999;
// If the computer is set to Dark Mode, set the "inverted" flag
NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
id style = globalPrefs[#"AppleInterfaceStyle"];
if ([style isKindOfClass:[NSString class]]) {
inverted = (NSOrderedSame == [style caseInsensitiveCompare:#"dark"]);
}
screenID = (CGDirectDisplayID)[self.deviceDescription[#"NSScreenNumber"] integerValue];
screenRect = CGDisplayBounds(screenID);
// Get the menubar rect
barRect = CGRectMake(0, 0, screenRect.size.width, 22);
displayImg = CGDisplayCreateImageForRect(screenID, barRect);
if (!displayImg) {
NSLog(#"Unable to create image from display");
CGColorSpaceRelease(csK);
return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
}
size_t bar_w = CGImageGetWidth(displayImg);
size_t bar_h = CGImageGetHeight(displayImg);
// Determine scale factor based on the CGImageRef we got back from the display
CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;
// Greyscale bitmap for menu bar
bm_bar = malloc(1 * bar_w * bar_h);
{
CGContextRef bmCxt = NULL;
bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
// Draw the menu bar in grey
CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);
uint8_t minVal = 0xff;
uint8_t maxVal = 0x00;
// Walk the bitmap
uint64_t running = 0;
for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
{
bm_bar_ptr = bm_bar + (bar_w * yi);
for (int xi = 0; xi < bar_w; xi++)
{
uint8_t v = *bm_bar_ptr++;
if (v < minVal) minVal = v;
if (v > maxVal) maxVal = v;
running += v;
}
}
running /= bar_w;
uint8_t threshold = minVal + ((maxVal - minVal) / 2);
//threshold = running;
// Walk the bitmap
bm_bar_ptr = bm_bar;
for (int yi = 0; yi < bar_h; yi++)
{
for (int xi = 0; xi < bar_w; xi++)
{
// Threshold all the pixels. Values > 50% go white, values <= 50% go black
// (opposite if Dark Mode)
// Could unroll this loop as an optimization, but probably not worthwhile
*bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
bm_bar_ptr++;
}
}
CGImageRelease(displayImg);
displayImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
CGContextRef bmCxt = NULL;
CGImageRef img_cg = NULL;
bm_compare_w = scaleFactor * IMG.size.width;
bm_compare_h = scaleFactor * 22;
// Create out comparison bitmap - the image that was passed in
bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);
NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
NSRect imgRect = imgRect_og;
img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];
CGContextClearRect(bmCxt, imgRect);
CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));
CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);
// Draw the image in grey
CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
CGContextDrawImage(bmCxt, imgRect, img_cg);
compareImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
// We start at the right of the menu bar, and scan left until we find a good match
int numberOfScanLines = barRect.size.width - IMG.size.width;
bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
// We use the meanValues buffer to keep track of how well the image matched for each point in the scan
meanValues = calloc(sizeof(CGFloat), numberOfScanLines);
// Walk the menubar image from right to left, pixel by pixel
for (int scanx = 0; scanx < numberOfScanLines; scanx++)
{
// Optimization, if we recently found a really good match, bail on the loop and return it
if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
break;
}
CGFloat xOffset = numberOfScanLines - scanx;
CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);
CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);
// Draw the image from our menubar
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);
// Blend mode difference is like an XOR
CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);
// Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);
CGContextFlush(compareCxt);
// Walk through the result image, to determine overall blackness
bm_compare_ptr = bm_compare;
for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
{
meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
bm_compare_ptr++;
}
meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));
// If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
// a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
if (meanValues[scanx] < 0.07) {
if (meanValues[scanx] < presumptiveMatchMeanVal) {
presumptiveMatchMeanVal = meanValues[scanx];
presumptiveMatchIdx = scanx;
}
}
CGImageRelease(displayCrop);
CGContextRelease(compareCxt);
}
}
// After we're done scanning the whole menubar (or we bailed because we found a good match),
// return the origin point.
// If we didn't match well enough, return NSZeroPoint
if (presumptiveMatchIdx >= 0) {
ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
ret.x -= (IMG.size.width + presumptiveMatchIdx);
ret.y -= 22;
}
CGImageRelease(displayImg);
CGImageRelease(compareImg);
CGColorSpaceRelease(csK);
if (bm_bar) free(bm_bar);
if (bm_compare) free(bm_compare);
if (meanValues) free(meanValues);
return ret;
}
#end
From the Apple NSStatusItem Class Reference:
Setting a custom view overrides all the other appearance and behavior settings defined by NSStatusItem. The custom view is responsible for drawing itself and providing its own behaviors, such as processing mouse clicks and sending action messages.