My OpenGL ES classes and code are derived from Apple's GLES2Sample code sample. I use them to show 3D objects rotating smoothly around one axis, at what I expect to be a constant rotation speed. Currently, the app uses a frame interval of 1, and each time the OpenGL view is drawn (in the EAGLView's drawView method), I rotate the model by a certain angle.
In practice, this gives decent results, but not perfect: during the rotation, when large parts of the object go out of sight, rendering becomes faster and the rotation thus does not have constant angular velocity. My question is: how do I make it smoother?
Although I welcome all suggestions, I already have one idea: measure the rendering FPS every half-second, and adjust the rotation angle at each redraw based on that. It does not sound very good, however, so: what do you think of this, and how would you handle the issue?
I tend to use a CADisplayLink to trigger new frames and a simple time calculation within the frame request to figure out how far to advance my logic.
Suppose you have a member variable, timeOfLastDraw, of type NSTimeInterval. You want your logic to tick at, say, 60 beats per second. Then (with a whole bunch of variables to make the code more clear):
- (void)displayLinkDidTick
{
// get the time now
NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];
// work out how many quantums (rounded down to the nearest integer) of
// time have elapsed since last we drew
NSTimeInterval timeSinceLastDraw = timeNow - timeOfLastDraw;
NSTimeInterval desiredBeatsPerSecond = 60.0;
NSTimeInterval desiredTimeInterval = 1.0 / desiredBeatsPerSecond;
NSUInteger numberOfTicks = (NSUInteger)(timeSinceLastDraw / desiredTimeInterval);
if(numberOfTicks > 8)
{
// if we're more than 8 ticks behind then just do 8 and catch up
// instantly to the correct time
numberOfTicks = 8;
timeOfLastDraw = timeNow;
}
else
{
// otherwise, advance timeOfLastDraw according to the number of quantums
// we're about to apply. Don't update it all the way to now, or we'll lose
// part quantums
timeOfLastDraw += numberOfTicks * desiredTimeInterval;
}
// do the number of updates
while(numberOfTicks--)
[self updateLogic];
// and draw
[self draw];
}
In your case, updateLogic would apply a fixed amount of rotation. If constant rotation is really all you want then you could just multiply the rotation constant by numberOfTicks, or even skip this whole approach and do something like:
glRotatef([NSDate timeIntervalSinceReferenceData] * rotationsPerSecond, 0, 0, 1);
instead of keeping your own variable. In anything but the most trivial case though, you usually want to do a whole bunch of complicated things per time quantum.
If you don't want the rendering to speed to vary, and your running open-loop (i.e. full tilt) with CADisplayLink or other animation timer, there are two things you can do:
1) Optimize your code so it never wanders below 60 FPS - the maximum frame rate for the device under any circumstances with your model.
2) At run time, measure the frame rate of your application through a few complete cycles and set the draw rate such that it will never exceed your lowest measured drawing performance.
I believe adjusting your rotation angle isn't the right approach for this problem because you're now trying to keep two parameters jiving with one another (draw rate and rotation rate) rather than simply pinning down a single parameter: draw rate.
Cheers.
Related
I am trying to explore the limits of what we are consciously aware of ... okay that sounds terribly pretentious, basically how long something has to be visible for for us to notice. But I cannot get my child to disappear fast enough.
valueX = SKLabelNode(fontNamed: "ArialRoundedMTBold")
valueX.position = CGPoint(x: 0, y: 0)
valueX.fontSize = 320
valueX.text = String("H")
valueX.fontColor = SKColor.white
self.addChild(valueX)
valueX.run(SKAction.scale(to: 0, duration: 0.00001))
This should be too fast to see, but it seems to stay for around a second. Is there some other technique I can use to make something appear very briefly?
I don't think that the SK engine will scale the sprite in-between draws i.e. screen refreshes, so it'll have to be on screen at full size for at least 1 frame, which is 1/60s. I suspect that the SK engine then works out how much to scale per frame, so in the next frame it's probably completely gone. E.g. if you wanted to scale to 25% over 1 second, SK would think that it's been 0.01666667s since the last draw, so the amount to scale by is 1/60 x 75% = 1.25%.
If the duration of the action is less than the time per frame, the effect will be 100% one frame and 0% the next.
If you research 'human persistence of vision' you'll find a lot of information (which I've not gone into myself), which may be why it appears to 'stay for around a second' rather than just 1/60s
As a very basic check, put a sprite on screen in didMoveTo(:view) at a random point then move it to a random point in update(), so it'll move every time update() runs (every 1/60s). If you see it flickering around, you'll know that you can perceive something appearing for 1/60th second but you won't be able to get this done any faster.
I'm fairly new to swift, and have been working on a game for fun, and i'm running into something I can't quite get my head around.
When the run button is pressed, the character moves forward with the following function
func move(dt: CGFloat) {
position.x += moveRate * dt
}
And when the jump button is pressed, the character jumps with the following function
func jump() {
physicsBody?.applyImpulse(CGVector(dx: 0, dy: jumpRate))
run(jumpAnimation!)
}
both work fine, but consider this senario. The player is running, and then they jump while still moving. While in the air, the player releases the move button and the player's x position stops dead. This obviously feels very unnatural, and i would like the player's x position to ease out.
Now i have also played with moving the character with physicsBody?.applyForce(CGVector(dx: 1000, dy: 0)) which would give that effect, but he seems to just gain more and more speed and you don't get a constant rate or "max speed" so to speak.
Could anybody share some insight with me? I'd love to learn anything I can about spritekit and game development in general. Thanks!
You should try to set the velocity instead of setting the X position. When setting the position you bypass all the physics behaviors.
You should also try to set it only when you actually press a button.
func move(dt: CGFloat) {
if Math.abs(moveRate) > 0.1 { // If player initiates movement. You can increase the value 0.1 if you want to filter move values
velocity = CGVector(dx: moveRate, dy: veloxity.dy)
}
}
It your character moves indefinitely like in space, linearDamping will be useful. it's used to simulate air friction, so values closer to 1 means more friction and values closer to 0 means less friction.
linearDamping = 0.85
Also, this way, moveRate isn't dt dependent but it should be lowered.
Try it, I haven't tested it yet, but that's basically how I would do it.
There are two schools of thought on platformer game "physics".
Don't use physics, do everything with positional incrementation.
Do everything with physics, since positional changes mess up physics
Since you're using physics for jumping, and physics jumping is fun:
There are three ways to create movement in a physics system:
Set the velocity as and when required. This is what Crazyrems is suggesting
Apply impulses as needed to increase and decrease rates of movement
Apply forces over time that increase or decrease rates of movement
Use fields to induce motion (too complex for this, and messy, but fun)
What you're attempting, with your physicsBody?.applyForce(CGVector(dx: 1000, dy: 0)) is the application of force over time. Number 3 in the list above. The longer you continue applying this force the faster the character moves.
Each of these techniques can be made to work, but they all need compensation for their various limitations and methodologies of simulation.
In the case of your approach, you need monitor speed and to set a maximum speed. Having reached maximum speed, if the player is still holding the button, only provide enough force to maintain speed (assuming you're using some form of constant resistance to slow the character).
Monitoring speed combined with force-over-time creates interesting acceleration trait possibilities - like very strong initial acceleration and then taper off acceleration as the player nears their maximum speed.
Slowing down when the player releases the button is the next focus. In all these approaches you need something that slows the player. This can be the application of opposing forces, or the use of friction or damping, as provided by the physics engine.
Is it possible to vary the speed of an object moving because of SKAction.followPath? For instance, let's say we use the code below to have a ball follow a rectangular path. The code will use a constant speed throughout the path. But what if we want to vary the speed of the object along the rectangle?
let goPath = SKAction.followPath(ballPath!.CGPath, duration: 2.5)
movingBall.runAction(goPath)
Is the only option to effectively have the ball follow a rectangular path built of separate lines with different speeds (as opposed to one path)?
Thanks!
two ways of accomplishing this.
FIRST WAY:
You can use SKAction.speedBy. You'd group SKAction.speedBy with your followPath. example:
movingBall.runAction(SKAction.group([
SKAction.followPath(ballPath!.CGPath, duration: 2.5)
SKAction.speedBy(4, duration: 2.5)
]))
Now the ball is going to reach 4 times the speed over 2.5 seconds. NOTE: now we're not going to be honoring the followPath duration. As time passes we're increasing the speed of the sprite. It's going to reach its destination in less than 2.5 seconds.
SECOND WAY:
The other way is to use a timing function. This is probably the better way to go because we have a lot more control over how fast the ball is going to move during the animation.
example:
let goPath = SKAction.followPath(ballPath!.CGPath, duration: 2.5)
goPath.timingFunction = {
t in
return powf(t, 3)
}
movingBall.runAction(goPath)
The way a timing function works is that it gives you a block that has the current elapsed time as a float. it's your job to use some kind of computation to modify the value that comes in, and then return it.
I'm just cubing whatever time comes into the function and returning it. This makes it so the ball starts off very slow, and then accelerates very quickly towards the end of the animation.
You can get really creative and use sin waves to make things bounce, etc. Just note that any returned values <0 are ignored.
With the followPath SKAction this isn't possible.
What you could do though is use the update method of the node to update its position manually.
This will take a bit of work to make it follow a path but you can do a lot more than is available with SKAction.
I get feedback from my users, that "from time to time" my Game-App has a bug were the ship gets completely uncontrollable. After investigating into this, it looks like the attitude, reported by CoreMotion drift away very fast (below a second) and it does that suddenly. You can play for up to five minutes, then it happens suddenly that the ship moves to one of the screen borders and does not move away from that point any more.
My question: Has anybody made the same experience with CoreMotion attitude and what are your ways or ideas to get control over this sudden, massive drifts?
The code I'm using to get the attitude in the update() of SpriteKit is:
if let motion = motionManager.deviceMotion {
let x = CGFloat(motion.attitude.yaw - basePosition.x)
let y = CGFloat(motion.attitude.roll - basePosition.y)
ship.physicsBody?.applyImpulse(CGVectorMake(X_SENSITITVITY * x, Y_SENSITITVITY * y))
}
where basePosition, X_SENSITITVITY, Y_SENSITITVITY are constant values in the game.
motionManager is defined by private var motionManager = CMMotionManager() at the top of the class.
As far as I understand the documentation, deviceMotion uses a combination of gravity and attitude measure to minimise the long term drift somehow.
Maybe also important to notice: When the Game runs in a silent environment without the vibrations of cars etc, it works perfectly fine.
I would like to have people to play my game whenever they need a rest - like on long train rides or flights - or kids in the backseat of the car.
I figured out the same drift problem. I compared the CMDeviceMotion's attitude with the CLLocation's magneticHeading. Therefor, I walked 10 times around a small table and put the device after every round on the exact same place.
I figured out, that the DeviceMotion's attitude drifts around 30 degrees every round. Thus, after 10 rounds the attitude is around 300 degrees off.
According to Apple's WWDC 2012 talk "Session 524: Understanding core motion" the used sensor fusion depends on the specified reference frame. Phil Adam mentions, that the sensor fusion algorithm also uses the magnetometer if the xArbitraryCorrected reference frame is specified. I did the same test with xArbitrary, xArbitraryCorrected and xMagneticNorth, but there is no difference. The compass's uncertainty is around 2 - 3 degree (with a heading filter of 1.0 degree).
Maybe it's a bug, I don't know. But I expected at least a difference between xArbitrary and xArbitraryCorrected.
ok, I've found a way to handle this. And as I see a number of views on this question there might be some interest in the answer and some people dealing with the same issue.
From my understanding, there're two things you can rely on:
The change of devices in motion. Specifically CMDeviceMotion.rotationRate
The attitude of a device towards earth. Specifically CMDeviceMotion.gravity
It looks like the first uses changes in current position by reading some kind of current forces. This is a current value and there's no need to sum up the values - and therefore no errors are summed up.
The second one (gravity) measures the current forces against earth. Again: current values, no sum up, no sum up of errors, no drift.
Anything else, especially CMDeviceMotion.attitude, takes measures and adds them up. Leading to adding up the small errors in each measure, leading to a drift in the result.
I've tried to use rotation rate and calculate the attitude myself by adding up. Well, Apple is not perfect in doing that, but way better then any of my solutions :-)
Talk is cheap. Where's the code? Here it is. This method is called during update() in SpriteKit (for every frame)
private func update(ship: SKSpriteNode) {
// Orientation can be landscape left or right
let orientation = UIApplication.sharedApplication().statusBarOrientation == UIInterfaceOrientation.LandscapeLeft ? -1.0 : 1.0
// Get rotation rate
let x = orientation*motionManager.deviceMotion.rotationRate.x
let y = orientation*motionManager.deviceMotion.rotationRate.y
// If user tilts device very slow it does not affect the ship
let xf = CGFloat(abs(x) >= X_RESPONSIVNESS ? X_SENSITITVITY*x : 0.0)
let yf = CGFloat(abs(y) >= Y_RESPONSIVNESS ? Y_SENSITITVITY*y : 0.0)
// Apply as impulse
ship.physicsBody?.applyImpulse(CGVectorMake(xf, yf))
}
As my game is more about "how does the user tilt the device to let me know where you want the ship go to" I simply used rotationRate to measure this and apply it as impulse. As an add-on you can rotate the device which means that what was a rotation towards the top of the screen is actually a rotation to the bottom after rotation. So I have to invert the impulse when the device is rotated.
You can now play the game in any direction. Landscape left, Landscape right, overhead, in a vibrating environment like cars or trains. It works like a charm. I'm even looking forward to a 2.5 hours flight next Saturday...
I'm trying to move a sprite around a CCTMXMap in a smooth fashion. I've figured out how (using CCActions) to move from tile to tile, but I get gaps in my animation (it pauses for a frame while it reevaluates which direction to walk). I've tried moving the character in a scheduled update: method, but that gets messy when you try and restrict the sprite to only moving from tile to tile. Any suggestions on how to get the clean, consistent animation without messy manual animation using update?
Yes, don't use actions. You'll always have the 1-frame delay problem when using CCActions.
Moving the sprite in update is really pretty simple. Especially if you restrict movement to a speed (points per frame) that is clearly divisible by the tile size. For example if your tiles are 40x30, then horizontal speeds of 1,2,4,8,10 would work fine. Vertically 1,2,3,5,6,10 would work.
Update the position by this number, cast it down to int, compare it with the destination location:
if ((int)currentPos.x == (int)targetPos.x && (int)currentPos.y == (int)targetPos.y)
{
NSLog(#"I'm there!");
}
The reason for casting to int is to avoid rounding errors in floating point values.
Another solution would be - especially if your character can only move in one direction at a time - to figure out the number of frames it will take him to get there. If the character has to move 40 points to the right, and he moves at 4 points per frame, it'll take him 10 frames. Then just count the number of frames (how many times the update method ran) and if it reaches 10 (or 0 if you count down) then you know that the character has arrived without needing to check his position.