Get a list of nodes in an specific area?
Asked Answered
E

7

7

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.

This is what I'm trying now:

import SpriteKit
import CoreMotion

class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()

override func didMoveToView(view: SKView) {
    self.physicsWorld.contactDelegate = self
    self.scaleMode = SKSceneScaleMode.AspectFit
    self.size = view.bounds.size

    // Add world
    world = SKShapeNode(rectOfSize: view.bounds.size)
    world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
    world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
    self.addChild(world)

    // Add player
    player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
    player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
    player.physicsBody.dynamic = false
    player.strokeColor = SKColor.blueColor()
    player.fillColor = SKColor.blueColor()
    player.position = CGPointMake(90, -50)
    world.addChild(player)
    }


override func update(currentTime: CFTimeInterval) {
    // Define rect position and size (area that will be evaluated for bodies)
    var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)

    // Show rect for debug
    rShape.removeFromParent()
    rShape = SKShapeNode(rect: r)
    rShape.strokeColor = SKColor.redColor()
    self.addChild(rShape)

    // Evaluate rect
    rShape.fillColor = SKColor.clearColor()
    self.physicsWorld.enumerateBodiesInRect(r) {
        (body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
         self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
         }
    }
}

This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.

Could this be because this is just beta or am I doing something wrong?

The rect should only be red only if the player is inside it (blue square), however it's red when the player is near

Elisabethelisabethville answered 20/7, 2014 at 14:11 Comment(2)
I think it would help if you added a link to a demo project.Priesthood
It seems this might be a bug in the latest beta, see this thread for other people experiencing similar issues finding bodies based on some kind of position. devforums.apple.com/message/1003884#1003884Duumvir
I
1

You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.

Think of it like a 'tractor beam' or a 'warp rectangle'.

Ing answered 24/7, 2014 at 17:44 Comment(1)
I implemented this solution. I must point out for anybody looking for something similar that this doesn't work by declaring the node locally and afterwards using enumerateBodiesInRect() since contacts are processed after each game cycle.Elisabethelisabethville
P
1

I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:

self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
    // enumerates all nodes in given frame
}
Priesthood answered 20/7, 2014 at 15:11 Comment(7)
Hi, this works great however I had to change CMutablePointer<ObjCBool> is not a subtype of UnsafePointer<ObjCBool> so I had to change it to CMutablePointer. Were there any reasons you chose the other?Elisabethelisabethville
@Elisabethelisabethville I was only following the method definition in SKPhysicsWorld's definitions file. This may have changed at some point though, what beta are you using? Anyway, use which ever the compiler lets you use. Beyond that, I had no reason for using what I did.Priesthood
I'm having some trouble implementing this. The method does work however I cannot define the exact area to be evaluated. I tried to paint the rect with SKShapeNode(rect: r) so I could see if it worked correctly, but seems it enumarates nodes that are nearby but outside the painted area. I just added my implementation to your answer.Elisabethelisabethville
Hey, I added a bounty and edited the question. I could get close to the solution with your answer but I'm still having problems defining the rect position and showing where it is on screen.Elisabethelisabethville
@Elisabethelisabethville why do you need to "show where it is on screen"? If you know that the sprite is in the given rect, can't you just draw that rect on screen?Synthiasyntonic
I wanted to show the rect because it seemed to me enumarateBodiesInRect wasn't working properly and it isn't. If you look at my code I'm drawing the rect on the screen and still enumerateBodiesInRect detects bodies outside it, I think it may have to do with scaling but I don't know. The rectangle on the screen is smaller than the actual area detected by enumerateBodiesOnScreen.Elisabethelisabethville
enumerateBodiesInRect starts detecting things 20px outside the rect. That's really the problem. Is there a chance the method can be so inaccurate?Elisabethelisabethville
I
1

You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.

Think of it like a 'tractor beam' or a 'warp rectangle'.

Ing answered 24/7, 2014 at 17:44 Comment(1)
I implemented this solution. I must point out for anybody looking for something similar that this doesn't work by declaring the node locally and afterwards using enumerateBodiesInRect() since contacts are processed after each game cycle.Elisabethelisabethville
K
1

I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.

enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.

I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.

The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.

Kappa answered 25/7, 2014 at 3:29 Comment(6)
could this be related to the fact Swift is in beta or something similar? The problem of using didBeginContact() is that I need to keep an array of every area I want to evaluate, I cannot just have a method to check an area at any time.Elisabethelisabethville
I was using SpriteKit with Objective C. While I am using XCode beta, this should be be functionality that has been gold for a year.Kappa
Can I ask why you need arrays of objects in the areas instead of simply checking upon contact beginning and end?Kappa
yes, I want to implement something like line of sight, so imagine I'm building a metal gear like game and use this method to know when enemies are "seeing" the player. I would need to have a "vision" body for every enemy and if I don't want to run an action immediatelly after the contact. If I need to do more checks the number of nodes would escalate very quickly.Elisabethelisabethville
How would rects (in enumerateBodiesInRect) help you with line of site? I would imagine the best thing in that instance is a "vision cone" which is represented by a triangle (or very tall trapezoid). This would map well with SKShapeNode. As for the "number of nodes", I'm not sure that matters too much. At most, you'd have a vision cone for every enemy, and you could even simplify that by determining the player's vision in 360 degrees, then doing a simple check to see if any enemy the player can see is facing the player. 1 cone per enemy would be the simplest, though.Kappa
For now I'm ok with a rect, probably when I get it to work I'll change it for a cone. I managed to get line of sight working using collission detection so I'll accept your answer when the bounty is nearer expiration! Still I'm having problem's with the vision node not adjusting it's position when it's parent moves. #24966033Elisabethelisabethville
S
1

The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.

I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.

In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.

In order to use enumerateBodiesInRect correctly, I had to do something as follows:

// get your world rect based on game logic
let worldRect = getWorldRect()

// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)

let worldScale = scene.worldNode.xScale // assert this is not 0

// now to get the scene rect relative to the world rect, in scene coordinates

let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)

world.physicsWorld.enumerateBodiesInRect(sceneRect) {
       // your code here
}

Hope this helps.

Serrell answered 22/9, 2015 at 22:15 Comment(2)
For example, sample output shows the difference: worldRect:(100.712, 16.0, 10.0, 121.637) vs scene:(123.212, 16.0, 10.0, 121.637) - in which my worldNode had moved a bit to the right.Serrell
Also note that enumerateBodies will return all sprite nodes whose sprite frame intersects with the scene rect, not necessarily its physics body rect which may be smaller than the sprite's frame.Serrell
B
1

I am not sure if this is a good practice. Correct me if not. But I am using

let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)

I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.

Brigadier answered 4/11, 2021 at 15:34 Comment(0)
S
0

There is only nodesAtPoint: method.

To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.

Subserve answered 20/7, 2014 at 14:31 Comment(1)
Hey, I added a bounty and edited the question. I don't think I'll use nodesAtPoint. I need to check an area or a line.Elisabethelisabethville
P
0

yes problem may occur because of your player's image, for example try to use 10px smaller body size:

player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));
Peasant answered 24/7, 2014 at 9:43 Comment(2)
I just tried I'm still having the same problem. The are evaluated is really imprecise, it starts detecting things 20px outside it.Elisabethelisabethville
I see, maybe solution is to use different alghoritms or soPeasant

© 2022 - 2024 — McMap. All rights reserved.