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.
Related
Good morning. I have a problem with Spritekit and the Accelerometer of an iPhone. I want to move a Node by tilting the device left or right in portrait mode. Exactly like in the Doodle Jump way.
Now i have this code:
import <CoreMotion/CoreMotion.h>
//Then at the implementation:
_motionManager = [[CMMotionManager alloc] init]; if ([_motionManager isAccelerometerAvailable] == YES) {
[_motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *data, NSError *error) {
float destX, destY; float currentX = _node.position.x; float currentY = _node.position.y; BOOL shouldMove = NO;
if(data.acceleration.x < -0.25) {
destX = currentX + (data.acceleration.x * kPlayerSpeed);
destY = currentY;
shouldMove = YES; }
else if (data.acceleration.x > 0.25) {
destX = currentX + (data.acceleration.x * kPlayerSpeed);
destY = currentY;
shouldMove = YES;
}
if((destX < 0)||(destX > self.frame.size.width)){shouldMove = NO;}
if(shouldMove) {
SKAction *action = [SKAction moveTo:CGPointMake(destX, destY) duration:0.2];
action.timingMode = SKActionTimingEaseOut;
[_node runAction:action];
}
}];
}
This code works but it`s not good enough. The Node makes huge movements even when i just tap on the device and the overall experience is not very statisfying.
I would like a code that moves the node just by the tilt of the device and not by the x-axis acceleration data. Something like if i tilt the device from 0 to 15 degree nothing happens and after that the node accelerates proportionally in dependency of the devices tilt. And the node should nod make sudden movements but fluent ones. Just like in the Doodle Jump way. I want that exact same behavior. Thanks.
I am trying to make an iPhone game and I am trying to add random images in different position.
Here is what I want to do
There is 6 different color oval nodes (enemy)
When the game starts I want there to be 5 enemy nodes.
When the player node contacts enemy node, enemy node will disappear and then right away another
enemy node will be added in different location.
But some times some nodes appear in same location so it looks like there is 4 nodes instead of 5.
If there is a node already in a specific location how can I not add another node there but some other location?
Below I added a part of the code I wrote.
It might be something very easy but I am new to programming and I could not figure that out.
Thank you,
-(void) addWaterBall {
for (int i = 0; i < 5; i++) {
NSUInteger randomWaterBall = [Util randomWithMin:0 max:8];
WaterBall *waterBall = [WaterBall waterBallOfType:randomWaterBall];
float y = self.frame.size.height - ((((self.frame.size.height/2)-10)/10) * [Util randomWithMin:1 max:10]);
float x = (self.frame.size.width/10) * [Util randomWithMin:1 max:10];
waterBall.position = CGPointMake(x, y);
waterBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:waterBall.size.width/2];
waterBall.physicsBody.dynamic = YES;
waterBall.physicsBody.affectedByGravity = NO;
waterBall.physicsBody.categoryBitMask = waterBallCategory;
waterBall.physicsBody.contactTestBitMask = sharkCategory ;
//waterBall.physicsBody.collisionBitMask = ;
[self addChild:waterBall];
}
}
What I would do is stick in a [self enumerateChildNodesWithName…….] after generating the random co-ordinates and compare the random x and y co-ordinates with those on each enumerated node, if they are the same or too close then generate new random co-ordinates. This is probably best done in a while loop.
-(void)addWaterBall
{
NSUInteger randomWaterBall = [Util randomWithMin:0 max:8];
WaterBall *waterBall = [WaterBall waterBallOfType:randomWaterBall];
waterBall.name = #"WaterBall";
[self enumerateChildNodesWithName:#"WaterBall" usingBlock:^(SKNode *node, BOOL *stop) {
float y = self.frame.size.height - ((((self.frame.size.height/2)-10)/10) * [Util randomWithMin:1 max:10]);
float x = (self.frame.size.width/10) * [Util randomWithMin:1 max:10];
node.position = CGPointMake(x, y);
node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:waterBall.size.width/2];
node.physicsBody.dynamic = YES;
node.physicsBody.affectedByGravity = NO;
node.physicsBody.categoryBitMask = waterBallCategory;
node.physicsBody.contactTestBitMask = sharkCategory ;
//waterBall.physicsBody.collisionBitMask = ;
[self addChild:waterBall];
}];
}
I'm using CMDeviceMotion and the attitude's quaternion to obtain the pitch and yaw values, which are then applied to a CC3Camera in a Cocos3D scene to rotate the camera around.
#define RadiansToDegrees(x) ((180 / M_PI) * x)
- (void)initializeScene
{
//...
CC3Camera *cam = [CC3Camera nodeWithName:#"Camera"];
cam.location = cc3v(0, 10, 0.0001);
cam.targetLocation = cc3v(0, 0, 0);
_cameraBoom = [CC3Node nodeWithName:#"CameraBoom"];
_cameraBoom.location = cc3v(0, 0, 0);
[_cameraBoom addChild:cam];
[self addChild:_cameraBoom];
[self setActiveCamera:cam];
_cameraBoom.rotation = cc3v(0, 90, 0);
//...
_motionManager = [[CMMotionManager alloc] init];
_referenceAttitude = nil;
_initialCameraRotation = _cameraBoom.rotation;
[self enableMotion];
}
- (void)enableMotion
{
CMDeviceMotion *deviceMotion = _motionManager.deviceMotion;
_referenceAttitude = deviceMotion.attitude;
_initialCameraRotation = _cameraBoom.rotation;
[_motionManager startDeviceMotionUpdates];
if (!_gyroTimer) {
_gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1 / 30.0
target:self
selector:#selector(doGyroUpdate)
userInfo:nil
repeats:YES];
}
}
- (void)doGyroUpdate
{
CMDeviceMotion *deviceMotion = _motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
if (_referenceAttitude != nil) {
[attitude multiplyByInverseOfAttitude:_referenceAttitude];
}
CMQuaternion quat = attitude.quaternion;
double pitch = RadiansToDegrees(atan2(2 * (quat.x * quat.w + quat.y * quat.z), 1 - 2 * (quat.x * quat.x + quat.z * quat.z)));
double yaw = RadiansToDegrees(asin(2 * (quat.x * quat.y + quat.w * quat.z)));
_cameraBoom.rotation = CC3VectorAdd(_initialCameraRotation, cc3v(pitch, yaw, 0));
}
The pitch is in range [-π, π]. When the device is faced up the pitch = 0 and it becomes π/2 as I take the device from the table and point it to take a picture (portrait mode). The [-π, π] range enables me to rotate the device 360°. When faced down (i.e. device is upside down) the pitch value is π.
The yaw range is only [-π/2, π/2]. It starts at 0 and goes to π/2 when I rotate the device to the left. But if I rotate it beyond π/2, the yaw value starts to decrease.
Can I get the yaw value in range [-π, π], just like the pitch? It would be more useful to be able to rotate the camera sideways by 180° (to the left and to the right, to have a full 360° view) instead of flipping the device vertically to look behind with the camera.
Here's how I did it eventually with the built-in functions:
- (void)doGyroUpdate
{
CMDeviceMotion *deviceMotion = _motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
if (_referenceAttitude != nil) {
[attitude multiplyByInverseOfAttitude:_referenceAttitude];
}
CMQuaternion quat = attitude.quaternion;
CC3Quaternion cc3Quat = CC3QuaternionMake(quat.x, quat.y, quat.z, quat.w);
CC3Vector rot = CC3RotationFromQuaternion(cc3Quat);
_cameraBoom.rotation = cc3v(-rot.x, -rot.z, rot.y);
}
The result:
Rotating the camera like so enables you to look around at the skybox as if you would normally look at the world through the device's back camera. My CC3Camera object is inside a sphere with a HDRi image mapped on to it, on the inside (see this post).
To smoothly rotate the camera:
[_cameraBoom runAction:[CC3ActionRotateTo actionWithDuration:0.15 rotateTo:cc3v(-rot.x, -rot.z, rot.y)]];
Hope this helps someone else too.
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;
}
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);