So I have an SKSpriteNode which has undergone two SKActions which modify it's shape and coloring however I want to return the modified SKSpriteNode as an SKTexture which I can apply to another SKSpriteNode. This is what I'm currently using.
1) Generate the "modified" laserNode and store it as a property
-(void)generateLaserSprite
{
SKSpriteNode *laserNode = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"Laser.png"]];
[laserNode runAction:[SKAction group:#[[SKAction colorizeWithColor:[SKColor redColor] colorBlendFactor:100 duration:0.0],
[SKAction scaleXBy:0.4 y:0.7 duration:0.0],
]]];
_laserSprite = laserNode
}
2) Then a method from the SKScene calls this method to retrieve a copy of the property
-(SKSpriteNode*)retrieveLaserSprite
{
SKSpriteNode *laserNode = [_laserSprite copy];
laserNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(laserNode.size.width*0.8, laserNode.size.height*0.8)];
//other laserNode.physicsBody modifications
return laserNode;
The problem with this currently is that when I call the second method (retrieveLaserSprite), the returned LaserNode then shows up on screen as the original image (pre-SKAction), and then you can visibly see the SKActions take place on-screen.
It should actually be possible to create a texture from a sprite whose rendering properties have been modified using the -(SKTexture *)textureFromNode: method of the SKView class.
Your code would go like this:
-(SKSpriteNode*)retrieveLaserSprite {
SKTexture *tempTexture = [self.view textureFromNode:_laserSprite];
SKSpriteNode *laserNode = [SKSpriteNode spriteNodeWithTexture:tempTexture];
laserNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(laserNode.size.width*0.8, laserNode.size.height*0.8)];
//other laserNode.physicsBody modifications
return laserNode;
}
You should be able to accomplish this task using something like:
-(SKSpriteNode*)retrieveLaserSprite
{
SKTexture *texture = [self.view textureFromNode:[_laserSprite copy]];
SKSpriteNode *laserNode = [SKSpriteNode spriteNodeWithTexture:texture];
laserNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(laserNode.size.width*0.8, laserNode.size.height*0.8)];
//other laserNode.physicsBody modifications
return laserNode;
}
Related
I develop an iOS game using SpriteKit (such a helpful framework to quickly make a game). I add texture and configure a physical body for a main character as image
The green rectangle is the frame of the physical body. I'm using the following code to create it
#interface MainCharacter : SKSpriteNode
#end
#implementation MainCharacter
+ (instancetype)mainCharacterAtPosition:(CGPoint)pos {
MainCharacter* mainChar = [[MainCharacter alloc] initWithTexture:[SKTexture textureWithImageNamed:#"stand_up"]];
mainChar.position = pos;
mainChar.xScale = 0.5f;
mainChar.yScale = 0.5f;
return mainChar;
}
- (instancetype)initWithTexture:(SKTexture *)texture {
if (self = [super initWithTexture:texture]) {
self.name = kCharacterName;
self.anchorPoint = CGPointMake(0.5f, 0.0f);
[self standup];
CGSize spriteSize = self.size;
CGPoint center = CGPointMake(spriteSize.width*(self.anchorPoint.x-0.5f), spriteSize.height*(0.5f-self.anchorPoint.y));
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize center:center];
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = kCharacterCategory;
self.physicsBody.contactTestBitMask = 0x0;
self.physicsBody.collisionBitMask = 0x0;
}
return self;
}
- (void)standup {
SKAction* standupAction = [SKAction setTexture:self.standupTexture resize:YES];
[self runAction:standupAction];
}
- (void)standdown {
SKAction* standownAction = [SKAction setTexture:self.standdownTexture resize:YES];
[self runAction:standownAction completion:^{
}];
[self performSelector:#selector(standup) withObject:nil afterDelay:1.0f];
}
MainCharacter is a class that inherits from SKSPriteNode, just an convienient class to manage a main character. Stand Up is a first state of the character. I have another state, temporarily called stand down (demonstrate as following image)
I add a swipe down gesture to make character stand down.
The green rectangle also the physical body but it's too large for the character. I want to make a physical body frame as the red rectangle.
Can anyone help me how to make the physical body smaller when my character stand down and enlarge the physical body after it stands up
You can destroy the current physics body self.physicsBody = nil; and then simply create a new one with the new size requirements.
I solve this problem by using 2 nodes for 2 states (as a suggestion): stand up state and stand down state. I named it
standupNode and standdownNode
First, add the standupNode to the game scene. If swipe donw gesture recognize, I remove the standupNode from game scene and add the standdownNode instead. On contrary, removing the standdownNode from the game scene then add the standupNode if character stands up
Does anyone know if there is a way to fade (over time) between two different SKTextures on an SKSpriteNode. I am assuming that you can't do this directly and plan to use a duplicate child sprite with a higher ZPosition to realise the fade, but I just wanted to check that there was not some method using SKAction(s) that I had over looked.
The following code should address this issue assuming the new texture fits overtop of the old one (it doesn't fade out the previous texture, but simply fades in the new one on top). I've left out minor implementation details such as timing mode.
-(void) fadeTexture:(SKTexture *)newTexture ontoSpriteNode:(SKSpriteNode *)referenceSpriteNode withDuration:(CFTimeInterval)duration {
SKSpriteNode * fadeInSprite = [self fadeInSpriteWithTexture:newTexture referenceSpriteNode:referenceSpriteNode];
[[referenceSpriteNode parent] addChild:fadeInSprite];
[fadeInSprite runAction:[SKAction sequence:#[
[SKAction fadeAlphaTo:1 duration:duration],
[SKAction runBlock:^{
[fadeInSprite removeFromParent];
[referenceSpriteNode setTexture:newTexture];
}]
]]];
}
-(SKSpriteNode *) fadeInSpriteWithTexture:(SKTexture *)newTexture referenceSpriteNode:(SKSpriteNode *)referenceSpriteNode {
SKSpriteNode * fadeInSprite = [SKSpriteNode spriteNodeWithTexture:newTexture size:[referenceSpriteNode size]];
[fadeInSprite setAlpha:0];
[fadeInSprite setAnchorPoint:[referenceSpriteNode anchorPoint]];
[fadeInSprite setPosition:[referenceSpriteNode position]];
return fadeInSprite;
}
I need to change the character appearance during the game. My character define by a node:
SKSpriteNode* _character;
....
SKTexture* tmp = [SKTexture textureWithImageNamed:string];
tmp.filteringMode = SKTextureFilteringNearest;
[textureArray addObject:tmp];
_character = [SKSpriteNode spriteNodeWithTexture:tmp];
Then under a certain condition in the game, if I check it satisfied, I will change the appearance by:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (condition_satisfied) {
...
_store.position = _character.position;
[_character removeFromParent];
...
//load new character texture here, I won't post the code, for short. Then
....
_character.position = _store.position;
...
[self addChild:_character];
}
I use a store node to store the position, because my character's position will change during the game by human touch. But in the game, it will always cause crash, collision... So I guess that is not the right way to restore the right position of character.
How should I do here ?
To change a texture just set SKSpriteNode's texture property:
_character.texture = [SKTexture textureWithImageNamed:#"image"];
Or use SKAction, if you want to add some fading effect when texture is changing:
SKAction *fadeOut = [SKAction fadeOutByDuration:0.3f];
SKAction *fadeIn = [SKAction fadeInByDuration:0.3f];
SKAction *changeTexture = [SKTexture setTexture:[SKTexture textureWithImageNamed:#"image"]];
[_character runAction:[SKAction sequence:#[fadeOut,changeTexture,fadeIn]]];
There is no need to removeFromParent the node.
If you need to store node's position, it's better to store it in #property:
#interface GameScene()
#property (nonatomic) CGPoint characterPosition;
#end
#implementation GameScene
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (condition_satisfied) {
...
self.characterPosition = _character.position;
[_character removeFromParent];
...
//load new character texture here, I won't post the code, for short. Then
....
_character.position = self.characterPosition;
...
[self addChild:_character];
}
#end
Instead of creating a SKSpriteNode to store the position of the node, you can simply do so in a CGPoint variable.
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (condition_satisfied) {
...
CGPoint characterPosition = _character.position;
[_character removeFromParent];
...
//load new character texture here, I won't post the code, for short. Then
....
_character.position = characterPosition;
...
[self addChild:_character];
}
I'm currently using Sprite Kit and Xcode to design a game. My character is usually in the state of running which consists of two images - code below:
bobSKTexture* Texture1 = [SKTexture textureWithImageNamed:#"bob1"];
bobTexture1.filteringMode = SKTextureFilteringNearest;
SKTexture* bobTexture2 = [SKTexture textureWithImageNamed:#"bob2"];
bobTexture2.filteringMode = SKTextureFilteringNearest;
SKAction* run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[birdTexture1, birdTexture2] timePerFrame:0.2]];
_bob = [SKSpriteNode spriteNodeWithTexture:birdTexture1];
[_bob setScale:2.0];
_bob.position = CGPointMake(self.frame.size.width / 4, CGRectGetMidY(self.frame));
[_bob runAction:run];
I want the image to change when a touch to the screen happens, only for a short amount of time and then I want it to return to the above code. How can I accomplish this?
As Jānis K said, it would be even easier to do like this when you want the image to change:
[_bob removeAllActions];
_bob.texture = [SKTexture textureWithImageNamed:NEW_TEXTURE];
[self performSelector:#selector(resetAnimation) withObject:nil afterDelay:2.0f];
NEW_TEXTURE is the name of the texture/image you want it to change to.
self would be the scene or wherever you call the code to make _bob
This is the resetAnimation method, defined in your scene or wherever you call the code to make _bob:
- (void)resetAnimation
{
SKAction* run = [SKAction repeatActionForever:[SKAction animateWithTextures:#[birdTexture1, birdTexture2] timePerFrame:0.2]];
[_bob runAction:run];
}
Is there possible to get current image name from SKTexture from SKSpriteNode?
I am a new in SpriteKit and I'm writing a little game. I need to detect when ninja will hit enemy. Something like this
I am doing SKAction like
- (void)setUpHit
{
SKTextureAtlas *hitAtlas = [SKTextureAtlas atlasNamed:#"Ninja_hit"];
SKTexture *hit1 = [hitAtlas textureNamed:#"Ninja_hit_1"];
SKTexture *hit2 = [hitAtlas textureNamed:#"Ninja_hit_2"];
SKTexture *hit3 = [hitAtlas textureNamed:#"Ninja_hit_3"];
SKTexture *hit4 = [hitAtlas textureNamed:#"Ninja_hit_4"];
SKAction *hitAnimation = [SKAction animateWithTextures:#[hit1, hit2, hit3, hit4]
timePerFrame:0.1];
SKAction *wait = [SKAction waitForDuration:0.3];
SKAction *goBack = [SKAction animateWithTextures:#[hit1, [SKTexture textureWithImageNamed:#"Ninja"]]
timePerFrame:0.1];
self.hitAction = [SKAction sequence:#[hitAnimation, wait, goBack]];
}
But hit is going only on images Ninja_hit_2.png or Ninja_hit_3.png.
So I need to detect current texture image name when i am doing intersectsNode ninja with enemy.
Now I am doing something like
if ([ninja intersectsNode:node] && !self.isKilled)
{
SKTexture *currentTexture = [ninja texture];
if ([self isHit:currentTexture])
{
//kill enemy here
}
}
where
- (BOOL)isHit:(SKTexture *)texture
{
NSString *description = [texture description];
NSRange range = [description rangeOfString:#"'"];
NSString *textureName = [description substringFromIndex:NSMaxRange(range)];
range = [textureName rangeOfString:#"'"];
textureName = [textureName substringToIndex:NSMaxRange(range) - 1];
if ([textureName isEqualToString:#"Ninja_hit_2.png"] ||
[textureName isEqualToString:#"Ninja_hit_3.png"])
{
return YES;
}
return NO;
}
I know that is not correct, but I can't find how to take current texture name or doing it correctly. Could you help me?
Hmm my strategy would be this:
On your ninja class have a property for your textures. Keep them around
#property (nonatomic, strong) NSArray * hitTextures;
Then store the hit textures (note this is a lazy loading getter method)
-(NSArray *)hitTextures{
if (_hitTextures == nil){
SKTexture *hit1 = [SKTexture textureWithImageNamed:#"Ninja_hit_1"];
SKTexture *hit2 = [SKTexture textureWithImageNamed:#"Ninja_hit_2"];
SKTexture *hit3 = [SKTexture textureWithImageNamed:#"Ninja_hit_3"];
SKTexture *hit4 = [SKTexture textureWithImageNamed:#"Ninja_hit_4"];
_hitTextures = #[hit1, hit2, hit3, hit4];
}
return _hitTextures;
}
Note that we don't need to make the SKTextureAtlas object explicitly:
When loading the texture data, Sprite Kit searches the app bundle for an image file with the specified filename. If a matching image file cannot be found, Sprite Kit searches for the texture in any texture atlases stored in the app bundle. If the specified image does not exist anywhere in the bundle, Sprite Kit creates a placeholder texture image.
Use this texture array to fill in your SKAction
SKAction *hitAnimation = [SKAction animateWithTextures:self.hitTextures
timePerFrame:0.1];
This allows you do change your -isHit method like so:
- (BOOL)isHit:(SKTexture *)texture
{
NSUInteger index = [self.hitTextures indexOfObject:texture];
if (index == 1 ||
index == 2)
{
return YES;
}
return NO;
}
This way you don't rely on an implementation detail of the -description method that is subject to change.
to detect the current name out of a SKAtlas Animation, this is not the best way but it works for me.
var tempstr = yoursprite.texture!.description
if(tempstr.rangeOfString("currentFrameName", options: NSStringCompareOptions.LiteralSearch, range: nil, locale: nil) != nil){
// do something
}
Look at the texture description of the SKSpriteNode and you should be able to see the name of the image
for node in self.children {
if node is SKSpriteNode && node.physicsBody != nil {
let spriteNode:SKSpriteNode = node as! SKSpriteNode
print("sprite node image name \(spriteNode.texture!.description)")
}
}
an example of this will print out on debug
sprite node image name 'Clear10x10' (10 x 10)
You can then RegX the third field for the image name, 'Clear10x10' in this example. Note that Apple may change this description format.