Accelerometer and Sprite Face Direction - ios

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!
}
}

Related

Position of SCNode Object infront of Camera With ARKit

I am trying to add Bullet in my Scene with ARKit.
Here is what i have done to add bullet in my Scene
{
SCNNode* systemNode = [SCNNode new];
SCNSphere *sphere = [SCNSphere sphereWithRadius:0.01];
systemNode.geometry = sphere;
systemNode.renderingOrder = 1;
SCNPhysicsShape *shape = [SCNPhysicsShape shapeWithGeometry:sphere options:nil];
systemNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeDynamic shape:shape];
systemNode.physicsBody.mass = 0.05;
systemNode.physicsBody.friction = 0.01;
systemNode.physicsBody.affectedByGravity = NO;
SCNScene *modelScene = [SCNScene sceneNamed:#"Cartridge45.scn" inDirectory:#"art.scnassets/models" options:nil];
Bullet *bulletNode = [Bullet new];
bulletNode = (Bullet *) [modelScene.rootNode childNodeWithName:#"cartridge" recursively:YES];
bulletNode.scale = SCNVector3Make(1.0, 1.0, 1.0);
bulletNode.renderingOrder = 1;
Bullet *bulletNode1 = [Bullet new];
bulletNode1 = (Bullet *) [modelScene.rootNode childNodeWithName:#"cartridge_1" recursively:YES];
bulletNode1.scale = SCNVector3Make(1.0, 1.0, 1.0);
bulletNode1.renderingOrder = 1;
SCNVector3 min, max;
[bulletNode getBoundingBoxMin:&min max:&max];
CGFloat collisionCapsuleRadius = 0.01;
CGFloat collisionCapsuleHeight = 0.1;
NSLog(#"El Bouncing Box. El min es collisionCapsuleRadius:%f collisionCapsuleHeight:%f", collisionCapsuleRadius, collisionCapsuleHeight);
SCNNode *bColNode = [SCNNode node];
NSString *nameCube = [NSString stringWithFormat:#"BulletB%d", rand()];
bColNode.name =nameCube;
bColNode.position = SCNVector3Make(0.0, 0.0, 1.2);// a bit too high to not hit the floor
bColNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeKinematic shape:[SCNPhysicsShape shapeWithGeometry:[SCNCapsule capsuleWithCapRadius:collisionCapsuleRadius height:collisionCapsuleHeight] options:nil]];
bColNode.physicsBody.categoryBitMask = CollisionCategoryBullet;
bColNode.physicsBody.contactTestBitMask = CollisionCategoryInvader;
[systemNode addChildNode:bColNode];
ARFrame *frame = self.sceneView.session.currentFrame;
SCNVector3 direction;
SCNVector3 position;
if (frame != nil) {
SCNMatrix4 mat = SCNMatrix4FromMat4(frame.camera.transform);
direction = SCNVector3Make(mat.m41, mat.m42, mat.m43);
position = SCNVector3Make(mat.m31*-1, mat.m32*-1, mat.m33*-1);
}
else {
direction = SCNVector3Make(0,0,-0.11);
position = SCNVector3Make(0,0,-0.1);
}
systemNode.position = SCNVector3Make(0,0,0);
bulletNode.position = position;
bulletNode1.position = position;
systemNode.name=#"bullet";
[systemNode addChildNode:bulletNode];
[systemNode addChildNode:bulletNode1];
SCNMatrix4 transform = SCNMatrix4FromMat4(frame.camera.transform);
SCNVector4 force = SCNVector4Make(0, 0,-5, 0);
SCNVector3 rotatedForce = [self to3:[self multiplyVector:transform :force]];
[systemNode.physicsBody applyForce:rotatedForce impulse:YES];
[self.sceneView.scene.rootNode addChildNode:systemNode];
}
The bullet is getting added at same direction disregards of view of camera in real world.

figuring out the conversions from UIKit coordinates to SKScene coordinates

I am new to SpriteKit but not to iOS. I'm having a great deal of difficulty figuring out the conversions from UIKit coordinates to SKScene coordinates. In addition, their relations to the units used for the SpriteKit physics engine evades me.
Here's what I'm trying to do: I am launching a sprite with an initial velocity in both x and y directions. The sprite should start near the screen's bottom left corner (portrait mode) and leave near the bottom right corner. Another sprite should appear somewhere on the previous sprite's trajectory. This sprite is static.
The blue frame is the screen boundary, the black curve is the first sprite's trajectory and the red circle is the static sprite.
Now using the screen as 640 x 960. In my SKScene's didMoveToView: method I set the gravity as such:
self.physicsWorld.gravity = CGVectorMake(0.0f, -2.0f);
Then I make the necessary calculations for velocity and position:
"t" is time from left corner to right;
"y" is the maximum height of the trajectory;
"a" is the acceleration;
"vx" is velocity in the x direction;
"vy" is velocity in the y direction;
and
"Tstatic" is the time at which the dynamic sprite will be at the static sprite's position;
"Xstatic" is the static sprite's x-position;
"Ystatic" is the static sprite's y-position;
-(void)addSpritePair {
float vx, vy, t, Tstatic, a, Xstatic, Ystatic, y;
a = -2.0;
t = 5.0;
y = 800.0;
vx = 640.0/t;
vy = (y/t) - (1/8)*a*t;
Tstatic = 1.0;
Xstatic = vx*Tstatic;
Ystatic = vy*Tmark + 0.5*a*Tstatic*Tstatic;
[self spriteWithVel:CGVectorMake(vx, vy) andPoint:CGPointMake(xmark, ymark)];
}
-(void)spriteWithVel:(CGVector)velocity andPoint:(CGPoint)point{
SKSpriteNode *staticSprite = [SKSpriteNode spriteNodeWithImageNamed:#"ball1"];
staticSprite.anchorPoint = CGPointMake(.5, .5);
staticSprite.position = point;
staticSprite.name = #"markerNode";
staticSprite.zPosition = 1.0;
SKSpriteNode *dynamicSprite = [SKSpriteNode spriteNodeWithImageNamed:#"ball2"];
dynamicSprite.anchorPoint = CGPointMake(.5, .5);
dynamicSprite.position = CGPointMake(1 ,1);
dynamicSprite.zPosition = 2.0;
dynamicSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:hoop.size.width/2];
dynamicSprite.physicsBody.allowsRotation = NO;
dynamicSprite.physicsBody.velocity = velocity;
dynamicSprite.physicsBody.affectedByGravity = YES;
dynamicSprite.physicsBody.dynamic = YES;
dynamicSprite.physicsBody.mass = 1.0f;
dynamicSprite.markerPoint = point;
dynamicSprite.physicsBody.restitution = 1.0f;
dynamicSprite.physicsBody.friction = 0.0f;
dynamicSprite.physicsBody.angularDamping = 0.0f;
dynamicSprite.physicsBody.linearDamping = 0.0f;
[self addChild:hoop];
[self addChild:marker];
}
When I execute the code, the following happens:
Can someone please help me out?

How to rotate a CGVector by a right angle?

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;
}

Recognize current device position as flat

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.

Box2d object throwing smoother and on same velocity

I have an box2d object which i am throwing from top to bottom and i have set its speed constant but when i run it , that object has different speed sometimes and how can i make this object more smoother.
Following are some methods to show how i have created box2d world and box2d body object.
#pragma -mark Box2D World
-(void)createWorld
{
// Define the gravity vector.
b2Vec2 b_gravity;
b_gravity.Set(0.0f, -9.8f);
// Do we want to let bodies sleep?
// This will speed up the physics simulation
bool doSleep = true;
// Construct a world object, which will hold and simulate the rigid bodies.
world = new b2World(b_gravity);
world->SetAllowSleeping(doSleep);
world->SetContinuousPhysics(true);
}
-(void) createWeb
{
freeBodySprite = [CCSprite spriteWithFile:#"web1.png"];//web_ani_6_1
//freeBodySprite.position = ccp(100, 300);
[self addChild:freeBodySprite z:2 tag:6];
CGPoint startPos = CGPointMake(100, 320/1.25);
bodyDef.type = b2_staticBody;
bodyDef.position = [self toMeters:startPos];
bodyDef.userData = freeBodySprite;
float radiusInMeters = ((freeBodySprite.contentSize.width * freeBodySprite.scale/PTM_RATIO) * 0.5f);
shape.m_radius = radiusInMeters;
fixtureDef.shape = &shape;
fixtureDef.density = 0.07f;
fixtureDef.friction = 0.1f;
fixtureDef.restitution = 0.1f;
circularObstacleBody = world->CreateBody(&bodyDef);
stoneFixture = circularObstacleBody->CreateFixture(&fixtureDef);
freeBody = circularObstacleBody;
}
-(b2Vec2) toMeters:(CGPoint)point
{
return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO);
}
-(b2Body *) getBodyAtLocation:(b2Vec2) aLocation {
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
b2Fixture* bodyFixture = b->GetFixtureList();
if (bodyFixture->TestPoint(aLocation)){
return b;
}
}
return NULL;
}
-(void) tick: (ccTime) dt
{
//It is recommended that a fixed time step is used with Box2D for stability
//of the simulation, however, we are using a variable time step here.
//You need to make an informed choice, the following URL is useful
//http://gafferongames.com/game-physics/fix-your-timestep/
int32 velocityIterations = 8;
int32 positionIterations = 3;
// Instruct the world to perform a single step of simulation. It is
// generally best to keep the time step and iterations fixed.
world->Step(dt, velocityIterations, positionIterations);
//Iterate over the bodies in the physics world
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL) {
//Synchronize the AtlasSprites position and rotation with the corresponding body
CCSprite *myActor = (CCSprite*)b->GetUserData();
myActor.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
This is my touch Event where i am getting angle and speed to throw .
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//get the location of the end point of the swipe
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
//CCLOG(#"Start -> %0.f || End -> %0.f",startPoint.x,location.x);
if (freeBody) {
//[self calcAngleAndRotateObjectStartingAtPoint:startPoint endingAtPoint:location];
self.isTouchEnabled = NO;
freeBody->SetType(b2_dynamicBody);
//this is the maximum force that can be applied
const CGFloat maxForce = 20;
//get the rotation b/w the start point and the end point
CGFloat rotAngle = atan2f(location.y - startPoint.y,location.x - startPoint.x);
//the distance of the swipe if the force
CGFloat distance = ccpDistance(startPoint, location) * 0.5;
//if (distance>maxForce)
distance = maxForce;
//else
// distance = 10;
//apply force
freeBody->ApplyForce(b2Vec2(cosf(rotAngle) * distance, sinf(rotAngle) * distance), freeBody->GetPosition());
//lose the weak reference to the body for next time usage.
freeBody = nil;
}
}
This is code i am using to throw , but sometimes its speed is faster and some time slower , and i have set maxForce = 20 for constant speed.
As the comment above world->Step() dictates, you should use fixed dt. Verify that dt is fixed and world->Step() is being called at regular interval.
Finnally i have solved this problem. i changed ApplyForce with SetLinearVelocity..
here is the code.
float spd = 10;
b2Vec2 velocity = spd*b2Vec2(cos(rotAngle), sin(rotAngle));
freeBody->SetLinearVelocity(velocity);

Resources