Why is SpriteKit not updating the shader’s uniforms? - sprite-kit

I added two shaders to my project, one is the same shader from the WWDC 2014 example, and the other has the same code but adding a custom uniform. The problem is that the sprite with the custom uniform is rendered static when the app is running, and with black color on SpriteKit editor in Xcode.
The shader with the custom uniform is below, u_speed is used to control how fast the waves move.
void main()
{
float currTime = u_time * u_speed;
vec2 uv = v_tex_coord;
vec2 circleCenter = vec2(0.5, 0.5);
vec3 circleColor = vec3(0.8, 0.5, 0.7);
vec3 posColor = vec3(uv, 0.5 + 0.5 * sin(currTime)) * circleColor;
float illu = pow(1. - distance(uv, circleCenter), 4.) * 1.2;
illu *= (2. + abs(0.4 + cos(currTime * -20. + 50. * distance(uv, circleCenter)) / 1.5));
gl_FragColor = vec4(posColor * illu * 2., illu * 2.) * v_color_mix.a;
}
This is a project I’ve put together with just the shaders and the two sprites showing the problem.

From what I can tell, there's nothing wrong with your code. There seems to be a bug in how GameScene.sks passes your custom shader uniform into the shader which is causing it to be 0, and the animation to freeze.
If you initialize everything in code rather than in the UI Builder, it works fine. Paste this into your didMoveToView: to see what I mean.
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor] size:CGSizeMake(100, 100)];
sprite.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:sprite];
SKShader* spin2x = [SKShader shaderWithFileNamed:#"Shader2"];
spin2x.uniforms = #[
[SKUniform uniformWithName:#"u_speed" float:(2.0)],
];
sprite.shader = spin2x;

Adding something like the code below in didMoveToView: did the trick, somehow the uniforms are there but are not read by the shader, this "refreshing" of the values did make it work normally.
for (SKSpriteNode *sprite in self.children) {
if ([sprite isKindOfClass:[SKSpriteNode class]]) {
for (SKUniform *uniform in sprite.shader.uniforms) {
if (uniform.uniformType == SKUniformTypeFloat) {
uniform.floatValue = uniform.floatValue;
}
}
}
}

Related

Why does Camera.transform.Rotate(X, Y, 0) change Z?

I would like to rotate a camera using a joystick.
The camera should rotate up and down and left and right, but it should not rotate around z.
I have tried the following lines:
float speed = 3.0f;
float yRotation = speed * fY;
float xRotation = speed * fX;
camera.transform.Rotate(-yRotation, xRotation, 0.0f);
It seems to work, but after a few joystick movements, I can see that the camera rotation's z-value has changed, and it looks like this:
Does anybody see an obvious mistake in my code, or is the problem located somewhere else?
Ok, here's how to do it, I have to use eulerAngles:
yaw += speedH * fX;
pitch -= speedV * fY;
pitch = Mathf.Clamp(pitch, -20, 30);
camera.transform.eulerAngles = new Vector3(pitch, yaw, 0.0f);

Using Sprite Kit, how can I let an object jump?

I am making a game with sprite kit and now I am wondering what the best way is to let the object 'jump'. So it will be launched up vertical with a few pixels.
This is the code of the object I want to jump:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"bal.png"];
sprite.position = CGPointMake(self.frame.size.width/4 + arc4random() % ((int)self.frame.size.width/2), (self.frame.size.height/2 + arc4random() % ((int)self.frame.size.height/2)));
sprite.color = [self randomColor];
sprite.colorBlendFactor = 1.0;
sprite.xScale = 0.2;
sprite.yScale = 0.2;
[self addChild:sprite];
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
self.physicsWorld.gravity = CGVectorMake(0.0f, -4.0f);
I would try to avoid setting object's velocity directly. Instead I'd apply forces and impulses to the objects. For most cases this works better as it doesn't break the physics simulation. For example this is how I'd make my object jump:
- (void) jump:(SKSpriteNode*)obj
{
if (obj.isTouchingGround)
{
CGFloat impulseX = 0.0f;
CGFloat impulseY = 25.0f;
[object.physicsBody applyImpulse:CGVectorMake(impulseX, impulseY) atPoint:obj.position];
}
}
Give a velocity to the body by changing the velocity property of the physicsBody associated with the sprite.
Specifically, you need to add
sprite.physicsBody.velocity = CGVectorMake(vx,vy);
EDIT : If you want to to do this through actions, make use of one of these two class methods of SKAction
+ (SKAction *)followPath:(CGPathRef)path duration:(NSTimeInterval)sec;
+ (SKAction *)moveByX:(CGFloat)deltaX y:(CGFloat)deltaY duration:(NSTimeInterval)sec;
However, using any of these requires you to calculate stuff based on your velocity vector and take care of gravity yourself(for giving suitable deltaX and deltaY or the correct path) which is quite unnecessary. Why not just mention velocity and let SpriteKit work it out for you?

