Correct Way to use Accelerometer - swift

Im using the accelerometer as the steering mechanism for my game. It works fine usually but occasionally it will act very odd. Randomly there is suddenly a large amount of input delay and every rotation I make isn't registered for a while. In some cases there might be a lot of delay in between commands entered right after each other leading to my character drifting in one direction for far too long. Is this due to game lag or is my code at fault? My code below.
actionMoveLeft = SKAction.moveBy(x: -3, y: 0, duration: 0.1)
actionMoveRight = SKAction.moveBy(x: 3, y: 0, duration: 0.1)
self.addChild(ship)
if motionManager.isAccelerometerAvailable == true {
motionManager.startAccelerometerUpdates(to: OperationQueue.current!, withHandler:{
data, error in
if (data!.acceleration.y) < -0.05 {
self.ship.run(self.actionMoveLeft)
}
else if data!.acceleration.y > 0.05 {
self.ship.run(self.actionMoveRight)
}
})
}

It's not guaranteed that the accelerometer will give you updates with regular intervals of 0.1 seconds (assuming that the deviceMotionUpdateInterval property of your CMMotionManager instance is set to 0.1). SKActions work fine when every action is executed at a discrete interval of time. But since the accelerometer gives you irregular interval updates, you may end up executing more actions at the same time.
An easy fix for that would be to remove the previous action every time:
if (data!.acceleration.y) < -0.05 {
self.ship.removeAction(forKey: "Move")
self.ship.run(self.actionMoveLeft, withKey: "Move")
}
But I still not recommend to use this approach, because the movement still doesn't look smooth, but it looks like your ship is moving jerkily. I suggest to use a SKPhysicsBody and directly manipulate its velocity property. Something like this:
// Inside the update method, assuming you have saved the acceleration vector
// and you have a deltaTime variable that holds the difference of time
// elapsed from the previous update
self.ship.physicsBody.velocity = CGVector(dx: self.ship.physicsBody.velocity.dx + CGFloat(acceleration.x * deltaTime), dy: self.ship.physicsBody.velocity.dy + CGFloat(acceleration.y * deltaTime))
If you don't want to use a physics body because you're just handling physics using your custom functions, then I suggest to compute the position of the ship manually at every frame.

Related

Gravity value in SpriteKit game scene

