I'm new to objective c and swift and I created a small app where small circles are rendered and once the player collides with a circle, the game ends. I managed to get everything to work, but how do I remove the nodes after they collide. I tried removeAllChildren(), but none of them disappear. When I use removeFromParent(), only 1 disappears. I want a way to remove all 3 nodes that will be rendered in the code below
//addEvilGuys() is called first
func addEvilGuys()
{
addEvilGuy(named: "paul", speed: 1.3, xPos: CGFloat(self.size.height/3))
addEvilGuy(named: "boris", speed: 1.7, xPos: frame.size.width/4 + 50)
addEvilGuy(named: "natasha", speed: 1.5, xPos: frame.size.width/4 + 150)
}
func addEvilGuy(#named:String, speed:Float, xPos: CGFloat)
{
evilGuyNode = SKSpriteNode(imageNamed: named)
evilGuyNode.zPosition = 10
evilGuyNode.physicsBody = SKPhysicsBody(circleOfRadius: 16)
evilGuyNode.physicsBody!.affectedByGravity = false
evilGuyNode.physicsBody!.categoryBitMask = ColliderType.BadGuy.rawValue
evilGuyNode.physicsBody!.contactTestBitMask = ColliderType.Hero.rawValue
evilGuyNode.physicsBody!.collisionBitMask = ColliderType.Hero.rawValue
evilGuyNodeCount++
var evilGuy = EvilGuy(speed: speed, eGuy: evilGuyNode)
evilGuys.append(evilGuy)
resetEvilGuy(evilGuyNode, xPos: xPos)
evilGuy.xPos = evilGuyNode.position.x
addChild(evilGuyNode)
}
func resetEvilGuy(evilGuyNode:SKSpriteNode, xPos:CGFloat)
{
evilGuyNode.position.y = endOfScreenBottom
evilGuyNode.position.x = xPos
}
It looks like in addEvilGuy you are recreating a stored property (i.e. that is visible for the entire class + whatever the access level allows) to create the SKSpriteNode that you're adding. This means that you are orphaning the previously created EvilGuy.
In addEvilGuy, replace
evilGuyNode = SKSpriteNode(imageNamed: named)
with
let evilGuyNode = SKSpriteNode(imageNamed: named)
and remove the property from your class (it doesn't seem like you have a need for in in a larger scope).
It also looks like you're creating EvilGuys and storing them in an array, which is good. So when you can remove all of them from the screen with a function like:
func removeAllEvilGuys(evilGuys: [EvilGuy]) {
for evilGuy in evilGuys {
evilGuy.eGuy.removeFromParent()
}
}
As a best practice advice, since you mentioned you're a beginner:
I'd recommend defining the characteristics of the evil guys in a .plist and then use the file to create an array of evil guys. This way you can easily make changes to the evil guys in that file without having to change anything in your code.
The code that creates an EvilGuy object should be separated from the one that adds the evil guy to the screen. As long as you are storing the SKNode of each one, you'll be able to add/remove without unnecessarily recreating the entire object.
Related
When I interact with the screen the objects in my game start to stutter. My FPS is at 60 and doesn't drop but the stuttering is still prevalent. I believe my problem is how I'm animating the objects on screen(code below).If anybody could help I would appreciate it.
I have an x amount of nodes inside an array called _activePool. In the Update function I am moving the nodes x position inside _activePool, adding new nodes when the last node in _activePool position is <= 25 and removing the first node in _activePool if it's position is <= -25.
if _cycleIsActive{
for obj in _activePool{
//move the obj in _activePool
obj.position.x += Float(dt * self.speedConstant);
}
let lastObj = _activePool.last;
if (lastObj?.position.x)! + getWidthOfNode(node: lastObj!) + Float(random(min: 15, max: 20)) <= 25{
// get new obj(pattern) and add to _activePool
self.getPatternData(sequencePassedIn: selectedSeq, level: self._currentLevel, randomPattern: randomPattern());
}
let firstObj = _activePool.first;
if (firstObj?.position.x)! + getWidthOfNode(node: firstObj!) <= -25{
// remove object and return to specific pool
firstObj?.removeFromParentNode();
returnItems(item: firstObj!);
_activePool.removeFirst();
}
}
I create several arrays and add them to a dictionary
func activatePools(){
temp1Pool = ObjectPool(tag: 1, data: []);
dictPool[(temp1Pool?.tag)!] = temp1Pool;
temp2Pool = ObjectPool(tag: 2, data: []);
dictPool[(temp2Pool?.tag)!] = temp2Pool;
for i in 0... dictPool.count {
obstacleCreationFactory(factorySwitch: i);
}
}
Creating my obstacles(enemies)
func obstacleCreationFactory(factorySwitch: Int){
Enemies = Enemy();
switch factorySwitch {
case 0:
for _ in 0...100{
let blueEnemy = Enemies?.makeCopy() as! Enemy
blueEnemy.geometry = (Enemies?.geometry?.copy() as! SCNGeometry);
blueEnemy.geometry?.firstMaterial?.diffuse.contents = UIColor.blue;
blueEnemy.tag = 1;
temp1Pool?.addItemToPool(item: blueEnemy);
}
case 1:
for _ in 0...100{
let redEnemy = Enemies?.makeCopy() as! Enemy
redEnemy.geometry = (Enemies?.geometry?.copy() as! SCNGeometry);
redEnemy.geometry?.firstMaterial?.diffuse.contents = UIColor.red;
redEnemy.tag = 2;
temp2Pool?.addItemToPool(item: redEnemy);
}
default:
print("factory error");
}
}
Without being able to look at the rest of your code base it’s really difficult to guess what would be causing your issue.
If somewhere you are creating a ton of temporary objects in a loop somewhere, you might consider creating a local autorelease pool to prevent memory spikes. Here is a good article that describes why in some situations it’s a good idea.
You could also be calling some particularly expensive functions on a timer or something. It’s difficult to say.
In short, you should consider using Xcode’s Profiling tools (called Instruments). Specifically I would recommend using Time Profiler to examine what functions are taking the most time and causing those spikes.
Here is a great WWDC session video that shows how you can use the time profiler, I’d recommend regularly profiling your app, especially when you have an issue like this.
When I run my program. The code I put into "override func sceneDidLoad()" runs two times.
E.g.
Note: I have no idea why this picture is not uploading, but it shows "spawn" happening twice.
This code should only run once when "sceneDidLoad()" is called.
Here is the code for the "sceneDidLoad" function, and for the "testSpawn()" function (which is the specific one that gave the duplicated printout).
class GameScene: SKScene {
var mapTerrain: SKTileMapNode!
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
//do zoom by change in scale in pinch. (E.g. if they start out 5 units apart and end up 15 units apart, zoom by a factor of 3
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 100, y: 100)
setupLayers()
loadSceneNodes()
setUpUI()
testSpawn()
//print("\(self.frame.width), \(self.frame.height)")
}
func testSpawn(){
let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: 1, health: 2)
RedLegion.position = mapTerrain.centerOfTile(atColumn: 0, row: 0)
RedLegion.team = "Red"
unitsLayer.addChild(RedLegion)
legionList.append(RedLegion)
print("spawn")
}
}
Note: Not all of the code is here (like "setUpLayers()"), if needed I can supply it, I just do not think it is neccessary.
Search your whole document for "print("spawn")" just to make sure that is the only time you call the function. Also check for "testSpawn()" to make sure it is only called once. Additionally, instead of relying on this print to count how many times the sceneDidLoad runs, place a print directly within your sceneDidLoad. Finally, check to make sure you are not creating the scene twice.
I've also seen this and submitted a bug report but apple responded saying that it is intended behavior. Apple said that it creates a dummy scene and then creates the actual scene. Before it runs the second time it gets rid of anything done the first time so you shouldn't get any errors from it. The bug is really hard to reproduce, one of my friends working off the same repository that I was but did not experience the bug.
I changed sceneDidLoad to didMoveToView:(SKView *)view if you are looking for a solution to this. Make sure you xcode is up to date.
In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.
I am working on a project with 20 sprites and I am noticing a lot of code repetition. My sprites are named: sprite1, sprite2, sprite3.... so I am looking for a way to reference a sprite based on 2 variables. The first simply being "sprite" and the second being the integer, so I can change or loop through a range at any time. Is it possible to do something like this?
Random example:
x = 1
while x <= 10 {
sprite & x.physicsBody.affectedByGravity = true
x++
}
instead of:
sprite1.physicsBody.affectedByGravity = true
sprite2.physicsBody.affectedByGravity = true
sprite3.physicsBody.affectedByGravity = true
sprite4.physicsBody.affectedByGravity = true
sprite5.physicsBody.affectedByGravity = true
so on...
This could be as simple as:
for sprite in [sprite1, sprite2, sprite3, /* ... */] {
sprite.physicsBody.affectedByGravity = true
}
However, having a bunch of local variables (or instance variables) like sprite1, sprite2, etc suggests that you're not making good use of your tools in some other way. What's meaningful about these sprites? Is there a way you can use the scene structure to better manage them?
For example, if all of the nodes you want to turn gravity on for are children of the same parent node, you could instead do something like:
for sprite in someContainerNode.children {
sprite.physicsBody.affectedByGravity = true
}
Or, if there's some subset of nodes in your scene that you need to do this for, you could give them all the same name (say, in the SpriteKit Scene Editor in Xcode) and use that to find them:
scene.enumerateChildNodesWithName("needsGravity") { node, stop in
node.physicsBody.affectedByGravity = true
}
I'm making a game in Xcode 6 using spriteKit and swift. I have a plane on scene, and to make it look like its moving, I'm create clouds off the scene to the left of the screen. I then us an SKAction to move the cloud to the right side of the screen. This works great. You then click to jump off the plane, and the plane moves up off the scene. I then have it start making the clouds on the bottom of the scene, then they move up off the top of the scene, but the problem is, the already existing clouds still have to move to the right side of the screen. My question is, how do I make it so all of the existing clouds stop their action that moves them to the right, then begin to move up exactly where they are? How do I access the group of existing clouds all at the same time once they have been created? I also want the clouds to slow down after you have jumped when you tap the screen to open your parachute, but this should be able to be done by ending the SKAction that moves the clouds, then using another SKAction on them that moves them up slower, but I don't know how to access a group of SKSpriteNodes.
Here is the code that I have to make the clouds:
//This is how the cloud is first declared at the top of the .swift file
var cloud = SKSpriteNode()
//This is the function that runs every certain interval through an NSTimer
func createCloud()
{
cloud = SKSpriteNode(imageNamed: "cloud")
cloud.xScale = 1.25
cloud.yScale = cloud.xScale/2
cloud.zPosition = -1
self.addChild(cloud)
if personDidJump == false
{
let moveRight = SKAction.moveToX(CGRectGetWidth(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(-CGRectGetWidth(cloud.frame), randomNumber)
cloud.runAction(moveRight)
}
if personDidJump == true
{
let moveUp = SKAction.moveToY(CGRectGetHeight(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(randomNumber, -CGRectGetHeight(cloud.frame))
cloud.runAction(moveUp)
}
}
Also, should I be worried about deleting the clouds when they move off the scene? Or can I just leave them there because you can't see them, and from what I've seen, they don't lag you.
Any help would be greatly appreciated. Thank you.
-Callum-
Put your clouds in an array. When your player jumps (or whenever you need to move them) run through the array and run your action on each cloud.
stackoverflow.com/a/24213529/3402095
^ That is where I found my answere ^
When you make a node give it a name:
myNode.name = "A Node Name"
self.addChild(myNode)
If there are a lot of these nodes, later you can change properties or preform SKAcions on these nodes or do whatever you want to do by using enumerateChildNodesWithName:
self.enumerateChildNodesWithName("A Node Name")
{
myNode, stop in
myNode.runAction(SKAction.moveToY(CGRectGetHeight(self.Frame), duration: 1)
}
I hope this is useful to whoever may need it.