SpriteKit - How do I check if a certain set of coordinates are inside an SKShapeNode? - coordinates

In my game, I'm trying to determine what points to dole out depending on where an arrow hits a target. I've got the physics and collisions worked out and I've decided to draw several nested circular SKShapeNodes to represent the different rings of the target.
I'm just having issues working out the logic involved in checking if the contact point coordinates are in one of the circle nodes...
Is it even possible?

The easiest solution specific to Sprite Kit is to use the SKPhysicsWorld method bodyAtPoint:, assuming all of the SKShapeNode also have an appropriate SKPhysicsBody.
For example:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:CGPointMake(100, 200)];
if (body != nil)
{
// your cat content here ...
}
If there could be overlapping bodies at the same point you can enumerate them with enumerateBodiesAtPoint:usingBlock:

You can also compare the SKShapeNode's path with your CGPoint.
SKShapeNode node; // let there be your node
CGPoint point; // let there be your point
if (CGPathContainsPoint(node.path, NULL, point, NO)) {
// yepp, that point is inside of that shape
}

Related

How to check for gameObject while Grid Snaping in Unity2D

I am trying to make a game which allows moving objects by snapping them on the grid, I already figured out to snape them to grid but there is one thing little problem, I want to check if there is already a game object placed on that same grid so that I won't let the dragging game object snap to that same spot but the thing is that I have a different game object shapes.
see for yourself
Click to see the image
how can I achieve that?
Since you're on a square grid I think the best way to do this is with Physics2D.Boxcast(). Basically what you're doing is casting a box at the snap vector before moving the game object.
So in your code before you move the game object to the snap location:
RaycastHit2D hit = Physics2D.BoxCast(snapTo, boxSize, 0.0f, Vector2.zero);
if (hit == null)
{
// We're clear to move
}
else
{
// Something is in the way
}
Where snapTo is the Vector2 of the location you're going to snap to and boxSize is a Vector2 equal to the size of one grid position (you might need to play around with this a bit). The last two arguments, 0.0f refers to the angle of the box, which we don't need so just set it to zero and Vector2.zero is the direction of the cast, but we're casting in one spot so this also doesn't matter.
I'm assuming that only one game object can occupy the space at once, so there will only ever be one hit. If there's a chance for more than one you can change it to RaycastHit2D[] hits and Physics2D.BoxCastAll then check if hits.Length is greater than 0.
I had some troubles with Physics2D.Boxcast() , so instead i used Physics2D.OverlapBox() and it is working just fine.
isColl = Physics2D.OverlapBox(ObjectToMove.position, size, 0f, layerM);
if (isColl == true)
{
// Something is in the way
}
else
{
//Clear to go
}

Spritekit endless runner parent SKNode

