I am in the process of making a game and I need an object to move only when the buttons are pressed. I have a method that begins the movement, and so far I am ending the movement of an object by having destroying the body of the object. The problem that I have is that I can't move the object again since the program will now crash. I'm wondering if there is a way to recreate the body once its destroyed since my current method of checking if the body still exists isn't working.
Here is my code.
NSMutableArray *spaceObjectsArray;
#pragma mark - HelloWorldLayer
#interface HelloWorldLayer()
-(void) initPhysics;
-(void) addNewSpriteAtPosition:(CGPoint)p;
-(void) createMenu;
#end
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(void)gameLogic:(ccTime)delta
{
[self addSpaceObjects];
}
-(void) addSpaceObjects
{
_spaceObject = [CCSprite spriteWithFile:#"blueDot.jpg"];
//create spaceObject body
b2BodyDef spaceObjectbBodyDef;
spaceObjectbBodyDef.type=b2_dynamicBody;
spaceObjectbBodyDef.userData = _spaceObject;
//make the location of spaceObject
CGSize winSize = [CCDirector sharedDirector].winSize;
int minX= _spaceObject.contentSize.width/2;
int maxX = winSize.width - _spaceObject.contentSize.width/2;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
_spaceObject.position = ccp(actualX, winSize.height + _ship.contentSize.height);
spaceObjectbBodyDef.position.Set(actualX/PTM_RATIO, (winSize.height+_ship.contentSize.height)/PTM_RATIO);
_spaceObjectBody= _world->CreateBody(&spaceObjectbBodyDef);
//create spaceObject shape
b2PolygonShape spaceObjectShape;
spaceObjectShape.SetAsBox(_spaceObject.contentSize.width/PTM_RATIO/2, _spaceObject.contentSize.height/PTM_RATIO/2);
//create spaceObject fixture
b2FixtureDef spaceObjectShapeDef;
spaceObjectShapeDef.shape= &spaceObjectShape;
spaceObjectShapeDef.density = 2;
spaceObjectShapeDef.restitution =0;
spaceObjectShapeDef.friction=0;
_spaceObjectFixture = _spaceObjectBody->CreateFixture(&spaceObjectShapeDef);
[self addChild:_spaceObject];
_spaceObject.tag=1;
[spaceObjectsArray addObject:_spaceObject];
//aply force on the object
int randomValue = ((arc4random() % 5) *-1);
b2Vec2 force = b2Vec2(0,randomValue);
_spaceObjectBody ->ApplyLinearImpulse(force, _spaceObjectBody->GetPosition());
}
init method that contains the body creations and definitions
-(id) init
{
if( (self=[super init])) {
CGSize s = [CCDirector sharedDirector].winSize;
//create spaceShip sprite and add it to the layer
_ship = [CCSprite spriteWithFile:#"theShip.gif" ];
_ship.position = ccp(s.width/2, 1.25*_ship.contentSize.height);
[self addChild:_ship];
//create the world
b2Vec2 gravity = b2Vec2_zero;
_world = new b2World(gravity);
//create ship body
b2BodyDef shipBodyDef;
shipBodyDef.type = b2_dynamicBody;
shipBodyDef.position.Set((s.width/2)/PTM_RATIO, (1.25*_ship.contentSize.height)/PTM_RATIO);
shipBodyDef.userData = _ship;
if(_shipBody == NULL){
_shipBody =_world->CreateBody(&shipBodyDef);
}
//create ship shape
b2PolygonShape shipShape;
shipShape.SetAsBox(_ship.contentSize.width/PTM_RATIO/2, _ship.contentSize.height/PTM_RATIO/2);
//create Ship definition and add to body
b2FixtureDef ShipShapeDef;
ShipShapeDef.shape = &shipShape;
ShipShapeDef.density = 3;
ShipShapeDef.friction =0;
ShipShapeDef.restitution =0;
_shipFixture = _shipBody->CreateFixture(&ShipShapeDef);
//make the paddles
//bottom left one
_paddle1 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomOfScreenX = 0 + _paddle1.contentSize.width/2;
int bottomOfScreenY = 0+_paddle1.contentSize.height/2;
_paddle1.position = ccp(bottomOfScreenX,bottomOfScreenY);
[self addChild:_paddle1];
//bottom right one
_paddle2 = [CCSprite spriteWithFile:#"spritePaddle.jpeg"];
int bottomRightOfScreenX = s.width - _paddle2.contentSize.width/2;
_paddle2.position = ccp(bottomRightOfScreenX, bottomOfScreenY);
[self addChild:_paddle2];
//continuously spawn spaceObjects
[self schedule:#selector(gameLogic:) interval:1];
// enable events
self.touchEnabled = YES;
// init physics
[self schedule:#selector(tick:)];
}
return self;
}
-(void)tick:(ccTime) delta
{//this method is to simulate physics and to test for the position of where objects should be if force has been applied to them
_world->Step(delta, 8, 8);
for (b2Body *b=_world->GetBodyList(); b; b=b->GetNext()){
if (b->GetUserData() != NULL){
CCSprite *shipData = (CCSprite *)b->GetUserData();
shipData.position = ccp(b->GetPosition().x *PTM_RATIO, b->GetPosition().y *PTM_RATIO);
}
}
}
The paddles to move the ship are touched logic
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Set up a way for touches to be turned into locations onscreen
NSSet *allTouches = [event allTouches];
UITouch *touch = [allTouches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location ];
//check to see if Left Paddle is being pressed
if (CGRectContainsPoint([_paddle1 boundingBox], location)){
b2Vec2 force = b2Vec2(-5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody ->GetPosition());
}
if (CGRectContainsPoint([_paddle2 boundingBox], location)){
b2Vec2 force = b2Vec2(5,0);
_shipBody->ApplyLinearImpulse(force, _shipBody->GetPosition());
}
}
The paddle box is no longer being touched logic
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
_world->DestroyBody(_shipBody);
}
-(void) dealloc
{
delete _world;
_world = NULL;
[super dealloc];
}
#end
Destroying the body to end the movement of it is not the best solution. You should only remove the body if you really don't want it to be part of the simulation any more.
There are several options for stopping the body's movement:
1 - Set its linear velocity to 0. This will bring it to an immediate stop. If something else is pushing on it (e.g. contact wit body), you will have to decide what to do.
body->SetLinearVelocity(b2Vec2(0,0)));
2 - Set its linear/angular damping to 0. This will dissipate the momentum it has so it will slowly stop. The factor you use should be greater than 0. The body will come to a halt more quickly with larger values and it will be resistant to movement from other bodies (if they bump it, it will slow down and stop again). Remember to turn the linear/angular damping back to 0 when you want the body to start moving.
body->SetLinearDamping(0.2);
body->SetAngularDamping(0.2);
3 - Give it a target position to seek to and set the position as the place you want it to be. This is basically a feedback control loop where you are applying a force to get it to move towards where you want the body to stay. It can be used to make a body follow paths, etc. The code below is part of a larger code base (you can see it here), but you should be able to get the general idea. This function applies thrust to the object so that it pushes towards a target.
void MovingEntity::ApplyThrust()
{
// Get the distance to the target.
b2Vec2 toTarget = GetTargetPos() - GetBody()->GetWorldCenter();
toTarget.Normalize();
b2Vec2 desiredVel = GetMaxSpeed()*toTarget;
b2Vec2 currentVel = GetBody()->GetLinearVelocity();
b2Vec2 thrust = GetMaxLinearAcceleration()*(desiredVel - currentVel);
GetBody()->ApplyForceToCenter(thrust);
}
Related
I am building a game using sprite kit and its a game where you send balls into a bucket and grow the bucket. As the buckets grow the balls (SKSpriteNodes) stay on the scene. Im trying to see how to keep high performance while managing thousands of nodes. Any idea how i can do this? After 700 or so the FPS in simulator goes below 10 tps.
Here is my code from my scene. Any help is appreciated.
//
// GameScene.m
//
#import "GameScene.h"
#implementation GameScene
#synthesize _flowIsON;
NSString *const kFlowTypeRed = #"RED_FLOW_PARTICLE";
const float kRED_DELAY_BETWEEN_PARTICLE_DROP = 0.1; //delay for particle drop in seconds
static const uint32_t kRedParticleCategory = 0x1 << 0;
static const uint32_t kInvisbleWallCategory = 0x1 << 1;
NSString *const kStartBtn = #"START_BTN";
NSString *const kLever = #"Lever";
NSString *const START_BTN_TEXT = #"Start Game";
CFTimeInterval lastTime;
-(void)didMoveToView:(SKView *)view {
[self initializeScene];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:kStartBtn]) {
[node removeFromParent];
//initalize to ON
_flowIsON = YES;
//[self initializeScene];
} else if ([node.name isEqualToString:kLever]) {
_leverNode = (SKSpriteNode *)node;
[self selectNodeForTouch:location];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPoint previousPosition = [touch previousLocationInNode:self];
CGPoint translation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);
[self panForTranslation:translation];
}
-(void)update:(CFTimeInterval)currentTime {
float deltaTimeInSeconds = currentTime - lastTime;
//NSLog(#"Time is %f and flow is %d",deltaTimeInSeconds, _flowIsON);
if ((deltaTimeInSeconds > kRED_DELAY_BETWEEN_PARTICLE_DROP) && _flowIsON) {
[self startFlow:kFlowTypeRed];
//only if its been past 1 second do we set the lasttime to the current time
lastTime = currentTime;
}
}
- (void) initializeScene {
SKLabelNode *startBtn = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
startBtn.text = START_BTN_TEXT;
startBtn.name = kStartBtn;
startBtn.fontSize = 45;
startBtn.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:startBtn];
//init to flow off
_flowIsON = NO;
// Set physics body delegate
self.physicsWorld.contactDelegate = self;
self.shouldRasterize = YES;
self.view.showsDrawCount = YES;
self.view.showsQuadCount = YES;
//Set collision mask for invisible wall
_nonWallNode = (SKSpriteNode *) [self.scene childNodeWithName:#"NonWall"];
_nonWallNode.physicsBody.categoryBitMask = kInvisbleWallCategory;
_nonWallNode.physicsBody.collisionBitMask = kRedParticleCategory;
_nonWallNode.physicsBody.contactTestBitMask = kRedParticleCategory | kInvisbleWallCategory;
}
- (void) startFlow:(NSString *)flowKey {
// //SKSpriteNode *redParticleEmitter = [SKSpriteNode spriteNodeWithImageNamed:#"RedFlowParticles"];
//
// SKShapeNode *redParticleEmitter = [[SKShapeNode alloc] init];
//
// CGMutablePathRef myPath = CGPathCreateMutable();
// CGPathAddArc(myPath, NULL, 0,0, 15, 0, M_PI*2, YES);
// redParticleEmitter.path = myPath;
//
// redParticleEmitter.lineWidth = 1.0;
// redParticleEmitter.fillColor = [SKColor blueColor];
// redParticleEmitter.strokeColor = [SKColor whiteColor];
// redParticleEmitter.glowWidth = 0.5;
//
// //set size to 20px x 20px
// //redParticleEmitter.size = CGSizeMake(10, 10);
SKSpriteNode *redParticleEmitter = [SKSpriteNode spriteNodeWithImageNamed:#"RedFlowParticles"];
//set size to 20px x 20px
redParticleEmitter.size = CGSizeMake(10, 10);
SKPhysicsBody *redParticleEmitterPB = [SKPhysicsBody bodyWithCircleOfRadius:redParticleEmitter.frame.size.width/2];
redParticleEmitterPB.categoryBitMask = kRedParticleCategory;
redParticleEmitterPB.collisionBitMask = kRedParticleCategory;
redParticleEmitterPB.contactTestBitMask = kRedParticleCategory | kInvisbleWallCategory;
//set this to 5% of the width of the scene
redParticleEmitter.position = CGPointMake(self.frame.size.width*0.05, self.frame.size.height);
redParticleEmitter.physicsBody =redParticleEmitterPB;
redParticleEmitter.name = #"RedParticle";
[self addChild:redParticleEmitter];
}
- (void)selectNodeForTouch:(CGPoint)touchLocation {
//1
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
//2
if(![_leverNode isEqual:touchedNode]) {
[_leverNode removeAllActions];
[_leverNode runAction:[SKAction rotateToAngle:0.0f duration:0.1]];
_leverNode = touchedNode;
//3
if([[touchedNode name] isEqualToString:kLever]) {
SKAction *sequence = [SKAction sequence:#[[SKAction rotateByAngle:degToRad(-4.0f) duration:0.1],
[SKAction rotateByAngle:0.0 duration:0.1],
[SKAction rotateByAngle:degToRad(4.0f) duration:0.1]]];
[_leverNode runAction:[SKAction repeatActionForever:sequence]];
}
}
}
float degToRad(float degree) {
return degree / 180.0f * M_PI;
}
- (CGPoint)boundLayerPos:(CGPoint)newPos {
CGSize winSize = self.size;
CGPoint retval = newPos;
retval.x = MIN(retval.x, 0);
retval.x = MAX(retval.x, -[self size].width+ winSize.width);
retval.y = [self position].y;
return retval;
}
- (void)panForTranslation:(CGPoint)translation {
CGPoint position = [_leverNode position];
if([[_leverNode name] isEqualToString:kLever]) {
[_leverNode setPosition:CGPointMake(position.x + translation.x, position.y + translation.y)];
}
// else {
// CGPoint newPos = CGPointMake(position.x + translation.x, position.y + translation.y);
// [_background setPosition:[self boundLayerPos:newPos]];
// }
}
# pragma mark -- SKPhysicsContactDelegate Methods
- (void)didBeginContact:(SKPhysicsContact *) contact {
if (([contact.bodyA.node.name isEqualToString:#"RedParticle"] && [contact.bodyB.node.name isEqualToString:#"NonWall"]) ||
([contact.bodyB.node.name isEqualToString:#"RedParticle"] && [contact.bodyA.node.name isEqualToString:#"NonWall"])) {
//NSLog(#"Red particle Hit nonwall");
//contact.bodyA.node.physicsBody.pinned = YES;
//once red particle passes the invisible wall we need to stop it from going back through the wall
}
}
- (void)didEndContact:(SKPhysicsContact *) contact {
//NSLog(#"didEndContact called");
if (([contact.bodyA.node.name isEqualToString:#"RedParticle"] && [contact.bodyB.node.name isEqualToString:#"NonWall"]) ||
([contact.bodyB.node.name isEqualToString:#"RedParticle"] && [contact.bodyA.node.name isEqualToString:#"NonWall"])) {
//NSLog(#"Red particle left");
contact.bodyB.collisionBitMask = kRedParticleCategory | kInvisbleWallCategory;
//once red particle passes the invisible wall we need to stop it from going back through the wall
}
}
#end
Try this:
Create an additional sprite node on screen to display all your static balls as a whole (explained below).
Create an array of CGPoint to keep track of the positions of all balls that stopped.
At regular intervals, check all active ball sprites to see which ones have come to a stop.
For each ball that has stopped, remove that srpite from the scene and instead add its position (CGPoint) to the array described in #2.
Render an image consisting of one ball instance at each position in the array, and assign that image (texture) to the sprite node described in #1.
Go back to #3 and repeat.
Note: I haven't used SpriteKit for a while and I'm not sure how to implement point #5, but it shouldn't be too difficult. SKEffectNode has an option (shouldRasterize) to cache its appearance -i.e., render once and reuse the same image on all subsequent frames.
Regarding the "regular intervals" described in step #3, the actual value (for example, every 10 frames) will depend on your measured performance and the dynamics of your actual game; you need to find it yourself. If it is too often, the overhead of rendering the static balls texture over and over will cause a performance hit. Too far apart, and you will spend more frames than necessary rendering many still, separate sprites that could have otherwise been "grouped".
Alternative Solution:
Instead of removing the sprites from screen when each ball becomes static, you could instead move them into a different container node (as children of it), and have that node be rasterized instead of rendering anew each frame.
This keeps each ball as a separate SKSpriteNode instance (even when the ones that are stopped) and allows for SpriteKit physics bodies (not sure if sprites with different parents can collide with each other, though. Never used SpriteKit physics).
In any case, the performance hit due to collision detection will increase with the number of balls, independent of whether you draw them each frame or not.
I don't know exactly what optimizations SpriteKit's physics does (e.g., prunning, etc.), but the naïve approach to collision between n objects is to test each object against every other object, so the worse case is O(n^2).
Final Thoughts:
Because you can safely assume that still balls do not move anymore, the "group" of still balls remains in the same shape all along (until new balls stop and are added, that is).
Ideally, you could calculate the "envelope" (a possibly non-convex polygon, with rounded corners) and collision-test the moving balls against that. Still not a trivial task, but at least it helps you skip collision-testing against the static balls in the inside of the group, which should never collide anyway (they are "shielded" by the balls in the boundary of the group).
Well your problem here is all those physics bodies, you have a 1000 sprites checking 1000 other sprites whether or not they need to be colliding. One way you can make this a little faster is to break your screen into sub sets and having your nodes collission detection only check the surrounding neighbor quadrants and its own for sprites. EG. break the screen into 9 sections, the top left section has its own bit mask, and can only collide with sprites in the top left,middle top, middle center, and let center sections. If this sprite moves to the middle top section, its category becomes middle top, and will only check sprites in the top left, middle top, top right, left center, middle center, and right center. The less checks the nodes have to make the better.
I'm new to cocos2d and objective-c. I know I'm missing something here, but I just can't find the solution. Hope someone can help....
My goal is to be able to click any of the sprites that I've placed on the screen. To start, I've put 2 sprites on the screen. (Each sprite is in the shape of a star).
The problem is, only the second sprite placed on the screen is clickable. My guess is that when I call addNewStar, it replaces _star with the latest star sprite, and takes the previous _star out of the physics node. I want all the stars I add to be in the physics node and be clickable. No clue how to do this.
Here is my code...hopefully someone can point out my mistake(s)!
#implementation MainScene {
CCSprite *_star;
CCPhysicsNode *_physicsNode;
CCNode *_ground;
CCLabelTTF *_scoreLabel;
BOOL _gameOver;
CCButton *_restartButton;
}
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
[self addNewStar];
[self addNewStar];
// set collision txpe
_ground.physicsBody.collisionType = #"level";
// set this class as delegate
_physicsNode.collisionDelegate = self;
}
-(void)addNewStar {
//This successfully loads my star onto the screen
_star = (Star *)[CCBReader load:#"Star"];
_star.physicsBody.collisionGroup = #"starGroup";
_star.physicsBody.collisionType = #"star";
_star.position = ccp(200,300);
[_physicsNode addChild:_star];
}
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair star:(CCNode *)star level:(CCNode *)level {
[self gameOver];
return TRUE;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
float ranNum1 = (arc4random_uniform(10000));
float ranNum2 = (arc4random_uniform(10000));
float sideForce = ranNum1 - ranNum2;
if (!_gameOver) {
CGPoint touchLocation = [touch locationInNode:_physicsNode];
if(CGRectContainsPoint([_star boundingBox], touchLocation))
{
[_star.physicsBody applyImpulse:ccp(sideForce, 1000.f)];
[_star.physicsBody applyAngularImpulse:2500.f];
}
}
}
- (void)restart {
CCScene *scene = [CCBReader loadAsScene:#"MainScene"];
[[CCDirector sharedDirector] replaceScene:scene];
}
- (void)gameOver {
if (!_gameOver) {
_gameOver = TRUE;
_restartButton.visible = TRUE;
_star.rotation = 90.f;
_star.physicsBody.allowsRotation = FALSE;
[_star stopAllActions];
CCActionMoveBy *moveBy = [CCActionMoveBy actionWithDuration:0.2f position:ccp(-2, 2)];
CCActionInterval *reverseMovement = [moveBy reverse];
CCActionSequence *shakeSequence = [CCActionSequence actionWithArray:#[moveBy, reverseMovement]];
CCActionEaseBounce *bounce = [CCActionEaseBounce actionWithAction:shakeSequence];
[self runAction:bounce];
}
}
#end
You only have one pointer to one star. So when you test if the star was touched in your touches began method then of course only the second one would do anything since your sole pointer is only pointing at the most recently created star.
To answer your question, the simple approach you could take is to add each star to an NSArray. Then in your touches began method you can loop over the array and see which star was touched.
Example:
// Recommend using properties over i-vars
#property (nonatomic, strong) NSMutableArray* starList;
// In init or onEnter...
self.starList = [NSMutableArray array];
// Each time you create a new star...
Star* star = ...;
[self addChild:star];
[self.starList addObject:star];
// In your touches began...
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
...
for (Star* star in self.starList)
{
// If star is touched...
// ...add your impulses, etc
}
}
This is touchBegan from my project. Maybe you can change code little bit for your need. What you can see here? After getting location of touch code searching b2Body which was touched. In your example you need to search your _physicsNode for _child that was touched.
for(UITouch *touch in allTouches)
{
CGPoint location = [touch locationInView:touch.view];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 worldLoc = b2Vec2(ptm(location.x), ptm(location.y));
// SEARCH YOUR CHILD FROM NODE HERE
for (b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetType() == b2_dynamicBody)
if (b->IsBullet() == false)
{
for (b2Fixture *f = b->GetFixtureList(); f; f = f->GetNext())
{
// Hit!
if (f->TestPoint(worldLoc))
{
//do stuff
}
break;
}
}
}
}
I am making a game which purpose it is to catch multiple objects that are falling from the top of the screen. In the bottom there is a basket to catch the objects. i managed to randomly spawn objects from the top dropping to the bottom using raywenderlich's tutorial : http://www.raywenderlich.com/42699/spritekit-tutorial-for-beginners
But what i want is that when i tap on that random object, the image of that object changes into another image , so just for imagination if the random objects are cats, after i tap them they have to become dogs, how do i have to program this?
edit this is what i got so far :
#import "MyScene.h"
static NSString* basketCategoryName = #"basket";
static NSString* monsterCategoryName= #"monster";
static const uint32_t projectileCategory = 0x1 << 0;
static const uint32_t monsterCategory = 0x1 << 1;
#interface MyScene() <SKPhysicsContactDelegate>
#property (nonatomic) SKLabelNode * scoreLabelNode;
#property int score;
#property (nonatomic) SKSpriteNode * basket;
#property (nonatomic) SKSpriteNode * monster;
#property (nonatomic) BOOL isFingerOnBasket;
#property (nonatomic) BOOL isFingerOnMonster;
#property (nonatomic) BOOL isTouching;
#property (nonatomic) NSTimeInterval lastSpawnTimeInterval;
#property (nonatomic) NSTimeInterval lastUpdateTimeInterval;
//#property (nonatomic, strong) SKSpriteNode *selectedNode;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
// Initialize label and create a label which holds the score
_score = 0;
_scoreLabelNode = [SKLabelNode labelNodeWithFontNamed:#"MarkerFelt-Wide"];
_scoreLabelNode.position = CGPointMake( CGRectGetMidX( self.frame ), 3 * self.frame.size.height / 4 );
_scoreLabelNode.zPosition = 100;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d", _score];
[self addChild:_scoreLabelNode];
// Set the background
SKTexture* groundTexture = [SKTexture textureWithImageNamed:#"AcornFlipTestBackground1136x640.png"];
groundTexture.filteringMode = SKTextureFilteringNearest;
for( int i = 0; i < 2 + self.frame.size.width / ( groundTexture.size.width * 2 ); ++i ) {
SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture:groundTexture];
[sprite setScale:1.0];
sprite.size = CGSizeMake(self.frame.size.width,self.frame.size.height);
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:sprite];
}
// Make grafity for sprite
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
// Make catching object sprite
self.basket = [SKSpriteNode spriteNodeWithImageNamed:#"bedTest.png"];
self.basket.position = CGPointMake(CGRectGetMidX(self.frame), _basket.frame.size.height * 0.5f);
self.basket.name = basketCategoryName;
[self addChild:self.basket];
// For default this is set to no until user touches the basket and the game begins.
self.isTouching = NO;
}
return self;
}
-(void)addAcorn{
if(_isTouching == YES) {
self.monster= [SKSpriteNode spriteNodeWithImageNamed:#"AcornFinal.png"];
// Determine where to spawn the monster along the X axis
int minX = self.monster.size.width;
int maxX = self.frame.size.width - self.monster.size.width;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX)+minX;
// Random position along the X axis as calculated above
// This describe from which way the acorns move
// - means moving from top to the right and + means moving from the top to the left
self.monster.position = CGPointMake(actualX ,self.frame.size.height+ self.monster.size.height);
self.monster.name = monsterCategoryName;
[self addChild:self.monster];
CGSize contactSize = CGSizeMake(self.monster.size.width - 5.0, self.monster.size.height - 10.0);
self.monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize]; // 1
self.monster.physicsBody.dynamic = YES; // 2
self.monster.physicsBody.categoryBitMask = monsterCategory; // 3
self.monster.physicsBody.contactTestBitMask = projectileCategory; // 4
self.monster.physicsBody.collisionBitMask = 0; // 5
// Determine speed of the monster
int minDuration = 8.0;
int maxDuration = 10.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX,-self.monster.size.height) duration:actualDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[self.monster runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 0.5) {
self.lastSpawnTimeInterval = 0;
[self addAcorn];
}
}
- (void)update:(NSTimeInterval)currentTime {
// Handle time delta.
// If we drop below 60fps, we still want everything to move the same distance.
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 60.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
self.isTouching = YES;
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* body = [self nodeAtPoint:location];
if ([body.name isEqualToString:basketCategoryName])
{
NSLog(#"Began touch on basket");
self.isFingerOnBasket = YES;
}
else if ([body.name isEqualToString:monsterCategoryName])
{
NSLog(#"Began touch on MONSTER");
self.isFingerOnMonster = YES;
}
}
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
if (self.isFingerOnMonster) {
// 2 Get touch location
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
// 3 Get node for paddle
SKSpriteNode* monster = (SKSpriteNode*)[self childNodeWithName: monsterCategoryName];
int oldPosition = monster.position.x + (location.x - previousLocation.x);
self.monster = [SKSpriteNode spriteNodeWithImageNamed:#"AcornFinal.png"];
monster.position = CGPointMake(oldPosition, monster.position.y);
NSLog(#"reached the touch though");
}
// 1 Check whether user tapped paddle
if (self.isFingerOnBasket) {
// 2 Get touch location
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
// 3 Get node for paddle
SKSpriteNode* basket = (SKSpriteNode*)[self childNodeWithName: basketCategoryName];
// 4 Calculate new position along x for paddle
int basketX = basket.position.x + (location.x - previousLocation.x);
// 5 Limit x so that the paddle will not leave the screen to left or right
basketX = MAX(basketX, basket.size.width/2);
basketX = MIN(basketX, self.size.width - basket.size.width/2);
// 6 Update position of paddle
basket.position = CGPointMake(basketX, basket.position.y);
CGSize contactSize = CGSizeMake(basket.size.width - 8.0, basket.size.height - 8.0);
basket.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
basket.physicsBody.dynamic = YES;
basket.physicsBody.categoryBitMask = projectileCategory;
basket.physicsBody.contactTestBitMask = monsterCategory;
basket.physicsBody.collisionBitMask = 0;
basket.physicsBody.usesPreciseCollisionDetection = YES;
}
}
- (void)projectile:(SKSpriteNode *)basket didCollideWithMonster:(SKSpriteNode *)monster {
NSLog(#"Hit");
[monster removeFromParent];
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
// 1
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
// 2
if ((firstBody.categoryBitMask & projectileCategory) != 0 &&
(secondBody.categoryBitMask & monsterCategory) != 0)
{
[self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node];
NSLog(#"test");
_score++;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d", _score];
}
}
// Removing this void will result in being able to drag the basket accross the screen without touching the basket itself.
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
self.isFingerOnBasket = NO;
self.isFingerOnMonster = NO;
}
#end
Once a touch is detected on a sprite (I assume you already got that working out) you can either create the sprite again with spriteNodeWithImageNamed. Make sure you save the previous node's position and set it again on the new sprite so it will match the position of the old sprite.
CGPoint oldPosition = touchedSprite.position;
touchedSprite = [SKSpritNode spriteWithImageNamed:#"imgge.png"];
touchedSprite.position = oldPosition;
// If you have any other sprite properties you will have to save them as well
You can also set the texture with setTexture method which will not require you to change anything else (e.g. position) :
[touchedSprite setTexture:[SKTexture textureWithImageNamed:#"image.png"]];
EDIT :
Answering your question in the comments you implement this in the touchesEnded method of the parent node of the sprites :
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
for (SKSpriteNode *sprite in fallingSprites) { // Assuming fallingSprite is an array containing the cat sprites you want to detect touches for
if (CGRectContainsPoint(sprite.frame, touchLocation)) {
[sprite setTexture:[SKTexture textureWithImageNamed:#"dog.png"]];
}
}
}
Another approach (Haven't tried it yet) is to subclass SKSpriteNode and implement the same method but without the touch detection in rect since if this method is called the sprite has been touched .
Each part of the skeleton model is a spritekit node. Find the node that you wish to change and update its texture property like this:
spriteKitNode.texture = // Updated SKTexture
In your TouchesEnded event:
Add a small skspritenode positioned at the touch location with a small physics body that last for a short duration. ("touch spritenode)
Set up contact between all possible transformed objects and the "touch spritenode"
Create a subclass of SKSpritenode for the falling objects that has a variable to stores their type.
In the method called from contact between the touch spritenode and the falling objects:
Remove the touchspritenode first
Create if statements to check which falling object was touched.
Update the image of the contacted falling object spritenode according to its type.
If you are following Raywenderlich you have access to the actual syntax
The sprite appears every time the screen is touched, then it shoots to the desired area. How would i make it that only one sprite will be on the scene at a time until it exits the scene or hits an object? (even though the screen is touched multiple times)
This is the projectile code
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// 1
CGPoint touchLocation = [touch locationInNode:self];
// 2
CGPoint offset = ccpSub(touchLocation, _player.position);
float ratio = offset.y/offset.x;
int targetX = _player.contentSize.width/2 + self.contentSize.width;
int targetY = (targetX*ratio) + _player.position.y;
CGPoint targetPosition = ccp(targetX,targetY);
// 3
CCSprite *projectile = [CCSprite spriteWithImageNamed:#"projectile.png"];
projectile.position = _player.position;
projectile.physicsBody = [CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints];
projectile.physicsBody.collisionGroup = #"playerGroup";
projectile.physicsBody.collisionType = #"projectileCollision";
[_physicsWorld addChild:projectile];
// 4
CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.5f position:targetPosition];
CCActionRemove *actionRemove = [CCActionRemove action];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove]]];
[[OALSimpleAudio sharedInstance] playEffect:#"pew-pew-lei.caf"];
}
If I understood your point correctly , you could simply add a flag , to be able to notice when there's already a sprite on the scene. Just declare on your class a
BOOL isSpritePresent;
Initialize it on your class custom id method.
-(id)init {
self=[super init];
isSpritePresent=NO;
return self; }
And then on the start of TouchBegan add something like
if(isSpritePresent){
return; //As there's already an sprite on the scene.
}
And at the end
isSpritePresent=YES;
And finally when the arrow or w/e it is reached its target , call a method to reset the Boolean.
Or... If you're looking forward to doing things simpler and you believe you have an specific time to let the user shoot again just add a delay after the other actions as ..
CCActionDelay *delay = [CCActionDelay actionWithDuration:1.2f];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove,delay]]];
I want to move one object over another object without bounce when they collide.
In Simple i have one ball(body) and i have one Rectangular line image(body), the image is slope 45 degree.
The ball fall from the top speed is 20 when the ball touch on the slope image(Static Body) it will bounce always and continue running.
When the ball come over the line image it will not bounce and slow down the speed according to the collision and ball stop in the end. Same as it happens in real world when ball collide with something , the ball speed will slow down and it turn according to the collision and then stop due to decreasing speed of collision.
I am doing this, but cant achieve the result what i want.
-(id) init
{
if( (self=[super init])) {
CGSize winSize = [CCDirector sharedDirector].winSize;
// Create a world
b2Vec2 gravity = b2Vec2(0.0f, 0.0f);
_world = new b2World(gravity);
///////////////////////// Ball ///////////////////////////////
// Create sprite and add it to the layer
CCSprite *ball = [CCSprite spriteWithFile:#"ball.png"];
ball.position = ccp(100, 200);
ball.tag = 1;
[self addChild:ball];
// Create ball body
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(127/PTM_RATIO, 210/PTM_RATIO);
ballBodyDef.userData = ball;
ballBody = _world->CreateBody(&ballBodyDef); // b2Body * ballBody
// Create circle shape
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
// Create shape definition and add to body
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 15.0f;
ballShapeDef.friction = 2.f;
ballShapeDef.restitution = 0.0f;
_ballFixture = ballBody->CreateFixture(&ballShapeDef);
b2Vec2 force = b2Vec2(73, -52);
ballBody->ApplyLinearImpulse(force, ballBodyDef.position);
//ballBody->SetLinearVelocity(b2Vec2(10,0)); // try
// ballBody->SetAngularVelocity(0); // try
///////////////////////// Ball ///////////////////////////////
// Create paddle and add it to the layer
CCSprite *paddle = [CCSprite spriteWithFile:#"paddle.png"];
paddle.position = ccp(winSize.width/2, 50);
[self addChild:paddle];
// Create paddle body
b2BodyDef paddleBodyDef;
paddleBodyDef.type = b2_staticBody; //b2_staticBody, b2_dynamicBody
paddleBodyDef.position.Set(winSize.width/2/PTM_RATIO, 50/PTM_RATIO);
paddleBodyDef.userData = paddle;
paddleBodyDef.angle = 75;
_paddleBody = _world->CreateBody(&paddleBodyDef);
// Create paddle shape
b2PolygonShape paddleShape;
paddleShape.SetAsBox(paddle.contentSize.width/PTM_RATIO/2, paddle.contentSize.height/PTM_RATIO/2);
// Create shape definition and add to body
b2FixtureDef paddleShapeDef;
paddleShapeDef.shape = &paddleShape;
paddleShapeDef.density = 25.0f;
paddleShapeDef.friction = 1.1f;
paddleShapeDef.restitution = 0.1f;
_paddleFixture = _paddleBody->CreateFixture(&paddleShapeDef);
// Restrict paddle along the x axis
b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(0.0f, 0.0f);
jointDef.collideConnected = true;
jointDef.Initialize(_paddleBody, _groundBody, _paddleBody->GetWorldCenter(), worldAxis);
_world->CreateJoint(&jointDef);
[self schedule:#selector(tick:)];
self.touchEnabled = YES;
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint != NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
if (_paddleFixture->TestPoint(locationWorld)) {
b2MouseJointDef md;
md.bodyA = _groundBody;
md.bodyB = _paddleBody;
md.target = locationWorld;
md.collideConnected = true;
md.maxForce = 1000.0f * _paddleBody->GetMass();
_mouseJoint = (b2MouseJoint *)_world->CreateJoint(&md);
_paddleBody->SetAwake(true);
}
// [self kick];
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint == NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
_mouseJoint->SetTarget(locationWorld);
}
Ok so You have mad very small mistak, problem is with gravity.
change it to
b2Vec2 gravity = b2Vec2(0.0f, -10.0f);