The basic idea
is very easy. Simplified you could say... a snake like line realized by a let's say 3px line is expanding across the screen collecting and interacting with different stuff, can be steered through user input. Like a continous line you would draw with a pen.
I've already started reading apple's documentation on Quartz/CG.
As I understand now, I need to put my rendering code into a UIView's drawRect.
Then I would need to set a timer (found some answers/posts here and there, nothing concrete though) that fires x times per second and calls setNeedsDisplay on the UIView.
Question:
How to realize the following:
Have whole snake on UIView 1 / (Layer ?), draw new part on UIView 2, merge them, so that the new part gets appended to UIView 1 (or CALayer instead of views ?). I ask explicitly about this cause I read, that one shouldn't redraw the same content over and over again, but just the new/moving part.
I hope you can provide me some sample code or some details which classes I should use and the strategy on how to use them / which calls to make.
Edit
OK, I see that my idea or what I've read before to realize this with Quartz and different views is not so wise... ( Daniel Bleisteiner )
So I switched to OpenGL now, well I'm looking into it, reading examples, Jeff LaMarche's OpenGL blog entries, etc..
I guess I would be able to draw my line. I think I would create classes for curves, straight lines / direction changes, etc. and then on user input I would create the related objects (depending on the steer input) store them in an array and then recreate and redraw the whole line by reading the object properties from the objects stored in the array on each frame. A simple line I would draw like this (code from Beginning iPhone Development)
glDisable(GL_TEXTURE_2D);
GLfloat vertices[4];
// Convert coordinates
vertices[0] = start.x;
vertices[1] = start.y;
vertices[2] = end.x;
vertices[3] = end.y;
glLineWidth(3.0);
glVertexPointer (2, GL_FLOAT , 0, vertices);
glDrawArrays (GL_LINES, 0, 2);
Maybe I will even find a way to antialias it but
now I'm more curious if my idea is good enough or if there are some better established strategies for this
and maybe someone could tell me how to seperate code for hud's, the line drawing itself and some menus that I will have to display f.e. at the beginning ... so that it's easy to transition from one "view" to another (like with a fade)? Some hints ?
Maybe you have also read a book, that will explain how to solve this kind of problems?
Or you can point me to another good answer / example code as I have huge problems in finding "animated drawing" examples.
this got me a little further, but it is still a little vague for me.
I don't know how to realize "update path that you draw (only with needed points + one last point that moves)"
Don't try to merge different views... setNeedsDisplay has a rect parameter that tells the core graphics part that only a certain part of the screen needs to be rendered again. Respect this parameter in your drawRect method and it should be enough for standard 2D games and tools.
If you intent to use intense graphics there is no other option than to use OpenGL. The performance is multiple times better and you won't have to care about optimizations... OpenGL does much in the background.
Use OpenGL ES. Then what you will want to do is create a run loop function or method which gets called by a CADisplayLink 30 to 60 times per second. 60 is the maximum.
So how does this work?
1) Create an assign-property for CADisplayLink. Do NOT retain your CADisplayLink, because display links and timers retain their target. Otherwise you would create a retain-cycle which may cause abandoned memory issues (which is even worse than a leak and much harder to discover).
#property (nonatomic, assign) CADisplayLink *displayLink;
2) Create and schedule the CADisplayLink:
- (void)startRunloop {
if (!animating) {
CADisplayLink *dl = [[UIScreen mainScreen] displayLinkWithTarget:self selector:#selector(drawFrame)];
[dl setFrameInterval:1];
[dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.displayLink = dl;
animating = YES;
}
}
Some things to note here: -setFrameInterval:1 tells the CADisplayLink to not skip any frame. This means: You get the maximum fps. BUT: This can be bad if your code needs longer than 1/60 seconds. In that case, it's better to set this to 2, for example. Makes your animation more fluid.
3) In your -drawFrame method do your OpenGL ES drawing as usual. The only difference is that this code gets called multiple times per second. Just keep track of time and determine in your code what you want to draw, and how you want it to be drawn. If you were to animate a rectangle moving from bottom left to top right with an animation duration of 1 second, you would simply interpolate the animation frames between start and end by applying a function which takes time t as argument. There are thousands of ways to do it. This is one of them.
4) When you're done or want to halt OpenGL ES drawing, just invalidate or pause your CADisplayLink. Something like this:
- (void)stopRunloop {
if (animating) {
[self.displayLink invalidate];
self.displayLink = nil;
animating = NO;
}
}
Related
My current project contains a gravity simulator where sprites move in accordance with the forces they experience in the game scene.
One of my features involve allowing moving sprites to draw a line behind them so you can see what paths they take.
Shown here:
However, as the Sprite continues it's movements around the screen, the FPS begins to dive. This can be seen in this second image where some time has passed since the sprite first started its movement.
When researching, I found other people had posted with similar problems:
Multiple skshapenode in one draw?
However, in the question above, the answer's poster detailed that it (The answer) was meant for a static image, which isn't something I want, because this line will change in real time depending on what influences the sprites path, this was reflected when I tried implementing a function to add a new Line to the old one which didn't work. That Code here
I'm asking if anyone can assist me in finding a way to properly stop this constant FPS drop that comes from all the draw operations. My current draw code consists of two Functions.
-(void)updateDrawPath:(CGPoint)a B:(CGPoint)b
{
CGPathAddLineToPoint(_lineToDraw, NULL, b.x, b.y);
_lineNode.path = _lineToDraw;
}
-(void)traceObject:(SKPlanetNode *)p
{
_lineToDraw = CGPathCreateMutable();
CGPathMoveToPoint((_lineToDraw), NULL, p.position.x, p.position.y);
_lineNode = [SKShapeNode node];
_lineNode.path = _lineToDraw;
_lineNode.strokeColor = [SKColor whiteColor];
_lineNode.antialiased = YES;
_lineNode.lineWidth = 3;
[self addChild:_lineNode];
}
updateDrawPath: Draws line to latest position of Sprite.
traceObject: Takes SKPlanetNode (Subclass of SKSpriteNode), and sets it up to have a line drawn after it.
If anyone can suggest a way to do this and also reduce the terrible overhead I keep accumulating, it would be fantastic!
A couple suggestions:
Consider that SKShapeNode is more or less just a tool for debug drawing mostly, due to the fact that it doesn't draw in batches it's really not suitable to make a game around that or to use it extensively (both many shapes as well as few but complex shapes).
You could draw lines using a custom shader which will likely be faster and more elegant solution, though of course you may have to learn how to write shader programs first.
Be sure to measure performance only on a device, never the simulator.
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?
I'm trying to implement "hand draw tool".
At the moment algorythm looks like that (I don't insert any code because methods are quite big, will try to explain an idea):
Drawing
In touchesStarted: method I create NSMutableArray *pointsArray and add point into it. Call setNeedsDisplay: method.
In touchesMoved: method I calculate points between last added point from the pointsArray and current point. Add all points to the pointsArray. Call setNeedsDisplay: method.
In touchesFinished: event I calculate points between last added point from the array and current point. Set flag touchesWereFinished. Call setNeedsDisplay:.
Render:
drawRect: method checks is pointsArray != nil and is there any data in it. If there is - it starts to traw circles in each point of this array. If flag touchesWereFinished is set - save current context to the UIImage, release pointsArray, set it to nil and reset the flag.
There are a lot disadvantages of this method:
It is slow
It becomes extremely slow when user touches and move finger for long time. Array becomes enormous
"Lines" composed by circles are ugly
I would like to change my algorithm to make it bit faster and line smoother.
In result I would like to have lines like on the picture at following URL (sorry, not enough reputation to insert an image): http://2.bp.blogspot.com/_r5VzEAUYXJ4/SrOYp8tJCPI/AAAAAAAAAMw/ZwDKXiHlhV0/s320/SketchBook+Mobile(4).png
Can you advice me, ho I can draw lines this way (smooth and slim on the edges)? I thought to draw circles with alpha gradient on the edges (to make lines smoother), but it will be extremely slowly IMHO.
Thanks for help
Update
I changed draw algorithm. Now every event I save UITouch and in the drawRect: method I draw path from prev. point to the current one. And dump UIImage from context every drawRect: invocation.
But I still have 2 questions:
Is it possible to draw more smooth. I mean if I draw quite fast, I easily can see that path path is a set of straight lines. But I want to draw curves, to make path smooth. Probably Bezier curves will help but I don't understand how they can help in this.
situation.
I want to start path with thiner line and finish with thiner line too. How I can do this line transform?
Thanks!
I have been searching around, but there seems no good answer for this simple question. So I am asking again: how to animate line-drawing in iphone dev?
Basically what I want is something like this:
#implementation MyUIView
- (void) triggerLineDrawing: (CGPathRef) path {
...
// animate line drawing here
// and the line should disappear automatically after a few seconds
}
Can it be done?
You can't do it automatically, only bu hand. To do it manually you should do something like this:
create array of your line points.
start an NSTimer, that will fire for example, 15 times a second.
every tick of a timer you have to find out, what part of the line you need to draw (look at linear interpolation)
update path that you draw (only with needed points + one last point that moves)
send setNeedsDraw message to view.
You can vary interpolation algorythm, speed of line drawing etc. to get the effect that you need.
I am making a simple iPhone drawing program as a personal side-project.
I capture touches event in a subclassed UIView and render the actual stuff to a seperate CGLayer. After each render, I call [self setNeedsLayout] and in the drawRect: method I draw the CGLayer to the screen context.
This all works great and performs decently for drawing rectangles. However, I just want a simple "freehand" mode like a lot of other iPhone applications have.
The way I thought to do this was to create a CGMutablePath, and simply:
CGMutablePathRef path;
-(void)touchBegan {
path = CGMutablePathCreate();
}
-(void)touchMoved {
CGPathMoveToPoint(path,NULL,x,y);
CGPathAddLineToPoint(path,NULL,x,y);
}
-(void)drawRect:(CGContextRef)context {
CGContextBeginPath(context);
CGContextAddPath(context,path);
CGContextStrokePath(context);
}
However, after drawing for more than 1 second, performance degrades miserably.
I would just draw each line into the off-screen CGLayer, if it were not for variable opacity! The less-than-100% opacity causes dots to be left on the screen connecting the lines. I have looked at CGContextSetBlendingMode() but alas I cannot find an answer.
Can anyone point me in the right direction? Other iPhone apps are able to do this with very good efficiency.
The problem is that with CGStrokePath() the current mutable path gets closed and drawn and a new path is created when you move your finger. So you probably end up with a lot of paths for one touch "session", at least that's what your pseudocode seems to do.
You can try to begin a new mutable path when touches begin, use CGAddLineToPoint() when the touches move und end the path when touches end (much like your pseudocode shows). But in the draw method, you draw a copy of the current mutable path, and the actual mutable path is still being elongated until the touches end, so you only get one path for the whole touch session. After the touches end you can add the path permanently - you can for example put all paths into an array and iterate over them in the draw method.
What SanHolo said - plus you may want to throttle the adding of points, so it only adds a new point no more often than every 10ms, say (you'd need to play with the interval). You can do that with a simple timer.
Also, how are you instructing the view that it needs to redraw itself? You might want to throttle that too - and it could be on a longer interval than the point capturing (e.g. capture points no more than every 10ms, and redraw no more often than every 200ms - again you'd need to play with the numbers).
In both cases you'd need to make sure that, if nothing happens for longer than the interval the last point is captured, or the redraw is requested. That's where the timer comes in.