Text Typing Effect in iPhone cocos2d Game - iphone

In many games, when a character is speaking (dialogue), the text has a typing effect, where it looks like you are watching the character type the text.
What would be a good way to achieve this look and (simple) "animation" for an iPhone game which uses cocos2d?
It's good if there's a way to do it with cocos2d, but I'm not completely opposed to layering a UIView subclass (UILabel?) on top of cocos2d's EAGL (OpenGL ES) view.

I ended up using built-in UIKit elements (UILabel) and adding them as subviews to the window.

the built-in UIKit could not works well with cocos2d-built-in UIKit,such as CCLabel,CCSprite.

I would suggest using a CCLabelTTF and CCAction as follows:
- (void) typeText
{
static NSUInteger currentCharacter = 0;
// Your instance variable stores the text you want to "type" in it:
_text;
// Sorry, I can't remember substring functions, this is pseudo-code:
NSString *substring = [_text getSubstringFrom:0 to:currentCharacter];
// Also can't remember how to get length of a string (yipes)
NSUInteger stringLength = [_text length];
if (currentCharacter < stringLength)
{
CCSequence *loop = [CCSequence actions:[CCDelayTime actionWithDuration:0.3f],[CCCallFunc actionWithTarget:self selector:#selector(typeText)],nil];
[self runAction:loop];
}
}
This is untested code. Also, it assumes that the typeText function is implemented in a subclass of CCNode because it calls [self runAction:]

Related

C4: Add panning to an object other than "self"

I watched the C4 tutorial on adding a pan gesture to an object and animating it to return to its original position when the panning is finished. I'm trying to add this to three individual objects. I have it working with one object so far to move it and reset it to a CGPoint, but for it to work, I have to add the pan gesture to "self", not the object. For reference, I'm pretty much using the code from here:
http://www.c4ios.com/tutorials/interactionPanning
If I add the gesture to the object itself, sure, it pans around, but then it just leaves itself at the last touch location. However, I'm assuming that leaving the gesture on "self" will affect more than just the object I want to move, and I want to be able to move the three objects individually.
I'm using roughly the same modification to the "move" method that's used in the example:
-(void)move:(UIPanGestureRecognizer *)recognizer {
[character move:recognizer];
if (recognizer.state == UIGestureRecognizerStateEnded) {
[character setCenter: charStartOrigin];
}
}
And then a new method to spawn the object:
-(void)createCharacters {
character = [C4Shape ellipse:charStart];
[character addGesture:PAN name:#"pan" action:#"move:"];
[self.canvas addShape:character];
}
The example link you are working from is sneaky. Since I knew that there was only going to be one object on the canvas I knew I could make it look like I was panning the label. This won't work for multiple objects, as you have already figured out.
To get different objects to move independently, and recognize when they are done being dragged, you need to subclass the objects and give them their own "abilities".
To do this I:
Subclass C4Shape
Add custom behaviour to the new class
Create subclassed objects on the canvas
The code for each step looks like the following:
subclassing
You have to create a subclass that gives itself some behaviour. Since you're working with shapes I have done it this way as well. I call my subclass Character, its files look like this:
Character.h
#import "C4Shape.h"
#interface Character : C4Shape
#property (readwrite, atomic) CGPoint startOrigin;
#end
I have added a property to the shape so that I can set its start origin (i.e. the point to which it will return).
Character.m
#import "Character.h"
#implementation Character
-(void)setup {
[self addGesture:PAN name:#"pan" action:#"move:"];
}
-(void)move:(UIGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded) {
self.center = self.startOrigin;
} else {
[super move:sender];
}
}
#end
In a subclass of a C4 object, setup gets called in the same way as it does for the canvas... So, this is where I add the gesture for this object. Setup gets run after new or alloc/init are called.
The move: method is where I want to override with custom behaviour. In this method I catch the gesture recognizer, and if it's state is UIGestureRecognizerStateEnded then I want to animate back to the start origin. Otherwise, I want it to move: like it should so I simply call [super move:sender] which runs the default move: method.
That's it for the subclass.
Creating Subclassed Objects
My workspace then looks like the following:
#import "C4WorkSpace.h"
//1
#import "Character.h"
#implementation C4WorkSpace {
//2
Character *charA, *charB, *charC;
}
-(void)setup {
//3
CGRect frame = CGRectMake(0, 0, 100, 100);
//4
frame.origin = CGPointMake(self.canvas.width / 4 - 50, self.canvas.center.y - 50);
charA = [self createCharacter:frame];
frame.origin.x += self.canvas.width / 4.0f;
charB = [self createCharacter:frame];
frame.origin.x += self.canvas.width / 4.0f;
charC = [self createCharacter:frame];
//5
[self.canvas addObjects:#[charA,charB,charC]];
}
-(Character *)createCharacter:(CGRect)frame {
Character *c = [Character new];
[c ellipse:frame];
c.startOrigin = c.center;
c.animationDuration = 0.25f;
return c;
}
#end
I have added a method to my workspace that creates a Character object and adds it to the screen. This method creates a Character object by calling its new method (I have to do it this way because it is a subclass of C4Shape), turns it into an ellipse with the frame I gave it, sets its startOrigin, changes its animationDuration.
What's going on with the rest of the workspace is this (NOTE: the steps are marked in the code above):
I #import the subclass so that I can create objects with it
I create 3 references to Character objects.
I create a frame that I will use to build each of the new objects
For each object, I reposition frameby changing its origin and then use it to create a new object with the createCharacter: method I wrote.
I add all of my new objects to the canvas.
NOTE: Because I created my subclass with a startOrigin property, I am able within that class to always animate back to that point. I am also able to set that point from the canvas whenever I want.

Adding object to multiple views

Im have a subclass of UIView, called PinView, which contains an image. Basically PinView gets added multiple times to my app, and then I perform a transform on PinView objects. The issue is that when I add a lot of PinViews, my app gets sluggish because I am transforming each PinView.
Ideally, I want to have one 'static' PinView that gets added to a UIView multiple times but i only have to transform it once. Currently this doesn't seem to work. Every time I add the static PinView to my UIView, it will only ever appear in the UIView once (due to it only being able to have one superview I'm guessing).
Im struggle to find out the best way to go about solving this problem - how do I use a single pointer to a PinView, add it multiple times to a UIView and be able to perform a transform on the PinView which gets passed on to PinViews displayed in my UIView? (by the way, the transform is always the same for all the PinViews).
Im assuming this will be the best way to get a performance improvement, if this is not the case please let me know.
UPDATE:
- (void)layoutSubviews
{
CGAffineTransform transform = CGAffineTransformMakeScale(1.0/self.zoomValue, 1.0/self.zoomValue);
NSMutableArray *mut = nil;
PinView *pinView = nil;
CallOutView *callOut = nil;
//get all dictionary entries
for(NSString *identifier in self.annotationsDict.allKeys){
//get the array from dictionary
mut = [(NSArray *)([self.annotationsDict objectForKey:identifier]) mutableCopy];
//change layout if not nil
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = ((PinView *)[mut objectAtIndex:PIN]);
pinView.transform = transform;
[mut replaceObjectAtIndex:PIN withObject:pinView];
}
if([[mut objectAtIndex:CALL_OUT] isKindOfClass:[CallOutView class]]){
callOut = ((CallOutView *)[mut objectAtIndex:CALL_OUT]);
callOut.transform = transform;
[mut replaceObjectAtIndex:CALL_OUT withObject:callOut];
if(pinView !=nil)callOut.center = CGPointMake(pinView.center.x, pinView.center.y - pinView.frame.size.height);
}
[self updateAnnotationsKey:identifier forObject:[NSArray arrayWithArray:mut]];
mut = nil;
pinView = nil;
callOut = nil;
}
}
UPDATE:
Removed the above and now just have:
- (void)layoutSubviews
{
CGAffineTransform transform = CGAffineTransformMakeScale(1.0/self.zoomValue, 1.0/self.zoomValue);
for(UIView *view in self.subviews){
view.transform = transform;
}
}
This can't be done I'm afraid. Each UIView instance can only be added to the screen once.
If all your views have similar transforms, you might have more luck using something like CAReplicatorLayer, which is a system for automatically creating duplicates of CALayers with different transforms.
That will only works if your views are all arranged in a grid or circle or something though. If they are just dotted randomly, it won't help.
If you are trying to draw more than 100 views, you're probably just bumping up against the fundamental performance ceiling of Core Animation on iOS.
The next approach to try would be to use OpenGL to draw your pins, perhaps using a library like Sparrow or Cocos2D to simplify drawing multiple transformed images with OpenGL (I'd recommend Sparrow as it integrates better with other UIKit controls - Cocos is more appropriate for games).
UPDATE:
This code is unnecessary:
mut = [(NSArray *)([self.annotationsDict objectForKey:identifier]) mutableCopy];
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = ((PinView *)[mut objectAtIndex:PIN]);
pinView.transform = transform;
[mut replaceObjectAtIndex:PIN withObject:pinView];
}
The code below is sufficient, because setting the transform doesn't modify the pointer to the object, so it will update the object in the array even if the array isn't mutable, and array objects are declared as 'id' so they don't need to be cast if you assign them to a variable of a known type.
mut = [self.annotationsDict objectForKey:identifier];
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = [mut objectAtIndex:PIN];
pinView.transform = transform;
}
I would also think you can remove the isKindOfClass: check if you only ever use those array indices for those object types. It may seem like a good precaution, but it carries a performance penalty if you're doing it over and over in a loop.
But for 10 views, I just wouldn't expect this to be that slow at all. Have you tried it without moving the callout centres. Does that perform better? If so, can you limit that to just the callouts that are currently visible, or move them using CGAffineTransformTranslate instead of setting the centre (which may be a bit quicker).