I'm familiar with swift but I've started dabbling my hand in spritekit. I've been following a tutorial about created an endless runner. The approach taken by the author is to create a single SKNode that contains all the children. This container node is then moved rather than moving all the child Nodes individually. But what's got me stumped is that the container node doesn't have a size associated with it so I'm a bit confused as to how/why this works and the author doesn't really explain it.
So we have
let containerNode = SKNode()
let thePlayer = Player("image":"player") //SKSpriteNode
let inc = 0
override func didMoveToView(){
self.anchorPoint = CGPointMake(0.5,0)
addChild(containerNode )
containerNode.addChild(player)
moveWorld()
}
func moveWorld(){
let moveWorldAction = SKAction.moveByX(-screenWidth, y:0, duration:6)
let block = SKAction.runBlock(movedWorld)
let seq = SKAction.sequence([moveWorldAction,block])
let repeatAction = SKAction.repeatActionForever(seq)
containerNode.runAction(repeatAction)
}
func movedWorld() {
inc = inc + 1
addObjects()
}
func addObjects() {
let obj = Object()
containerNode.addChild(obj)
let ranX = arc4random_uniform(screenWidth)
let ranY = arc4random_uniform(screenHeight)
obj.position = CGPointMake(screenWidth * (inc + 1) + ranX, ranY)
}
There's some conversion that I've omitted in the code above from int to float but it's not necessary for the point I want to understand.
I get why when new objects are created we do the multiple by the increment, but what I don't get is the containerNode doesn't have a size, so why do it's children show? Is this the most efficient way to do this?
I'm assuming that it's just convenience rather than moving all the other objects individually but the fact that it doesn't have a size is confusing me.
Since an SKNode isn't rendered, it doesn't need (or have) a size property. Only SKNode subclasses (SKLabelNode, SKShapeNode, etc.) that are visible require a size (declared explicitly or calculated implicitly). For example, SpriteKit needs to know the size of an SKSpriteNode with a 20 x 20 texture to correctly render it. You can use SKNode's calculateAccumulatedFrame method to determine the total size (a rectangle) of all of the descendants (other than other SKNodes) in its node tree.
If I understand the documentation for SKNode correctly this little section should answer your question:
Every node in a node tree provides a coordinate system to its
children. After a child is added to the node tree, it is positioned
inside its parent’s coordinate system by setting its position
properties. A node’s coordinate system can be scaled and rotated by
changing its xScale, yScale, and zRotation properties. When a node’s
coordinate system is scaled or rotated, this transformation is applied
both to the node’s own content and to that of its descendants.
Source
Again, if I'm reading this correctly, it seems that when a node is added to a node tree it inherits it's parent coordinate system. So by default containerNode is the same size as self in this case.
Also according to the frame property of a SKNode:
The frame is the smallest rectangle that contains the node’s content,
taking into account the node’s xScale, yScale, and zRotation
properties. Source
Which again sounds like if a frame isn't set it takes the smallest rectangle (self in this case or it might be understood that it's the children of containerNode I'm not sure) as it's own frame.
Whatever the case is, it's inheriting it from another SKNode.

Detect other Spritenode within range of Spritenode?

I have a (moving) sprite node.
I'd like to detect other (moving) sprite nodes within a certain range of this node. Once one is detected, it should execute an action.
The playing an action part is no problem for me but I can't seem to figure out the within-range detection. Does have any ideas how to go about this?
A simple, but effective way to do this is comparing the position's in your scene's didEvaluateActions method. didEvaluateActions gets called once a frame (after actions have been evaluated but before physics simulation calculations are run). Any new actions you trigger will start evaluating on the next frame.
Since calculating the true distance requires a square root operation (this can be costly), we can write our own squaredDistance and skip that step. As long as our range/radius of detect is also squared, our comparisons will work out as expected. This example shows detect with a "true range" of 25.
// calculated the squared distance to avoid costly sqrt operation
func squaredDistance(p1: CGPoint, p2: CGPoint) -> CGFloat {
return pow(p2.x - p1.x, 2) + pow(p2.x - p1.x, 2)
}
// override the didEvaluateActions function of your scene
public override func didEvaluateActions() {
// assumes main node is called nodeToTest and
// all the nodes to check are in the array nodesToDetect
let squaredRadius: CGFloat = 25 * 25
for node in nodesToDetect {
if squareDistance(nodeToTest.position, p2: node.position) < squaredRadius {
// trigger action
}
}
}
If the action should only trigger once, you'll need to break out of the loop after the first detection and add some sort of check so it does not get triggered again on the next update without the proper cool down period. You may also need to convert the positions to the correct coordinate system.
Also, take a look at the documentation for SKScene. Depending on your setup, didEvaluateActions might not be the best choice for you. For example, if your game also relies on physics to move your nodes, it might be best to move this logic to didFinishUpdate (final callback before scene is rendered, called after all actions, physics simulations and constraints are applied for the frame).
Easiest way I can think of without killing performance is to add a child SKNode with an SKPhysicsBody for the range you want to hit, and use this new nodes contactBitMask to determine if they are in the range.
Something like this (pseudo code):
//Somewhere inside of setup of node
let node = SKNode()
node.physicsBody = SKPhysicsBody(circleOfRadius: 100)
node.categoryBitMask = DistanceCategory
node.contactBitMask = EnemyCategory
sprite.addNode(node)
//GameScene
func didBeginContact(...)
{
if body1 contactacted body2
{
do something with body1.node.parent
//we want parent because the contact is going to test the bigger node
}
}

