I'm trying to experiment with Sprite Kit's joint system. So far, I've had difficulty making even a single strand of "string"/"rope". The end goal is to produce something like a basketball net where there will be 1 left rope (vertical), 1 right rope (vertical) & at least 2 ropes in-between connecting the left & right rope (horizontally) - 4 ropes in total. Afterwards, figuring out how to make the 2 horizontal ropes not collide with an object (cannonball?) when it passes through them. Does anyone know how to do something like this? I've been trying this for 2 weeks and my eyes & fingers are sore.
You can connect several physics bodies together with limit joints to create a rope like structure which is affected by collisions with other bodies. My code is quite messy but here is the Scene class for a sprite kit project. It has a basketball net and when you tap the screen it spawns a ball.
with screenshot here https://dl.dropboxusercontent.com/s/flixgbhfk2yysr8/screenshot.jpg?dl=0
EDIT: sorry about that, code is now in obj-C
EDIT: paste these methods into your SKScene subclass:
-(void)addBasketAtPos:(CGPoint)pos WithSize:(CGSize)size HoopThickness:(CGFloat)hoopThickness RopeThickness:(CGFloat)ropeThickness GapSize:(CGFloat)gapSize {
int nSect=ceil(size.height/(ropeThickness*4));
SKSpriteNode *hoop=[SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(size.width+hoopThickness, hoopThickness)];
hoop.position=pos;
//[self addChild:hoop];
SKNode *lHoopEdge=[SKNode node];
lHoopEdge.position=CGPointMake(pos.x-size.width/2, pos.y);
lHoopEdge.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:hoopThickness/2];
lHoopEdge.physicsBody.dynamic=false;
lHoopEdge.physicsBody.restitution=0.9;
[self addChild:lHoopEdge];
SKNode *rHoopEdge=[SKNode node];
rHoopEdge.position=CGPointMake(pos.x+size.width/2, pos.y);
rHoopEdge.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:hoopThickness/2];
rHoopEdge.physicsBody.dynamic=false;
rHoopEdge.physicsBody.restitution=0.9;
[self addChild:rHoopEdge];
NSMutableArray *rope1=[self makeRopeAtPos:lHoopEdge.position node:lHoopEdge num: nSect gap:size.height/nSect width:ropeThickness];
NSMutableArray *rope2=[self makeRopeAtPos:rHoopEdge.position node:lHoopEdge num: nSect gap:size.height/nSect width:ropeThickness];
CGFloat insetStep=(size.width-gapSize)/2/nSect;
for (int i=0;i<rope1.count;++i) {
SKNode *a=[rope1 objectAtIndex:i];
SKNode *b=[rope2 objectAtIndex:i];
a.position=CGPointMake(a.position.x+i*insetStep, a.position.y);
b.position=CGPointMake(b.position.x-i*insetStep, b.position.y);
}
for (int i=0;i<rope1.count;++i) {
/*
SKNode *n1=[rope1 objectAtIndex:i];
SKNode *n2=[rope2 objectAtIndex:i];
SKPhysicsJointLimit* joint=[self joinBodyA:n1.physicsBody bodyB:n2.physicsBody ropeThickness:ropeThickness];
[self.physicsWorld addJoint:joint];
*/
if (i>0) {
[self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope1 objectAtIndex:i]).physicsBody
bodyB:((SKNode*)[rope2 objectAtIndex:i-1]).physicsBody
ropeThickness:ropeThickness]];
[self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope2 objectAtIndex:i]).physicsBody
bodyB:((SKNode*)[rope1 objectAtIndex:i-1]).physicsBody
ropeThickness:ropeThickness]];
}
if (i==rope1.count-1) {
[self.physicsWorld addJoint:[self joinBodyA:((SKNode*)[rope1 objectAtIndex:i]).physicsBody
bodyB:((SKNode*)[rope2 objectAtIndex:i]).physicsBody
ropeThickness:ropeThickness]];
}
}
[self addChild:hoop];
}
-(NSMutableArray*)makeRopeAtPos:(CGPoint)p node:(SKNode*)node num:(int)num gap:(CGFloat)gap width:(CGFloat)width {
NSMutableArray *array=[[NSMutableArray alloc] init];
SKNode *last=node;
CGPoint pos=p;
CGPoint anchor=pos;
for (int i=0; i<num; ++i) {
pos=CGPointMake(pos.x, pos.y-gap);
last=[self makeRopeSegAtPos:pos prev:last anchor:anchor first:(i==0) width:width];
anchor=pos;
[array addObject:last];
}
return array;
}
-(SKNode*)makeRopeSegAtPos:(CGPoint)pos prev:(SKNode*)prev anchor:(CGPoint)anchor first:(BOOL)first width:(CGFloat)width {
//SKNode *r=[SKNode node];
SKSpriteNode *r=[SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(anchor.y-pos.y, width)];
r.position=pos;
r.anchorPoint=CGPointMake(0, 0.5);
if (first) {
//r.size=CGSizeMake(10, 20);
r.constraints=#[[SKConstraint orientToPoint:anchor inNode:self offset:nil]];
}else {
r.constraints=#[[SKConstraint orientToNode:prev offset:nil]];
}
r.physicsBody=[SKPhysicsBody bodyWithCircleOfRadius:width];
r.physicsBody.allowsRotation=NO;
//r.physicsBody.density=0.2;
r.physicsBody.linearDamping=0.8;
[self addChild:r];
SKPhysicsJointLimit *j=[SKPhysicsJointLimit jointWithBodyA:r.physicsBody bodyB:prev.physicsBody anchorA:r.position anchorB:anchor];
[self.physicsWorld addJoint:j];
return r;
}
-(SKPhysicsJoint*)joinBodyA:(SKPhysicsBody*)bodyA bodyB:(SKPhysicsBody*)bodyB ropeThickness:(CGFloat)ropeThickness {
SKPhysicsJointLimit *j=[SKPhysicsJointLimit jointWithBodyA:bodyA bodyB:bodyB anchorA:bodyA.node.position anchorB:bodyB.node.position];
SKSpriteNode *rope=[SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(j.maxLength, ropeThickness)];
rope.anchorPoint=CGPointMake(0, 0.5);
rope.constraints=#[[SKConstraint distance:[SKRange rangeWithUpperLimit:1] toNode:bodyA.node],[SKConstraint orientToNode:bodyB.node offset:nil]];
SKAction *keepLength=[SKAction repeatActionForever:[SKAction sequence:#[[SKAction waitForDuration:1/60],[SKAction runBlock:^{
rope.size=CGSizeMake(sqrtf(powf(bodyA.node.position.x-bodyB.node.position.x, 2)+powf(bodyA.node.position.y-bodyB.node.position.y, 2)), rope.size.height);
}]]]];
[rope runAction:keepLength];
[self addChild:rope];
return j;
}
use
[self addBasketAtPos: CGPointMake(self.size.width/2, self.size.height*0.7)
WithSize: CGSizeMake(100, 100)
HoopThickness: 10
RopeThickness: 5
GapSize: 80
];
to add a basket
see new screenshot here: https://dl.dropboxusercontent.com/s/xe07ri70rgpxp47/screenshot%202.jpg?dl=0
Related
I've reviewed similar questions and do believe I'm asking about different thing.
I have a player node which has it's own body and I want to check when it's been moved to some part of the level (exit node). Examples of this may be putting some dynamic item in a box, or parking a car at the specific place.
In my case player is SKSpriteNode with physicsBody created from similar texture (bodyWithTexture) and exit is SKNode with no visual and physicsBody created from polygon path (4 points, non rectangle).
I have a code which does something I wan't, but I do believe there maybe some cases it's doing something I don't want and there are better ways to do it.
int contactsCount = 0;
- (void)didBeginContact:(SKPhysicsContact *)contact{
if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) & exitCategory) {
contactsCount++;
if (contactsCount == 4) {
[player runAction:[SKAction colorizeWithColor:[UIColor greenColor] colorBlendFactor:1.0 duration:1.0] completion:^{
self.physicsWorld.speed = 0;
}];
}
return;
}
[player runAction:[SKAction colorizeWithColor:[UIColor redColor] colorBlendFactor:1.0 duration:1.0] completion:^{
self.physicsWorld.speed = 0;
}];
}
- (void)didEndContact:(SKPhysicsContact *)contact{
if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) & exitCategory) {
contactsCount--;
return;
}
}
I am having some trouble getting the userData values from my object. I know the userData is being set for the objects because else where in my code I am able to access it without any problems.
I created a SKNode *column3 to hold all the buttons that are in that column. which was added to scene like so:
column3 = [SKNode node];
[self addChild:column3];
column3.name = #"column3";
when I create the buttons I assign them to the appropriate column like so:
[column3 addChild:newButton];
Later in my code I need to loop through the column3 node group and get the #"buttonRow" userData from each object in that group. For some reason it only gives me "NULL" for the values. The NSLog's are just what I was using to test, they have no real importance to me.
I need to get this userData in order to shift all my buttons down to take up any empty spaces on screen when any button is deleted/removed from that column. Game will shift buttons down and add new ones to top to fill column back up.
I tried changing column3.children to self.children and it game me all the column nodes IE/column1, column2, column3 etc.. so I am not really sure why it does not work. Been reading and trying to figure out for a while now.
for(SKNode * child in column3.children) { //loop through all children in Column3.
SKSpriteNode* sprite = (SKSpriteNode*)child;
NSString* name = sprite.name;
NSLog(#"child name %#", name);
NSString *childRowTmp = [child.userData objectForKey:#"buttonRow"];
NSLog(#"child row %#", childRowTmp);
int childRowNumber = [childRowTmp intValue];
NSLog(#"child row # %i", childRowNumber);
}
Any help or tips would be great, thank you.
UPDATE:
here is how I create the button using my button class file I created.
//Create Blue Button.
NSString *rand = [self genRandStringLength:10];
newButton = [[ButtonNode alloc] initWithButtonType:1 column:3 row:i uniqueID:rand];
newButton.name = rand;
[column3 addChild:newButton]; //add the button to correct column
[column3Array addObject:newButton];
blueTotal++;
totalButtons++;
column3Total++;
here is the custom class file where the Object is created.
-(id)initWithButtonType:(int)buttonType column:(int)buttonColumn row:(int)buttonRow uniqueID:(NSString *)uniqueID {
self = [super init];
uniqueStr = uniqueID;
rowNumber = buttonRow;
columnNumber = 3; //this is just hard coded for testing
buttonNumber = 1;
[self addButtonBlue];
}
here is the part of the class that creates and adds the button
- (void) addButtonBlue {
SKSpriteNode *button;
//button type 1
button = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:kBoxSize];
button.name = uniqueStr;
button.physicsBody.categoryBitMask = blueCategory;
button.physicsBody.contactTestBitMask = blueCategory;
button.physicsBody.collisionBitMask = blueCategory | redCategory | yellowCategory | greenCategory | orangeCategory;
NSString *tmpColumn = [NSString stringWithFormat:#"%i",columnNumber];
NSString *tmpType = [NSString stringWithFormat:#"%i",buttonNumber];
NSString *tmpRow = [NSString stringWithFormat:#"%i",rowNumber];
button.userData = [NSMutableDictionary dictionary];
[button.userData setValue:uniqueStr forKey:#"buttonID"];
[button.userData setValue:tmpType forKeyPath:#"buttonType"];
[button.userData setValue:tmpColumn forKeyPath:#"buttonColumn"];
[button.userData setValue:tmpRow forKey:#"buttonRow"];
button.position = CGPointMake(xPos , yPos );
[self addChild:button];
}
I am having the same issue, see below the code which generates a green or red sprite. Attempting to give it the colour value within the SKSpriteNode.userData. In my log output the userData is (null)!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
For a workaround I guess we could create a reference dictionary where the SKSpriteNode instances are the keys and a NSDictionary of data are the values.
//----- * UPDATE ** -------
And theres no wonder, after the userData was initialised, another sprite was being initialised in its place, leaving the userData nil!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite;
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
I am using SpriteKit.
The code below basically makes a lattice of dots on the screen. However, I want to call each 'dot' a different name based on its position, so that I can access each dot individually in another method. I'm struggling a little on this, so would really appreciate if someone could point me in the right direction.
#define kRowCount 8
#define kColCount 6
#define kDotGridSpacing CGSizeMake (50,-50)
#import "BBMyScene.h"
#implementation BBMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
// Background
self.backgroundColor = [SKColor colorWithRed:0.957 green:0.957 blue:0.957 alpha:1]; /*#f4f4f4*/
CGPoint baseOrigin = CGPointMake(35, 385);
for (NSUInteger row = 0; row < kRowCount; ++row) {
CGPoint dotPosition = CGPointMake(baseOrigin.x, row * (kDotGridSpacing.height) + baseOrigin.y);
for (NSUInteger col = 0; col < kColCount; ++col) {
SKSpriteNode *dot = [SKSpriteNode spriteNodeWithImageNamed:#"dot"];
dot.position = dotPosition;
[self addChild:dot];
//6
dotPosition.x += kDotGridSpacing.width;
}
}
}
return self;
}
Here is an image of what appears on screen when I run the above code...
http://cl.ly/image/3q2j3E0p1S1h/Image1.jpg
I simply want to be able to call an individual dot to do something when there is some form of user interaction, and I'm not sure how I would do that without each dot having a different name.
If anyone could help I would really appreciate it.
Thanks,
Ben
- (void)update:(NSTimeInterval)currentTime {
for(SKNode *node in self.children){
if ([node.name containsString:#"sampleNodeName"]) {
[node removeFromParent];
}
}
}
Hope this one helps!
You can set the name property of each node inside the loop.
Then you can access them with self.children[index].
If you want to find a specific node in your children, you have to enumerate through the array.
Update:
To clarify how to search for an item by iterating, here is a helper method:
- (SKNode *)findNodeNamed:(NSString *)nodeName
{
SKNode *nodeToFind = nil;
for(SKNode *node in self.children){
if([node.name isEqualToString:nodeName]){
nodeToFind = node;
break;
}
}];
return nodeToFind;
}
I have a problem with my cocos2d game. I am trying to delete a projectile shot by an enemy every 5 seconds (each projectile is supposed to have a lifetime of 5 seconds), but I cannot figure out how to do it. I get the error of
Assertion failure in -[CCTimer initWithTarget:selector:interval:]
Here is my code:
-(void)projectileShooting:(ccTime)dt
{
[self schedule:#selector(projectileShooting:) interval:2.5];
projcount++;
if([proj count] <= 15 ){
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
for(CCSprite *projectile in proj){
[self schedule:#selector (deleteprojectile:projectile:) interval:5];
}
}
}
}
for(CCSprite *enem2 in enemytwo)
{
if( [proj count] <= 15){
CCSprite * projectilebull = [CCSprite spriteWithFile:#"Projectile.png"];
CGPoint MyVector = ccpSub(enem2.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enem2.contentSize.width/2+10);
MyVector = ccpMult(MyVector,-1);
projectilebull.position = ccpAdd(enem2.position, MyVector);
[self addChild:projectilebull];
[proj addObject:projectilebull];
for(CCSprite *projectile in proj){
}
}
}
}
-(void)deleteprojectile:(CCSprite *)protime:(ccTime)dt{
NSMutableArray *timepro = [[NSMutableArray alloc]init];
[timepro addObject:protime];
for(CCSprite *objecttime in timepro){
[proj removeObject:objecttime];
[self removeChild:objecttime cleanup:YES];
}
}
It's a bit of a hack but this is what I use in my program, until I find a more elegant solution. I have a method in my game layer that I call to remove a node from its parent, like so:
-(void)removeNode:(CCNode*)node {
[node removeFromParentAndCleanup:YES];
}
And when I want to schedule a node for deletion after a delay, I call it like this:
[self performSelector:#selector(removeNode:) withObject:node afterDelay:delay];
Simple, and it works.
Change argument name in your selector to protime instead of projectile. The selector must match the signature defined in you object's class definition.
Your selector was not defined properly and probably the Timer is checking if the object implements the given selector.
I did not have time to test it so thanks to #RamyAlZuhouri for confirming.
-(void)spriteMoveFinishedid)sender //when sprite is outside of screen,it's deleted;
{
CCSprite *sprite = (CCSprite *)sender;
if (sprite.tag == 1) //it's enemy;
{
escapeNum++;
[self removeChild:sprite cleanup:YES];
if (escapeNum == 10)
{
[self stopGameEx]; //Because the speed of every enemy isn't same to other and there are bullets,it maybe happens that two sprites disappear at the same time, and the program stop at this with error --- program received signal:“EXC_BAD_ACCESS”。
}
//...........
}
}
How to resolve it?
Inside if loop, change sprite tag to mark it as expired sprite, and some where else remove.
enum
{
kTagExpiredSprite = 999 //any number not ur sprite tag(1)
};
.....
-(void)spriteMoveFinishedid)sender //when sprite is outside of screen,it's deleted;
{
CCSprite *sprite = (CCSprite *)sender;
if (sprite.tag == 1) //it's enemy;
{
escapeNum++;
sprite.tag = kTagExpiredSprite;
if (escapeNum == 10)
{
[self stopGameEx]; //Because the speed of every enemy isn't same to other and there are bullets,it maybe happens that two sprites disappear at the same time, and the program stop at this with error --- program received signal:“EXC_BAD_ACCESS”。
}
//...........
}
}