I'm trying to create a game using Apple's SpriteKit game engine.
While implementing some physics-based calculations in the game, I noticed that the calculated results differ from what effectively then happens to objects.
Example: calculating a body's trajectory through projectile motion's equations causes the body to actually fall down much sooner/quicker than what calculated.
How can I make the physics engine match the real-world physics laws when calculating something gravity-related?
I think I know what's going on with the sample code you have supplied on GitHub, which I'll reproduce here as questions on SO should contain the code:
//
// GameScene.swift
// SpriteKitGravitySample
//
// Created by Emilio Schepis on 17/01/2020.
// Copyright © 2020 Emilio Schepis. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var subject: SKNode!
override func didMove(to view: SKView) {
super.didMove(to: view)
// World setup (no friction, default gravity)
// Note that this would work with any gravity set to the scene.
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsBody?.friction = 0
subject = SKShapeNode(circleOfRadius: 10)
subject.position = CGPoint(x: frame.midX, y: 30)
subject.physicsBody = SKPhysicsBody(circleOfRadius: 10)
subject.physicsBody?.allowsRotation = false
// Free falling body (no damping)
subject.physicsBody?.linearDamping = 0
subject.physicsBody?.angularDamping = 0
addChild(subject)
// Set an arbitrary velocity to the body
subject.physicsBody?.velocity = CGVector(dx: 30, dy: 700)
// Inaccurate prediction of position over time
for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
let inaccuratePosition = SKShapeNode(circleOfRadius: 2)
inaccuratePosition.strokeColor = .red
// These lines use the projectile motion equations as-is.
// https://en.wikipedia.org/wiki/Projectile_motion#Displacement
let v = subject.physicsBody?.velocity ?? .zero
let x = v.dx * time
let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * pow(time, 2)
inaccuratePosition.position = CGPoint(x: x + subject.position.x,
y: y + subject.position.y)
addChild(inaccuratePosition)
}
// Actual prediction of position over time
for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
let accuratePosition = SKShapeNode(circleOfRadius: 2)
accuratePosition.strokeColor = .green
// These lines use the projectile motion equations
// as if the gravity was 150 times stronger.
// The subject follows this curve perfectly.
let v = subject.physicsBody?.velocity ?? .zero
let x = v.dx * time
let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * 150 * pow(time, 2)
accuratePosition.position = CGPoint(x: x + subject.position.x,
y: y + subject.position.y)
addChild(accuratePosition)
}
}
}
What you've done is to:
Created an object called subject with a physicsBody and placed it
on screen with a initial velocity.
Plotted predicted positions for an object with that velocity under
gravity via the inaccuratePosition node, using Newton's laws of
motion (v = ut + 1/2at²)
Plotted predicted positions for an object with that velocity under
gravity * 150 via the accuratePosition node, using Newton's laws of
motion
All this is is didMoveTo. When the simulation runs, the path of the node subject follows the accuratePosition path accurately.
I think what's happening is that you are calculating the predicted position using subject's physicsBody's velocity, which is in m/s, but the position is in points, so what you should do is convert m/s into point/s first.
So what's the scale factor? Well from Apple's documentation here; it's.... 150 which is too much of a coincidence 😀, so I think that's the problem.
Bear in mind that you set the vertical velocity of your object to 700m/s - that's 1500mph or 105000 SK point/s. You'd expect it to simply disappear out through the top of the screen at high speed, as predicted by your red path. The screen is somewhere between 1,000 and 2,000 points.
Edit - I created a sample project to demonstrate the calculated paths with and without the multiplier.
https://github.com/emilioschepis/spritekit-gravity-sample
TL;DR - When calculating something gravity-related in SpriteKit multiply the gravity of the scene by 151 to obtain an accurate result.
When trying to solve this issue I first started reading the SpriteKit documentation related to gravity:
https://developer.apple.com/documentation/spritekit/skphysicsworld/1449623-gravity
The documentation says:
The components of this property are measured in meters per second. The default value is (0.0,-9.8), which represent’s Earth’s gravity.
Gravity, however is calculated in m/s^2 and not in m/s.
Thinking that was an error in the implementation of gravity in SpriteKit I began thinking that maybe real-world-based physics laws could not be applied in the scene.
I did, however, come across another documentation page about the linear gravity field that correctly reported that gravity is measured in m/s^2 in SpriteKit.
https://developer.apple.com/documentation/spritekit/skfieldnode/1520145-lineargravityfield
I then setup a simple free falling scene where I applied an initial velocity to a physics body and then calculated the expected trajectory, while comparing it to the actual trajectory.
The x-axis calculations were accurate from the start, suggesting that the only problem was with the gravity's value.
I then tried manually modified the gravity in the scene until the actual trajectory matched the predicted one.
What I found is that there is a "magic" value of ~151 that has to be factored in when using the physics world's gravity property in the game.
Modifying, for example, the y-axis calculations for the trajectory from
let dy = velocity.dy * time + 0.5 * gravity * pow(time, 2)
to
let dy = velocity.dy * time + 0.5 * 151 * gravity * pow(time, 2)
resulted in accurate calculations.
I hope this is useful to anyone who might encounter the same problem in the future.

How do I create a blinking effect with SKEmitterNode?

I have used the particle emitter to create a background with stars. It looks ok, but I would like them to blink, or flicker. The closest I get is when I change the birthrate and lifetime variables so that particles disappear and appear at different places. I would like the particles to remain in the same place and fade in and out, randomly, though. Any ideas on how to do this? This is what I've got so far:
I don't think you can do much directly in the editor. If you're comfortable working with code for adjusting the emitter, you have a couple of possibilities: setting a particle action to animate color or alpha or scale or texture, or a custom shader to do whatever sort of animation. (I'm assuming based on your picture with a basically infinite lifetime that you don't want things to move or disappear. That may rule out keyframing, but perhaps having the keyframe sequence set to repeat mode with the frames spaced by really tiny values would work.)
Another possibility since positions are static would be to just make some fixed sprites scattered around at random and have them run actions to animate them. We've used this approach before with ~100 animated sprites against a backdrop that has a bunch of dimmer stars, and it looked pretty good. Something along these lines:
let twinklePeriod = 8.0
let twinkleDuration = 0.5
let bright = CGFloat(0.3)
let dim = CGFloat(0.1)
let brighten = SKAction.fadeAlpha(to: bright, duration: 0.5 * twinkleDuration)
brighten.timingMode = .easeIn
let fade = SKAction.fadeAlpha(to: dim, duration: 0.5 * twinkleDuration)
fade.timingMode = .easeOut
let twinkle = SKAction.repeatForever(.sequence([brighten, fade, .wait(forDuration: twinklePeriod - twinkleDuration)]))
for _ in 0 ..< 100 {
let star = SKSpriteNode(imageNamed: "star")
star.position = CGPoint(x: .random(in: minX ... maxX), y: .random(in: minY ... maxY))
star.alpha = dim
star.speed = .random(in: 0.5 ... 1.5)
star.run(.sequence([.wait(forDuration: .random(in: 0 ... twinklePeriod)), twinkle]))
addChild(star)
}
That's cut-and-pasted from various bits and simplified some, so there may be typos, but it should give the idea. If you keep the emitter, you can try something like the twinkle above as the particle action. I don't see how you can change the relative periods of particles though like you could with separate sprites, and the only offsets would come from differences in the birth time of the particles.