Creating a sprite during didBeginContact

I've been working on some SpriteKit tutorials. I understand the whole collision thing and have verified with NSLog that a collision is being registered between my two objects. However for some really strange reason my sprite is not being created (or rather shown) when it's done during didBeginContact.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CNPhysicsCategoryPlayer | CNPhysicsCategoryRock))
{
NSLog(#"ouch");
SKSpriteNode *bigOuch = [SKSpriteNode spriteNodeWithImageNamed:#"star"];
bigOuch.position = CGPointMake(200, 200);
[self addChild:bigOuch];
}
}
I get the ouch log message but no sprite appears.
I have tried the same (sprite creation) code in other parts of my program and have no issues. What am I doing wrong?
I was stuck on the exact same issue a while back. You can create a SKSpriteNode and add it to the view but it does not get displayed. The short of it is that I ended up creating an array and adding any sprites I needed to create during the didBeginContact phase. During the update phase I checked the array and added them to my view. Just remember to empty the array after you are done. Otherwise you will end up with the same sprite being added over and over again.

Make randomised sprites in a specific co-ordinate do something

I was wondering if anyone could help me with my program,
I have randomised my sprites into a specific set of co-ordinates.
I want one of the sprites that is at that specific co-ordinate, to be able to make them do something when they are at this random co-ordinate. The problem i am having is that i have to make a long list of if statements saying if this sprite is here do this if another sprite is here do the exact same thing.
if (red1.position.y>=0 && red1.position.y<=63) {
id r1animation = [CCMoveTo actionWithDuration:0.2 position:ccp(red1.position.x,33)];
[red1 runAction:r1animation];
}
if (red2.position.y>=0 && red2.position.y<=63) {
id r2animation = [CCMoveTo actionWithDuration:0.2 position:ccp(red2.position.x,33)];
[red2 runAction:r2animation];
}
i want to be able to say if any of the sprites are at that exact co-ordinate then move them to a point, in a short amount of code as possible. so basically grouping the sprites or something i'm not sure.
Thanks
i want to be able to say if any of the sprites are at that exact co-ordinate then move them to a point
Firstly, specify the 'hotspot' programatically:
CGPoint hotspot = ccp(32,32); // convenience macro,
//creates a CGPoint with x = 32, y = 32
You should store a reference to all your sprites in an array when you create them (you can use cocos2d's 'tagging' also, but I usually like to use an array for simplicity)
-(void)init {
//.. misc
// creating sprite returns a reference so keep it in an array
CCSprite* curSprite = [CCSprite spriteWithFile: //...etc]
[self.spriteArray addObject: curSprite];
// add all sprite references to your array
}
Now you can iterate over this array to see if any of the sprite's frames overlap the hotspot:
-(BOOL) checkAllSpritesForCollision
{
for (CCSprite *sp in self.spriteArray)
{
CGRect spriteRect = sp.frame;
if (CGRectContainsPoint(spriteRect,hotspot))
{
// run your action on sp...
}
}
// you might like to return YES if a collision happened?
}
This is a brute force method of checking whether every sprites frame contains a given point. There are many ways to skin this cat of course, but hopefully this will set you on a better path.
What you can do is to calculate the distance:
float pointX = thePoint.position.x;
float pointY = thePoint.position.y;
float pointDeltax = sprite.position.x-pointX;
float pointDeltay = sprite.position.y-pointY;
float pointDist = sqrt(pointDeltax*pointDeltax+pointDeltay*pointDeltay);
But maybe davbryns solution suits your purpose better.