Cocos2D calling an object init from a scene layer init

Hi I'm currently working on an iPhone game, top-down Strategy RPG (kinda like Fire Emblem), I have my tiled map set up, and the gameplay layer and some characters and enemies set up and drawn on the screen and moving around. My question really just to help wrap my head around how I can initialize my characters easliy. My character init is simple, it just loads the animations and set the stats as such:
//Hero Class
-(id)init
{
if(self = [super init])
{
characterClass = kHeroClass;
[self initAnimations];
[self declarePlayer:Hero withLevel:1 withStrength:15 withDefence:14 withMindpower:15 withSpeed:26 withAgility:26 withLuck:12 withEndurance:10 withIntelligence:15 withElement:kFire withStatus:kStatusNormal];
}
return self;
}
and so in the game scene, can I just be like:
(in .h file)
PlayerCharacter *mainChar;
#property(retain)PlayerCharacter *mainChar;
(in .m file)
-(id) init
{
if((self=[super init]))
{
//the usual stuff
mainChar = [MainCharacter init];
return self;
}
}
However, I've seen online and in tutorials people using
MainCharacter *mainChar = [MainCharacter alloc];
would that be the same as
mainChar = [MainCharacter init];
if not could someone help clarify which syntax to use. Thanks very much :D Have a great day!
I think you should consider reading quickly through some introduction tutorials. This one is awesome and will get you used to the syntax and semantics of Objective-C:
http://cocoadevcentral.com/d/learn_objectivec/
alloc will allocate memory for an object and init will set things up like a regular constructor. You will also see initWith... style functions which can also be used like so:
MyObjectClass *instance = [[MyObjectClass alloc] init];
This then needs to be released in the same class it was created in the dealloc method.
As for setting up objects, it's better to not use a really long method name declarePlayer:Hero withLevel... but rather:
Set up the object and then alter properties:
Player *player = [[Player alloc] init];
player.health = 10;
player.armor = 20;
...
Once you become familiar with Objective-C as a language, tacking cocos2d and any other code will be much easier. For that, you can visit the the programming guide and find tutorials around the web like at www.learn-cocos2d.com.

