I'm still fairly new to programming and what I tried to do is the following: So I separated my Enemies from my game scene to a different class. In the Enemy class.m file I declared 6 methods. Every method represents a new level, which will get called from the game scene. So in the methods I declare the sprite's image, path, shooting particle type, etc.. Here's an example of the level 1 method in the EnemyClass.m file:
#implementation EnemyClass
+(void)enemiesLevel1
{
EnemyName = #"enemy1";
SKSpriteNode* enemy = [SKSpriteNode spriteNodeWithImageNamed:EnemyName];
pathSpeed = 3;
CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(0,0,400,400), NULL);
SKAction *followTrack = [SKAction followPath:path
asOffset:NO
orientToPath:YES
duration:pathSpeed];
SKAction *forever = [SKAction repeatActionForever:followTrack];
SKAction *addEnemy = [SKAction runBlock:^{
[self addChild: enemy];
}];
SKAction *enemySequence = [SKAction sequence:#[addEnemy, forever]];
[self runAction: enemySequence];
}
However, Xcode is states two issues:
No known class method for selector "addChild"
and
No known class method for selector "runAction"
I'm calling the method from GameScene.m with:
[EnemyClass enemiesLevel1]
Here's EnemyClass.h if anyone's wondering:
#interface EnemyClass : NSObject
+(void)enemiesLevel1;
+(void)enemiesLevel2;
+(void)enemiesLevel3;
+(void)enemiesLevel4;
+(void)enemiesLevel5;
+(void)enemiesLevel6;
#end
It may seem like a dumb question, but I am still new and I would greatly appreciate any help!
BTW: I did import the sprite kit framework.
Your EnemyClass doesn't have addChild or runAction methods, as it doesn't inherit from SKNode or a subclass of SKNode.
change :
#interface EnemyClass : NSObject
to
#interface EnemyClass : SKNode
Update :
Also, you have enemiesLevel1 etc defined as class methods. They need to be instance methods if you intend to sue addChild with them.
Here's a question that might help you :
What is the difference between class and instance methods?
Be sure to read all the answers, as a few of them have good info you should be aware of.
Change
+(void)enemiesLevel1
{
....
}
To
-(void)enemiesLevel1
{
....
}
The + in front of a method means it is a class method, and the - means it is an instance method. An instance method can not be called from a class method. Please read the link prototypical provided for you so you can better understand the differences between them.
Related
Problem
When the node hierarchy is encoded, as is common during application state preservation or a “game save”, nodes running SKAction actions with code blocks must be handled specially, since the code blocks cannot be encoded.
Example 1: Delayed Callback after Animation
Here, an orc has been killed. It is animated to fade out and then remove itself from the node hierarchy:
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
[orcNode runAction:[SKAction sequence:#[ fadeAction, removeAction ]]];
If the orc node is encoded and then decoded, the animation will restore properly and complete as expected.
But now the example is modified to use a code block that runs after the fade. Perhaps the code cleans up some game state once the orc is (finally) dead.
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
SKAction *cleanupAction = [SKAction runBlock:^{
[self orcDidFinishDying:orcNode];
}];
[orcNode runAction:[SKAction sequence:#[ fadeAction, removeAction, cleanupAction ]]];
Unfortunately, the code block will not encode. During application state preservation (or game save), if this sequence is running, a warning will be issued:
SKAction: Run block actions can not be properly encoded,
Objective-C blocks do not support NSCoding.
After decoding, the orc will fade and be removed from parent, but the cleanup method orcDidFinishDying: will not be called.
What is the best way to work around this limitation?
Example 2: Tweening
The SKAction customActionWithDuration:actionBlock: seems a beautiful fit for tweening. My boilerplate code for this kind of thing is this:
SKAction *slideInAction = [SKAction customActionWithDuration:2.0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
CGFloat normalTime = (CGFloat)(elapsedTime / 2.0);
CGFloat normalValue = BackStandardEaseInOut(normalTime);
node.position = CGPointMake(node.position.x, slideStartPositionY * (1.0f - normalValue) + slideFinalPositionY * normalValue);
}];
Unfortunately, customActionWithDuration:actionBlock: cannot be encoded. If the game is saved during the animation, it will not restore properly on game load.
Again, what is the best way to work around this limitation?
Imperfect Solutions
Here are solutions I have considered but don’t like. (That said, I’d love to read answers that successfully champion one of these.)
Imperfect Solution: Use performSelector:onTarget: rather than runBlock: in the animation. This solution is imperfect because arguments cannot be passed to the invoked selector; context for the call can only be expressed by the target and the name of the selector. Not great.
Imperfect Solution: During encoding, remove the SKAction sequence from any relevant nodes and advance the program state as if the sequence had completed. In the first example, that would mean setting the node alpha immediately to 0.0, removing the orc node from parent, and calling orcDidFinishDying:. This is an unfortunate solution for at least two reasons: 1) It requires special handling code during encoding; 2) Visually, the node won’t get a chance to finish its animation.
Imperfect Solution: During encoding, remove the SKAction code blocks from any relevant nodes, and recreate them during decoding. This is non-trivial.
Imperfect Solution: Never use SKAction code blocks, especially after a delay. Never rely on the completion of an animation in order to restore good app state. (If you need to schedule a future event in an encodable way, build your own event queue not using code blocks.) This solution is imperfect because runBlock and customActionWithDuration:actionBlock: are just so damn useful, and it would be a shame (and a recurring trap for newbies) to consider them evil.
Encodable lightweight objects can model the kinds of SKAction code blocks that we want to use (but can’t).
Code for the below ideas is here.
Replacement for runBlock
The first encodable lightweight object replaces runBlock. It can make an arbitrary callback with one or two arguments.
The caller instantiates the lightweight object and sets its properties: target, selector, and arguments.
The lightweight object is triggered in a runAction animation by the standard no-argument [SKAction performSelector:onTarget:]. For this triggering action, the target is the lightweight object and the selector is a designated “execute” method.
The lightweight object conforms to NSCoding.
As a bonus, the triggering SKAction retains a strong reference to the lightweight object, and so both will be encoded along with the node running the actions.
A version of this lightweight object could be made that retains the target weakly, which might be nice and/or necessary.
Here is a draft of a possible interface:
#interface HLPerformSelector : NSObject <NSCoding>
- (instancetype)initWithTarget:(id)target selector:(SEL)selector argument:(id)argument;
#property (nonatomic, strong) id target;
#property (nonatomic, assign) SEL selector;
#property (nonatomic, strong) id argument;
- (void)execute;
#end
And an accompanying implementation:
#implementation HLPerformSelector
- (instancetype)initWithTarget:(id)target selector:(SEL)selector argument:(id)argument
{
self = [super init];
if (self) {
_target = target;
_selector = selector;
_argument = argument;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_target = [aDecoder decodeObjectForKey:#"target"];
_selector = NSSelectorFromString([aDecoder decodeObjectForKey:#"selector"]);
_argument = [aDecoder decodeObjectForKey:#"argument"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_target forKey:#"target"];
[aCoder encodeObject:NSStringFromSelector(_selector) forKey:#"selector"];
[aCoder encodeObject:_argument forKey:#"argument"];
}
- (void)execute
{
if (!_target) {
return;
}
IMP imp = [_target methodForSelector:_selector];
void (*func)(id, SEL, id) = (void (*)(id, SEL, id))imp;
func(_target, _selector, _argument);
}
#end
And an example of using it:
SKAction *fadeAction = [SKAction fadeOutWithDuration:3.0];
SKAction *removeAction = [SKAction removeFromParent];
HLPerformSelector *cleanupCaller = [[HLPerformSelector alloc] initWithTarget:self selector:#selector(orcDidFinishDying:) argument:orcNode];
SKAction *cleanupAction = [SKAction performSelector:#selector(execute) onTarget:cleanupCaller];
[orcNode runAction:[SKAction sequence:#[ fadeAction, removeAction, cleanupAction ]]];
Replacement for customActionWithDuration:actionBlock:
A second encodable lightweight object replaces customActionWithDuration:actionBlock:. This one is not so simple, however.
Again, it is triggered by the no-argument [SKAction performSelector:onTarget:], invoking a designated execute method.
A customActionWithDuration:actionBlock: has a duration. But the triggering performSelector:onTarget: does not. The caller must insert a companion waitForDuration: action into her sequence if it depends on duration.
The lightweight object is initialized with a target, selector, node, and duration.
When it is triggered, the lightweight object tracks its own elapsed time and periodically calls the selector on the target, passing it the node and the elapsed time.
The lightweight object conforms to NSCoding. On decoding, if already triggered, it resumes calling the selector for the remainder of its configured duration.
Limitations
I have implemented a version of these proposed classes. Through light use I've already found an important limitation: Nodes encoded with a running SKAction sequence restart the sequence from the beginning upon decoding.
I subclass the SKSpriteNode to create a SKButton in SKButton.h
#interface SKButton : SKSpriteNode
now I want to change the button image by method in SKButton.m
self = [SKSpriteNode spriteNodeWithImageNamed:image];
here self is SKButton but it is giving me error
Cannot assign to 'self' outside of a method in the init family
Incompatible pointer types assigning to 'SKButton *' from 'SKSpriteNode *'
I also tried this in SKButton.m
self = [self spriteNodeWithImageNamed:image];
It gives me error
No visible #interface for 'SKButton' declares the selector
'spriteNodeWithImagedNamed:'
spriteNodeWithImageNamed: is a class method on SKSpriteNode that returns a new SKSpriteNode using the supplied image. Essentially, what you are trying to do with this line -
self = [SKSpriteNode spriteNodeWithImageNamed:image];
is change the current object into a new object - which you can't do. Even if you could, it would be a new SKSpriteNode, not a new SKButton.
What you need to do is manipulate the texture property of your node -
self.texture=[SKTexture textureWithImageNamed:image];
In this line:
self = [SKSpriteNode spriteNodeWithImageNamed:image];
you are downcasting a SKSpriteNode to SKButton which is incorrect as a SKSpriteNode instance does not have the extra implementation provided by SKButton.
Since SKButton is a SKSpriteNode subclass it contains all its functionality so you can just do:
- (void)buttonClicked {
[self setTexture:[SKTexture textureWithImageNamed:"buttonClicked.png"]];
}
I have a class called Blade.h and I make A SKSpriteNode on that!
now I make new instance of that class on Main Class.
#import "Blade.h"
#implementation Blade
-(void) GenerateBalde
{
SKSpriteNode *blade = [SKSpriteNode spriteNodeWithImageNamed:#"blade"];
[blade runAction:[SKAction rotateByAngle:M_PI duration:1]];
[self addChild:blade];
}
#end
now, I want to have an instance of this object in my main class and I wanna detect the physic collision the Blade with another Sprites in my main class!
I know how to make an instance , but I wanna know how to detect the collision of different objects in different classes + physic!
please advice!
It would be better if your Blade was a subclass of SKSpriteNode and then in its init method use:
if (self = [super initWithImageNamed:#"blade"]){
//more code
}
return self;
Another solution is to use a property, so instead of creating a SKSpriteNode in generateBlade method use this:
In Blade.h add #property (nonatomic, retain) SKSpriteNode *bladeSprite.
In your generateBlade method, use this:
self.bladeSprite = [SKSpriteNode spriteNodeWithImageNamed:#"blade"];
[self.bladeSprite runAction:[SKAction rotateByAngle:M_PI duration:1]];
[self addChild:self.bladeSprite];
Then, in your MainScene, in update: method, you can check the collision using this code:
-(void)update:(CFTimeInterval)currentTime {
if (CGRectIntersectsRect(otherObject.frame, blade.bladeSprite.frame){
//Collisioning!
}
}
If you are using a SKSpriteNode subclass as mentioned in first lines, you could just do blade.frame instead of blade.bladeSprite.frame (you won't need to set a property).
I'm working on a project in Objective-C / Sprite Kit and cannot get Sprite Kit actions to work, I have tried everything I have seen but nothing has worked.
Here is some code:
myscene.h
#property (strong, nonatomic) SKAction *jumpAction;
#property (strong, nonatomic) SKAction *kneelAction;
#property (strong, nonatomic) SKAction *runAction;
myscene.m (init w/ size method)
[self setupCharacter];
[self createDpad];
[self spawnStartupClouds];
//self.physicsWorld.gravity = CGVectorMake(0.2,-2);
self.physicsWorld.gravity = CGVectorMake(0.2 ,-2);
self.physicsWorld.contactDelegate = self;
[self setupActions];
myscene.m (setupActions method)
-(void) setupActions{
SKTextureAtlas *jumpAtlas = [SKTextureAtlas atlasNamed:#"jump"];
SKTexture *jumpTex1 = [jumpAtlas textureNamed:#"jump1.png"];
SKTexture *jumpTex2 = [jumpAtlas textureNamed:#"jump2.png"];
SKTexture *jumpTex3 = [jumpAtlas textureNamed:#"jump3.png"];
NSArray *jumpAtlasTexture = #[jumpTex1, jumpTex2, jumpTex3];
SKAction* jumpAtlasAnimation = [SKAction animateWithTextures:jumpAtlasTexture timePerFrame:0.1];
SKAction* wait = [SKAction waitForDuration:0.5];
jumpAction = [SKAction sequence:#[jumpAtlasAnimation, wait]];
BBCharacter* leader = (BBCharacter*)[self childNodeWithName:#"character1"];
}
-(void)setupCharacter{
NSLog(#"Setup character");
leader = [BBCharacter node];
leader.position = CGPointMake(100, 230);
[self addChild:leader];
}
It also seems (in setupActions) it cannot "see" the SKAction jumpAction...
When you declare a property in Objective-C in any class interface, you need to access it in the implementation using self.propertyName. You can also access the instance variable associated with that property by using _propertyName. So, the jumpAction property can be accessed using either self.jumpAction or simply _jumpAction.
This is because the property is not actually an instance variable, but an encapsulation over the class' data. Please have a look at the documentation for a proper explanation.
Alternatively, to be able to access the property using it's exact name, you can explicitly synthesize it in the implementation.
#implementation MyScene //synthesize properties below this.
#synthesize jumpAction, kneelAction, runAction;
After doing so, you can access the properties directly, as you already do in your code.
A suggestion: You do not need to use properties for SKAction objects unless they need to be accessed by other classes.
hope this will help you
ok , By seeing your code i am able to understand that you have number images on your jump.atlas folder
and You want to animate your player to those texture when player jumps off .
so first of all add this line
[_playerNode runAction: jumpAction];
I am not seeing this line in your code.
You have all the actions set up, now you just need to tell the node to perform the action with the runAction method. If you are running the action then be most specific when you say you cannot get the actions to work.
In my game, which is using cocos2d, there is going to be many different types of enemies, which all look different, and move all in different ways. Also, there is going to be a couple of different gamemodes, which both use the same enemies. As there will be different gamemodes, I decided to make each of my enemies have their own CCSprite class. In those there will be the way that the sprites move, the animation, etc. When one of these sprite is needed in my game, they will be spawned in to the scene. The only thing is, how do I do this? How do I call for one of the sprites to be create on the screen when they are using a class of their own?
If you want to tell me another way than having these sprites having their own classes, that is fine, but keep in mind that I will be having a couple of different gamemodes. If I do the code for the sprites in the CCLayer class of that gamemode, well I will have to write the code twice, which will take time.
Thanks.
You can just subclass CCSprite and override the default initializer initWithTexture:rect:
example taken from here
#implementation MySprite
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
if( (self=[super initWithTexture:texture rect:rect]))
{
// initialize your ivars here
//ivar1 = xxx;
//ivar2 = yyy;
//ivar3 = zzz;
}
return self;
}
#end
// And to create an instance of MySprite you simply do:
MySprite *sprite = [MySprite spriteWithFile...];
// or any of the supported CCSprite methods.
you can have a super class say EnemySprite that looks like this
#interface EnemySprite : CCSprite
- (void)addToLayer:(CCLayer *)layer;
- (void)removeFromLayer:(CCLayer *)layer;
#end
than create a subclass for each type of enemy for example:
#inteface BigEnemySprite : EnemySprite
#end
#implementation BigEnemySprite
- (void)addToLayer:(CCLayer *)layer {
[layer addChild:self];
// animation code for your big enemy
}
- (void)removeFromLayer:(CCLayer *)layer {
[layer removeChild:self];
// animation code
}
#end
than you can use them like
EnemySprite *enemy = [BigEnemySprite spriteFromFile:file];
[enemy addToLayer:self];