|
In order to break this down
we will visit our old friend velocity, which is a vector. A vector is a
magnitude coupled with a direction. Velocity is a vector whose magnitude is
representative of speed. When the player collides with a portal we can extract
the speed component of our velocity and redirect it to form a new velocity
vector.
Now what term from the last
column do we remember most of all? Those who remembered "surface normal"
get a pat on the back along with a cookie. When a portal is applied to a wall,
floor, or ramp in the game, the surface normal of the surface is communicated
to the portal inhabiting it.
We then take the velocity we entered a portal with,
and break it down into components. To do this we multiply by the surface
normals of the exit portal.
In the end, Portal uses the traditional
teleportation mechanic with a velocity and orientation change. Here's a simple code
snippet, again written in Blitz3D, which is an excellent tool for quick
prototyping like this.
;====== UPDATE PLAYER PHYSICS ======
Function updatePlayerPhysics()
player.playerClass = Object.playerClass( localPlayer )
AlignToVector( player\skeleton, 0, 1, 0, 2, 0.05 )
portalCollision = EntityCollided( player\skeleton, PORTALSTATIC )
If ( portalCollision <> 0 And portalOpen() )
EntityType( player\skeleton, STATIC )
For portal.portalClass = Each portalClass
If portal\entrance <> portalCollision Then Exit
Next
PositionEntity( player\skeleton, EntityX( portal\entrance ), EntityY( portal\entrance ), EntityZ( portal\entrance ) )
velocity# = Sqr( player\vX# ^ 2 + player\vY# ^ 2 + player\vZ# ^ 2 )
player\vX# = portal\nX# * velocity# * 1.2
player\vY# = portal\nY# * velocity# * 1.2
player\vZ# = portal\nZ# * velocity# * 1.2
RotateEntity( player\skeleton, VectorPitch( portal\nX#, portal\nY#, portal\nZ# ), EntityYaw( portal\entrance ) + 180 - ( EntityYaw( portalCollision ) - EntityYaw( player\skeleton ) ), 0 )
TranslateEntity( player\skeleton, 10 * portal\nX#, 10 * portal\nY#, 10 * portal\nZ# )
EntityType( player\skeleton, DYNAMIC )
CaptureWorld()
UpdateWorld()
RenderWorld( 1 )
Else
If ( EntityCollided( player\skeleton, STATIC ) )
player\vX# = 0
player\vZ# = 0
If ( CollisionNY( player\skeleton, 1 ) > 0.5 )
player\vY# = 0
MoveEntity( player\skeleton, player\mX#, 0, player\mZ# )
player\jumpTimer = MilliSecs()
EndIf
Else
player\vX# = player\vX#
player\vY# = player\vY# - 1
player\vZ# = player\vZ#
TranslateEntity( player\skeleton, player\vX#, player\vY#, player\vZ# )
MoveEntity( player\skeleton, player\mX#, 0, player\mZ# )
EndIf
If player\jumpTimer + 250 > MilliSecs() Then player\vY# = player\vY# + player\mY#
EndIf
End Function
;======
|
Knowing how stuff really works is glorious.
Thanks for choosing to publish this, Gamasutra editors!
I also honestly hope that people won't start to say that anyone could have made Portal, notably because it's not that hard to "find" the code behind the rule. This is something recurrent you hear from jaleous mouths, but the point is that one guy did it, you others didn't, and too bad for you.
I suggest one slight modification to the transformation of the player velocity : instead of changing the velocity direction to the normal of the exit portal, thus changing any movement at a right angle to the portal normal into forward motion, i propose projecting the velocity vector onto the basis vectors of the entrance portal. This gives back the players velocity relative to the entrance portal's orientation. This new velocity can then be multiplied with the basis vectors of the exit portal, resulting in the "correct" transition.
Might be a few more lines of code (and a bit more of a hassle with setting up the basis vectors), but allows far more possibilities when playing around with portals.
Things could even be taken further when using different sizes of portals (ie differing, non-normalized basis vectors for the entrance and exit portals), in combination with scaling of objects passing through a portal. For a very nice example of this, see Peter Molyneux's GDC 2005 "Room" demo ( http://www.youtube.com/watch?v=vGiPUx9Zgi0 ).
PS: Very looking forward to you next article!
The Room from GDC 2005 is really interesting. I did not get whether they included this mechanic in BW2???
Maybe the next installment of the column could come back to Portal and analyze that part as well.
If not, I'd like to see the next one be about Braid's time rewinding mechanics! Seems like Jon Blow has described how he did it enough times that it would be easy to reverse-engineer. But even when you know you could do it yourself if you had to, there's something about seeing someone else actually implement it and share the code - even if it's a non-refined implementation - that gets the creative juices flowing. Thanks for the articles Jeremy!