Create a subclass of a SKSpriteNode subclass - objective-c

Let's say I want to create a bunch of different types of Spaceships. I want to setup a base spaceship class that I can use to create other spaceships with minor differences.
My base class looks like this.
// BaseSpaceship.h
#interface SpaceshipNode : SKSpriteNode
#property NSColor color;
#property CGFloat engineThrust;
+ (id)baseSpaceshipWithImageNamed:(NSString *)name;
#end
// BaseSpaceship.m
#implementation BaseSpaceship
+ (id)baseSpaceshipWithImageNamed:(NSString *)name {
BaseSpaceship *ship = [BaseSpaceship spriteNodeWithImageNamed:name];
ship.color = [NSColor redColor];
ship.engineThrust = 2.0;
return ship;
}
#end
I can create a ship in MyScene.m like this just fine.
BaseSpaceship *baseClass = [BaseSpaceship baseSpaceshipWithImageNamed:#"BaseShip"];
However, I'm not sure how to create a subclass of BaseSpaceship, for example, DestroyerSpaceship. I'm not sure if I should be using static methods or not. The examples I've seen online use static methods to instantiate SKSpriteNodes. This is what I came up with, but it's wrong.
// DestroyerSpaceship.h
#interface DestroyerSpaceship : BaseSpaceship
#property CGFloat missileThrust;
- (id)makeDestroyerSpaceship;
#end
// DestroyerSpaceship.m
#implementation DestroyerSpaceship
- (id)makeDestroyerSpaceship{
DestroyerSpaceship *ship = [DestroyerSpaceship baseSpaceshipWithImageNamed:#"DestroyerShip"];
ship.engineThrust = 2.0;
// ship doesn't have missileThrust, program crashes
ship.missileThrust = 3.0;
return ship;
}
#end
Ultimately, I want to be able to do something like this.
DestroyerSpaceship* a = [DestroyerSpaceship makeDestroyerSpaceship];
EvilSpaceship* b = [EvilSpaceship makeEvilSpaceship];
NiceSpaceship* c = [NiceSpaceship makeNiceSpaceship];
And have them all inherit basic properties and methods from BaseSpaceship.

The answer is less complex than you think. Well, the code might be a bit more complex, but once you have the structure it is most flexible. Creating the different types of spaceships will also be a lot more readable.
You can override the initializer method in the subclass. As a sidenote, use (instancetype) instead of (id) (source: instancetype # NSHipster).
As you are adding custom body sprites to the object, I would opt to subclass SKNode instead of SKSpriteNode (so #interface SpaceshipNode : SKNode instead of #interface SpaceshipNode : SKSpriteNode).
#interface SpaceshipNode : SKNode
#property SKColor * color; // Use SKColor instead of NSColor
#property CGFloat engineThrust;
#end
// ...
#implementation SpaceshipNode
- (instancetype) init {
if (self == [super init]) {
NSLog(#"A new SpaceshipNode was just init'ed.");
// set some default initial values here that all brand-new SpaceshipNodes will inherit
// perhaps create and add a basic body sprite
// SKSpriteNode * body = ...;
// [self addChild:body];
// set thrust
self.engineThrust = 2.0;
}
return self;
}
Then you can subclass and create a new type of spaceship. Awesome!
#interface DestroyerSpaceship : SpaceshipNode
#property CGFloat missileThrust;
#end
#implementation DestroyerSpaceship
- (instancetype) init {
// note that [super init] will call the SpaceshipNode's init method
if (self = [super init]) {
NSLog(#"A new DestroyerSpaceship was just init'ed.");
// add a body sprite
// SKSpriteNode * body = ...;
// [self addChild:body];
// a Destroyer is much faster than your average spaceship
self.engineThrust = 10.0;
// set class specific variables
self.missileThrust = 5.f;
}
return self;
}
Now, you can just call:
SpaceshipNode * newSpaceShip = [SpaceshipNode new]; // short for [[SpaceshipNode alloc] init];
DestroyerSpaceship * newDestroyer = [DestroyerSpaceship new];
These two lines will log the following. The last two lines are caused by the Destroyer, which first calls the SpaceshipNode init, and then the Destroyer-specific init method.
A new SpaceshipNode was just init'ed.
A new SpaceshipNode was just init'ed.
A new DestroyerSpaceship was just init'ed.
And you can even use it like this:
SpaceshipNode * newUnidentifiedVessel = [DestroyerSpaceship new];
if ([newUnidentifiedVessel isKindOfClass:[DestroyerSpaceship class]]) {
NSLog(#"We are under attack! Route power to shields!");
}

- (instancetype)makeDestroyerSpaceship{
if (self = [super baseSpaceshipWithImageNamed:#"DestroyerShip"]) {
self.engineThrust = 2.0;
self.missileThrust = 3.0;
}
return self;
}

Related

Proper subclassing of SKSpriteNode and instance initialization

I am trying to subclass SKSpriteNode so that I can use it to create different characters with different properties for a game. I know this should be a trivial task, but I am having a hard time understanding how to initialize a variable when instances are created.
In the header I have:
#import <SpriteKit/SpriteKit.h>
#interface MyClass : SKSpriteNode
#property int myVariable;
#end
And in the implementation:
#import "MyClass.h"
#interface MyClass ()
#end
#implementation MyClass
#synthesize myVariable;
- (id)init {
if (self = [super init]) {
myVariable = 100;
}
return self;
}
When I create the node in the scene:
#implementation GameScene
{
MyClass *mySprite;
}
- (void)didMoveToView: (SKView *) view
{
mySprite = [MyClass spriteNodeWithImageNamed:#"image.png"];
}
The value of myVariable is 0, which I assume it means that spriteNodeWithImageNamed does not execute the init method. If I use:
mySprite = [[MyClass alloc] init]; // or mySprite = [MyClass new];
mySprite = [MyClass spriteNodeWithImageNamed:#"image.png"];
The variable name is correctly set to 100, but then reverted to 0 by spriteNodeWithImageNamed.
I have also tried:
mySprite = [[MyClass alloc] init]; // or mySprite = [MyClass new];
[mySprite setTexture:[SKTexture textureWithImageNamed:#"image.png"]];
In this case the value of myVariable is correct, but no image appears when I add the node to the scene. What init method is called when spriteNodeWithImageNamed is used? Am I improperly overriding the SKSpriteNode init method?
You have done everything correctly with creating a class, but you need to keep in mind that you are calling the factory method spriteNodeWithImageNamed, which then initializes and allocates by itself. Thus, if you want to use your custom init method you need to override the factory method as well.
Inside of your implementation of MyClass, override the method to call your initializer:
+ (instancetype) spriteNodeWithImageNamed:(NSString *)imageName{
MyClass *newClass = [[MyClass alloc] init];
SKTexture *spriteTexture = [SKTexture textureWithImageNamed:imageName];
newClass.size = spriteTexture.size;
newClass.texture = spriteTexture;
return newClass;
}
Hope this helps, let me know if you have questions.
*EDIT: Im actually a little tempted to ditch this, only because SKSpriteNode doesn't have an init method (or at least not that we know of). So unless you want to dive deep into Apple's mysteries then just do it this way:
+ (instancetype) spriteNodeWithImageNamed:(NSString *)imageName{
MyClass *newClass = [[MyClass alloc] initWithImageNamed:imageName];
newClass.myVariable = 100;
return newClass;
}
You could instead override initWithImage to put in myVariable, but the whole point is that I'd avoid trying to use the init initializer with SKSpriteNode.
Here is how I do it. Create your own custom init method, and simply call the default spritenode initWithImage.
-(id)initShipWithType:(ShipType)type {
self = [super initWithImageNamed:[self getShipSprite:type]];
if (self) {
}
return self;
}

Objective-C can't tell where the instances of pointers are coming from

I'm a newb for Objective-C...hopefully the terminology in the title is correct...but anyhow. I am following a tutorial and was a bit confused where instances of certain pointers were showing up. I never saw them explicitly defined, and then later the tutorial would modify them. For an example, look below (I tried to include only the necessary code. I put stars * next to the lines that I am confused by the most. Basically I'm not understanding where _meta and _hud are coming from and how I can call methods from them. I guess I would be less confused if they were hud and meta, without the _). Thanks, sorry if it's an amateur question.
#interface PlayGameLayer()
#property (strong) CCTMXLayer *meta;
#property (strong) HudLayer *hud;
#end
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
PlayGameLayer *layer = [PlayGameLayer node];
[scene addChild: layer];
HudLayer *hud = [HudLayer node];
[scene addChild:hud];
layer.hud = hud;
return scene;
}
-(id) init
{
if( (self=[super init]) ) {
....
self.meta = [_tileMap layerNamed:#"Meta"];
_meta.visible = NO; **********************************************
....
}
return self;
}
-(void)setPlayerPosition:(CGPoint)position {
CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid){
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties){
NSString *collectible = properties[#"Collectable"];
if (collectible && [collectible isEqualToString:#"True"])
{
[_meta removeTileAt:tileCoord]; ****************************************
self.numCollected++;
[_hud numCollectedChanged:_numCollected];*********************************
}
}
}
_player.position = position;
}
#implementation HudLayer
{
CCLabelTTF *_label;
}
- (id)init
{
self = [super init];
if (self) {
....
}
return self;
}
#end
The instance variables _meta and _hud are implicitly generated by the compiler, as a result of your property definitions:
#property (strong) CCTMXLayer *meta;
#property (strong) HudLayer *hud;
This is a fairly recent addition to Objective-C. You used to need to use #synthesize in your .m file in order to create corresponding instance variables. However, starting from Xcode 4.4, if you don't include a #synthesize for a property, the compiler will automatically generate one for you. For your properties, it's implicitly generating:
#synthesize meta = _meta;
#synthesize hud = _hud;
Here's an article with more details if you're interested.

imageitem class define

i need to build an application that define an array that should be made of image items.
every image iten has an image, a name and a photographer name.
i build my image item class and i want you to check if my define is correct and good(i just start to learn objective c).
i want you to emphasize on the set's methods.
here is the photoitem.h:
#import <Foundation/Foundation.h>
#interface photoItem : NSObject
{
UIImage *imageView;
NSString *photoNameLabel;
NSString *photographerNameLabel;
UIButton *viewPhoto;
}
#property(readonly) NSString *name;
#property(readonly) NSString *nameOfPhotographer;
#property(readonly) UIImage *imageItem;
-(id)makePhotoItemWIthPhoto:(UIImage*)image name:(NSString*)photoName photographer: (NSString*)photographerName;
#end
here is my photoitem.m:
#import "photoItem.h"
#implementation photoItem
#synthesize name;
#synthesize nameOfPhotographer;
#synthesize imageItem;
-(id)makePhotoItemWIthPhoto:(UIImage*)image name:(NSString*)photoName photographer:(NSString*)photographerName
{
[self setName:photoName];
[self setNameOfPhotographer:photographerName];
[self setImageItem:image];
return self;
}
-(void) setName:(NSString *)name
{
photoNameLabel = name;
}
-(void) setNameOfPhotographer:(NSString *)nameOfPhotographer
{
photographerNameLabel = nameOfPhotographer;
}
-(void)setImageItem:(UIImage *)imageItem
{
imageView = imageItem;
}
#end
i hope you could fix my errors(if there are some).
thanks.
Two problems come to mind:
1) -(id)makePhotoItemWIthPhoto:name:photographer: might be better as -(id)initWithPhoto:name:photographer:. Otherwise the caller needs to alloc and init an object first so that self is valid, then call your method. At that point, the return of self doesn't make sense.
Example:
-(idinitWithPhoto:(UIImage*)image name:(NSString*)photoName photographer:(NSString*)photographerName {
self = [super init];
if (self) {
[self setName:photoName];
[self setNameOfPhotographer:photographerName];
[self setImageItem:image];
}
return self;
}
2) The three readonly properties don't seem to have any purpose since they have no connection to the variables that you initialize in the makePhotoItemWIthPhoto: method.

i try to understand initializers in obj-c and i can't

I try to understand how to work simple init funcion and I don't know where I have made a mistake. Can somebody assist?
Rectangle.h
#interface Rectangle : NSObject
{
int width;
int height;
}
-(id)initObject;
#end
Rectangle.m
#implementation Rectangle
-(id)initObject{
if (self = [super init]) {
height = 5;
width = 7;
}
return self;
}
#end
And in ViewController.h i import Rectangle.h, declare *rect object and in .m i execute(? run?) initObject.
ViewController.h
#import <UIKit/UIKit.h>
#import "Rectangle.h"
#interface ViewController : UIViewController
{
Rectangle *rect;
}
#end
ViewController.m
-(void)viewDidLoad
{
rect = [[Rectangle alloc] initObject];
NSLog(#"%#", rect);
[super viewDidLoad];
}
initObject return me:
2011-11-21 09:43:02.625 initializers[43693:f803] <Rectangle: 0x6ab1660>
The only problem with your code that I can see is you called your initializer -initObject for no good reason. It's not taking any parameters at all, so you really should just call it -init like every other parameterless initializer in the system.
As for the log output, I imagine your confusion lies in the fact that it says <Rectangle: 0x6ab1660>. This is perfectly normal. The default implementation of -description (the method that returns this output) is the name of the class of the object followed by the object's address. In other words, -[NSObject description] is likely to be implemented something like the following:
- (NSString *)description {
return [NSString stringWithFormat:#"<%#: %p>",
NSStringFromClass([self class]),
self];
}
This means that instance variables of your object are not going to be printed. A number of built-in classes do print their instance variables when logged, but this was implemented specifically for that class and is not a generic mechanism. If you want to verify that your Rectangle object is correct, you could implement -description like so:
- (NSString *)description {
return [NSString stringWithFormat:#"<%#: %p width=%d, height=%d>",
NSStringFromClass([self class]),
self,
width,
height];
}
No error! Since your Rectangle class has no description method, calling NSLog(#"%#", rect); will return the class of the object, followed by its address in memory.
If you want to print width and height of the rectangle you may use something like:
in Rectangle.h
#interface Rectangle : NSObject
{
int width;
int height;
}
-(id)initObject;
#property int width, height;
#end
in Rectangle.m
#implementation Rectangle
#synthesize width, height;
-(id)initObject{
if (self = [super init]) {
height = 5;
width = 7;
}
return self;
}
#end
and then call
NSLog(#"width=%d, height=%d", [rect width], [rect height]);

Where is my variable ? objective-c

I initialize a view(Image) through:
Image *myImageView = [[Image alloc]init];
myImageView.myId = randomImageNumber;
[myImageView initWithImage:myImage];
At the Image class I do a Log(LOG1) and get the previously set randomImageNumber.
Later on, in the very same Class, I do a second Log(LOG2).
Why does my second log have no value anymore ?
Here my implementation-file of the Class Image:
#synthesize myId;
-(id) initWithImage: (UIImage *) anImage
{
NSLog(#"LOG1%d",myId);
if ((self = [super initWithImage:anImage]))
{
self.userInteractionEnabled = YES;
}
return self;
}
}
-(void)touchesBegan...
....
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"LOG2%d",myId);
}
The "return self" empties myId which i declared in the header-file and which was set at the initialisation.
How do I prevent that ?
my Headerfile looks like this:
#interface Image : UIImageView
{
int myId;
}
#property (assign) int myId;
#end
I think I found it:
https://www.google.com/maps/place/Variable/#53.626739,10.025728,17z/data=!3m1!4b1!4m2!3m1!1s0x47b1885360fab615:0x584b82c7dfb5f612
Can you check if this Variable is yours?
besties
phil
Couple things in your code. NEVER call init more than once on an object, that just screws up your object.
Change it to this:
Image *myImageView = [[Image alloc] initWithImage:myImage];
myImageView.myId = randomImageNumber;
That is your problem, by default when initializing a subclass of NSObject, all properties are set to 0 (or nil if they are pointers).
If you need to have a default value for myId then do this:
// Image.m
#implementation
// other code
-(id) initWithImage:(UIImage *) image
{
if (self = [super initWithImage:image])
{
self.myId = randomImageNumber;
}
return self;
}
// other code
#end