From there, after six days and seven nights, you arrive at Zobeide, the white city, well exposed to the moon, with streets wound about themselves as in a skein.
They tell this tale of its foundation : men of various nations had an identical dream. They saw a woman running at night through an unknown city; she was seen from behind, with long hair, and she was naked. They dreamed of pursuing her. As they twisted and turned, each of them lost her.
After the dream, they set out in search of that city; they never found it, but they found one another; they decided to build a city like the one in the dream. In laying out the streets, each followed the course of his pursuit; at the spot where they had lost the fugitive’s trail, they arranged spaces and walls differently from the dream, so she would be unable to escape again.
This was the city of Zobeide, where they settled, waiting for that scene to be repeated one night. None of them, asleep or awake, ever saw the woman again. The city’s streets were streets where they went to work every day, with no link any more to the dreamed chase. Which, for that matter, had long been forgotten.
New men arrived from other lands, having had a dream like theirs, and in the city of Zobeide, they recognized something from the streets of the dream, and they changed the positions of arcades and stairways to resemble more closely the path of the pursued woman and so, at the spot where she had vanished, there would remain no avenue of escape.
The first to arrive could not understand what drew these people to Zobeide, this ugly city, this trap.
When we first conceived of Take My Hand, we envisioned a 2D Parkour Platformer in which players could take different routes through a city : a sort of open-world 2D parkour platformer.
You would be running and jumping and catching and launching each other along the buildings of a street until you came to an alley, at which point you could hold hands and have the camera turn to let you run down the alley instead OR leap across the alley gap and pursue your journey along the same streets and rooftops.
We quickly realised that, given the time and the resources at our disposal, the idea would most likely turn into a level design nightmare (not to mention the technical complications it implied) but we had already fallen in love with those sweeping camera turns, and with the idea that our characters could reach the corner of a block and have their field of view pivot to reveal an entire section of the city for them to explore and overcome.
After some research into developing our own system, we decided to use PixelPlacement’s iTweenPath to lay out the splines for our levels – inspired in no small part by their Path-constrained Characters example and by Roots, a previous VFS Game Design Final Project which we knew had implemented iTweenPath with some good results. Roots and the Path-constrained Character Example had something in common, however, their movement requirements were much more limited than ours.
[call_to_action]Before I go on, I’d like to give a shout-out to Ben Kanbour, the programmer for Roots, whose help in laying out the foundations of our movement code cannot be overstated[/call_to_action]
For those of you unfamiliar with iTween, it presents a few interesting problems :
[section_title icon=”fa-forward” text=”First Problem : Consistent Speed”]
- The position of an object on the path is represented as a percentage float value –
0% being the beginning of the path and 100% being its end. - That percentage is distributed based on the number of nodes forming the path
So that, in a path made up of 5 nodes : A, B, C, D and E –
– A’s percentage value is 0%
– B’s is 25%,
– C’s is 50%,
– D’s is 75%, and
– E’s is a 100%
This means an object placed at % 37.5 will be halfway between nodes B and C irrespective of the length of the path or the relative distance between nodes.
- More importantly, an object “moving” at a consistent percentage per second will move from A-to-B in the same amount of time as it will from B-to-C, etc. meaning that objects move faster between distant nodes than nearby ones.
- This was a particular issue since the paradigm of a parkour platformer required reliable accelerations and speeds to sell the experience. We couldn’t allow character metrics to vary depending on the structure of the spline, and it quickly became apparent that manually placing the nodes at equidistant positions wouldn’t be precise enough (not to mention that it would be a nightmare for Maria, our Level Designer).
[toggle first_toggle=”yes” title=”Show Solution”]
We implemented a combination of the solutions presented to the “iTween even velocity” question, including a hack of Felipetnh’s iTweenPathConstantSpeed all tied to a function call so that we could control the order of operation and ensure the path was smoothed before the position of checkpoints, ledge grabs etc. was calculated.
Maria could then place nodes approximately equidistant, and the smoothing pass would take care of minor imperfections – we had initially hoped that it would remove the need for manual adjustments altogether, but large distance discrepancies still resulted in noticeable changes in character movement speeds and jump distances.
[/toggle]
[section_title icon=”fa-crosshairs” text=”Second Problem : Objective Object Position”]
- While iTween’s .PointOnPath() function returns the Vector3 position of a percentage on the path, there is no built-in method to return the percentage position of an object relative to the path.
- Since all horizontal character movement had to occur as a delta in their percentage position on the path, however, this posed us some real issues regarding the placement of ledge-grabs and the mechanics permitting one character to catch or throw the other : we needed to know the percentage position of grab points in order to place the character in the right position.
[toggle title=”Show Solution”]
I eventually built a static recursive binary search function, stored in the Game Manager, which returned the percentage position of a Transform relative to an iTweenPath array. It essentially finds the Vector3 of each binary search point using .PointOnPath() and compares the sqrMagnitude to the Transform’s position to find the nearest point.
In order to control its cost, this recursive function is clamped to run a number of times set by a global value n, and returns the nearest percentage float after n iterations.
[code language=”csharp” collapse=”false”]
public static float RecursiveBinarySearch(Vector3 target, Vector3[] path, float min, float max, int clamp)
{
clamp–;
float _mid = min + (max-min)/2;
float _product = 0f;
if(clamp > 0)
{
Vector3 vMin = iTween.PointOnPath(path, min);
vMin.y = target.y;
Vector3 vMax = iTween.PointOnPath(path, max);
vMax.y = target.y;
if((target-vMin).sqrMagnitude > (target-vMax).sqrMagnitude)
{
return RecursiveBinarySearch(target, path, _mid, max, clamp);
}
else
{
return RecursiveBinarySearch(target, path, min, _mid, clamp);
}
else
{
return _mid;
}
}
[/code]
[/toggle]
I’m currently experiencing the same sort of problems in using iTween. I really like the functionality it provides, but as you say, certain aspects could stand some improvement.
I need smooth motion along the path as well, and I was disappointed to see virtually no difference between the “speed” and “time” hash parameters. I hope that spacing the nodes about the same distance from each other will suffice for my purposes.
With respect to the VisualPathEditor, I was also surprised to see such a simple implementation of node addition, so I made a couple of simple changes: (1) new paths start with 0 points, since no sensible points can be obtained in the context of a public variable definition, and (2) newly added points either replicate the last point in the path or use the position of the gameObject to which the path is attached. Those two changes alleviated a lot of my headaches.
Anyway, the point is that I’m hoping you post the solution code for the constant path movement soon! Oh, and I like that the design of your post uses expandable solutions.
Hi Renaud, While researching how best to address iTween path’s… ehrm… issues i happenned upon your blog and found it very informative. See, we’re also using the pathing as a sort of guide for an obstacle avoidance game, and as our player is flying I had a lot of camera and bounce code to iron out. Essentially we just place a gameobject with a moveto on an itween path, and our player programmatically follows that, veering to mouse click points on the screen in order to avoid obstacles.
So, there I was, happy with the prototype level, and then I gave it to a level designer along with a quick hack to the path editor to support N number of pathnodes.
Obviously, what came next was as you experienced. Headaches abounded as our tidy little bezier based obstacle avoidance game showed anomaly after anomaly, and the level designer complained at length about the editor.
I’d love to take a look at what you’ve done, as it seems we’ve experienced a lot of the same issues with iTween’s paths.
Great post, I admire the writing style 🙂 A little off topic here but what theme are you using? Looks pretty cool.
Hi there,
Apologies for the delay.
The theme is WooThemes’ Merchant, and I can whole-heartedly recommend WooThemes in general: http://www.woothemes.com/products/merchant/
All the best,
Renaud
The problem you have here is a relsut of the way that shadow pass is created.You are using the floor that don’t cover the whole image.Take a look at the shadow pass alone:You’ll notice that wherever you have alpha of 0 the shadow pass is black.In may opinion (I’m not the only one) this is a bug. In those areas shadow pass should be white.If it were as it should be, i.e. white your image wouldn’t change in those areas (multiplying by white doesn’t change image as you remember).Here we have antialiased edges of your object and you get dark fringe around it.The way to solve it is to take the alpha, pass it through Invert node, add it to shadow pass and use the relsut as the shadow pass.
Renaud,
I wanted to let you know how much I enjoy your writing style.
I am currently going through the same problems that you wrote about in this article. I am really looking forward to seeing your solutions when you post them.
Congratulations on a great game! Its a lot of fun and looks like it was quite an undertaking.
Hey thanks a lot for this code. I found it very helpful. I’m using it to keep a sprite constrained to a path while the user drags their finger around the screen. I have experienced some problems with your recursive binary search though.
I set the min to 0 and the max to 1 (because the percent is normalized) and I noticed it seems to stick around 0.5. I tried with a max of 0.9 and it sticks less. The closer i get to 1 the more it sticks in the middle.
Is this the rounding error you were talking about or a problem with the function? I’ve gotten around the problem by animating to the target percent instead of using it directly.
Hi Sam,
I haven”t looked at this code since I wrote it, to be honest. I intended to clean it up and comment it out for people, but life and it’s myriad projects got in the way.
On face value, it makes a certain amount of sense for the binary search to stick around 0.5, if it is going to stick somewhere. I didn’t have any such problem, but I intended the function to be used in specific instances rather than on update, which is what it sounds like you are doing.
The rounding error should only really be a problem if your path is very long (at which point, the percentage value of your position on the path becomes too precise a decimal and the rounding error becomes an issue).
Using 0 and 1 as the min and max value makes a lot of sense if you don’t know where on the path the sprite is when you run the function, what number are you using as a clamp? You could try increasing the clamp value and seeing if that resolves the problem (that will, of course, add to the recursions and therefore the cost).
In any case, you’ll probably get a much smoother motion by animating to the target rather than setting the sprite’s position.
If your path is long, you’ll need a larger clamp value, I would normally advise against running the recursive search on update (it’s potentially an awful lot of calculations to run every frame), but I have to admit that I can’t think of a more efficient solution for your setup off the top of my head.
One thing you might consider is setting up the search (using a coroutine for example) to run every e.g. 30 frames rather than every frame. If you’re animating to your target, you might not notice any difference in performance, and you’ll be saving yourself a lot of expensive distance checks etc.
Let me know if that helps, or if you have any other questions!
All the best,
R.
I also noticed it’s a huge pain to move a spline. Before asking a coworker to tune paths, I modified the iTween visual path editor so that holding Control or Option selects all the nodes, and they will move together when dragged. Clicking individual nodes will deselect them and exclude them from the group dragging. The code is online here:
http://wiki.unity3d.com/index.php/ITweenPathEditorPlus
Hi Dan,
That’s an excellent idea!
As it happens I’m working with iTween again at the moment to help our artists prototype a map concept, if you don’t mind I’d love to integrate your code into the Editor extensions while I’m at it.
All the best,
R.
Feel free! (Technically, you don’t even need to ask.)
Hi RENAUD ,
I am working on a rail track based game, and i am using iTweenPath as a track path. Now I am unable to follow carriage train to their engine.So please Help me i am totally stuck now since 4 day.Please Help me..How to move carriage with engine.Any kind of help will be appreciated.
Thanks.
Hi Sumit,
It’s hard to be very constructive without taking a look at your code, but I’m going to assume that you’re moving your engine using .PutOnPath() and that your carriages are a predictable distance away from the next carriage/engine ahead of them.
In which case, what I would do to start with is give carriages a constant offset % from the previous carriage and put them all in a list. That way, whenever you move the engine, you can iterate through the list and move all the carriages.
So in essence, you iterate through your list and add your offset constant to the percentage position of carriage i-1 to get the desired position for carriage i. This will almost certainly not work perfectly, but it’ll give you starting point.
This offset % will probably be very small. You could probably figure it out programmatically if you wanted to but, since it most likely won’t change from carraige to carriage unless your carriages vary in size, I’d just make it a constant.
Let me know if that helps,
R.
I think your tips will work for me,Hey I have one more problem ,the problem is How to rotate cube(gameobject) face according to path curve movement on iTweenPath,I have used orientToPath but it’s not giving smooth rotation of cube according to path curve. Please help me
Thanks a lot