I want objects that I created to "fall" down the view of the controller. I also want an infinite amount of objects to fall, so continuing the animation until the the user goes to another view controller. I used a for-loop to make up to 100 objects. Here is the code...
SCNMaterial *blackMaterial = [SCNMaterial material];
blackMaterial.diffuse.contents = [UIColor blackColor];
int xcoordinate = arc4random_uniform(20);
int xcoordinateTwo = arc4random_uniform(20);
for (int i = 0; i < 100; i++){
SCNText *x = [SCNText textWithString:#"X" extrusionDepth: 2.75];
SCNNode *xNode = [SCNNode nodeWithGeometry:x];
xNode.position = SCNVector3Make(xcoordinate, 15.0, -60.0);
xNode.scale = SCNVector3Make(2.0, 2.0, 0.45);
x.materials = #[blackMaterial];
x.chamferRadius = 5.0;
SCNAction *moveTo = [SCNAction moveTo:SCNVector3Make(xcoordinate, -100.0, -60.0)duration:10.0];
[xNode runAction:moveTo];
SCNTorus * torus = [SCNTorus torusWithRingRadius:6.30 pipeRadius:2.30];
SCNNode *torusNode = [SCNNode nodeWithGeometry:torus];
torusNode.position = SCNVector3Make(xcoordinateTwo, 15.0, -60.0);
torus.materials = #[blackMaterial];
torusNode.eulerAngles = SCNVector3Make(-1.5708, 0, 0);
[torusNode runAction:moveTo];
[scene.rootNode addChildNode:torusNode];
[scene.rootNode addChildNode:xNode];
}
The problem for me is that only one of each object is being created instead of a 100. Can I anyone help me with what the issue is.
You really are creating 100 of each object:
NSLog(#"%#", scene.rootNode.childNodes);
They all have the same origin and destination positions because your calls to arc4random_uniform are outside the loop. They're all in the same place so it looks like just one node.
Moving the random number calls inside will disperse your nodes but they'll all be created at the same simulation time. To generate them continuously, you can construct an [SCNAction sequence:[...]] with an array of actions: a block action to create a new node and add it to the tree, followed by a delay action. Wrap that in a repeatActionForever: and make your root node perform that action. You'll also want to remove nodes when they reach destination or go out of view.
Related
I'm playing around with some of the new SpriteKit tools and I've run into a frustrating problem.
As you know, iOS 8 introduced the SKFieldNode class which enables the user to create custom "force fields" which affect other SKNodes. I have gotten great results using the springField and the radialGravityFields, however I have yet to figure out how to use the magneticField and electricField types.
Let me explain.
The following code produces absolutely no effects on node4, the SKSpriteNode which I would like to be affected by the SKFieldNode.
SKSpriteNode *node4 = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"Red"] size:CGSizeMake(25.0, 25.0)];
node4.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:12.5];
node4.position = CGPointMake(300.0, 250.0);
node4.physicsBody.dynamic = YES;
node4.physicsBody.charge = 30;
node4.physicsBody.linearDamping = 3;
node4.physicsBody.affectedByGravity = NO;
node4.physicsBody.collisionBitMask = 0;
node4.physicsBody.mass = 1;
SKFieldNode *centerNode = [SKFieldNode magneticField];
centerNode.position = CGPointMake(150.0, 200.0);
centerNode.strength = 100000000000;
centerNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:25];
centerNode.physicsBody.charge = -30;
centerNode.physicsBody.dynamic = NO;
centerNode.falloff = 0;
The node is drawn on screen, yet stays completely static. As you can see, this happens even with a falloff of 0 and a strength of a 10000000000.
Changing the line:
SKFieldNode *centerNode = [SKFieldNode magneticField];
Into:
SKFieldNode *centerNode = [SKFieldNode springField];
Causes the centerNode to act as a springField, and sends the node4 all over the place.
Using an electricField, rather than a magneticField, yields no results either.
Before someone asks, yes, I am adding both nodes to the scene, I just didn't include those lines.
Can anyone see where I'm going wrong?
There's not enough code here for me to test it fully, but I see two likely problems up front:
If node4 isn't moving to start with, a magnetic field has no effect on it. SpriteKit's magnetic field models the second half of the Lorentz force equation (F = qv тип B), where the force on a particle relates to its charge and velocity.
You're setting node4's linear damping to greater than the maximum. The linearDamping property takes a value between 0 and 1, where 1.0 fully arrest's a body's motion. Values greater than 1 are probably clamped to 1.0, so your body shouldn't be moving even if hit with tremendous force.
Probably unrelated: you don't need to add a charged physics body to the field node unless you want that node to be affected by other electric/magnetic fields.
I have the same problem only when I create nodes by writing code. If you work with SpriteKit Editor, it works well!
You can add a new sks file and then unarchive it to SKScene by this code:
extension SKScene {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
scene.size = GameKitHelper.sharedGameKitHelper().getRootViewController().view.frame.size
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
Open the sks file and drag sprites on the scene, it really works well.
my script below does exactly what i need it to do, add blocks to a scene in any random order in the view. the only problem is, as i increase the amount of "block" nodes, they tend to overlap on one another and clump up, I'm wondering if there is a way i can add a "barrier" around each block node so that they cannot overlap but still give a random feel? My current code is below:
-(void) addBlocks:(int) count {
for (int i = 0; i< count; i++) {
SKSpriteNode *blocks = [SKSpriteNode spriteNodeWithImageNamed:#"Ball"];
blocks.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:(blocks.size.width/2)];
blocks.physicsBody.dynamic = NO;
blocks.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
[self addChild:blocks];
int blockRandomPositionX = arc4random() % 290;
int blockRandomPositionY = arc4random() % 532;
blockRandomPositionY = blockRandomPositionY + 15;
blockRandomPositionX = blockRandomPositionX + 15;
blocks.position = CGPointMake(blockRandomPositionX, blockRandomPositionY);
}
}
Any help highly appreciated, thanks!
To prevent two nodes from overlapping, you should check the newly created node's random position with intersectsNode: to see if it overlaps any other nodes. You also have to add each successfully added node into an array against which you run the intersectsNode: check.
Look at the SKNode Class Reference for detailed information.
Thanks advance for your time!
I'm doing a project with box2d. When I first touch the screen, a bird will be created in the box2d world, as follows
- (void)createBird
{
//init bird
bird = [CCSprite spriteWithFile:#"bird.png"];
bird.scale = 23/[bird boundingBox].size.width;
bird.position = center;
[self addChild:bird z:12];
}
Then, if the touch ends, the bird will be given a force
//add box2d body
b2BodyDef birdBodyDef;
birdBodyDef.type=b2_dynamicBody;
birdBodyDef.userData = (__bridge void*)bird;
birdBodyDef.position.Set(bird.position.x/PTM_RATIO,bird.position.y/PTM_RATIO);
birdBody=world->CreateBody(&birdBodyDef);
//create circle shape
b2CircleShape birdShape;
birdShape.m_radius=([bird boundingBox].size.width-6)/PTM_RATIO/2;
// Create shape definitio and add to body
b2FixtureDef birdFixtureDef;
birdFixtureDef.shape=&birdShape;
birdFixtureDef.density=1.6f;
birdFixtureDef.friction=0.3f;
birdFixtureDef.restitution=0.3f;
birdFixture = birdBody -> CreateFixture(&birdFixtureDef);
// Apply force
b2Vec2 force = b2Vec2(-2.0f*distance,-2.0f*distance);
birdBody->ApplyForce(force,birdBody->GetWorldCenter());
birdBody->SetLinearDamping(0.2f);
Then, when I touch the screen again, another bird will be created with the "createBird" method.
I want to delete the first bird after it has been created for 5 seconds. But at that time, the "CCSprite *bird" and "b2Body *birdBody" will be pointing to the second bird, so how can I delete the first one?
Thanks!
I'm not sure I understand your question exactly, but this might help. Call before you create the new bird.
if ( bird ) {
id delayTimeAction = [CCDelayTime actionWithDuration:5.0];
id removeMySprite = [CCCallFuncND actionWithTarget:bird selector:#selector(removeFromParentAndCleanup:) data:(void*)NO];
[bird runAction:[CCSequence actions: delayTimeAction, removeMySprite, nil]];
}
edit:
init an array
NSMutableArray *birds = [NSMutableArray new];
Then call this in create bird function
[birds addObject:newBird];
if ( [birds count] > 1 ) {
oldBird = [birds firstObject];
//Call remove function on oldBird
[birds removeObject:oldBird];
}
One way would be giving your bird tag and then saving them into an Nsmutuable dictionary.
Keep traversing that dictionary and then after your bool variable for destroy is been set destroy the body.....
I have done the same thing using classes.... The fact is you will need to destroy body any case. (Else you will face fps issues later)
I am trying to create a game, something slightly similar to Jetpack Joyride. At certain points it will have an object that the player has to fly between - like the pipes on flappy birds. I have managed to create the pipe objects however I wish for the objects to move up and down on the screen, making it harder for the player to jump between. My code for placing the objects is:
// Maths
float availableSpace = HEIGHT(self) - HEIGHT(floor);
float maxVariance = availableSpace - (2*OBSTACLE_MIN_HEIGHT) - VERTICAL_GAP_SIZE;
float variance = [Math randomFloatBetween:0 and:maxVariance];
// Bottom object placement
float minBottomPosY = HEIGHT(floor) + OBSTACLE_MIN_HEIGHT - HEIGHT(self);
float bottomPosY = minBottomPosY + variance;
bottomPipe.position = CGPointMake(xPos,bottomPosY);
bottomPipe.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0,0, WIDTH(bottomPipe) , HEIGHT(bottomPipe))];
bottomPipe.physicsBody.categoryBitMask = blockBitMask;
bottomPipe.physicsBody.contactTestBitMask = playerBitMask;
// Top object placement
topPipe.position = CGPointMake(xPos,bottomPosY + HEIGHT(bottomPipe) + VERTICAL_GAP_SIZE);
topPipe.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0,0, WIDTH(topPipe), HEIGHT(topPipe))];
topPipe.physicsBody.categoryBitMask = blockBitMask;
topPipe.physicsBody.contactTestBitMask = playerBitMask;
How would I go about moving the objects up and down?
Thanks for any help :)
All such operations on a node are done using SKAction objects. Read up on the SKAction class here.
In your case, using an SKAction to move the nodes up and down would go something like this:
SKAction *actionMoveUp = [SKAction moveByX:0 y:20 duration:0.5];
SKAction *actionMoveDown = [actionMoveUp reversedAction];
SKAction *actionMoveUpDown = [SKAction sequence:#[actionMoveUp, actionMoveDown]];
SKAction *actionMoveDownRepeat = [SKAction repeatActionForever:actionMoveUpDown];
[bottomPipe runAction:actionMoveDownRepeat];
[topPipe runAction:actionMoveDownRepeat];
The code given above will make your top and bottom pipe go up and down by a factor of 20 pixels repeatedly.
The code below is where I am right now, I've made it so I can click a button and change from one sprite on a sprite sheet to another. The fundamentals I'm working out may be crap, I don't really know what I'm doing! By all means, steer me straight if this is so.
What I want to know is how to loop over all the sprites in my sprite sheet. Currently I can just flip between the first and second one (only one time) via button click.
UIImageView test = new UIImageView();
test.Image = UIImage.FromFile("necromancerSpriteSheet.png");
CGImage img = test.Image.CGImage;
CALayer layer = new CALayer();
layer.Contents = img;
layer.Bounds = new RectangleF(0, 0, 128, 128);
layer.Position = new PointF(150, 250);
UIImageView theImage = new UIImageView();
layer.ContentsRect = new RectangleF(0, 0, 0.33f, 0.33f);
layer.RemoveAllAnimations();
theImage.Layer.AddSublayer(layer);
button.TouchUpInside += (s, e) => {
layer.ContentsRect = new RectangleF(0.33f, 0, 0.33f, 0.33f);
layer.RemoveAllAnimations();
theImage.Layer.AddSublayer(layer);
};
I would expect the process would be to have all the sublayers loaded/initialized then simply loop over them in some way. I haven't had any luck figuring out how to achieve this yet though.
Here is my test spritesheet if it helps...
There are many ways of solving this problem, but if you want to use CoreAnimation to drive the animation on the sheet, my suggestion is that you use a keyframe animation (CAKeyFrameAnimation) to drive the animation.
To simplify things, I would merely put all of the items in a single row, and then use something like:
var anim = (CAKeyFrameAnimation) CAKeyFrameAnimation.FromKeyPath ("position.x");
anim.Values = new NSNumber [9];
var times = new NSNumber [9];
for (int i = 0; i < 9; i++){
anim.Values [i] = NSNumber.FromFloat (i*WIDTH);
times [i] = NSNumber.FromFloat (i/9f);
}
anim.CalculationMode = CAKeyFrameAnimation.CalculationDiscrete
You might need to tweak the animation above a little, I did the above without testing.