Objective C: Using UIImage for Stroking

I am trying to apply a texture for my brush but i'm really having a hard time figuring how it is done.
Here's the image of my output.
I used an UIImage that just follows the touch on the screen but when i swipe it faster the result is on the right side "W", and on the left side that's the result when i swipe it slow.
i tried using the CGContextMoveToPoint and CGContextAddLineToPoint i don't know how apply the texture.
Is it possible to use UIImage for the stroke texture?
Here's my code
UIImage * brushImageTexture = [UIImage imageNamed:#"brushImage.png"];
[brushImagetexture drawAtPoint:CGPointMake(touchCurrentPosition.x, touchVurrentPosition.y) blendMode:blendMode alpha:1.0f];
You need to manually draw the image at each point along the line from the previous point to the current point.
CGPoint vector = CGPointMake(currentPosition.x - lastPosition.x, currentPosition.y - lastPosition.y);
CGFloat distance = hypotf(vector.x, vector.y);
vector.x /= distance;
vector.y /= distance;
for (CGFloat i = 0; i < distance; i += 1.0f) {
CGPoint p = CGPointMake(lastPosition.x + i * vector.x, lastPosition.y + i * vector.y);
[brushImageTexture drawAtPoint:p blendMode:blendMode alpha:1.0f];
}

Trouble tying a CCSprite deceleration to the ccTime dt

I've got a subclass of CCSprite that knows how to move itself based on two float properties, velX and velY. I call the subclass's - (void)update:(ccTime)dt method from the method of the same name in my game layer.
I use dt to scale how much I move the player and it works great. I'd like to use dt to scale a deceleration factor to make how the player slows down consistent regardless of how often it's updated.
But it just makes my CCSprite not even show up.
Here's the CCSprite class...
#import "Player.h"
#define kDeceleration 0.95
#implementation Player
#synthesize velX, velY;
# pragma mark
+ (id)player
{
Player *player = nil;
if ((player = [[[super alloc] initWithFile:#"rocket.png"] autorelease])) {
player.velX = 0.0;
player.velY = 0.0;
}
return player;
}
- (void)update:(ccTime)dt
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
// move
self.position = ccp(self.position.x + self.velX * dt, self.position.y + self.velY * dt);
if (self.position.x < -self.contentSize.width/2) self.position = ccp(winSize.width + self.contentSize.width/2, self.position.y);
if (self.position.x > winSize.width + self.contentSize.width/2) self.position = ccp(-self.contentSize.width/2, self.position.y);
if (self.position.y < -self.contentSize.width/2) self.position = ccp(self.position.x, winSize.height + self.contentSize.width/2);
if (self.position.y > winSize.height + self.contentSize.width/2) self.position = ccp(self.position.x, -self.contentSize.width/2);
// decelerate
self.velX *= kDeceleration * 0.0165 / dt; // works if the line is: self.velX *= kDeceleration;
if (fabs(self.velX) < 1.0) self.velX = 0.0;
self.velY *= kDeceleration * 0.0165 / dt; // works if the line is: self.velY *= kDeceleration;
if (fabs(self.velY) < 1.0) self.velY = 0.0;
}
- (void)draw
{
[super draw];
glLineWidth(1);
ccDrawCircle(ccp(self.contentSize.width/2, self.contentSize.height/2), 3*self.contentSize.width/4, CC_DEGREES_TO_RADIANS(360), 60, NO);
}
#end
The problem is in the // decelerate section. If I leave out the * 0.0165 / dt part of the two lines that decelerate the player, it works, but it's way faster on the phone than the simulator due to framerate differences. This should scale it, but it just screws it up.
I've tried all kinds of NSLogging and if I use dt at all, I get nan for the value of my velX and velY properties.
Could it have something to do with using the method name -update?
Try using this formula instead:
float decelerator = pow(kDeceleration, 60 * dt);
self.velX *= decelerator;
self.velY *= decelerator;
The logic behind the math:
Suppose the framerate on simulator is at 30fps compared to the normal 60fps on device. Thus, for every frame on simulator, the device already shows 2 frames. Thus every call on the update on simulator should give the same result for 2 calls on device. After two calls on device, self.velX has been multiplied by kDeceleration two times, meaning the new value is equal to self.velX * kDeceleration * kDeceleration. By same logic, if the framerate on simulator is 1/3 of that on device, the new value is self.velX * kDeceleration * kDeceleration * kDeceleration. Thus, we can generalize it as self.velX * pow(kDeceleration, n) where n is the number of times the update method to be called to catch up with framerate of 60fps, which is 60 * dt.

Change Sprite Anchorpoint without moving it?

I am trying to change my Sprite anchor point so that I can rotate over a 0.0f,0.0f anchorpoint. At first my object is rotation at the default anchor point (0.5f,0.5f). However later on I need it to rotate over a 0.0,0.0 AnchorPoint.
The problem is I cannot change the anchor point and change the position accordingly, so it stays on the same position, without the object appearing to quickly move and reposition to its original point.
Is there a way I can set the anchor point and the position of my Sprite at once, without it moving at all?. Thank you.
-Oscar
I found a solution to this with a UIView elsewhere, and rewrote it for cocos2d:
- (void)setAnchorPoint:(CGPoint)anchorPoint forSprite:(CCSprite *)sprite
{
CGPoint newPoint = CGPointMake(sprite.contentSize.width * anchorPoint.x, sprite.contentSize.height * anchorPoint.y);
CGPoint oldPoint = CGPointMake(sprite.contentSize.width * sprite.anchorPoint.x, sprite.contentSize.height * sprite.anchorPoint.y);
newPoint = CGPointApplyAffineTransform(newPoint, [sprite nodeToWorldTransform]);
oldPoint = CGPointApplyAffineTransform(oldPoint, [sprite nodeToWorldTransform]);
CGPoint position = sprite.position;
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
sprite.position = position;
sprite.anchorPoint = anchorPoint;
}
This is a good question, and I don't know the full answer yet.
As you may have noticed, the anchorPoint cannot be changed without affecting scale and rotation.
For scaled sprites:
You have to simultaneously change the anchorPoint and position of your sprite. See this question for a hint
For rotated sprites:
Intuition says you would need to simultaneously change anchorPoint, rotation, and position. (I have no idea how to compute this.)
NOTE: I'm still learning graphics programming, so I'm not 100% able to compute this stuff yet.
I've needed this a couple of times and decided to make a extension for CCNode, tested it abit and seems to work fine. Can be really useful to some :)
It's tested with 1.x but It should work fine in 2.x too. Supports transformed nodes and HD.
Just add this to your project and import whenever you need it - It will be added to all classes deriving from CCNode. (CCSprite, CCLayer)
Interface
#import "cocos2d.h"
#interface CCNode (Extensions)
// Returns the parent coordinate for an anchorpoint. Useful for aligning nodes with different anchorpoints for instance
-(CGPoint)positionOfAnchorPoint:(CGPoint)anchor;
// As above but using anchorpoint in points rather than percentage
-(CGPoint)positionOfAnchorPointInPoints:(CGPoint)anchor;
//Sets the anchorpoint, to not move the node set lockPosition to `YES`. Setting it to `NO` is equal to setAnchorPoint, I thought this would be good for readability so you always know what you do when you move the anchorpoint
-(void)setAnchorPoint:(CGPoint)a lockPosition:(BOOL)lockPosition;
#end
Implementation
#import "CCNode+AnchorPos.h"
#implementation CCNode (Extensions)
-(CGPoint)positionOfAnchorPoint:(CGPoint)anchor
{
float x = anchor.x * self.contentSizeInPixels.width;
float y = anchor.y * self.contentSizeInPixels.height;
CGPoint pos = ccp(x,y);
pos = CGPointApplyAffineTransform(pos, [self nodeToParentTransform]);
return ccpMult(pos, 1/CC_CONTENT_SCALE_FACTOR());
}
-(CGPoint)positionOfAnchorPointInPoints:(CGPoint)anchor;
{
CGPoint anchorPointInPercent = ccp(anchor.x/self.contentSize.width, anchor.y/self.contentSize.height);
return [self positionOfAnchorPoint:anchorPointInPercent];
}
-(void)setAnchorPoint:(CGPoint)a lockPosition:(BOOL)lockPosition
{
CGPoint tempPos = [self positionOfAnchorPoint:a];
self.anchorPoint = a;
if(lockPosition)
{
self.position = tempPos;
}
}
#end
Cocos2d-x + Fixed scale
YourClass.h
virtual cocos2d::Vec2 positionFromSprite(cocos2d::Vec2 newAnchorPoint, cocos2d::Sprite *sprite);
YourClass.m
Vec2 YourClass::positionFromSprite(Vec2 newAnchorPoint, cocos2d::Sprite *sprite) {
Rect rect = sprite->getSpriteFrame()->getRect();
Vec2 oldAnchorPoint = sprite->getAnchorPoint();
float scaleX = sprite->getScaleX();
float scaleY = sprite->getScaleY();
Vec2 newPoint = Vec2(rect.size.width * newAnchorPoint.x * scaleX, rect.size.height * newAnchorPoint.y * scaleY);
Vec2 oldPoint = Vec2(rect.size.width * oldAnchorPoint.x * scaleX, rect.size.height * oldAnchorPoint.y * scaleY);
Vec2 position = sprite->getPosition();
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
return position;
}