Rejecting gravity effect SceneKit

This's my first project using SpriteKit.
I'm using these nodes
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
physicsBody.isAffectedByGravity = true
physicsBody.mass = 1
geometryNode.physicsBody = physicsBody
let force = SCNVector3(0, 9.8, 0)
let position = SCNVector3(0, 0, 0)
geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: false)
scnScene.rootNode.addChildNode(geometryNode)
My goal is to see the object stuck in the centre of my scene.
As read within SceneKit's documentation, SceneKit uses SI so Mass 1 means 1 kg.
Gravity force applied to the mass centre of this object is -9.8N ( 1kg * 9.8 m/s^2 ) on the Y-axis.
Applying 9.8N to the mass centre should bring the resultant force equal to 0 so no one force is applied and the object should be stuck in the centre but in my project, it falls down.
Where am I wrong?
It looks to me like you apply the force when you create the node.
From the developer docs on applyForce:
Discussion This method accelerates the body without imparting any
angular acceleration to it. The acceleration is applied for a single
simulation step (one frame).
I think you need to move your applyForce call to your update method in the scene. so that the force is constantly applied.

Swift 2: Stop movement after applyImpulse

How can I stop a sprite after it has been applied an impulse like this:
player.physicsBody!.applyImpulse(CGVectorMake(50, 0))
And is it possible to make the movement decrease over a time period? (2 seconds)
In order to stop the movement of the physicsBody, you can utilise the 'velocity' variable like so:
//this will reset the x, y based velocity to a halt/stop
player.physicsBody?.velocity = CGVectorMake(0, 0)
//if you would also like to stop any rotation that may be present
player.physicsBody?.angularVelocity = 0
To address your second question you should look into 'linearDamping' to affect velocity and 'angularDamping' to affect angularVelocity (rotation). These physicsBody parameters allow you to slow the velocity over time once an impulse is applied (similar to friction).
//These values should be set when creating the physicsBody.
//should experiment with these values to get the desired effect.
player.physicsBody?.linearDamping = 1.10
player.physicsBody?.angularDamping = 0.25

Swift 2: Removing Slowly increase of speed

I am trying to effect the movement speed of a sprite in my app. When i apply velocity to the sprite it slowly increases in speed until it reaches a maximum speed. I am looking for a method to remove the smooth increase in speed and just have the exact same speed all the time (Until i change the velocity)
Feel free to add a comment if you have any questions.
enemy1 = SKSpriteNode(texture: enemyTexture)
enemy1.position = CGPoint(x: CGRectGetMidX(self.frame) - 300, y: CGRectGetMidY(self.frame) - 300)
enemy1.physicsBody = SKPhysicsBody(texture: enemyTexture, size: enemyTexture.size())
enemy1.physicsBody!.affectedByGravity = false
enemy1.physicsBody!.allowsRotation = false
self.addChild(enemy1)
This is the velocity i apply to the sprite. I am doing it in the update function for a continuing movement speed:
override func update(currentTime: NSTimeInterval) {
enemy1.physicsBody!.velocity = CGVectorMake(70, 0)
}
Instead of changing the velocity, have you tried to apply force vector for each update like this:
override func update(currentTime: NSTimeInterval) {
enemy1.physicsBody!.applyForce = CGVectorMake(70, 0)
}
I tried your code and it works correctly for me (sprite has a constant speed from the very beginning). It always worked like that. You can check this by pasting your code in an empty SpriteKit project. Currently, what you have described looks like you are actually using applyForce method rather than changing velocity vector directly.
The solution certainly is to change velocity property directly, like you are saying that you are doing right now. Probably you are overwriting this behaviour somewhere in your code accidentally, because what you have described isn't reproducible (with the code you've provided).