hi guys I have this Sprite Kit cannon game that it works perfectly but with one problem. if I point the cannon to a very top or very bottom as you can see in the image, the bullet since it is spawning from the center of the cannon's sprite, it shows clearly that the bullet is being fired not from the cannon's tunnel but from the center of the image, how can I fix this bullet position so that it would always spawn from the mouth of the tunnel.
here is my code so far.
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [_Player position];
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
bullet = [SKSpriteNode spriteNodeWithImageNamed:#"cannonbullet"];
bullet.xScale = 0.06;
bullet.yScale = 0.06;
bullet.position =
CGPointMake(location.x+_Player.zRotation,location.y+_Player.zRotation);
bullet.zPosition = 0;
CGPoint offset = rwSub(touchLocation, bullet.position);
if (offset.x <= 0) return;
[self addChild:bullet];
CGPoint direction = rwNormalize(offset);
CGPoint shootAmount = rwMult(direction, 400);
CGPoint realDest = rwAdd(shootAmount, bullet.position);
float velocity = 480.0/1.0;
float realMoveDuration = self.size.width / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[bullet runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
[self animStarter];
}
as you can see in the image the bullet is spawning from a very off location if the cannon is not shooting straight.
Make sure that your cannon's barrel sprite image is lined up with the center X axis of the image (see the picture). You can drag out or copy the image if you want to duplicate the project.
Below is the code to rotate the cannon to the touch location and fire a cannonball once it has reached the the desired rotation angle. I think you are still using the zombie conga code so I used its functions for your convenience.
#import "MyScene.h"
static inline CGPoint CGPointSubtract(const CGPoint a,
const CGPoint b)
{
return CGPointMake(a.x - b.x, a.y - b.y);
}
static inline CGPoint CGPointMultiplyScalar(const CGPoint a,const CGFloat b)
{
return CGPointMake(a.x * b, a.y * b);
}
static inline CGFloat CGPointLength(const CGPoint a)
{
return sqrtf(a.x * a.x + a.y * a.y);
}
static inline CGPoint CGPointNormalize(const CGPoint a)
{
CGFloat length = CGPointLength(a);
return CGPointMake(a.x / length, a.y / length);
}
static inline CGFloat CGPointToAngle(const CGPoint a)
{
return atan2f(a.y, a.x);
}
static inline CGFloat ScalarSign(CGFloat a)
{
return a >= 0 ? 1 : -1;
}
static inline CGFloat ScalarShortestAngleBetween(const CGFloat a, const CGFloat b)
{
CGFloat difference = b - a;
CGFloat angle = fmodf(difference, M_PI * 2);
if (angle >= M_PI) {
angle -= M_PI * 2;
}
return angle;
}
static const float ROTATE_RADIANS_PER_SEC = 4 * M_PI;
static const float MOVE_POINTS_PER_SEC = 120.0;
#implementation MyScene
{
SKSpriteNode *cannon;
NSTimeInterval _lastUpdateTime;
NSTimeInterval _dt;
CGPoint _velocity;
CGPoint _lastTouchLocation;
BOOL fireCannon;
CGPoint destination;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
[self createCannon];
fireCannon = false;
}
return self;
}
-(void)createCannon
{
cannon = [SKSpriteNode spriteNodeWithImageNamed:#"cannon"];
cannon.position = CGPointMake(self.size.height/2, self.size.width/2);
[self addChild:cannon];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
fireCannon = true;
destination = touchLocation;
_lastTouchLocation = touchLocation;
CGPoint offset = CGPointSubtract(touchLocation, cannon.position);
CGPoint direction = CGPointNormalize(offset);
_velocity = CGPointMultiplyScalar(direction, MOVE_POINTS_PER_SEC);
}
-(void)update:(CFTimeInterval)currentTime
{
if (_lastUpdateTime) {
_dt = currentTime - _lastUpdateTime;
} else {
_dt = 0;
}
_lastUpdateTime = currentTime;
[self rotateSprite:cannon toFace:_velocity rotateRadiansPerSec:ROTATE_RADIANS_PER_SEC];
}
- (void)rotateSprite:(SKSpriteNode *)sprite
toFace:(CGPoint)velocity
rotateRadiansPerSec:(CGFloat)rotateRadiansPerSec
{
float targetAngle = CGPointToAngle(velocity);
float shortest = ScalarShortestAngleBetween(cannon.zRotation, targetAngle);
float amtToRotate = rotateRadiansPerSec * _dt;
if (ABS(shortest) < amtToRotate)
{
amtToRotate = ABS(shortest);
}
sprite.zRotation += ScalarSign(shortest) * amtToRotate;
if ((ABS(shortest) == amtToRotate) && (fireCannon == true))
{
fireCannon = false;
[self fire:targetAngle];
}
}
-(void)fire:(float)targetAngle
{
SKSpriteNode *cannonBall = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(5, 5)];
cannonBall.position = cannon.position;
[self addChild:cannonBall];
int x = cannon.position.x + 1000 * cos(targetAngle);
int y = cannon.position.y + 1000 * sin(targetAngle);
[cannonBall runAction:[SKAction moveTo:CGPointMake(x, y) duration:2]];
}
#end
Related
I am working with the SpriteKit's Spaceship Demo and I want it to rotate to the location of where I click on the screen and then move toward it.
The current code I have only makes it move up the screen starting at position 50,50:
sprite.position = CGPointMake(50,50);
SKAction *fly = [SKAction moveByX:0.0F y:50.0F duration:1];
[sprite runAction:[SKAction repeatActionForever:fly];
How would I make it work?
So long answer, this will help you.(I benefited from raywenderlich's book)
//Math Utilities
static inline CGPoint CGPointSubtract(const CGPoint a,
const CGPoint b)
{
return CGPointMake(a.x - b.x, a.y - b.y);
}
static inline CGPoint CGPointMultiplyScalar(const CGPoint a,
const CGFloat b)
{
return CGPointMake(a.x * b, a.y * b);
}
static inline CGFloat CGPointLength(const CGPoint a)
{
return sqrtf(a.x * a.x + a.y * a.y);
}
static inline CGPoint CGPointNormalize(const CGPoint a)
{
CGFloat length = CGPointLength(a);
return CGPointMake(a.x / length, a.y / length);
}
static inline CGPoint CGPointAdd(const CGPoint a,
const CGPoint b)
{
return CGPointMake(a.x + b.x, a.y + b.y);
}
static const float SHIP_SPEED = 60.0;
#implementation yourScene
{
SKSpriteNode *ship;
NSTimeInterval _lastUpdateTime;
NSTimeInterval _dt;
CGPoint _velocity;
CGPoint _lastTouchLocation;
}
-(void)didMoveToView:(SKView *)view
{
ship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
ship.position = CGPointMake(50, 50);
[self addChild:ship];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self.scene];
[self moveShipToward:touchLocation];//Move Toward
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
{
if (_lastUpdateTime) {
_dt = currentTime - _lastUpdateTime;
} else {
_dt = 0;
}
_lastUpdateTime = currentTime;
CGPoint offset = CGPointSubtract(_lastTouchLocation, ship.position);
float distance = CGPointLength(offset);
if (distance < SHIP_SPEED * _dt) {
ship.position = _lastTouchLocation;
_velocity = CGPointZero;
} else {
[self moveSprite:ship velocity:_velocity];
[self rotateSprite:ship toFace:_velocity];
}
}
}
- (void)moveShipToward:(CGPoint)location
{
_lastTouchLocation = location;
CGPoint offset = CGPointSubtract(location, ship.position);
CGPoint direction = CGPointNormalize(offset);
_velocity = CGPointMultiplyScalar(direction, SHIP_SPEED);
}
- (void)moveSprite:(SKSpriteNode *)sprite
velocity:(CGPoint)velocity
{
CGPoint amountToMove = CGPointMultiplyScalar(velocity, _dt);
sprite.position = CGPointAdd(sprite.position, amountToMove);
}
- (void)rotateSprite:(SKSpriteNode *)sprite
toFace:(CGPoint)direction
{
sprite.zRotation = atan2f(direction.y, direction.x);
}
I'm using this code to detect and see if the users tap was inside the frame of my SKSpriteNode, and if it is, remove the node from the screen. But I only want the node that was tapped to disappear.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
if ((location.x > self.crate.frame.origin.x && location.x < self.crate.frame.origin.x + self.crate.frame.size.width) &&
(location.y > self.crate.frame.origin.y && location.y < self.crate.frame.origin.y + self.crate.frame.size.height)) {
[self.crate removeFromParent];
}
}
}
In my update method, I am calling a method, addCrate: to spawn the node every second.
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 1) {
self.lastSpawnTimeInterval = 0;
[self addCrate];
}
}
- (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];
}
This is the method that it is calling.
- (void)addCrate {
// Create sprite
self.crate = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(30, 30)];
//self.crate.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.crate.frame.size];
// Determine where to spawn the crate along the X axis
int minX = self.crate.size.width / 2;
int maxX = self.frame.size.width - self.crate.size.width / 2;
int rangeX = maxX - minX;
int actualX = (arc4random_uniform(rangeX)) + minX;
// Create the crate slightly off-screen along the top,
// and along a random position along the X axis as calculated above
self.crate.position = CGPointMake(actualX, self.frame.size.height + self.crate.size.height/2);
[self addChild:self.crate];
self.crate.size = CGSizeMake(50, 50);
// Determine speed of the crate
int actualDuration = 3.5;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.crate.size.height/2) duration:actualDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[self.crate runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
But when I run on my iPhone, only sometimes the tap is registered and the block is removed from the screen, and sometimes it doesn't. Again, I want the node that was tapped on to disappear and only that node.
Thank you!
U1:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
[self addCrate];
}
return self;
}
- (void)addCrate {
// Create sprite
self.crate = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(30, 30)];
self.crate.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(30, 30)];
self.crate.userInteractionEnabled = YES;
//self.crate.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.crate.frame.size];
// Determine where to spawn the crate along the X axis
int minX = self.crate.size.width / 2;
int maxX = self.frame.size.width - self.crate.size.width / 2;
int rangeX = maxX - minX;
int actualX = (arc4random_uniform(rangeX)) + minX;
// Create the crate slightly off-screen along the top,
// and along a random position along the X axis as calculated above
self.crate.position = CGPointMake(actualX, self.frame.size.height + self.crate.size.height/2);
[self addChild:self.crate];
self.crate.size = CGSizeMake(50, 50);
// Determine speed of the crate
int actualDuration = 3.5;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.crate.size.height/2) duration:actualDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[self.crate runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
SKNode *touchedNode = [self nodeAtPoint:touchLocation];
NSLog(#"touchLocation x: %f and y: %f", touchLocation.x, touchLocation.y);
if (touchedNode != self) {
NSLog(#"Removed from parent.");
[touchedNode removeFromParent];
}
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 1) {
self.lastSpawnTimeInterval = 0;
[self addCrate];
}
}
- (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];
}
I think you should use combination of setting node.name property while creating crates and checking them in touchBegan: method.
Something like this:
SKSpriteNode *crate = [SKSpriteNode spriteNodeWithTexture:tex];
crate.name = #"crate";
And touchBegan: method:
.....
if ([touchedNode.name isEquelToString:#"crate"]){
// do something with that node
}
.....
Upd1:
Instead of writing this stuff:
if ((location.x > self.crate.frame.origin.x && location.x < self.crate.frame.origin.x + self.crate.frame.size.width) &&
(location.y > self.crate.frame.origin.y && location.y < self.crate.frame.origin.y + self.crate.frame.size.height)) {
[self.crate removeFromParent];
}
use:
if(CGRectContainsPoint(self.frame, touchPoint)){
// do something
}
Upd2:
Don't see in your code that you are setting userInteractionEnabled = YES on crate nodes.
Upd3:
Here is an example:
//
// BGMyScene.m
// Test1
//
// Created by AndrewShmig on 3/10/14.
// Copyright (c) 2014 Bleeding Games. All rights reserved.
//
#import "BGMyScene.h"
#implementation BGMyScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15
green:0.15
blue:0.3
alpha:1.0];
// first label
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
// myLabel.userInteractionEnabled = YES;
myLabel.text = #"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:myLabel];
// second label
SKLabelNode *myLabel2 = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
// myLabel2.userInteractionEnabled = YES;
myLabel2.text = #"Hello, World!";
myLabel2.fontSize = 30;
myLabel2.position = CGPointMake(100, 100);
[self addChild:myLabel2];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInNode:self];
SKNode *touchedNode = [self nodeAtPoint:touchLocation];
NSLog(#"touchLocation x: %f and y: %f", touchLocation.x, touchLocation.y);
if (touchedNode != self) {
NSLog(#"Removed from parent.");
[touchedNode removeFromParent];
}
}
- (void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
}
#end
You'll see following screen:
After tapping on "Hello, World!" labels they will be removed from parent node.
I am trying to shoot arrow with force in left to right direction on Touches Ended method.I applied linear impulse to B2Body, but it is not shooting in correct direction.
Here is my code
(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"ended");
//Add a new body/atlas sprite at the touched location
CGPoint location;
for( UITouch *touch in touches )
{
location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
}
if (fabs(ccpDistance(startTouchPosition, location)) < 3) {
return;
}
CGPoint diff = ccpSub(startTouchPosition, location);
diff = CGPointMake(-diff.x, -diff.y);
//CGFloat distanceX = bird.position.x - 50.0;
// CGFloat distanceY = bird.position.y - 160.0;
CGFloat velocityX = diff.x*-1/5;
CGFloat velocityY = diff.y*-1/5;
b2Vec2 birdVelocity = b2Vec2(velocityX,velocityY);
//make b2Body
CGFloat sphereX = arrow.position.x/PTM_RATIO;
CGFloat sphereY = arrow.position.y/PTM_RATIO;
b2BodyDef bodyDef;
bodyDef.position.Set(sphereX,sphereY);
bodyDef.type = b2_dynamicBody;
int num = 6;
b2Vec2 verts[] = {
b2Vec2(-56.5f / PTM_RATIO, 0.8f / PTM_RATIO),
b2Vec2(-51.9f / PTM_RATIO, -2.6f / PTM_RATIO),
b2Vec2(53.7f / PTM_RATIO, -2.2f / PTM_RATIO),
b2Vec2(58.7f / PTM_RATIO, 0.7f / PTM_RATIO),
b2Vec2(52.5f / PTM_RATIO, 3.0f / PTM_RATIO),
b2Vec2(-55.0f / PTM_RATIO, 1.2f / PTM_RATIO)
};
b2PolygonShape polyShape;
polyShape.Set(verts, num);
b2FixtureDef fixtureDef;
fixtureDef.shape= &polyShape;
fixtureDef.density=4;
fixtureDef.restitution=0.1;
fixtureDef.friction=0.5;
b2Body * physicsBird = world->CreateBody(&bodyDef);
physicsBird->CreateFixture(&fixtureDef);
physicsBird->GetUserData();
arrow.rotation=atan2f(arrow.position.x-startTouchPosition.x,arrow.position.y-startTouchPosition.y)*180/M_PI +90;
direction = arrow.rotation;
b2Vec2 force = b2Vec2(direction, 24.8);
physicsBird->ApplyLinearImpulse(force, physicsBird->GetWorldCenter());
physicsBird->SetLinearDamping(0.4);
}
but it is not working.Please help!
In my app, i used SMRotatory wheel for rotation. It is working fine as rotation regarding.
But i want to detect that rotation is clockwise or anti-clockwise??
//My code is as follow:
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGFloat radians = atan2f(container.transform.b, container.transform.a);
// NSLog(#"rad is %f", radians);
CGPoint pt = [touch locationInView:self];
float dx = pt.x - container.center.x;
float dy = pt.y - container.center.y;
float ang = atan2(dy,dx);
float angleDifference = deltaAngle - ang;
// NSLog(#"%f",angleDifference);
if (angleDifference>=0) {
//NSLog(#"positive");
[[NSUserDefaults standardUserDefaults]setValue:#"positive" forKey:#"CheckValue"];
}
else
{
// NSLog(#"negative");
[[NSUserDefaults standardUserDefaults]setValue:#"negative" forKey:#"CheckValue"];
}
container.transform = CGAffineTransformRotate(startTransform, -angleDifference);
bg.transform=CGAffineTransformRotate(startTransform, -angleDifference);
return YES;
}
Try something like this:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentTouch1=[touch locationInView:self.superview];
CGPoint currentTouch2=[touch previousLocationInView:self.superview];
CGFloat x = currentTouch2.x - currentTouch1.x;
CGFloat y = currentTouch2.y - currentTouch1.y;
CGFloat dist = sqrtf(x*x + y*y);
CGFloat cx = currentTouch1.x - self.superview.bounds.size.width / 2;
CGFloat cy = currentTouch2.y - self.superview.bounds.size.height / 2;
CGFloat cdist = sqrtf(cx*cx + cy*cy);
CGFloat angle = atan2(dist, cdist);
//NSLog(#"%f",angle);
CGFloat radians = atan2f(super.transform.b, super.transform.a);
mDegrees = radians * (180 / M_PI);
if (mDegrees < 0.0)
{
mDegrees+=360;
}
NSLog(#"Angle is = %f",angle); //positive is clockwise, negative is counterclockwise
CGAffineTransform transform = CGAffineTransformRotate(self.transform, angle);
self.transform = transform;
touch1 = currentTouch1;
touch2 = currentTouch2;
}
How can I rotate sprite around his center in cocos2d in ccTouchesMoved. I found some code, but this not what I need. I need to rotate sprite like photo in Photo gallery app.
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *allTouch = [touches allObjects];
if([allTouch count] > 1){
UITouch *touch = [allTouch objectAtIndex:0];
CGPoint point = [touch locationInView: [touch view]];
point = [[CCDirector sharedDirector] convertToGL:point];
float angleRadians = atanf((float)point.y / (float)point.x);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
imageFromPicker.rotation = imageFromPicker.rotation + cocosAngle;
}
}
probably is not the best way but im using this:
When I touch the sprite with two fingers, i use the arctan of the two points to rotate the sprite:
first i look for the direction with this function:
static inline int ccw(CGPoint p0, CGPoint p1, CGPoint p2)
{
int dx1, dx2, dy1, dy2;
dx1 = p1.x - p0.x; dy1 = p1.y - p0.y;
dx2 = p2.x - p0.x; dy2 = p2.y - p0.y;
int v1 = dx1 * dy2;
int v2 = dy1 * dx2;
if (v1 > v2)
return 1;
if (v1 < v2)
return -1;
if ((dx1*dx2 < 0) || (dy1*dy2 < 0))
return -1;
if ((dx1*dx1 + dy1*dy1) < (dx2*dx2 + dy2*dy2))
return 1;
return 0;
}
and after on the ccTouchesMoved method I do this:
if ([allTouches count] == 2) {
rotating=TRUE;
NSArray *twoTouches = [allTouches allObjects];
UITouch *first = [twoTouches objectAtIndex:0];
UITouch *second = [twoTouches objectAtIndex:1];
CGPoint a = [first previousLocationInView:[touch view]];
CGPoint b = [second previousLocationInView:[touch view]];
CGPoint c = [first locationInView:[touch view]];
int direction =ccw(b, a, c);
float pX= fabs(c.x-b.x);
float pY= fabs(c.y-b.y);
float rotation = atan2(pY, pX);
// rotTotal= (rotTotal+rotacion)/10;
if(direction<0)
YourSprite.rotation=(YourSprite.rotation-rotation);
else
YourSprite.rotation=(YourSprite.rotation+rotation);
}
it works for me, hope it works for you too