Array of CCSprite's VS CCSpriteBatchNode and an NSMutable array? - iphone

I have a little archer game I'm working on, and previously in my code I put every arrow sprite into a CCSprite[7]; array, and inside ccTime I would update the x/y coordinates and do some math to make the arrows move beautifully and smooth. So all the math/angles/movement works.
Later, while trying to implement collision detection, I couldn't use a data type that would have made my life so much easier, I think it was CGRect, and it would get the contents of an sprite and check if that was intersecting another sprite. The error said that I had to use members of a NSMutable array or something like that, which is great because this is better for memory anyway, to put my sprites inside a batchNode and an NSMutable array. But there is a problem.
In every tutorial I've seen, projectiles move based on an action sequence with a predetermined time. Just an example of an action sequence (not in my code)
id action = [Sequence actions:
[ScaleTo actionWithDuration:.3 scale:0.7f],
[ScaleTo actionWithDuration:.3 scale:1.0f],
nil];
Everywhere I've seen, this is how sprites move around, but I cant do that because arrow velocities change depending on how long touches is held, angles that make the arrow look realistic and stuff like that.
So in my code in touchesBegan:
ccTouchesBegan:(NSSet *) blah blah {
...
...
self.nextProjectile = [[CCSprite spriteWithFile:#"arrow.png"];
_nextProjectile.rotation = vector2 - 90; //this is angle of where the user touched screen
//add projectiles to array
_nextProjectile.tag = arrowTracker;
[_batchNode addChild:_nextProjectile z:1];
[_projectiles addObject:_nextProjectile];
//Release? If I don't have this the arrow fails to move at all... and it was in the tutorial
if(_nextProjectile.tag == 1){
[_nextProjectile release];
_nextProjectile = nil;
}
}
Every time I touch, the first arrow doesn't shoot out (this is NOT the problem, I can fix this easy) and the arrows shoot out perfectly, the movement is the exact same as when I was using a CCSprite array. The only problem is, every time I call ccTouchesBegan if the previous arrow was in mid-flight, it will stop all actions and just sit there. Mid air. So my problem is a logic error, obviously I'm doing something wrong in touchesBegan because it terminates the projection of the previous arrow!
So my questions are:
How do I fix this.
Should I just stick with CCsprite[7] (array of sprites)? Instead of finding the image's contents, I could find the endpoint of the arrow and just check if that intersects another image, but this would take much more work/math/memory(I'm not too sure exactly on how memory works in general in programming... but I'm pretty sure CCSprite array takes more memory.
EDIT---------------------------------------------------------------------------------------
This is where the arrow's position is updated.
-(void)callEveryFrame:(ccTime)dt{
...
...
//move selected arrows
for(int xe = 0; xe < 7; xe++{
float x = _theArrowArray[xe].position.x;
float y = _theArrowArray[xe].position.y;
vyArray[xe] += gravity; vyArray is the velocity on the y axis array, I'm just adding gravity
x += vxArray[xe] *dt; // dt is after (ccTime) in the method definition
y += vyArray[xe] *dt;
CGPoint newLocation = CGPointMake(x,y);
_theArrowArray[xe].position = newlocation;
//The Code above this moves the arrows inside the CCSprite array, not the batch/nsmutable array.
//The code below is just a copy and paste with a little change to it, for the batchnode/nsmutable
float x2 = _nextProjectile.x; // mextProjectile was declared earlier in my code
float y2 = _nextProjectile.y;
vyArray[xe] += gravity; vyArray is the velocity on the y axis array, I'm just adding gravity
x2 += vxArray[xe] *dt*1.2; // This way(dt*1.2), both arrows are being shot out but this one has more gravity to it, so you can tell which arrow is which and see that both are working.
y2 += vyArray[xe] *dt*1.2;
CGPoint newLocation2 = CGPointMake(x2,y2);
_nextProjectile.position = newlocation2;
}

Don't release the projectile unless the nextProjectile property retains it. CCSprite spriteWithFile returns an autoreleased object, which is retained by the batchNode and the projectiles array.
Weird thing is, the projectile is never set to have a tag == 1 so the code that releases the projectiles is probably going to be skipped.
My guess is regarding #1 that the projectile will be stopped but it isn't removed because it's still added to the node hierarchy. It would be helpful to see the code that actually removes the projectiles.
As for your second question I don't understand your concern. You have 7 projectiles. Whether they use 7 Bytes, 700 Bytes or 7 Kilobytes simply doesn't matter. This amount of memory is still negligible compared to even the smallest of textures.
Do yourself a favor and use the regular Foundation collections like NSMutableArray to store your objects. For one, they will retain added objects and release them when removed. You also get errors in case your code has a bug that causes the array to overrun. C style arrays may be a bit faster and may take less memory, but they're also inherently unsafe and need to be handled with much greater care.

Related

Get x and y 'coordinates' from object speed and direction

I have a player object that controls like the ship in Asteroids, using speed and direction. This object is fixed in the middle of the screen, but can rotate. Movement of this object is a visual illusion as other objects move past it.
I need to get x and y coordinates of this player object, from an origin of (0, 0) at room start. x and y do not provide this info as the object does not move. Does anyone know how I can get 'fake coordinates', based on the speed and direction?
One thing to make sure is that you're not just getting x and y on their own, as that will get the current object's x and y position. Instead, make sure to reference the object you're trying to get. For example:
var objectX = myShip.x;
var objectY = myShip.y;
show_debug_message("x: " + string(objectX));
show_debug_message("y: " + string(objectY));
I think you are thinking about it wrong. You do not need "fake coordinates". Real coordinates are fine. Give the ship and asteroids/enemies whatever coordinates and velocity vectors you want; randomly generate them if the game is like Asteroids.
The coordinates do not have to be fake; it is just that when you render in your game loop, you render a particular frame of reference. If the origin is the center of the screen, when you paint an object at (x,y) paint it as though it were at (x - ship_x, y - ship_y) -- including the ship, which will be at (0,0). If you wanted to make rotation relative to the ship too, you could do the same thing with rotation.
Now, you have your question tagged as game-maker. I have no idea if game-maker lets you control how sprites are painted like this. If not then you need to maintain the real coordinates as separate properties of objects and let the official (x,y) coordinates be relative to the ship. The trouble with this is that you will have to update all of the objects everytime the ship moves. But like I said I don't know how GameMaker works -- if it is a problem maybe ask a question more specific to GameMaker.
You'll need to think what you'll use to move the ship around, but then use that code on different variables.
Normally, you'll update the x or y if you want to move the ship, but since you're not going to do that, simply use a custom variable that replaces the x and y value (like posx or posy), and use them on the code that would otherwise be used to move the ship around.

Unity 2D Tilemap, and ever increasing Total Object Count

I'm creating a game that uses an 80x40 2D Tilemap, using procedural terrain generation. Currently, creating the map works flawlessly, as well as re-building with a button press (as many times as I want). And, I'm getting really great graphical performance (~800 of FPS).
However, I'm noticing that every time I rebuild the map, I get an increasing Total Object Count in the Profiler-memory section. The increase per map re-build is around 2500 to 2700 objects. This is not quite the full amount of tiles (3200) but is close enough that I suspect the sprite drawing as the source of the leak.
Reading online indicates that there is potentially a memory leak with the Material renderer. I'm not sure if the method I'm using to redraw the map falls within this category or not. Here's how I'm doing it...
I have a bunch of sprite atlases... for various auto-tiling terrain types. Essentially, I do the following...
Vector3Int v3 = new Vector3Int(0, 0, 0);
TerrainTile theTile = ScriptableObject.CreateInstance<TerrainTile>();
for (int x = 0; x < theWorld.Width; x++)
{
for (int y = 0; y < theWorld.Height; y++)
{
int nValue = theWorld.squares[x, y].tN_value[tType];
theTile.sprite = Terrain_atr.GetAutoTileSprite(nValue);
theTilemap.SetTile(v3, (TileBase)theTile);
}
}
GetAutoTileSprite just grabs a sprite from a sprite atlas based on an auto-tile rule.
So, does the above method of painting sprites fall into the Material renderer memory leak?
I can't find any other source of objects in my code, as I simply reuse all variables every time I rebuild the map... I'm not (as far as I can see) creating anything new.
Thanks for any insight.

Can't change SKPhysicsJointLimit maxLength after adding the joint to SKPhysicsWorld in Sprite Kit

So, I've created a ring of SKSpriteNodes which are essentially rectangles that are joined together using pin joints. I'd like to suspend this ring inside of an SKShapeNode circle. I have connected each of the nodes to the SKShapeNode using SKPhysicsJointLimit's.
This all works fine, and I get the effect that I'm looking for if I set the maxLength to the "right" number, which I determine subjectively.
I've stored all of the limit joints inside of an Array, so I can get easy access, and I've added them to the physicsWorld.
All of this works properly and as expected, though it took a little work to get the code right. These things are picky!
Now, what I'd really like to do is set the maxLength equal to some large number and then gradually reduce that number over time so that the ring goes from "hanging loosely" to "pulled taut"
I've tried a variety of approaches -- none of which seem to work. First, I tried:
SKAction *tighten = [SKAction customActionWithDuration:1 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
CGFloat t = elapsedTime/duration;
CGFloat p = t*t;
CGFloat newLimit = fromLimit*(1-p) + toLimit*p;
for (int i = 0; i < _connectors.count; i++){
[[_connectors objectAtIndex:i] setMaxLength:newLimit];
}
}];
[_ring runAction:tighten];
but this simply didn't do anything at all.
I finally realized that you don't appear to be able to change the properties of a joint after it has been added to the physicsWorld. So, I tried something like this in the update method of the scene:
[scene.physicsWorld removeJoint:[_connectors objectAtIndex:i]];
[[_connectors objectAtIndex:i] setMaxLength:newLimit];
[scene.physicsWorld addJoint:[_connectors objectAtIndex:i]];
but, this didn't work either. It threw the error:
'Cant add joint <PKPhysicsJointRope: 0x114e84350>, already exists in a world'
even though I've clearly removed it. So, I don't really know where to go from here. The documentation on joints and the existing material on the internet is very thin. I suppose the next thing I would try is removing all joints and et-adding them, but that's going to be a huge pain.
Any thoughts would be really quite helpful!
As noted by OP it appears the joint must be removed from physics world before the change applies. The below worked for me.
for joint in joints
{
physicsWorld.removeJoint(joint)
joint.maxLength = yourNewValue
physicsWorld.addJoint(joint)
}

Multiple CCTMXTiledMaps for iPhone Game

So I want to divide my game into chunks by using several different CCTMXTiledMaps.
I am able to load the maps into my main 'HelloWorldLayer'. I am also able to detect whether the player sprite collides with a tile with the property of 'collectable'.
My problem occurs when I add several CCTMXTiledMap nodes to the game, as it doesn't do the collectible tile detection on all of them, just the first one.
Here is my working code that does the check, but only for the first added CCTMXTledMap:
CGPoint point = [self getTileCoordForPosition:position :map];
CCTMXLayer *metaLayer = [map layerNamed:#"Meta"];
CCTMXLayer *foregroundLayer = [map layerNamed:#"Foreground"];
CCSprite *metaTile = [metaLayer tileAt:point];
CCSprite *foregroundTile = [foregroundLayer tileAt:point];
if (foregroundTile)
{
NSLog(#"HIT!");
// Remove the meta tile and the foreground tile
[metaLayer removeTileAt:point];
[foregroundLayer removeTileAt:point];
}
How can I make this code do the check for every CCTMXTiledMap node that has been added?
The problem was that I was calculating the tile map positions wrong, in a tile map co-ordinates to map position function.
I was multiplying by the CC_SCALE_RATIO() function, or something like that (going off the top of my head), and it was mis-calculating the pixel positioning.
Just thought I'd write in an answer since I found the solution. Hope it helps somebody!

cocos2d starting particles from a specific time in the future

I am developing a cocos2d based app with a space background in which I am exploiting a CCQuadParticleSystem to make blinking stars. I have generated this particle system with ParticleDesigner. As soon as I load the particle system white dots representing stars start appearing in the background and after a while they fade out so that, after few seconds in which the particle system reaches the regime state, a night sky full of stars comes out.
My problem is that I would like to know if there is a way to make the particle system starting from a specific time in the future (for instance t0 = 3sec) so that I do not have to wait to have all the starts blinking.
I hope I have clearly explained the problem
thank you in advance
Andrea
I did this and it worked exactly the way I wanted it to.
for (int i = 0 ; i < 300 ; i++)
[emitter update:.1];
Have you tried a
id actions = [CCSequence actions:[CCDelayTime actionWithDuration:3.0f],
[CCCallFunc actionWithTarget:self selector:#selector(onStallComplete)],
nil];
[self runAction:actions];
ok, granted it is probably abusing the original intent of the API's , but is useful
watch for re-entrancy in onStallComplete if you have multiple such delayed reactions.
note: newbie at SO, hope the code snippet comes out looking right
I assume you are using some kind of updateWithDelta: method in your game loop in order to update the particles. If you want the particles to start after a certain interval, make your own timer.
Edit: Based on your comment below, my method is still good, it just needs some tweaking. You need only remove the condition in the updateWithDelta: method on the particle system. That way, it will still update for those 3 seconds, but will not render, and therefore look the way you are describing.
In the .h file:
BOOL particleShouldUpdate;
float particleTimer;
In your init method:
particleShouldRender = NO;
particleTimer = 3.0f;
In your updateWithDelta: method:
if(!particleShouldRender){
particleTimer -= delta;
if(particleTimer < 0){
particleShouldRender = YES;
}
}
// update your particle.
Finally, in your render method:
if(particleShouldRender){
// render your particle.
}
Note that from this point, if you want to stop it rendering, you need only reset the 2 variables like as in the init method, and the same effect will occur.
EDIT2: Upon further clarification, we only need to adapt the init method of your particle. I will make 2 assumptions here, and you need only change them slightly to fit your needs. Suppose that your update cycle is 60 frames per second, the minimum particle lifespan is 1.01, and that you want 3 seconds of updates before you start the game. Then in the init method, try:
for(float delta = 0.0f; delta < 3.0f; delta += (1/60)){
[particle updateWithDelta:(float)(1/60)];
}
This will update your particle like it normally would, but without rendering at each interval, and before anything else gets updated. Alternatively, if you are worried about speed when updating your particle, you can try:
for(int i = 0; i < 3; i++){
[particle updateWithDelta:1];
[particle updateWithDelta:0.02];
}
This will be faster, but may have a few issues depending on your particles parameters.
EDIT3: So looking into this further, cocos2D does not let you do this for some reason. I found a similar question online to this, and they suggested you play with the posVar and speed to make them large enough while you are transitioning into the scene, and once you have fully transitioned into the scene, reset the values to normal. You may want to give that a try!
Hope that Helps!
I do not think there is a way to fast forward your particle system 3 seconds into the future. Alternatively I can imagine two different solutions depending on your circumstances:
Load the scene with the particle behind another scene (e.g. an empty black scene). After 3 seconds switch to the scene with the now nice looking particle effect. This could work f it is ok for you that the user needs to wait for 3 seconds and you only do not want them to see the particle system while everything is clunked together or if you have another scene before the scene with the particle system anyway.
Record the particle system, store it in a file then replay it in your scene. With recording I mean store the position and color of each of your particles. The drawback is, that it will look the same everytime and if you want to run it longer than what you recorded you need to make sure replaying it in a loop still looks good.
I can't think of a way to implement this directly, but could you try something like this as a workaround? I'm afraid I haven't been able to test this yet due to other errors, but working on it.
{
...
//This attempts to make 3 seconds pass 100 times quicker
[[CCScheduler sharedScheduler] setTimeScale:100];
[self schedule:#selector(cancelIncreasedTimeScale) interval:3];
...
}
int numberOfTimesCancelled = 0;
-(void) cancelIncreasedTimeScale
{
numberOfTimesCancelled ++;
//Two because the scheduler is also called straight away? Remove if it's only called after waiting an initial (3/100) seconds
if (numberOfTimesCancelled == 2) {
[self unschedule:#selector(cancelIncreasedTimeScale)];
[[CCScheduler sharedScheduler] setTimeScale:1];
}
}
The issue I do worry about is the other items on your scene would also run 100 times faster. Is that an issue?