Extending a LHSprite from LevelHelper - ios

I am trying to extend a LHSprite from LevelHelper so I can add different behavior to different elements.
Ok, so far so good, but what do you want?
Basically imagine a set of characters that have different movements. I want to be able to define a class Character that extends LHSprite and defines a move method. All characters should extend this Character class and define their own movement. This way I can add elements to the map and I can treat them (in terms of movement) in the same way.
Ok, I understood that, but what have you done?
So far I have followed this link about custom LHSprites but I am facing a problem:
The first difference from my case to that one is that I don't add my elements using the LevelHelper (at least the ones I am trying to extend). I add my elements in code because I want a random number of those elements in a random position.
So I have made this init method that creates a cop (that extends Character and Character extends LHSprite). This method actually looks more to "add to loader" method but whatever:
- (id) initInLoader:(LevelHelperLoader *) loader andNumber: (int) i
atPos: (CGPoint) pos{
self = (Cop *) [ loader createBatchSpriteWithName:#"cop_01"
fromSheet:#"copSheet" fromSHFile:#"enemies" ];
_uniqueName = [ NSString stringWithFormat:#"Cop_%d", i + 1 ];
[ self setUniqueName: _uniqueName];
[ self prepareAnimationNamed:#"cop" fromSHScene:#"enemies" ];
[ self setAnimationDelayPerUnit: 1.0f/70.0f ];
self.position = pos;
[ self playAnimation ];
}
So far so good. I can see my cop standing and animated. However, when I try to call the move method (or any other method) it gives me an unrecognized selector crash.
I believe this error happens because of the cast to (Cop *) but I don't know how to surpass this.

I found out that I needed to add a tag to the level helper. However, this tag wasn't updating in the source files so I had to add it manually in the LevelHelperLoader.h, at the enum LevelHelper_TAG.
Then I had to register that tag when I initialize the LevelHelper
[[LHCustomSpriteMgr sharedInstance] registerCustomSpriteClass:[Cop class]
forTag:COP_TAG];
And I had to pass that tag when I get that element from the loader:
self = (GlowWorm *) [ loader createBatchSpriteWithName:#"cop_01"
fromSheet:#"copSheet"
fromSHFile:#"enemies"
tag: COP_TAG];
EDIT
To answer the user that made a new answer with a problem
Don't forget to tell about the new class to LevelHelper before you initialize LevelHelper. You can do that with this line:
[[LHCustomSpriteMgr sharedInstance] registerCustomSpriteClass:[Cop class]
forTag: COP_TAG ];

Related

Variable Assignment and string arrays Obj-c (iOS) for beginner

I created a single view app, added a label, an un-editable text view and a button, I have an array of strings. Really simply just want to click the button and change the string at random.
- (IBAction)viewNextPressed:(id)sender {
NSArray *affirmationStrings = #[
#"String 1 Pressed",
#"String 2 Pressed",
#"String 3 Pressed"
];
//Generate a random index from our array
int randomNIndex = arc4random() % [affirmationStrings count];
//Display a string from our array
self.displayAffirmationText.text = affirmationStrings[randomNIndex];
}
#end
Obviously this works fine for this example but its horribly inefficient as its generating the array each time the button is clicked. Where is the best place to store the array so its generated upon load and I can just access it when needed?
I see viewDidLoad but as a beginner I want to try to understand best practice for simple tasks. Secondly is the way I am storing strings fine for a large sample of say 500-1k+ strings?
For a relatively small number of strings, a better option would be to:
add either a property or an instance variable to your class
initialise said property or instance variable either in your init method, or, in the case of a View Controller, in viewDidLoad.
So, in the case of an instance variable:
#implementation MyViewController
{
NSArray *_affirmationStrings;
}
...
- (void)viewDidLoad
{
[super viewDidLoad];
_affirmationStrings = #[ ... list of strings ... ];
}
Then refer to it via _affirmationStrings.
In the case of a property, visible to other classes, read-only, with lazy initialization:
In .h:
#interface MyViewController : UIViewController
#property (readonly) NSArray *affirmationStrings
#end
In .m:
- (NSArray *)affirmationStrings
{
if (!_affirmationStrings)
_affirmationStrings = #[ ... list of strings ... ]
return _affirmationStrings;
}
Then refer to it via self.affirmationStrings.
There are also alternatives to make it read/write (so you can set the values from another class), or visible only within the class, etc.
If you want to handle lots of strings, you probably at the very least want to move the list outside of your code, to an external file (text file, JSON, XML, plist...). You can then either load it from there at once and keep it, or load it on demand (and forget about it once you no longer need it, hence reloading it again if you need it again).
You could also store the data in a database, either via Core Data or directly with SQLite.
It really all depends on your goals/requirements.

Calling a instance method while using childNodeWithName

Is it possible to call an instance method without using an instance variable or #property?
Here is how I create an instance of a class. Within the method, I try to call on the class's instance movement method to force the instance to move:
-(void)createCharacterNPC
{
int randomness = [self getRandomNumberBetweenMin:1 andMax:20];
for (int i = 0; i < randomness; i += 1)
{
NSString *npcName = [NSString stringWithFormat:#"anNPC%i", randomness];
NPCclass *NPC = [[NPCclass alloc] initWithName:npcName];
NPC.position = CGPointMake(self.size.width/2, self.size.height/2);
NPC.zPosition = 1.0;
[_worldNode addChild:NPC];
// THIS OBVIOUSLY WORKS. But I can't use this technique outside this method.
[NPC beginMovement];
// THIS IS WHAT I WANT, BUT XCODE DOESN'T ALLOW ME TO WRITE CODE THIS WAY.
[[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
}
}
Is there a way to allow [[_worldNode childNodeWithName:#"anNPC1"] beginMovement]; to work? Or some way similar to this so I wouldn't have to have create an instance variable of NPC (like so: _NPC)?
I'm asking because all of this is happening inside a mini-game scene and NPCclass will be initialized a random number amount of times (with arc4random() method). NPCclass moves on its own using vector (physics in a platformer) movement but I need to initialize its movement method right after creation then I need to periodically access each individually created instance of NPCclass using its name in other methods of the scene. Since I don't know how many NPCclass instances will be created each time the mini-game is played, I CAN'T use IVAR's or something like #property NPCclass *anNPC;
Please help.
Xcode complains about
[[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
because the method -childNodeWithName returns an SKNode object. Instances of the SKNode class do not respond to the selector -beginMovement (or as Xcode puts it, no visible #interface declares the selector -beginMovement). Xcode shows this to you to force you to make sure you wrote what you wanted to write. Since you are sure, you can tell Xcode that the returned object is of the type NPCclass.
(NPCclass *)[_worldNode childNodeWithName:#"anNPC1"]
Now you can expand the statement to call -beginMovement.
[(NPCclass *)[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
Note
There are a few concepts which you might be confusing. NPCclass is a class. +node is a class method of SKNode, which you can call with [NPCclass node];. -beginMovement is an instance method, called with:
NPCclass *npc = [NPCclass node];
[npc beginMovement];
Or:
[(NPCclass *)anyObject beginMovement];
// make sure anyObject responds to this selector though, or you app will crash.
Class methods are prefixed with a +, instance methods with -.
Class methods do not use an instance, just the class name.
As an example consider the
NSString` class method: `+ (id nullable)stringWithContentsOfFile:(NSString * nonnull)path
and a usage:
NSString *fileData = [NSString stringWithContentsOfFile:filePath];

NSMutableArray Interaction Troubles With Collisions

I am having trouble getting objects added to my NSMutableArray to log properly (which definitely means they won't process any of the appropriate functions correctly) with Spritebuilder [version 1.4.9, from the Apple App Store]. I am creating several objects using the same class, but each new one is overriding the older objects which exist. I thought an array would help keep things in order (and then on collision, I could call the array to check for which object was collided with), but it simply is not working that way - at all. Here is the relevant code.
Main.h
#property Coven *coven;
#property Nellie *nellie;
#property NSMutableArray *array;
//Physics, other things
Main.m
/Adding other things...
-(void) addCovenMember{
//This function is called on a RANDOM time interval
_array = [[NSMutableArray] alloc]init];
for (i = 0, i < 15, i++){
_coven = (Coven*) [CCBReader load:#"CovenMember"];
[_array addChild:_coven];
}
[_physicNode addChild:_coven];
}
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair nellie:(Nellie*)nellie coven:(Coven*)coven{
for (_coven in _array){
NSLog(#"%#",_coven.name)
if (CGRectIntersectsRect(_nellie.boundingBox, _coven.boundingBox){
NSLog(#"We're intersecting!");
}
}
Coven. h
//Nothing Important Here
Coven.m
-(void)didLoadFromCCB{
self.physicsBody.CollisionType = #"coven";
}
Nellie.h
//Nothing Here
Nellie.m
-(void) didLoadFromCCB{
self.physicsBody.CollisionType = #"nellie";
}
The collision is logging with every collision - but only as the name of the LATEST _coven member to be generated, no matter what I am colliding with. This also means that the _coven.boundingBox is solely on the latest _coven member and interaction only occurs when I hit the new member as soon as it generates on to the screen.
Any ideas? Any help?
Note: This is also posted on the Spritebuilder website - I decided to post it here as well because answers can be a little slow on those forums.
The -(void) addCovenMember overwrites (creates a new instance) of _array every time it's called. Thus, when you try to iterate in -ccPhysicsCollisionBegin: you'll only ever see 1 coven.
Add a nil check around your array creation:
if(_array == nil) {
_array = [[NSMutableArray] alloc]init];
}
The for loop in the -addCovenMember method looks broken (at least not a c loop). Reaplace the , with ;:
for (i = 0; i < 15 i++){
Also, using for(_coven in _array) seems wrong, you already have a property self.coven (presumably) with a backing _coven ivar. Try changing it to for(Coven * c in self.array) and use the local c in the loop:
for (Coven * c in _array){
NSLog(#"%#",c.name)
if (CGRectIntersectsRect(_nellie.boundingBox, c.boundingBox){
NSLog(#"We're intersecting!");
}
}
To everyone out in the world struggling with their ccPhysicsCollisions, arrays may not be the answer - this was a simple fix that left me incapacitated for days.
Using the basic ccPhysicsCollisionsBegan that ships with spritebuilder, try this without arrays first:
Scene.m
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair nellie:(Nellie*)nellie coven:(Coven*)coven{
[_coven stopAction:coven.path];
}
I initially created the method with:
[_coven stopAction:_coven.path];
Yes, that (underscore) set me back three weeks. Be sure you refer to the object interacting through the physics delegate, and not the object itself, which in my case, was constantly being overwritten by the new ones being generated.
Check your underscores.
Solved! :D
Thanks to #thelaws for your help! I'll get better at Obj C... eventually.

how to create a instance of objective-c class by name?

I want get something like:
#define weaken(object) ...
----
ClassABCD * abcd = [ClassABCD new];
weaken(abcd);
weakAbcd.tag = 0;
----
I have some code below:
#define weaken(x) __weak typeof(x) weak##x = x
But it only can use "weakabcd", and not "weakAbcd". Any idea why, and how to fix it?
Based on the question title, NSClassFromString() will do it. Based on the code you offer, I'm not so sure the two match.
This is a line from one of my projects where I am creating a UITableViewCell subclass instance based on the string variable prefCellClassName that I define elsewhere. (For completeness, prefCellStyle and prefCellReuseIdentifier are also variables that I define elsewhere.)
cell = [[NSClassFromString(prefCellClassName) alloc] initWithStyle:prefCellStyle reuseIdentifier:prefCellReuseIdentifier];
To make a class from a string:
id someObject = NSClassFromString("MyClassName");
When you do something like this, it's always good to check if your object responds to the method you're trying to pass it like so:
if ([someObject respondsToSelector:#selector(someMethod)])
{
[someObject someMethod];
}
It's features like this that make objective-c the dynamic language that it is

EXC_BREAKPOINT: Message sent to deallocated instance

I get the above message in XCode 4.6. I've done a pretty thorough search and but nothing seems to match the exact circumstances surrounding my issue. Admittedly, I'm relatively new to iOS dev, and memory-management has never been my strong suit, but this just has me completely miffed.
I have an instance variable theLink which is defined in the class Game as follows:
#interface Game : NSObject
// Class objects
#property(nonatomic,retain) NSMutableArray *queryItems;
#property(nonatomic,retain) NSMutableArray *theArray;
#property(nonatomic,retain) NSString *theLink;
#property(nonatomic,retain) NSString *thePath;
theLink is set in the makeGame method which is called in the method initialiseGame in my view controller:
- (void) initialiseGame
{
bool gameCreated = FALSE;
while (!gameCreated)
{
gameCreated = [theGame makeGame:#"ptl"];
}
[loadingIndicator stopAnimating];
[loading setText:#"Tap to Start"];
[self performSelector:#selector(setLabels) withObject:nil afterDelay:0.0];
}
(Note: the performSelector afterDelay is used to allow the view to update before continuing. Bit of a hack but I couldn't work out a better way!)
The app then loads the game, and when the user taps the screen to start, the next method which is called from the view controller is:
- (void) setupLink
{
...
for(int i=0; i<[theGame.theLink length]; i++) {
...
}
}
It is on this reference to theGame.theLink where I'm am getting the crash.
What has me most confused is that if I call theGame.theLink from inside the initialiseGame method, it is displays correctly, and also calling any other variable from the Game class (such as thePath or theArray works perfectly, so theGame object has not been deallocated in it's entirety, only the variable theLink.
It seems to me that the variable is being deallocated somewhere as the view controller is being updated. I haven't released the variable, and can't work out why only this variable is being deallocated. As I said at the start, memory-management is not my strength!
Any help/ideas would be hugely appreciated. Let me know if you require any more details.
Thanks heaps,
Andrew
EDIT: Setting of theLink within makeGame
- (bool) makeGame:(NSString*)gameType
{
...
[self getLink];
}
- (void) getLink
{
...
if (... && ((arc4random() % 10) > 8))
{
theLink = #"Animals";
}
}
There are many different ways theLink may be set, depending on random numbers and other factors. This is the most basic form which simply sets it to a static string. It doesn't matter how theLink is set or what it is set to, the program always crashes at the same point.
If theLink is being set to the parameter being passed to it ,#"ptl" or some similar temporary string, it will give you a problem, because it is just a pointer pointing at the current location that is holding #"ptl". After the makeGame method is completed, your system will assume that it is all done with #"ptl" and just free it up.
When you make an #"stringwhatever" in your code, it is supposed to be the equivalent of making an NSObject that is an immutable literal instance of #"stringwhataver". It should, in theory handle all the reference counting in a nice way, but when you are doing your own memory management, there are so many ways to lose count of your references.
There's a pretty simple rule to follow. If you've declared properties, access them via the property. To do otherwise (as you are doing above, with theLink = ...) bypasses all of the memory management built into the property accessors.
self.theLink = ...
Would have solved this problem under MRC. Switching to ARC has "solved" your problem without you understanding the root cause.

Resources