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.
[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)
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);
return RecursiveBinarySearch(target, path, min, _mid, clamp);