Efficient SpriteKit Spatial Hashing Collision Detection For 2D Game - ios

I've been looking around for tutorials on spatial hashing for Objective C or spriteKit. I've found examples in other languages, but I can't Seem to figure it out. I've gotten this far but I'm stuck.
This Loads Enemy Sprites To An NSMutableDictionary, I divide Their x,y positions by 64 to get their cellID (I use cellID as a key for the enemy in NSMutableDictionary).
- (void)loadEnemies {
TMXObjectGroup *enemiesGroup = [self.map groupNamed:#"enemies"];
for (NSDictionary *enemyDict in enemiesGroup.objects) {
NSString *enemyType = enemyDict[#"type"];
NSString *firstFrameName =
[NSString stringWithFormat:#"%#1.png",enemyType];
CGPoint enemyPosition =
CGPointMake([enemyDict[#"x"] floatValue], [enemyDict[#"y"] floatValue]);
CGPoint cellNumber =
CGPointMake(enemyPosition.x / 64, enemyPosition.y / 64);
NSString *CellID =
[NSString stringWithFormat:#"%#",NSStringFromCGPoint(cellNumber)];
SMCEnemy *enemy = [[NSClassFromString(enemyType)alloc]
initEnemyWithImageNamed:firstFrameName Position:enemyPosition];
enemy.position = enemyPosition;
enemy.player = self.cat;
enemy.map = self.map;
enemy.delegate = self;
[self.map addChild:enemy];
enemy.zPosition = 900;
//enemies added to NSMutableDictionary
[self.enemeyDictionary setObject:enemy forKey:CellID];
}
}
Ive added enemies to an NSMutableDictionary using CellID as a key.
This Checks For Collisions with the player, I first check the distance between the player and each enemy; if the distance is less than 1000, then enemy becomes active.
- (void)checkForEnemyCollisions:(SMCEnemy *)enemy {
CGFloat distance = CGPointDistance(enemy.posiiton, self.player.position);
if (distance < 1000){
enemy.isActive = YES;
} else {
enemy.isActive = NO;
}
if (enemy.isActive && enemy.life > 0) {
//cat hurt
if ((CGRectIntersectsRect(self.cat.collisionBoundingBox, enemy.collisionBoundingBox) || CGRectIntersectsRect(self.cat.collisionBoundingBox, enemy.attackBox)) && self.cat.isActive && !enemy.hidden && enemy.canHurtPlayer){
enemy.attackBox = CGRectNull;
if (enemy.position.x < self.cat.position.x) {
[self.cat bounceWithDirection:1];
}else{
[self.cat bounceWithDirection:-1];
}
[self.cat tookHit:self.cat];
[self movementCoolDownForCharacter:self.cat coolDownTime:.5];
}
// enemy hurt
if (CGRectIntersectsRect(enemy.collisionBoundingBox, self.cat.attackBox)&& !enemy.isHurt && enemy.isStun && !enemy.hidden) {
CGRect intersection = CGRectIntersection(enemy.collisionBoundingBox,self.cat.attackBox);
_hit.position = CGPointMake(intersection.origin.x, intersection.origin.y);
_hit.hidden = NO;
SKAction *DoneAction = [SKAction runBlock:(dispatch_block_t)^() {
_hit.hidden = YES;
}];
SKAction *moveLaserActionWithDone = [SKAction sequence:#[enemy.hitAnim,DoneAction]];
[_hit runAction:moveLaserActionWithDone];
enemy.isHurt = YES;
[enemy tookHit:self.cat];
[self movementCoolDownForCharacter:enemy coolDownTime:.5];
}
}
The Update Method
- (void)update:(NSTimeInterval)currentTime {
NSTimeInterval delta = currentTime - self.previousUpdateTime;
self.previousUpdateTime = currentTime;
if (delta > 0.1) {
delta = 0.1;
}
for (SMCEnemy *enemy in self.enemies) {
[self checkForEnemyCollisions:enemy];
}
}
I want to use spatial hashing instead of going through every enemy item in the enemyArray to improve performance.
Im stuck Any Help would be really awesome! :D

Related

Optimizing 2d tile collision found on Ray Wenderlich (objective-c)

I've been building a platformer game with sprite kit, and I've ran into an issue when attempting to change the size of my player spritenode to better match the art. I am using this algorithm from a Ray Weinderlich tutorial in conjunction with JSTileMap.
- (void)checkForAndResolveCollisionsForPlayer:(Player *)player forLayer:(TMXLayer *)layer
{
NSInteger indices[8] = {7, 1, 3, 5, 0, 2, 6, 8};
player.onGround = NO; ////Here
for (NSUInteger i = 0; i < 8; i++) {
NSInteger tileIndex = indices[i];
CGRect playerRect = [player collisionBoundingBox];
CGPoint playerCoord = [layer 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:layer];
if (gid != 0) {
CGRect tileRect = [self tileRectFromTileCoords:tileCoord];
//NSLog(#"GID %ld, Tile Coord %#, Tile Rect %#, player rect %#", (long)gid, NSStringFromCGPoint(tileCoord), NSStringFromCGRect(tileRect), NSStringFromCGRect(playerRect));
//1
if (CGRectIntersectsRect(playerRect, tileRect)) {
CGRect intersection = CGRectIntersection(playerRect, tileRect);
//2
if (tileIndex == 7) {
//tile is directly below Koala
player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y + intersection.size.height);
player.velocity = CGPointMake(player.velocity.x, 0.0); ////Here
player.onGround = YES; ////Here
} else if (tileIndex == 1) {
//tile is directly above Koala
player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y - intersection.size.height);
} else if (tileIndex == 3) {
//tile is left of Koala
player.desiredPosition = CGPointMake(player.desiredPosition.x + intersection.size.width, player.desiredPosition.y);
} else if (tileIndex == 5) {
//tile is right of Koala
player.desiredPosition = CGPointMake(player.desiredPosition.x - intersection.size.width, player.desiredPosition.y);
//3
} else {
if (intersection.size.width > intersection.size.height) {
//tile is diagonal, but resolving collision vertically
//4
player.velocity = CGPointMake(player.velocity.x, 0.0); ////Here
float intersectionHeight;
if (tileIndex > 4) {
intersectionHeight = intersection.size.height;
player.onGround = YES; ////Here
} else {
intersectionHeight = -intersection.size.height;
}
player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y + intersection.size.height );
} else {
//tile is diagonal, but resolving horizontally
float intersectionWidth;
if (tileIndex == 6 || tileIndex == 0) {
intersectionWidth = intersection.size.width;
} else {
intersectionWidth = -intersection.size.width;
}
//5
player.desiredPosition = CGPointMake(player.desiredPosition.x + intersectionWidth, player.desiredPosition.y);
}
}
}
}
}
//6
player.position = player.desiredPosition;
}
I am just wondering if there is a more efficient way, or a different algorithm to handle tile collisions of this nature (contact with tiles surrounding the player) that will take into account if my player's sprite is resized to be larger. As it is, if I resize my sprite to be taller than two tile heights or two tile widths, since the tiles are 16px by 16px, it causes the entire tile collision to break down due to it using a 3 by 3 grid to handle tile collisions. My goal is to be able to handle tile collisions without having to add physicsbodies to every "wall" tile, and have the algorithm function independently of the players size. Or at least be able to choose the option with the least performance overhead. I've been thinking and researching tile collision for a few weeks and those are the only two options I have seen function. Thank you everyone for the input in advance, please let me know if any more information is necessary.

Updating a node's position while adding new ones?

I have a enemy node that moves by updating its position in the update method, I add new nodes in a set interval, when two of the same nodes appear, the most recent one updates its position and the last node doesn't update its position, how do I write my code accordingly to update all the positioning of my enemy nodes when I add more?
The Enemy Method
-(void)Enemies {
SKTexture *pM1 = [SKTexture textureWithImageNamed:#"enemy-1"];
enemy = [SKSpriteNode spriteNodeWithTexture:pM1];
enemy.zPosition = 8;
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.physicsBody.categoryBitMask = fEnemyCategory;
enemy.physicsBody.contactTestBitMask = fPlatformCategory | fMainPlatformCategory | fPlayerCategory | fPitOfCertainDoomCategory;
enemy.physicsBody.collisionBitMask = fPlatformCategory | fPlayerCategory | fEnemyCategory;
enemy.physicsBody.allowsRotation = NO;
enemy.physicsBody.friction = 0.3;
enemy.physicsBody.linearDamping = 0.7;
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.affectedByGravity = YES;
enemy.physicsBody.usesPreciseCollisionDetection = YES;
enemy.position = CGPointMake(CGRectGetMidX(self.frame) + 30, CGRectGetMidY(self.frame));
enemy.position = CGPointMake(730, enemy.position.y - 129);
NSLog(#"Spawned");
[self addChild:enemy];
[enemy runAction:runAnimation];
}
Update Method
-(void)update:(CFTimeInterval)currentTime {
if (gameStart == YES & gameOver == NO) {
if (enemyExists == YES) {
if (switchMovement == NO) {
enemy.position = CGPointMake(enemy.position.x - 3.66, enemy.position.y);
}
else if (switchMovement == YES) {
enemy.position = CGPointMake(enemy.position.x + 2.83, enemy.position.y);
}
}
This is how you would implement what #LearnCocos2D has mentioned. I make no promises on casting the object as a spritenode properly ;)
Define Array:
NSMutableArray *array = [NSMutableArray alloc] init];
Add Enemy to Array:
[array addObject:myObject]; //Directly after [self addChild:enemy];
New Update:
-(void)update:(CFTimeInterval)currentTime {
if (gameStart == YES & gameOver == NO)
{
for (NSObject* o in array1)
{
SKSpriteNode enemyTemp = (SKSpriteNode*)o;
if (switchMovement == NO)
{
enemyTemp.position = CGPointMake(enemyTemp.position.x - 3.66, enemy.position.y);
}
else if (switchMovement == YES)
{
enemyTemp.position = CGPointMake(enemyTemp.position.x + 2.83, enemy.position.y);
}
}
}

What will be the best approach implementing infinite/repeating scrolling world in SpriteKit?

While moving the character it must stay inside a rectangle centred on the screen and all other game objects must scroll.
But as you get to the edge of the world it must repeat.
The characters can move in any direction.
You have to divide the world in 3 sections. Section 1 and 3 must be identical. If you reach the end of the world (Section 3) you can switch back to section 1.
http://www.youtube.com/watch?v=-FX-tFks5pg
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_background = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
_background.anchorPoint = CGPointMake(0, 0);
_background.name = #"background";
_background.position = CGPointMake(0, 0);
[self addChild:_background];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (_lastUpdateTime) {
_deltaTime = currentTime - _lastUpdateTime;
} else {
_deltaTime = 0;
}
_lastUpdateTime = currentTime;
if (_deltaTime > 1) {
_deltaTime = 1.0 / 60.0;
}
[self enumerateChildNodesWithName:#"background" usingBlock:^(SKNode *node, BOOL *stop) {
node.position = CGPointMake(node.position.x - backgroundMoveSpeed * _deltaTime, node.position.y);
if (node.position.x < - (node.frame.size.width + 100)) {
[node removeFromParent];
}
}];
if (_background.position.x < -bound) {
//bound = 500
SKSpriteNode *temp = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
temp.anchorPoint = CGPointMake(0, 0);
temp.name = #"background";
temp.position = CGPointMake(_background.position.x + _background.frame.size.width, 0);
[self addChild:temp];
_background = temp;
}
the background image's size is 2048x640, so you should change the bound according to your background image's size.

Set sprite using background position

I am new to cocos2d so please help me if u can
I have background moving from right to left, and the background contains small windows with 3 rows of windows
_spaceDust1 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_spaceDust2 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
CGPoint dustSpeed = ccp(0.1 , 0.1);
CGPoint bgSpeed = ccp(0.05 , 0.05);
[_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)];
Now add enemies wich also move from right to left with same speed
_robbers = [[CCArray alloc] initWithCapacity:kNumAstroids];
for (int i = 0; i < kNumAstroids; ++i) {
CCSprite *asteroid = [CCSprite spriteWithSpriteFrameName:#"robber.png"];
asteroid.visible = NO;
[_batchNode addChild:asteroid];
[_robbers addObject:asteroid];
}
in update method:
double curTime = CACurrentMediaTime();
if (curTime > _nextRunemanSpawn) {
float randSecs = [self randomValueBetween:0.20 andValue:1.0];
_nextRunemanSpawn = randSecs + curTime;
float randY = 80.0;
float randY1 = 185.0;
float randY2 = 293.0;
float randDuration = [self randomValueBetween:5.2 andValue:5.2];
float randDuration1 = [self randomValueBetween:1.0 andValue:1.0];
CCSprite *asteroid = [_robbers objectAtIndex:_nextRobber];
_nextRobber++;
if (_nextRobber >= _robbers.count) {
_nextRobber = 0;
}
//[asteroid stopAllActions];
int winChoice = arc4random() % 3;
if (winChoice == 0) {
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY);
asteroid.visible = YES;
}
else if(winChoice == 1){
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY1);
asteroid.visible = YES;
}else {
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY2);
asteroid.visible = YES;
}
[asteroid runAction:[CCSequence actions:[CCMoveBy actionWithDuration:randDuration position:ccp(-winSize.width-asteroid.contentSize.width, 0)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],nil]];
All is going well but i want to set this enemies in to window and in random position
so how can i set x-argument of enemies so it can be fix in to window of the background?
For some reason I cannot comment so this is written as an answer. What exactly are you trying to do? I am a bit confused. It sounds like you want the X position to be set so that it is in one of 3 random positions, is that correct?

How do I find which CCSprite in an array is closest to a given position?

I've been trying to create AI for my Enemy class, and I'm having problems updating which Enemy is closest to Player. It works when there's only two enemies but when theres 3 or more thats when I get issues. I only want the closest to attack, but all the enemies except the farthest do.
I think it's because with three or more sprites one can be in between the others making his position safePosition and attackPosition, but how would I get the middle enemies to realize when any tempEnemy is in front of them?
CCArray *fellowEnemy = [self allyArray];
for (int i = 0; i<[fellowEnemy count]; i++) {
Enemy *tempEnemy = [fellowEnemy objectAtIndex:i];
if (tempEnemy == self) continue;
CGPoint tempDifference = ccpSub(player.position, tempEnemy.position);
CGPoint selfDifference = ccpSub(player.position, self.position);
float tempToPlayer = ccpLength(tempDifference);
float selfToPlayer = ccpLength(selfDifference);
if (tempToPlayer > selfToPlayer) {
newPosition = attackPosition;
}else if (tempToPlayer < selfToPlayer){
newPosition = safePosition;
}else{
newPosition = safePosition;
}
}
[self setPosition:newPosition];
Thank You
If I understand your question correctly, you are trying to determine the enemy closest to the player. The one in the middle will always be the closest to the player. So you just gonna have to find out the one which has minimum distance to the player.
CCArray *fellowEnemy = [self allyArray];
if( fellowEnemy.count > 0 )
{
Enemy *closestEnemy = nil;
float minDistance = 0;
for (int i = 0; i<[fellowEnemy count]; i++) {
Enemy *tempEnemy = [fellowEnemy objectAtIndex:i];
if (tempEnemy == self) continue;
if( closestEnemy == nil )
{
closestEnemy = tempEnemy;
minDistance = ccpDistance(player.position, closestEnemy.position);
continue;
}
float curDistance = ccpDistance(player.position, tempEnemy.position);
if( curDistance < minDistance )
{
minDistance = curDistance;
closestEnemy = tempEnemy;
}
}
if( closestEnemy != nil )
{
// Here you have closest enemy
// Write your remaining logic (position, attack... whatever)
}
}

Resources