I want to store direction of my sprite as CGVector,
I have only 4 possible vectors:
CGVector up = CGVectorMake(0, 100);
CGVector down = CGVectorMake(0, -100);
CGVector left = CGVectorMake(-100, 0);
CGVector right = CGVectorMake(100, 0);
and I have 2 events:
-(void) turnLeft;
-(void) turnRight;
in case that now (my_sprite.direction == CGVector(0,100)) and event turnRight happened how can I get CGVector(100, 0)???
P.S. I don't want to many if or switch statements, because in the future should be much more vectors.
Since you want the ability to use more directions in the future, It would be better to just store angle and speed.
- (void)applyDirectionChange{
CGFloat x = sinf(self.angle)*self.speed;
CGFloat y = cosf(self.angle)*self.speed;
self.direction = CGVectorMake(x,y);
}
- (void)turnRight{
self.angle += 90*M_PI/180;
[self applyDirectionChange];
}
- (void)turnLeft{
self.angle -= 90*M_PI/180;
[self applyDirectionChange];
}
if you still want to keep your constant vectors, put them in an array in the right order and have an current direction index pointing to the right vector:
//declarations
NSUInteger currentDirectionIndex;
NSUInteger numDirections;
CGVector[4] directions;
//initialize them somewhere
currentDirectionIndex = 0;
numDirections = 4;
directions[0] = up;
directions[1] = right;
directions[2] = down;
directions[3] = left;
//in your methods
- (void)turnRight{
currentDirectionIndex++;
if(currentDirectionIndex>=numDirections)
currentDirectionIndex = 0;
self.direction = directions[currentDirectionIndex];
}
- (void)turnLeft{
currentDirectionIndex--;
if(currentDirectionIndex<0)
currentDirectionIndex = numDirections-1;
self.direction = directions[currentDirectionIndex];
}
Let's rearrange your vectors into this order:
CGVector up = CGVectorMake( 0, 100);
CGVector right = CGVectorMake( 100, 0);
CGVector down = CGVectorMake( 0, -100);
CGVector left = CGVectorMake(-100, 0);
Now we can see that rotating a vector 90 degrees clockwise is the same as swapping the coordinates and then negating the Y coordinate:
CGVector vectorByRotatingVectorClockwise(CGVector in) {
CGVector out;
out.dx = in.dy;
out.dy = -in.dx;
return out;
}
Related
I have to create a simulation in landscape which displays a sine function and a node which moves on the given path (the sine wave). So far I've created the sine wave with a simple sine wave with it's period (2*PI).
The question is how do I create the simulation of an infinite wave and the node moving up and down ?
My code so far is:
(CGMutablePathRef)sineWithAmplitude:(CGFloat)amp frequency:(CGFloat)freq
width:(CGFloat)width centered:(BOOL)centered
andNumPoints:(NSInteger)numPoints {
CGFloat offsetX = 0;
CGFloat offsetY = amp;
// Center the sinusoid within the shape node
if (centered) {
offsetX = -width/2.0;
offsetY = 0;
}
CGMutablePathRef path = CGPathCreateMutable();
// Move to the starting point
CGPathMoveToPoint(path, nil, offsetX, offsetY);
CGFloat xIncr = width / (numPoints-1);
// Construct the sinusoid
for (int i=1;i<numPoints;i++) {
CGFloat y = (amp * sin(2*M_PI*freq*i/(numPoints-1))*multiplier);
CGPathAddLineToPoint(path, nil, i*xIncr+offsetX, y+offsetY);
}
return path;
And the update method:
/* Called before each frame is rendered */
self.scaleMode = SKSceneScaleModeResizeFill;
// Create an SKShapeNode
SKShapeNode *node = [SKShapeNode node];
node.position = CGPointMake(x, y);
// Assign to the path attribute
node.path = [self sineWithAmplitude:20.0 frequency:frequency1 width:400.0 centered:YES andNumPoints:70];
//node.frame.size.height
node.strokeColor = [SKColor blackColor];
[self addChild:node];
x = x+400;
Until now I only display a wave, but the result should move right and look like the image.
The circle is the node that goes up and down while the wave simulates too. Any help will be greatly appreciated.
I have a sprite and I want to move it to random points forever. I have written this code, but I don't think it is efficient.
-(void)addBoss {
SKSpriteNode *boss = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
boss.position = CGPointMake(self.size.width + boss.size.width / 2.0, self.size.height / 2.0);
boss.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:boss.size];
boss.physicsBody.dynamic = YES;
boss.physicsBody.categoryBitMask = bossCategory;
boss.physicsBody.contactTestBitMask = bossContact;
boss.physicsBody.collisionBitMask = 0;
boss.zPosition = 1;
self.boss = boss;
self.bossHealth = bossHP;
CGPoint destination = CGPointMake(self.size.width - boss.size.width / 2.0, boss.position.y);
float time = length(boss.position, destination) / bossSpeed;
SKAction *move = [SKAction moveTo:destination duration:time];
[self addChild:boss];
[self.boss runAction:[SKAction sequence:#[move, [SKAction runBlock:^{
[self artificialIntelligence];
}]]]];
}
- (void)moveBoss {
float minimumX = self.size.width / 2.0 + self.boss.size.width / 2.0;
float maximumX = self.size.width - self.boss.size.width / 2.0;
float minimumY = self.boss.size.height / 2.0;
float maximumY = self.size.height - self.boss.size.height / 2.0;
int rangeX = maximumX - minimumX;
int rangeY = maximumY - minimumY;
float x = arc4random() % rangeX + minimumX;
float y = arc4random() % rangeY + minimumY;
CGPoint dest = CGPointMake(x, y);
float duration = length(self.boss.position, dest) / putinSpeed;
[self.boss runAction:[SKAction moveTo:dest duration:duration] completion:^{
[self moveBoss];
}];
}
-(void)artificialIntelligence {
[self moveBoss];
}
This code works fine, but I don't think that calling the move method recursively after movement finished is not the best solution.
What is the best way to solve this kind of problem?
This is a quick and dirty way to do what you asked:
-(void)moveCharacter {
SKNode *playerNode;
// get random position
CGPoint destination = [self randomPosition];
if(!CGPointEqualToPoint(playerNode.position, destination)) {
// check xPos
if(playerNode.position.x > destination.x) {
playerNode.position = CGPointMake(playerNode.position.x-1, playerNode.position.y);
}
if(playerNode.position.x < destination.x) {
playerNode.position = CGPointMake(playerNode.position.x+1, playerNode.position.y);
}
// check yPos
if(playerNode.position.y > destination.y) {
playerNode.position = CGPointMake(playerNode.position.x, playerNode.position.y-1);
}
if(playerNode.position.y < destination.y) {
playerNode.position = CGPointMake(playerNode.position.x, playerNode.position.y+1);
}
} else {
destinationReached = YES;
}
}
-(CGPoint)randomPosition {
CGRect screenRect = [[UIScreen mainScreen] bounds];
int xPos = arc4random() % (int)screenRect.size.width;
int yPos = arc4random() % (int)screenRect.size.height;
return CGPointMake(xPos, yPos);
}
There are many different variations on how to move your character. You could for example divide the difference of x,y into a fixed number of movements as to have the player move in a smooth line from point A to B. If you are physics to move your player, you would have to use CGVector instead of directly changing his x,y position.
I'm trying to set my camera node's rotation, and the value's are there, but it never changes from 0,0,0,0...
initialize the player node (left out other settings, the node has no geometry, but its physics body does have geometry)
playerNode = [SCNNode node];
and I set it's position and add it to the scene's root node...
- (void)viewDidLoad
{
[super viewDidLoad];
// create a new scene
SCNScene *scene = [SCNScene scene];
scene.physicsWorld.gravity = SCNVector3Make(0, -9, 0);
scene.physicsWorld.timeStep = 1.0/360;
// add world node
worldNode = [SCNNode node];
worldNode.name = #"world";
[scene.rootNode addChildNode:worldNode];
// add terrain
.../* terrain stuff */
// add player node
playerNode = [SCNNode node];
playerNode.name = #"player";
playerNode.position = SCNVector3Make(0, 0, 0);
playerNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeDynamic shape:[SCNPhysicsShape shapeWithGeometry:[SCNCylinder cylinderWithRadius:0.2 height:1] options:nil]];
playerNode.physicsBody.angularDamping = 0.9999;
playerNode.physicsBody.damping = 0.9999;
playerNode.physicsBody.rollingFriction = 0;
playerNode.physicsBody.friction = 0;
playerNode.physicsBody.restitution = 0;
playerNode.physicsBody.velocityFactor = SCNVector3Make(1, 0, 1);
playerNode.physicsBody.categoryBitMask = playerCategory;
[scene.rootNode addChildNode:playerNode];
// create and add a camera to the scene
SCNNode *cameraNode = [SCNNode node];
[playerNode addChildNode:cameraNode];
cameraNode.camera = [SCNCamera camera];
cameraNode.camera.xFov = 53;
cameraNode.camera.zNear = 0.01;
cameraNode.camera.zFar = 5000;
// place the camera
cameraNode.position = SCNVector3Make(0, 0, 0);
.../* rest of view did load */
}
and then trying to set the rotation:
-(void)lookGestureRecognized:(UIPanGestureRecognizer *)gesture {
SCNView *scnView = (SCNView *)self.view;
CGPoint translation = [gesture translationInView:self.view];
NSLog(#"lookGestureRecognized: translation x %g y %g", translation.x, translation.y);
CGFloat hAngle = acos(((float)translation.x / 200) - (float)M_PI_2);
CGFloat vAngle = acos(((float)translation.y / 200) - (float)M_PI_2);
// rotate hero
[playerNode.physicsBody applyTorque:SCNVector4Make(0, 1, 0, hAngle) impulse:YES];
// tilt camera
[SCNTransaction setAnimationDuration:0.0];
elevation = MAX((float)-M_PI_4, MIN((float)M_PI_4, elevation + vAngle));
NSLog(#"elevation: %g", elevation);
SCNVector4 cameraRotation = SCNVector4Make(1, 0, 0, elevation);
cameraNode.rotation = cameraRotation;
// cameraNode.transform = SCNMatrix4Rotate(cameraNode.transform, 1, 0, 0, elevation); // tried this, didn't work either
NSLog(#"cameraNode.rotation = x %g y %g z %g, w %g", cameraNode.rotation.x, cameraNode.rotation.y, cameraNode.rotation.z, cameraNode.rotation.w);
// reset translation
[gesture setTranslation:CGPointZero inView:self.view];
}
the elevation is being calculated correctly, but trying to set it to the node's rotation fails... the log always says 0,0,0,0...
NSLog sample output:
2015-02-17 14:37:11.732 usingGestureRecognizer.01[96111:289778] lookGestureRecognized: translation x 0 y 0.5
2015-02-17 14:37:11.733 usingGestureRecognizer.01[96111:289778] elevation: -0.785398
2015-02-17 14:37:11.733 usingGestureRecognizer.01[96111:289778] cameraNode.rotation = x 0 y 0 z 0, w 0
Any ideas?
(Side note, mimic'ing the code I found from an example, translated it from swift to obj-c, and the sample code works perfectly)
The simple answer? I'm a newb.
The actual answer? Be very careful when copying code from a tutorial... I had declared a private variable SCCNode cameraNode, and was trying to change this node's rotation, however, the actual camera node doing the camera work was declared as a private method variable in viewDidLoad...
So, again, thank you very much for your help guys, this site is invaluable to me, I have learned so much, but I'm personally 0 for 2 asking questions, both of mine have just been MY oversights. Thanks again, sincerely
I am making a simple game where my sprite is moved through the use of accelerometer. My sprite is a circle with eyes on it. I want to rotate the sprite in a way that it is always facing towards the direction it is moving. The below code successfully moves the sprite using the accelerometer but I am having trouble rotating the sprite to face the direction of the movement.
-(void) moveHeroFromAccelaration
{
GLKVector3 raw = GLKVector3Make(_motionManager.accelerometerData.acceleration.x,_motionManager.accelerometerData.acceleration.y,_motionManager.accelerometerData.acceleration.z);
if(GLKVector3AllEqualToScalar(raw, 0))
return;
static GLKVector3 ax, ay, az;
ay = GLKVector3Make(0.63f, 0.0f, -0.92f);
az = GLKVector3Make(0.0f, 1.0f, 0.0f);
ax = GLKVector3Normalize(GLKVector3CrossProduct(az, ay));
CGPoint accel2D = CGPointZero;
accel2D.x = GLKVector3DotProduct(raw, az); accel2D.y = GLKVector3DotProduct(raw, ax);
accel2D = CGPointNormalize(accel2D);
static const float steerDeadZone = 0.18;
if (fabsf(accel2D.x) < steerDeadZone) accel2D.x = 0; if (fabsf(accel2D.y) < steerDeadZone) accel2D.y = 0;
float maxAccelerationPerSecond = 160.0f;
_hero.physicsBody.velocity =
CGVectorMake(accel2D.x * maxAccelerationPerSecond, accel2D.y * maxAccelerationPerSecond);
//1
if (accel2D.x!=0 || accel2D.y!=0) {
//2
float orientationFromVelocity = CGPointToAngle(CGPointMake(_hero.physicsBody.velocity.dx,
_hero.physicsBody.velocity.dy));
float angleDelta = 0.0f;
//3
if (fabsf(orientationFromVelocity-_hero.zRotation)>1) { //prevent wild rotation
angleDelta = (orientationFromVelocity-_hero.zRotation);
} else {
//blend rotation
const float blendFactor = 0.25f; angleDelta =
(orientationFromVelocity - _hero.zRotation) * blendFactor; angleDelta =
ScalarShortestAngleBetween(_hero.zRotation, _hero.zRotation + angleDelta);
}
_hero.zRotation += angleDelta;
// face the hero in the moving direction
[_hero runAction:[SKAction rotateToAngle:orientationFromVelocity duration:0.25 shortestUnitArc:YES]]; <---- THIS IS WHERE I NEED HELP!
}
}
So I have this app Im working on where you can roll the ball around the screen by tilting the device around(accelerometer). How can I alter the code below so that I don't have to hold the phone flat and have that as my neutral balance point. What I want is that whatever tilt you have with the device at the moment when the app loads, that will be the neural balance point. From that current angle your holding the device that is the neutral point. Neutral balance point meaning the point where the ball is pretty much still. Hope thats clear as to what I would like. Also the app is landscapeRight only.
note The code below works 100 percent well just like it need it to work for my app.Just I need to hold the phone flat to roll the ball around...
CGRect screenRect;
CGFloat screenHeight;
CGFloat screenWidth;
double currentMaxAccelX;
double currentMaxAccelY;
#property (strong, nonatomic) CMMotionManager *motionManager;
-(id)initWithSize:(CGSize)size {
//init several sizes used in all scene
screenRect = [[UIScreen mainScreen] bounds];
screenHeight = screenRect.size.height;
screenWidth = screenRect.size.width;
if (self = [super initWithSize:size]) {
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.accelerometerUpdateInterval = .2;
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
[self outputAccelertionData:accelerometerData.acceleration];
if(error)
{
NSLog(#"%#", error);
}
}];
}
return self;
}
-(void)outputAccelertionData:(CMAcceleration)acceleration{
currentMaxAccelX = 0;
currentMaxAccelY = 0;
if(fabs(acceleration.x) > fabs(currentMaxAccelX))
{
currentMaxAccelY = acceleration.x;
}
if(fabs(acceleration.y) > fabs(currentMaxAccelY))
{
currentMaxAccelX = acceleration.y;
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
//set min and max bounderies
float maxY = screenHeight - (self.ball.size.width/2);
float minY = 0 + (self.ball.size.width/2);
float maxX = screenWidth - (self.ball.size.height/2);
float minX = 0 + (self.ball.size.height/2);
float newY = 0;
float newX = 0;
//left and right tilt
if(currentMaxAccelX > 0.05){
newX = currentMaxAccelX * -10;
}
else if(currentMaxAccelX < -0.05){
newX = currentMaxAccelX*-10;
}
else{
newX = currentMaxAccelX*-10;
}
//up and down tilt
newY = currentMaxAccelY *10;
newX = MIN(MAX(newX+self.ball.position.x,minY),maxY);
newY = MIN(MAX(newY+self.ball.position.y,minX),maxX);
self.ball.position = CGPointMake(newX, newY);
}
First, Larme's comment gives the correct answer for determining the starting point.
However, if you are trying to determine device tilt (attitude), you want to use the gyroscope, not the accelerometer. The accelerometer tells how fast the device is moving in each direction. That's useful for determining if the user is quickly moving or shaking the device but doesn't help you at all determine whether the device is being tilted. The gyroscope provides the device's current attitude and the rate of rotation.
Since it sounds like you are trying to implement a ball that will "roll" around a table as the user tilts the device, you probably want to get the attitude. To get the attitude, use startDeviceMotionUpdatesToQueue:withHandler:. Then you can use the attitude property of the CMDeviceMotion object to find out how the device is oriented on each axis.
As it was mentioned, we need to catch an initial device position (accelerometer value) and use it as zero reference. We catch reference value once when game starts and subtract this value from every next accelerometer update.
static const double kSensivity = 1000;
#interface ViewController ()
{
CMMotionManager *_motionManager;
double _vx, _vy; // ball velocity
CMAcceleration _referenceAcc; // zero reference
NSTimeInterval _lastUpdateTimeInterval; // see update: method
}
Initially, ball is motionless (velocities = 0). Zero reference is invalid. I set significant value in CMAcceleration to mark it as invalid:
_referenceAcc.x = DBL_MAX;
Accelerometer updates. As the app uses landscape right mode only we map y-acceleration to x-velocity, and x-acceleration to y-velocity. accelerometerUpdateInterval factor is required to make velocity values independent of update rate. We use negative sensitivity value for x-acceleration, because direction of accelerometer X axis is opposite to landscape right orientation.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_vx = 0;
_vy = 0;
_referenceAcc.x = DBL_MAX;
_motionManager = [CMMotionManager new];
_motionManager.accelerometerUpdateInterval = 0.1;
[_motionManager
startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
CMAcceleration acc = accelerometerData.acceleration;
if (_referenceAcc.x == DBL_MAX) {
_referenceAcc = acc;
_referenceAcc.x *= -1;
_referenceAcc.y *= -1;
}
_vy += kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
_vx += -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;
}];
self.ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
self.ball.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:self.ball];
}
return self;
}
Your update: method does not respect currentTime value. Intervals between update calls can be different. It would be better to update distance according to time interval.
- (void)update:(NSTimeInterval)currentTime {
CFTimeInterval timeSinceLast = currentTime - _lastUpdateTimeInterval;
_lastUpdateTimeInterval = currentTime;
CGSize parentSize = self.size;
CGSize size = self.ball.frame.size;
CGPoint pos = self.ball.position;
pos.x += _vx * timeSinceLast;
pos.y += _vy * timeSinceLast;
// check bounds, reset velocity if collided
if (pos.x < size.width/2) {
pos.x = size.width/2;
_vx = 0;
}
else if (pos.x > parentSize.width-size.width/2) {
pos.x = parentSize.width-size.width/2;
_vx = 0;
}
if (pos.y < size.height/2) {
pos.y = size.height/2;
_vy = 0;
}
else if (pos.y > parentSize.height-size.height/2) {
pos.y = parentSize.height-size.height/2;
_vy = 0;
}
self.ball.position = pos;
}
EDIT: alternative way
By the way, I found an alternative way solve it. If you use SpriteKit, it is possible to configure gravity of physics world in response to accelerometer changes. In that case there's no need to move a ball in update: method.
We need to add physics body to a ball sprite and make it dynamic:
self.physicsWorld.gravity = CGVectorMake(0, 0); // initial gravity
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.ball.size.width/2];
self.ball.physicsBody.dynamic = YES;
[self addChild:self.ball];
And set updated gravity in accelerometer handler:
// set zero reference acceleration
...
_vy = kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
_vx = -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;
self.physicsWorld.gravity = CGVectorMake(_vx, _vy);
Also we need to set physical bounds of the screen in order to limit ball movement.
Why don't you just take the numbers, at the point of start up, as a baseline and save them as a class property. Any further readings you have you can simply add/subtract the current numbers with your baseline. Unless I am wrong, that should give you the desired results.