I'm using a sprite kit to create a game.
I have a player node which I control by touch but then I have an enemy node that moves on its own. I need the enemy to be able to shoot projectiles on its own. I'm guessing I would need a method for this and to call it. I need the direction of the projectiles to shoot wherever my player is. Any suggestions?
Here is the method I have:
-(void)monstershoot
{
SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:#"projectile"];
projectile.position = _enemy.position;
CGPoint offset = rwSub(_player.position, projectile.position);
if (offset.x <= 0) return;
[_background addChild:projectile];
CGPoint direction = rwNormalize(offset);
CGPoint shootAmount = rwMult(direction, 1000);
CGPoint realDest = rwAdd(shootAmount, projectile.position);
// 9 - Create the actions
float velocity = 480.0/1.0;
float realMoveDuration = self.size.width / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[projectile runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
But nothing happens when I call it.
It will do "nothing" in two cases: the image doesn't exist or the enemy is right of the player. Replace this line
if (offset.x <= 0) return;
by a check if the length of the offset vector is 0. You could write a method to calculate the length by using
sqrtf(powf(offset.x,2) + powf(offset.y,2))
Further you should use the length of shootAmount to calculate
float realMoveDuration = 1000 / velocity;
which will give you the same (and correct) speed for every projectile. But otherwise I see nothing wrong with your code.
Related
Could you please help me with the problem I have?
define CC_RADIANS_TO_DEGREES(ANGLE) ((ANGLE) * 57.29577951f) // PI
-(void)didMoveToView:(SKView )view {
/ Setup your scene here */
_skyColor = [SKColor colorWithRed:113.0/255.0 green:197.0/255.0 blue:207.0/255.0 alpha:1.0];
[self setBackgroundColor:_skyColor];
//Setup the array to hold the walking frames
NSMutableArray *padlingFrames = [NSMutableArray array];
//Load the TextureAtlas for the bear
SKTextureAtlas *kajakAnimatedAtlas = [SKTextureAtlas atlasNamed:#"KajakImages"];
//Load the animation frames from the TextureAtlas
long numImages = kajakAnimatedAtlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"kajak_0%d", i];
SKTexture *temp = [kajakAnimatedAtlas textureNamed:textureName];
[padlingFrames addObject:temp];
}
_kajakPadlingFrames = padlingFrames;
//Create kajak sprite, setup position in middle of the screen, and add to Scene
SKTexture *temp = _kajakPadlingFrames[0];
_kajak = [SKSpriteNode spriteNodeWithTexture:temp];
_kajak.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[_kajak setScale:0.2];
[self addChild:_kajak];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent )event {
/ Called when a touch begins */
CGPoint touchLocation = [[touches anyObject] locationInNode:self];
[self updateRotate:touchLocation];
}
(void)updateRotate:(CGPoint)touchLocation
{
float deltaX = touchLocation.x - _kajak.position.x;
float deltaY = touchLocation.y - _kajak.position.y;
float angle = atan2f(deltaY, deltaX);
SKAction *action = [SKAction rotateByAngle: CC_RADIANS_TO_DEGREES(angle) duration: 5.0];
[_kajak runAction:action];
}
The kayak is not rotate in the direction the I touch. It is some thing I missing? Please help me. Tank you in advance
The zero angle for SKSpriteNodes is pointing up (as opposed to standard trig functions that treat zero angle as pointing right). I had to create a special angle calculation function to take that into account in my game (see below, in Swift).
You may also need to make sure that the coordinate system you are using for the positions is the same for the sprite and the touch location. From your code (i'm not very comfortable with Obj-C) I believe they are both base on the scene's bounds but I'm not certain.
// angle between two points
// ------------------------
// SpriteKit's zero angle is pointing up
// atan2 returns an angle pointing right
// we're usung sprite kit's conventions so removing 90° from the result
//
func spriteAngleFrom(start:CGPoint, to finish:CGPoint) -> CGFloat
{
let deltaX = finish.x - start.x
let deltaY = finish.y - start.y
return atan2(deltaY,deltaX) - CGFloat.pi/2
}
You also need to use rotateToAngle, not rotateByAngle. And I would suggest you also specify shortestUnitArc : true.
I am trying to move a SKSpriteNode in a circular path, pause that animation, and then reverse it from where it left off.
Here is what I have so far.
CGPathRef circle = CGPathCreateWithEllipseInRect(CGRectMake(self.frame.size.width/2-75,self.frame.size.height/2-75,150,150), NULL);
self.circlePathActionClockwise = [SKAction followPath:circle asOffset:NO orientToPath:YES duration:5.0];
self.circlePathActionClockwise = [SKAction repeatActionForever:self.circlePathActionClockwise];
[self.ball runAction:self.circlePathActionClockwise];
self.ball.speed = 1.0;
This animated the ball in a circle. Then when I want to pause and reverse, I try:
[self.ball runAction:[self.circlePathActionClockwise reversedAction]];
self.ball.speed = 1.0;
This reverses the animation, but it does not start it from where it left off. Instead it treats it was a new animation and starts from the beginning.
Is there a way I can start an action from self.ball's current position?
Thanks a lot!
The steps to reverse the direction of a sprite that is following a circular path are
Compute the angle of the sprite at the stopping point (see point a and angle theta in the Figure 1)
Create a new circular path that starts at angle theta
Run an action in the opposite direction
Figure 1. A sprite following a counter-clockwise circular path
Here's an example of how to do that:
Step 1: compute the starting angle of the new path based on the current position of the sprite.
- (CGFloat) angleOnCircleWithPosition:(CGPoint)position andCenter:(CGPoint)center
{
CGFloat dx = position.x - center.x;
CGFloat dy = position.y - center.y;
return atan2f(dy, dx);
}
Step 2: create a circular path given a center point and radius. Create the path starting from the specified angle (in radians).
- (CGPathRef) circlePathRefWithCenter:(CGPoint)center radius:(CGFloat)radius andStartingAngle:(CGFloat)startAngle
{
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddRelativeArc(circlePath, NULL, center.x, center.y, radius, startAngle, M_PI*2);
CGPathCloseSubpath(circlePath);
return circlePath;
}
You will need to declare the following instance variables:
BOOL clockwise;
SKSpriteNode *sprite;
CGFloat circleRadius;
CGPoint circleCenter;
Step 3: Reverse the direction of the sprite when the user taps the screen. It assumes that you've created a sprite and added it to the scene and set the radius and center of the circle path.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[sprite removeActionForKey:#"circle"];
CGFloat angle = [self angleOnCircleWithPosition:sprite.position andCenter:circleCenter];
CGPathRef path = [self circlePathRefWithCenter:circleCenter radius:circleRadius andStartingAngle:angle];
SKAction *followPath = [SKAction followPath:path asOffset:NO orientToPath:YES duration:4];
if (clockwise) {
[sprite runAction:[SKAction repeatActionForever:followPath] withKey:#"circle"];
}
else {
[sprite runAction:[[SKAction repeatActionForever:followPath] reversedAction] withKey:#"circle"];
}
// Toggle the direction
clockwise = !clockwise;
}
I am programming a game and want to move a sprite along a fixed path (straights and curves - think railroad engine) but I want to be able to drive the animation in terms of the sprites velocity - and change that velocity in response to game events.
followPath:duration: in SKAction is close, but there seems no way to adjust the speed "on the fly" - I definitely care about sprite speed - not 'duration to follow path'.
CGPath seems like the right construct for defining the path for the sprite, but there doesn't seem to be enough functionality there to grab points along the path and do my own math.
Can anyone suggest a suitable approach?
You can adjust the execution speed of any SKAction with its speed property. For example, if you set action.speed = 2, the action will run twice as fast.
SKAction *moveAlongPath = [SKAction followPath:path asOffset:NO orientToPath:YES duration:60];
[_character runAction:moveAlongPath withKey:#"moveAlongPath"];
You can then adjust the character's speed by
[self changeActionSpeedTo:2 onNode:_character];
Method to change the speed of an SKAction...
- (void) changeActionSpeedTo:(CGFloat)speed onNode:(SKSpriteNode *)node
{
SKAction *action = [node actionForKey:#"moveAlongPath"];
if (action) {
action.speed = speed;
}
}
Try some think like this :
CGMutablePathRef cgpath = CGPathCreateMutable();
//random values
float xStart = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float xEnd = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
//ControlPoint1
float cp1X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float cp1Y = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.height ];
//ControlPoint2
float cp2X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ];
float cp2Y = [self getRandomNumberBetween:0 to:cp1Y];
CGPoint s = CGPointMake(xStart, 1024.0);
CGPoint e = CGPointMake(xEnd, -100.0);
CGPoint cp1 = CGPointMake(cp1X, cp1Y);
CGPoint cp2 = CGPointMake(cp2X, cp2Y);
CGPathMoveToPoint(cgpath,NULL, s.x, s.y);
CGPathAddCurveToPoint(cgpath, NULL, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
SKAction *planeDestroy = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5];
[self addChild:enemy];
SKAction *remove2 = [SKAction removeFromParent];
[enemy runAction:[SKAction sequence:#[planeDestroy,remove2]]];
CGPathRelease(cgpath);
I'm making a simple space shooter in Sprite Kit. I have asteroid rock sprites that are randomly added to my scene. I give them a random velocity within a certain range. However, once added to the scene their velocity decreases every second and eventually they stop. as they get closer to the bottom of the screen their velocity decreases at a slower rate. I have gravity of my physics world set to (0,0).
below is the method that adds the rocks to the scene
- (void)addRock {
SPRockNode *rock = [SPRockNode rock];
float dy = [SPUtil randomWithMin:SPRockMinSpeed max:SPRockMaxSpeed];
rock.physicsBody.velocity = CGVectorMake(0, -dy);
float y = self.frame.size.height + rock.size.height;
float x = [SPUtil randomWithMin:10 + rock.size.width max:self.frame.size.width - 10 - rock.size.width];
rock.position = CGPointMake(x, y);
[self addChild:rock];
}
I set up the physics world in my scenes initWIthSize method
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0, 0);
below is the code for my rock node class
+ (instancetype)rock {
SPRockNode *rock = [self spriteNodeWithImageNamed:#"rock"];
rock.name = #"Rock";
float scale = [SPUtil randomWithMin:50 max:100] / 100.0f;
rock.xScale = scale;
rock.yScale = scale;
NSInteger randomInt = [SPUtil randomWithMin:1 max:3];
if (randomInt == 1) {
SKAction *oneRevolution = [SKAction rotateByAngle:-M_PI*2 duration: 1.0];
SKAction *repeat = [SKAction repeatActionForever:oneRevolution];
[rock runAction:repeat];
} else {
SKAction *oneRevolution = [SKAction rotateByAngle:M_PI*2 duration: 1.0];
SKAction *repeat = [SKAction repeatActionForever:oneRevolution];
[rock runAction:repeat];
}
[rock setupPhysicsBody];
return rock;
}
- (void)setupPhysicsBody {
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
self.physicsBody.affectedByGravity = NO;
self.physicsBody.categoryBitMask = SPCollisionCategoryRock;
self.physicsBody.collisionBitMask = 0;
self.physicsBody.contactTestBitMask = SPCollisionCategoryProjectile | SPCollisionCategoryShip;
}
any ideas on what causing them to slow down? I dont modify their velocity anywhere else but the addRock method. is there air resistance or something like that on by default? I just dont understand why their velocity is decreasing.
It sounds like you are having an issue with linear damping.
From SKPhysicsBody reference :
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.
Try adding this :
rock.physicsBody.linearDamping = 0.0f;
It's worthwhile to give the SKPhysicsBody class reference a reading at least once. It only takes a short while, but can save you a ton of time if you have become acquainted with it's properties and methods.
Okay guys, I have two sprites one the enemy who moves to pick up items around the map as the items appear and it does that using SKActions then also I have the wall which he shouldn't go through. anyway. all I am trying to do is to make him bounce out if he hits the wall. How would I go about that?
Here is the code below:
// for the Enemy wall I have this code:
- (void) EnemyBelongings {
EnemyWall = [SKSpriteNode spriteNodeWithImageNamed:#"EnemyWall#2x"];
EnemyWall.name = #"hisWall";
EnemyWall.position = CGPointMake(512, 260);
EnemyWall.xScale = 0.09;
EnemyWall.yScale = 0.09;
EnemyWall.physicsBody.categoryBitMask = PhysicsCategoryEnemyWall;
EnemyWall.physicsBody.contactTestBitMask = PhysicsCategoryEnemy;
EnemyWall.physicsBody.collisionBitMask = 0;
[self addChild:EnemyWall];
}
// For the enemy character I have this code
- (void) Enemy {
_Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
_Enemy.position = CGPointMake(520, _Enemy.size.height/1.50);
_Enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width];
_Enemy.physicsBody.usesPreciseCollisionDetection = YES;
_Enemy.physicsBody.categoryBitMask = PhysicsCategoryEnemy;
_Enemy.physicsBody.contactTestBitMask = PhysicsCategoryEnemyWall;
_Enemy.physicsBody.collisionBitMask = 0;
[self addChild:_Enemy];
}
// For the enemy movement I have this code
- (void) EnemySpawner {
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
[_Enemy runAction:wait];
SKAction *actionXMove2 = [SKAction moveToX:ForEnemy.position.x duration:0.14];
SKAction *actionYMove2 = [SKAction moveToY:ForEnemy.position.y duration:0.14];
[_Enemy runAction:actionYMove2];
[_Enemy runAction:actionXMove2];
}];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if (contact.bodyA.categoryBitMask == PhysicsCategoryEnemy && contact.bodyB.categoryBitMask
== PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyA.node; // a is the enemy here
// Code here
SKNode *wall = contact.bodyB.node; // b is the enemy's wall here
// Code here
}
else if (contact.bodyB.categoryBitMask == PhysicsCategoryEnemy &&
contact.bodyA.categoryBitMask == PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyB.node;
// Code here
SKNode *wall = contact.bodyA.node;
// Code here
}
}
You need to applyImpulse on the node's physicsBody to make it simulate naturally and interact with other physicsBodies.
Please look at my answer here on how to do the same.
Copy the rwAdd, rwSub, rwMult, rwLength and the rwNormalize methods from this tutorial by Ray Wenderlich.
Then, try using this code:
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
CGPoint offset = rwSub(location, ForEnemy.position);
CGPoint direction = rwNormalize(offset);
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
[_Enemy.physicsBody applyImpulse:impulseVector];
}];
PhysicsWorld should simulate bounces automatically. Just set your nodes' physics properties accordingly. Look for the 'restitution' property. It will determine the bounciness of your objects.
And don't use SKAction to move your enemies. This will just "teleport" your nodes around. They don't get to have any real velocity in the physicsWorld (and hence don't bounce). Instead, apply forces to your bodies (or set velocity vector manually).