How to create Enemies class in cocos2d

I am using cocos2d for the iphone
I want to create a class for Enemies
see my code
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Enemies : CCSprite {
float imageHeight;
}
#property float imageHeight;
+(id) enemies;
#end
#implementation Enemies
#synthesize imageHeight;
+(id) enemies{
return [[[self alloc] initWithFile:#"enemy.png"] autorelease];
}
#end
I want the to set the imageHeight
float imageHeight = [enemy texture].contentSize.height;
so whenever I create an instance of the class Enemies I it should have imageHeight set properly?
Thanks
note: I am trying to improve this code
ahmed, keep the enemies method the same, and add this to your class:
-(id) init {
if (self = [super init]) {
float textureHeight = [enemy texture].contentSize.height;
[self setImageHeight:textureHeight];
}
return self;
}
This will set imageHeight for all new instances of the Enemies class.
Override initWithFile in Enemies class.
Then Inside it call, [super initWithFile passing the same Filename string]
After which you can set the imageHeight variable in the next line as the texture would have been loaded and height will be accessible.
Cocos2d automatically sets the height of some things based on the image itself. Look for the sprite's setHeight property, it should be easy to find in the docs.
However, your question looks like it could use rewording. Your "Enemy" should be data, not related to any display (sprites) whatsoever, unless you're going to make a really small app.
Check out Beginning XNA books for nice examples of how to abstract superclass enemy and players, if you can use C# and translate between iOS. They will work very much the same. (I wish I knew a good game programming book for iPhone, but all of the ones I open have flaws or bad design that only experienced programmers can get past.
Anywho, if you mix data and images, it usually means to redesign your code

NSMutableArray and memory dealloc

Im making an app for the iphone using cocos2d and i am trying to figure out the best approach for removing items from a NSmutableArray and from the layer at the same time.
What i mean by this is that the objects within the array inherit from ccNode and contain a ccsprite which i have added as a child to the cclayer. The below code is in a cclayer that has the nsmutablearray called bonusicons.
-(void) AddNewBonusIcon: (int) colour :(int) pos{
BonusIcon *newbonus;
CGSize winSize = [[CCDirector sharedDirector] winSize];
int maxX = winSize.width;
int maxY = winSize.height;
int posX, posY;
newbonus = [[BonusIcon alloc] init];
[newbonus setBonusColour: colour];
int bonusOffset = 0;
posX = anchorX;
posY = anchorY;
bonusOffset = [bonusIcons count]*([newbonus.bonus_sprite boundingBox].size.width/2 + 12);
newbonus.bonus_sprite.position = ccp(posX+bonusOffset,posY);
[newbonus.bonus_sprite setTag:pos];
[self addChild:newbonus.bonus_sprite];
[bonusIcons addObject:newbonus ];
[newbonus release];
}
This appears to do what i want for adding the objects sprite to screen and adding the objects to the nsmutablearray. Now of course this is probably not the correct way to do it so shout at me if not!
next i try to delete the objects from the array and from the screen. I can delete them from the array with no problems i just do the following
for (int i = INITIAL_BONUSES-1; i>=0; i--) {
[bonusIcons removeObjectAtIndex:i];
}
this of course leaves the sprites on screen. so how do i approach what i am trying to do so that i can remove both the sprites from screen and the objects from array that the sprite is associated with. I can remove the sprites from the screen by using the tags and typing
[self removeChildByTag:i cleanup:YES]; but then i get errors when trying to remove items from the array . i assume because i have deleted a part of the object already and the dealloc of the ccnode can no longer find the sprite to release?
so any pointers/tips etc of how i should be doing this would be much appreciated. I have read a bunch of stuff on memory management which i believe is my current issue but i just dont seem to be getting it right.
thanks all
edit: ok since posting this i have removed the sprite dealloc from the ccnode itself and added it to the cclayer above it. This has stopped the crashing so i guess i was right with the problem i was having. I of course do not think the way i solved it is the most ideal way but it will do until i find a better way.
You don't have it in the code you posted, but your question seems to strongly imply that you are calling dealloc. The only place you should ever call dealloc is [super dealloc] at the end of a class's dealloc method. Calling it on anything but super or in any other place is wrong and will lead to errors about prematurely deallocated objects (because, well, that's what it does).
If this is what you're doing, I strongly suggest you read Apple's memory management guide. It lays out how memory management works in Cocoa very simply yet thoroughly.