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.
Related
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
I am using this tutorial http://www.raywenderlich.com/1768/uiview-tutorial-for-ios-how-to-make-a-custom-uiview-in-ios-5-a-5-star-rating-view for custom rating-bar. but I am not able to give half rating. I am trying to display half star when user touch on imageview. Please suggest me
Finally I fixed half-star problem.
if (!self.editable) return;
//The rating starts out as 0 and then builds from there.
CGFloat newRating = 0;
//loop through the image collection backwards so if it exits the loop it will have identified the MAX
for(int i = self.imageViews.count - 1; i >= 0; i--) {
UIImageView *imageView = [self.imageViews objectAtIndex:i];
CGFloat distance = touchLocation.x - imageView.frame.origin.x;
CGFloat frameWidth = imageView.frame.size.width;
if (distance <= 0){
//this means that the click was to the left of the frame
continue;
}
if (distance /frameWidth >.75) {
//If this ratio is >.75 then you are to the right 3/4 of the image or past th image
newRating = i + 1;
break;
} else {
//you didn't drop out or mark the entire star, so mark it half.
newRating = i + 0.5;
break;
}
}
self.rating = newRating;
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.
Hello good people of stack overflow
I stand before you today with a (not very great) cocos2d issue.
How can I check collisions WHILE in an action, instead of checking the target tile?
I am working with a tilemap that has a meta layer where there are collidable tiles. The following code works when I click the collidable tile, but not when I click beyond it, if that happens he will just walk straight through.
Currently, I detect collisions with this code:
-(void)setPlayerPosition:(CGPoint)position {
CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid) {
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties) {
NSString *collision = properties[#"Collidable"];
if (collision && [collision isEqualToString:#"True"]) {
return;
}
}
}
float speed = 10;
CGPoint currentPlayerPos = _player.position;
CGPoint newPlayerPos = position;
double PlayerPosDifference = ccpDistance(currentPlayerPos, newPlayerPos) / 24;
int timeToMoveToNewPostition = PlayerPosDifference / speed;
id moveTo = [CCMoveTo actionWithDuration:timeToMoveToNewPostition position:position];
[_player runAction:moveTo];
//_player.position = position;
}
By the way, this gets called from this method:
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
[_player stopAllActions];
CGPoint touchLocation = [touch locationInView:touch.view];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
CGPoint playerPos = _player.position;
CGPoint diff = ccpSub(touchLocation, playerPos);
int diffx = diff.x;
int diffy = diff.y;
int adiffx = diffx / 24;
int adiffy = diffy / 24; //Porque my tile size is 24
if ( abs(diff.x) > abs(diff.y) ) {
if (diff.x > 0) {
playerPos.x = playerPos.x + adiffx * 24;
} else {
playerPos.x = playerPos.x + adiffx * 24;
}
} else {
if (diff.y > 0) {
playerPos.y = playerPos.y + adiffy * 24;
}else{
playerPos.y = playerPos.y + adiffy * 24;
}
}
[self setPlayerPosition:playerPos];
}
I tried
[self schedule:#selector(setPlayerPosition:) interval:0.5];
Without any luck, that just instantly crashed the app at this
NSAssert( pos.x < _layerSize.width && pos.y < _layerSize.height && pos.x >=0 && pos.y >=0, #"TMXLayer: invalid position");
And i can't really blame it for crashing there.
How do I constantly check for collisions with the collidable meta tiles while a CCMoveTo is running?
In your init, schedule a collision detecting method:
[self schedule:#selector(checkCollisions:)];
Then define it as follows:
-(void)checkCollisions:(ccTime)dt
{
CGPoint currentPlayerPos = _player.position;
CGPoint tileCoord = [self tileCoordForPosition:currentPlayerPos];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid)
{
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties)
{
NSString *collision = properties[#"Collidable"];
if (collision && [collision isEqualToString:#"True"])
{
[_player stopAllActions];
return;
}
}
}
}
I using a similar meta layer. I personally skipped using MoveTo, and created my own update code that both moves the sprite and does collision detection.
I figure out what the potential new position would be, then I find out what tile position is in all four corners of the sprite at that new position, then I cycle through all tiles that the sprite would be on and if any are collidable, I stop the sprite.
I actually go a step further and check if moving only the x, or only the y changes the results, then move to that position instead if it does.
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)
}
}