How to prevent spawning overlap in swift sprite kit? - swift

If I'm trying to spawn in enemies in 2 defined areas of the screen (top and bottom with a middle section where they can't spawn in), how do I prevent them from spawning on top of or too near each other .
My sprites are relatively quite small to the screen, and the only suggestion I've found on here is to create an array of possible positions and every time you use one of those positions take it off the list, but first of all I don't know how that would even look, second of all I've got SO many possible positions because I'm working with 5px high sprites, and I want for them to be able to respawn once that area is clear.
My method of choosing top or bottom is just picking a random number 1 or 2, and depending I've got 2 functions that make them either on top or on bottom.
I just need for no 2 objects to spawn in a ball's diameter from each other. Any ideas how to incorporate that into my spawning?
edit:
//Create your array and populate it with potential starting points
var posArray = Array<CGPoint>()
posArray.append((CGPoint(x: 1.0, y: 1.0))
posArray.append((CGPoint(x: 1.0, y: 2.0))
posArray.append((CGPoint(x: 1.0, y: 3.0))
//Generate an enemy by rolling the dice and
//remove its start position from our queue
let randPos = Int(arc4random()) % posArray.count
posArray[randPos]
posArray.removeAtIndex(randPos)
...
//Play game and wait for enemy to die
//Then repopulate the array with that enemy's start position
posArray.append(enemyDude.startPosition)
This is the recommendation I found, but this gives errors "expected separator" that I don't really know how to fix.
And so really, I'd have to make a HUGE array of possible positions going across the X and Y covering all areas, or is there some better way to do this?

Not spawning a node on top of another is simply enough by using intersectsNode(_ node: SKNode) -> Bool.
As for not spawning too close, that's another story. The only way you can do that is too have all your current nodes in an array, enumerate the array and check each node's position to that of the spawning node. Dependent on your parameters, you either spawn or not spawn.
I am not versed in Swift so you will have to translate the code yourself.
-(void)testMethod {
// an array with all your current nodes
NSMutableArray *myArray = [NSMutableArray new];
// the potential new spawn node with the proposed spawn position
SKSpriteNode *mySpawnNode = [SKSpriteNode new];
BOOL tooClose = NO;
// enumerate your node array
for(SKSpriteNode *object in myArray) {
// get the absoulte x and y position distance differences of the spawn node
// and the current node in the array
// using absolute so you can check both positive and negative values later
// in the IF statement
float xPos = fabs(object.position.x - mySpawnNode.position.x);
float yPos = fabs(object.position.y - mySpawnNode.position.y);
// check if the spawn position is less than 10 for the x or y in relation
// to the current node in the array
if((xPos < 10) || (yPos < 10))
tooClose = YES;
}
if(tooClose == NO) {
// spawn node
}
}
Note that the array should be a property and not declared in the scope I have it in the example.

Related

Unity .GetParticles() high CPU spike

Im using .GetParticles() on a particlesystem to then destroy the particles when they are outisde of a certain range. My script is below and the particle system variable is cached. I attached the deep profile picture of what is taking the CPU and it points to GetParticles(). I can't find any documentation to why this would be a bad approach, as the script is only attached to one single game object.
Any insight appreciated, thanks!
private void Update()
{if (Time.time > (delayTimer + 0.02f))
{
delayTimer = Time.time;
transform.position = new Vector3(transform.position.x, Water.transform.position.y - 2f, transform.position.z);
//Gets the change in the waters height and resets it
var waterHeightChange = Mathf.Abs(Water.transform.position.y - waterHeight);
waterHeight = Water.transform.position.y;
//Gets the distance to the top of the water to pop the bubble if surpassed
var DistanceToDestroy = Mathf.Abs((Water.transform.position.y + (Water.transform.localScale.y / 2f) - .15f) - transform.position.y);
//Creates a particle array sized a the emitters maximum particles
//Gets the particles from the emitter system and transfers into array of particles, returns total number of particles
//Only called on occasion (once at start really) to make allocation as minimal as possible
if (pSystemParticles == null || pSystemParticles.Length < pSystem.main.maxParticles)
{
pSystemParticles = new ParticleSystem.Particle[pSystem.main.maxParticles];
}
int numParticlesAlive = pSystem.GetParticles(pSystemParticles);
for (int i = 0; i < numParticlesAlive; i++)
{
//Changes the height accordingly for each particle
newPos.Set(pSystemParticles[i].position.x, pSystemParticles[i].position.y, pSystemParticles[i].position.z + waterHeightChange);
//Grab the 'y' positional height relative to the emitter
var particleHeight = newPos.z;
if (particleHeight >= DistanceToDestroy)
{
//Deletes particle if needed
pSystemParticles[i].remainingLifetime = 0;
}
}
//Sets the particle system from the modified array
pSystem.SetParticles(pSystemParticles, numParticlesAlive);
}
}
I'm not sure why you get this spike but this seems to be outside of your control. Maybe you could use the Collision Module, put an invisible plane above your waterline where the particles should stop and then set up the collision module such that the particles lose their whole lifetime when they collide (Lifetime Loss = 1). That way they should disappear when colliding with the plane. Could be faster because particles would never have to enter the C# world plus it's a lot less code to write.

How to move a rotated SCNNode in SceneKit, on its "own" axis?

The image below shows a rotated box that should be moved horizontally on the X and Z axes. Y should stay unaffected to simplify the scenario. The box could also be the SCNNode of the camera, so I guess a projection does not make sense at this point.
So lets say we want to move the box in the direction of the red arrow. How to achieve this using SceneKit?
The red arrow indicates -Z direction of the box. It also shows us it is not parallel to the camera's projection or to the global axes that are shown as dark grey lines of the grid.
My last approach is the product of a translation matrix and a rotation matrix that results in a new transformation matrix. Do I have to add the current transform to the new transform then?
If yes, where is the SceneKit function for the addition of matrices like SCNMatrix4Mult for multiplication or do I have to write it myself using Metal?
If no, what I'm missing out with the matrix calculations?
I don't want to make use of GLKit.
So my understanding is that you want to move the Box Node along its own X axis (not it's parents X axis). And because the Box Node is rotated, its X axis is not aligned with its parent's one, so you have the problem to convert the translation between the two coordinate systems.
The node hierarchy is
parentNode
|
|----boxNode // rotated around Y (vertical) axis
Using Transformation Matrices
To move boxNode along its own X axis
// First let's get the current boxNode transformation matrix
SCNMatrix4 boxTransform = boxNode.transform;
// Let's make a new matrix for translation +2 along X axis
SCNMatrix4 xTranslation = SCNMatrix4MakeTranslation(2, 0, 0);
// Combine the two matrices, THE ORDER MATTERS !
// if you swap the parameters you will move it in parent's coord system
SCNMatrix4 newTransform = SCNMatrix4Mult(xTranslation, boxTransform);
// Allply the newly generated transform
boxNode.transform = newTransform;
Please Note: The order matters when multiplying matrices
Another option:
Using SCNNode coordinate conversion functions, looks more straight forward to me
// Get the boxNode current position in parent's coord system
SCNVector3 positionInParent = boxNode.position;
// Convert that coordinate to boxNode's own coord system
SCNVector3 positionInSelf = [boxNode convertPosition:positionInParent fromNode:parentNode];
// Translate along own X axis by +2 points
positionInSelf = SCNVector3Make(positionInSelf.x + 2,
positionInSelf.y,
positionInSelf.z);
// Convert that back to parent's coord system
positionInParent = [parentNode convertPosition: positionInSelf fromNode:boxNode];
// Apply the new position
boxNode.position = positionInParent;
Building on #Sulevus's correct answer, here's an extension to SCNNode that simplifies things by using the convertVector rather than the convertPosition transformation, in Swift.
I've done it as a var returning a unit vector, and supplied an SCNVector3 overload of multiply so you can say things like
let action = SCNAction.move(by: 2 * cameraNode.leftUnitVectorInParent, duration: 1)
public extension SCNNode {
var leftUnitVectorInParent: SCNVector3 {
let vectorInSelf = SCNVector3(x: 1, y: 0, z: 0)
guard let parent = self.parent else { return vectorInSelf }
// Convert to parent's coord system
return parent.convertVector(vectorInSelf, from: self)
}
var forwardUnitVectorInParent: SCNVector3 {
let vectorInSelf = SCNVector3(x: 0, y: 0, z: 1)
guard let parent = self.parent else { return vectorInSelf }
// Convert to parent's coord system
return parent.convertVector(vectorInSelf, from: self)
}
func *(lhs: SCNVector3, rhs: CGFloat) -> SCNVector3 {
return SCNVector3(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs)
}
func *(lhs: CGFloat, rhs: SCNVector3) -> SCNVector3 {
return SCNVector3(x: lhs * rhs.x, y: lhs * rhs.y, z: lhs * rhs.z)
}
}
The far easier way this is usually done:
The usual, normal, and extremely easy way to do this in any game engine or 3D engine is:
You simply have a wrapper node, which, holds the node in question.
This is indeed the entire point of transforms, they enable you to abstract out a certain motion.
That's the whole point of 3D engines - the GPU just multiplies out all the quaternions on the way down to the object; it's wholly pointless to (A) figure out in your head the math and (B) do it manually (indeed in the CPU).
In Unity it's "game objects", in scene kit it's "nodes" and so on.
In all 3D engines, including scene kit, almost everything has one or more "holders" around it.
To repeat, the reasons for this are (A) it's the entire raison d'etre of a game engine, to achieve performance in multiplying out the quaternions of every vertex and (B) sheer convenience and code solidity.
One of a million examples ...
Of course you can trivially do it in code,
cameraHolder.addChildNode(camera)
In the OP's example. It looks like you would use cameraHolder only to rotate the camera. And then for the motion the OP is asking about, simply move camera.
It's perfectly normal to have a chain of a number of nodes to get to an object.
This is often used for "effects". Say you have an object, which sometimes has to "vibrate up and down". You can have one node which only does that movement. Note that then, all the animations etc for that movement only have to be on that node. And critically, they can run independently of any other animations or movements. (And indeed you can just use the node elsewhere to jiggle something else.)

Swift 3 (SpriteKit): Locking the x axis of a SKSpriteNode and its physicsBody

I really need to know how to lock the x axis of an SKSpriteNode and its physicsBody. I need to keep the SKSpriteNode dynamic and affectedByGravity. The node is on a slope, so this is why it's x axis is moved due to gravity. However, I don't want the x axis of this SKSpriteNode to move due to gravity. Is there a way to lock the x axis in order to achieve this?
Thanks for any help :D
Edit: I have tried to apply a constraint to the x value like this:
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints?.append(xConstraint)
However this doesn't work and I'm not sure why and how to fix it. Anyone know of a solution?
Edit 2: SKPhysicsJointPin is actually looking more promising. In the comments of the first response/answer to this question, I have been trying to figure how to properly use it in my situation.
An example of my code:
let node = SKSpriteNode(imageNamed: "node")
enum collisionType: UInt32 {
case node = 1
case ground = 2
case other = 4 //the other node is unrelated to the ground and node
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
//Setup node physicsBody
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody?.categoryBitMask = collisionType.node.rawValue
node.physicsBody?.collisionBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.contactTestBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.isDynamic = true
node.physicsBody?.affectedByGravity = true
addChild(node)
//Physics Setup
physicsWorld.contactDelegate = self
}
The node is on top of the ground, and the ground is composed of various SKSpriteNode lines that have a volumeBased physicsBody. The ground keeps adding new lines at the front, and removing the ones at the back, and changing the x value of each line by a negative (so the ground appears to moving - this process is performed in an SKAction). These lines (the parts of the ground) are on an angle which is why the node's x axis moves. I want the node to always be at the front of the ground (e.g. always on top of the newly created line). Currently, setting the position of the node like this locks the x axis (solving my issue):
override func didSimulatePhysics() {
//Manage node position
node.position.x = 195
node.position.y = CGFloat([yPosition of the first line of the ground - the yPosition keeps changing])
}
Note: ^This^ function is inside the GameScene class
The x axis actually stays the same like this. However, the issue is that now the physicsBody of the node is lower than the centre of the node (which didn't happen before).
A node's constraints property is nil by default. You'll need to create an array of one or more constraints and assign it to the property. For example
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints = [xConstraint]
Update
You may want to use a camera node instead of moving the ground in the scene. With a camera node, you move the main character and the camera instead of the ground.
I think you could set the linearDamping property to 0.0
The linearDamping is a property that reduces the body’s linear velocity.
This property is used to simulate fluid or air friction forces on the
body. The property must be a value between 0.0 and 1.0. The default
value is 0.1. If the value is 0.0, no linear damping is applied to the
object.
You should pay attention also to the other forces applied to your SKSpriteNode. The gravitational force applied by the physics world for example where dx value, as you request , should be setted to 0.0:
CGVector(dx:0.0, dy:-4.9)
Remember also that when you apply other forces vectors like velocity you should maintain the dx property to 0.0 as constant if you want to block the x axis.
You could find more details to the official docs
Update (after your details to the comments below):
You could also anchored your sprite to the ground with an SKPhysicsJoint (I don't know your project so this is only for example):
var myJoint = SKPhysicsJointPin.joint(withBodyA: yourSprite.physicsBody!, bodyB: yourGround.physicsBody!, anchor: CGPoint(x: yourSprite.frame.minX, y: yourGround.frame.minY))
self.physicsWorld.add(myJoint)
You can work with the anchor property to create a good joint as you wish or adding more joints.

Check when an SKSpriteNode passes a certain point or line

In my game, there is an SKSpriteNode, which we will call a "gate," that is moving upwards in the scene. When the gate reaches the top of the screen, it moves back down to the bottom and begins traveling upwards again.
The way the player loses is if the character, which is located at a constant y position towards the top of the scene, collides with the gate. I have the collision and 'death' all worked out, but now I need to check if the character successfully passed 'through' the gate (meaning the gate moved past the character without hitting him). Basically, I need to know how to check when one moving node crosses a certain y position (in this case, that position is the character's y position). If this occurs, I want to increase the score by one.
Here is a rough sketch of the situation:
(click here for the rough sketch)
One thing I have tried is in the update method, if the gate's y position ever equals the character's y position, I know the two nodes have crossed, so I should increase the score. It looks like this:
override func update(currentTime: CFTimeInterval) {
if gate.position.y == character.position.y {
print("score")
score++
}
}
but doesn't seem to work. Is there any other way to check when the gate crosses the character's y position?
Thanks!
What may be happening is that the y coordinates aren't the same. For example if gate.position.y = 100, and character. position.y = 101, it won't work. Try this:
override func update(currentTime: CFTimeInterval) {
let minY = gate.position.y - 10
let maxY = gate.position.y + 10
if character.position.y > minY && character.position.y < maxY {
print("score")
score++
}
}
You can adjust the number 10 to see what works best. Hope this helps

object stop falling when reach a specific point in unity3d

I am making a game in which the balls are falling down on a plane one ball fall over the other ball and they make a line, which goes from bottom to top. I want that at certain point on y axis the ball stop falling. Don't know how to do it the code I used until now for balls to fall down is:
function calling(){
functionsRandom.Range(0, functions.Length);
}
function sphereA() {
var go = Instantiate(sphere,new Vector3(Random.Range(-3, 3),Random.Range(-3,3),-12.78451),Quaternion.identity);
go.renderer.material.color = Color(Random.value, Random.value, Random.value);
}
function sphereB() {
var go = Instantiate(sphere1,new Vector3(Random.Range(-3, 3),Random.Range(-3,3),-12.78451),Quaternion.identity);
go.renderer.material.color = Color(Random.value, Random.value, Random.value);
}
I used random.range, so that the ball falls from points between it both in x and y, for x it is working, but it is not working for y.
Add all the instantiated spheres to an array go[] instead of go. which are in line. Now at certain point where you want to stop generating other spheres, take the count of the sphere in the array. If the count is equal to the max limit. Then DON'T instantiate the spheres. If any doubts, come to unity3d chatroom.