SceneKit smooth camera movement - macos

What is the standard method for smooth camera movement within SceneKit (OpenGL)?
Manually changing x,y isn't smooth enough, yet using CoreAnimation creates "pulsing" movement. The docs on SceneKit seem to be very limited so any examples would be appreciated, I'm currently doing this:
- (void)keyDown:(NSEvent *)theEvent {
int key = [theEvent keyCode];
int x = cameraNode.position.x;
int y = cameraNode.position.y;
int z = cameraNode.position.z;
int speed = 4;
if (key==123) {//left
x-=speed;
} else if (key==124) {//right
x+=speed;
} else if (key==125) {//down
y-=speed;
} else if (key==126) {//up
y+=speed;
}
//move the camera
[SCNTransaction begin];
[SCNTransaction setAnimationDuration: 1.0];
// Change properties
cameraNode.position = SCNVector3Make(x, y, z);
[SCNTransaction commit];
}

To minimise the pulsing movements (due to the key repeat) you can use an "easeOut" timingFunction:
//move the camera
[SCNTransaction begin];
[SCNTransaction setAnimationDuration: 1.0];
[SCNTransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// Change properties
cameraNode.position = SCNVector3Make(x, y, z);
[SCNTransaction commit];
That said, the best thing to do here is probably to manage a target position (a vector3) yourself and update the position of the camera at every frame to go to this target smoothly.

I've been experimenting with this. The best I've found so far is to record the state of the input keys in internal state, modified by keyDown: and keyUp:, and run an NSTimer to apply them. The timer uses the actual, measured time delta between firings to determine how far to move the camera. That way irregular timings don't have too much effect (and I can call my method to update the camera position at any time without worrying about changing its movement speed).
It takes some work to make this behave correctly, though. keyDown: and keyUp: have some obnoxious behaviours when it comes to game input. For example, repeating keys. Also, they may fire even after your view loses focus or your app goes to the background, if keys are held down across the transition. Etc. Not insurmountable, but annoying.
What I haven't yet done is add acceleration and deceleration, which I think will aid the perception of it being smooth. Otherwise it feels pretty good.

I move camera using this code:
let lerpX = (heroNode.position.x - followCamera.position.x) * 0.05
let lerpZ = (heroNode.position.z - followCamera.position.z) * 0.05
followCamera.position.x += lerpX
followCamera.position.z += lerpZ

Related

Opposite of glscissor in Cocos2D?

I found a class called ClippingNode that I can use on sprites to only display a specified rectangular area: https://github.com/njt1982/ClippingNode
One problem is that I need to do exactly the opposite, meaning I want the inverse of that. I want everything outside of the specified rectangle to be displayed, and everything inside to be taken out.
In my test I'm using a position of a sprite, which will update frame, so that will need to happen to meaning that new clipping rect will be defined.
CGRect menuBoundaryRect = CGRectMake(lightPuffClass.sprite.position.x, lightPuffClass.sprite.position.y, 100, 100);
ClippingNode *clipNode = [ClippingNode clippingNodeWithRect:menuBoundaryRect];
[clipNode addChild:darkMapSprite];
[self addChild:clipNode z:100];
I noticed the ClippingNode class allocs inside but I'm not using ARC (project too big and complex to update to ARC) so I'm wondering what and where I'll need to release too.
I've tried a couple of masking classes but whatever I mask fits over the entire sprite (my sprite covers the entire screen. Additionally the mask will need to move, so I thought glscissor would be a good alternative if I can get it to do the inverse.
You don't need anything out of the box.
You have to define a CCClippingNode with a stencil, and then set it to be inverted, and you're done. I added a carrot sprite to show how to add sprites in the clipping node in order for it to be taken into account.
#implementation ClippingTestScene
{
CCClippingNode *_clip;
}
And the implementation part
_clip = [[CCClippingNode alloc] initWithStencil:[CCSprite spriteWithImageNamed:#"white_board.png"]];
_clip.alphaThreshold = 1.0f;
_clip.inverted = YES;
_clip.position = ccp(self.boundingBox.size.width/2 , self.boundingBox.size.height/2);
[self addChild:_clip];
_img = [CCSprite spriteWithImageNamed:#"carrot.png"];
_img.position = ccp(-10.0f, 0.0f);
[_clip addChild:_img];
You have to set an extra flag for this to work though, but Cocos will spit out what you need to do in the console.
I once used CCScissorNode.m from https://codeload.github.com/NoodlFroot/ClippingNode/zip/master
The implementation (not what you are looking for the inverse) was something :
CGRect innerClippedLayer = CGRectMake(SCREENWIDTH/14, SCREENHEIGHT/6, 275, 325);
CCScissorNode *tmpLayer = [CCScissorNode scissorNodeWithRect:innerClippedLayer];
[self addChild:tmpLayer];
So for you it may be like if you know the area (rectangle area that you dont want to show i.e. inverse off) and you know the screen area then you can deduct the rectangle are from screen area. This would give you the inverse area. I have not did this. May be tomorrow i can post some code.

How to Randomly Move Sprites and Keep Collisions Working - Cocos2d?

I want to have a random sprite spawn and move across the screen.
I'm using CGRectIntersectsRect to detect collisions between the player and the randomly spawned sprites.
I've done this, the code works fine - when I have a set interval.
However, when I add randomness to the sprite's spawn times, collisions do not work all the time. Most collisions do not work at all.
I'm not sure what I'm doing wrong and would really appreciate any help in the right direction.
I think it has something to do with the schedule interval and how long it actually takes for the sprite to move across the screen.
Not sure though.
Also, if you could, I would also like to know the best way to remove enemySprite from the scene after it is off the screen?
Here's my code:
-(void)targetTimer {
[self schedule: #selector(enemySprite:) interval: 3.0f];
}
-(void)enemySprite:(id)sender {
CGSize winSize = [[CCDirector sharedDirector] winSize];
//SPAWN ENEYMY
enemySprite = [CCSprite spriteWithFile:#"eneymySprite.png"];
enemySprite.position = ccp (winSize.width/16, winSize.height/5);
[self addChild:enemySprite z:300];
CCAction *moveEnemyRight = [CCMoveTo actionWithDuration:3 position:ccp (winSize.width/1, winSize.height/5) ];
[enemySprite moveEnemyRight];
if ( enemySprite.position.y >= winSize.width ) {
//Best Way to Remove enemySprite from Scene?
}
NSLog(#"Collision");
[self unschedule:#selector(enemySprite:)];
unsigned int t = arc4random()%4 + 1;
[self schedule:#selector(enemySprite:) interval: t];
}
You have to craete an array of your enemies to be able to check if they leave game area (screen in your case). In your code this part
if ( enemySprite.position.y >= winSize.width ) {
//Best Way to Remove enemySprite from Scene?
}
will never be called. Because enemySprite.position.y >= winSize.width will be always NO as you just created this sprite and place it to the game area with coordinate
ccp(winSize.width/16, winSize.height/5)

Unity 3D Spinning a gameobject

I am trying to spin a 3D Gameobject in unity. It is actually a cylinder with just a texture of a poker chip on it. I want to spin it 360 degrees once it has collided with a raycast. It is working just fine in the Unity emulator, however, on the device itself the chip comes to a stop after its spin and then continues to spin in an endless loop. Here is a snippet of the code in question. Thank you for any help in advance.
// Spin the chip
if (Animate) {
if (Speed > 0 && Chip.tag.Contains("Chip")) {
Chip.transform.Rotate(0, Speed*Time.deltaTime, 0);
Speed -= 3;
Debug.Log(Speed);
}
else {
// Reset
Animate = false;
Speed = 360;
Chip.transform.localRotation = Quaternion.Euler(0.0,0.0,0.0);
}
}
To summorize this the best I can the gameobject Chip is assigned when it collides on raycast as such
// Set the chip
Chip = hit.transform;
Everything is done in the update function. Once the raycast hits it calls a betting function then after the betting is calculated it changes the Boolean Animate to true causing the spinning of the chip.
Something is setting Animate = true in some other code, hard to tell whats going on without seeing the rest of it.
Put some debug next to every spot where Animate is set to true, you should see something else setting it, only possible explanation as to why it continues to spin.
Another option is to use the Animation tool and instead of rotating, you just play the animation which performs the rotation for you.
Edit: Chances are its around the touch code, cause when you debug in the editor your using key strokes. A gotcha I've experienced a few times.
James Gramosli is correct in that some other code is triggering the animation again and it is most likely your touch code. It is a common problem when moving between editor and a touch-enabled device. You can determine if this is the case by using the UnityRemote to verify the control flow of your code.
That said, I would change your code to the following which removes the spin code from the Update loop that runs every frame. It is a small optimization, but primarily it cleans up the architecture and makes it more modular and a little neater.
It is not clear from your code snippet, but I will assume you are using UnityScript.
In your script that handles the touch code when you click on the chip, insert this line:
hit.transform.SendMessage("Spin", hit.transform, SendMessageOptions.DontRequireReceiver);
Put this code in a separate script called "SpinChip" and then add the script to your chip object.
var StartSpeed = 360.0;
var Deceleration = 3.0;
function Spin()
{
if (Animating)
{
print("Chip is already spinning, not starting another animation");
return;
}
/*
This code isn't necessary if this exists in a separate script and is only ever attached to the clickable chip
if (!gameObject.tag.Contains("Chip"))
{
print("That wasn't a chip you clicked");
return;
}
*/
print("Chip has been told to spin");
StartCoroutine(SpinningAnimation);
}
function SpinningAnimation()
{
print("Chip spin start");
transform.localRotation = Quaternion.identity;
Speed = StartSpeed;
Animating = true;
while (Speed > 0)
{
transform.Rotate(0, Speed*Time.deltaTime, 0);
Speed -= Deceleration;
yield; // wait one frame
}
print("Chip has completed the spin");
Animating = false;
}
What this code does is create a co-routine that runs once per update loop when activated that will spin the chip for you, and is independent of your actual button clicking code.
var rotSpeed: float = 60; // degrees per second
function Update(){
transform.Rotate(0, rotSpeed * Time.deltaTime, 0, Space.World);
}
Here is a code that rotates your game object, you can use it just with a vector 3 :transform.Rotate(x, y, z); or with Space transform.Rotate(x, y, z, Space.World);
rotSpeed is the rotation speed.
In your update function . The Bool variable Animate may becoming true . This may be reason your cylinder continues to rotate.
Other Solution is : You can create an animation of your cylinder and then take a stopwatch . So that after sometime you can stop you animation using the time of stopwatch

Smooth animation in Cocos2d for iOS

I move a simple CCSprite around the screen of an iOS device using this code:
[self schedule:#selector(update:) interval:0.0167];
- (void) update:(ccTime) delta {
CGPoint currPos = self.position;
currPos.x += xVelocity;
currPos.y += yVelocity;
self.position = currPos;
}
This works however the animation is not smooth. How can I improve the smoothness of my animation?
My scene is exceedingly simple (just has one full-screen CCSprite with a background image and a relatively small CCSprite that moves slowly).
I've logged the ccTime delta and it's not consistent (it's almost always greater than my specified interval of 0.0167... sometimes up to a factor of 4x).
I've considered tailoring the motion in the update method to the delta time (larger delta => larger movement etc). However given the simplicity of my scene it's seems there's a better way (and something basic that I'm probably missing).
The scheduler will try to accommodate and call your selector as per your interval but if there are other processes running, it can be earlier or later (hence why the inconsistency).
Instead, multiply your xVelocity and yVelocity by delta - this should scale the velocities into a far smoother motion.
For example:
- (void) update:(ccTime) delta {
CGPoint currPos = self.position;
currPos.x += (xVelocity * delta);
currPos.y += (yVelocity * delta);
self.position = currPos;
}
Try using the default [self scheduleUpdate] method rather than calling it directly as you are doing, see if that makes a difference. This method is designed for what you are doing and may be smoother.

Alternative to using CABasicAnimation callbacks?

CAAnimation does not provide a mechanism for assigning callback functions other than the standard "animationDidStart:"/"animationDidStop:" methods.
I have a custom UIControl that utilizes 2 CALayers that overlap. The purpose of this control is similar to an old fashioned sonar. The top layer's contents contains an image that gets rotated constantly (call this layer "wand"). Beneath that layer is a "spriteControl" layer that renders blips as the wand passes over them.
The objects that the blips represent are pre-fetched and organized into invisible CAShapeLayers by the spriteControl. I am using a CABasicAnimation to rotate the wand 10 degrees at a time, then utilizing the "animationDidStop:" method to invoke a method on the spriteControl that takes the current rotation value of the wand layer (a.k.a. heading) and animates the alpha setting from 1.0 to 0.0 for simulating the blip in and fade out effect. Finally, the process is started over again indefinitely.
While this approach of using the CAAnimation callbacks ensures that the timing of the wand reaching a "ping" position (i.e. 10deg, 20deg, 270deg, etc) always coincide with the lighting of the blips in the other layer, there is this issue of stopping, recalculating, and starting the animation every 10 degrees.
I could spawn an NSTimer to fire a method that queries the angle of the wand's presentation layer to get the heading value. However, this makes it more difficult to keep the wand and the blip highlighting in sync, and/or cause some to get skipped altogether. This approach is discussed a bit here: How can I callback as a CABasicAnimation is animating?
So my question is whether or not there is anything I can do to improve the performance of the wand layer rotation without reimplementing the control using OpenGL ES. (I realize that this would be easily solved in an OpenGL environment, however, to use it here would require extensive redesign that simply isn't worth it.) While the performance issue is minor, I can't shake the feeling that there is something simple and obvious that I could do that would allow the wand to animate indefinitely without pausing to perform expensive rotation calculations in between.
Here is some code:
- (void)rotateWandByIncrement
{
if (wandShouldStop)
return;
CGFloat newRotationDegree = (wandRotationDegree + WAND_INCREMENT_DEGREES);
if (newRotationDegree >= 360)
newRotationDegree = 0;
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(newRotationDegree), 0, 0, 1);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
animation.duration = WAND_INCREMENT_DURATION;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = FALSE;
animation.delegate = self;
[wandLayer addAnimation:animation forKey:#"transform"];
}
- (void)animationDidStart:(CAAnimation *)theAnimation
{
if (wandShouldStop)
return;
NSInteger prevWandRotationDegree = wandRotationDegree - WAND_INCREMENT_DEGREES;
if (prevWandRotationDegree < 0)
prevWandRotationDegree += 360;
// Pulse the spriteControl
[[self spriteControl] pulseRayAtHeading:prevWandRotationDegree];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
// update the rotation var
wandRotationDegree += WAND_INCREMENT_DEGREES;
if (wandRotationDegree >= 360)
wandRotationDegree = 0;
// This applies the rotation value to the model layer so that
// subsequent animations start where the previous one left off
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(wandRotationDegree), 0, 0, 1);
[CATransaction begin];
[CATransaction setDisableActions:TRUE];
[wandLayer setTransform:rotationTransform];
[CATransaction commit];
//[wandLayer removeAnimationForKey:#"transform"];
[self rotateWandByIncrement];
}
Let's say it takes 10 seconds for the radar to make one complete rotation.
To get the wand to rotate indefinitely, attach a CABasicAnimation to it with its Duration property set to 10 and its RepeatCount property set to 1e100f.
The blips can each be animated using their own instance CAKeyframeAnimation. I won't write the details, but for each blip, you specify an array of opacity values (I assume opacity is how you're fading out the blips) and an array of time percentages (see Apple's documentation).

Resources