AE:Authoring custom camera animations

From OniGalore
Jump to: navigation, search

This explains how to author single-keyframe OBAN and use them for linearly interpolated camera movements. The primary motivation is the recording of high-quality (scripted) scenes for the trailer (although actual new cutscenes for the game could eventually be authored in the same way). Two major drawbacks of manual camera:

  • Camera turning, be it mouse or keyboard, is not affected by gs_input_accel. Therefore, slow, smooth camera movements are limited to fixed-orientation panning, and that only along a limited set of lines (up to 3 simultaneous keypresses of movement keys).
  • Although the panning velocity is affected by gs_input_accel, the movement occurs in real-time, not in game-time. Therefore, you can't use Ctrl+Shift+L, which is the only way to capture lossless, lag-free footage even for the most complex scenes.

Arguably the player camera (or player orbiting, which is affected by the same parameters) can be used in creative ways to emulate smooth panning and rotation (all the more so if a special camera character is designed for that purpose) but I felt like interpolation was badly needed in any case.

Interpolation of camera movement between single-keyframe OBAN is actually quite common. Whenever the view snaps to a door that's being unlocked, that's typically a call like:

cm_interpolate name_of_a_static_OBAN_that_points_at_the_door 0

When the camera snaps to a new location/direction and then smoothly pans/rotates to another location/direction, it's typically something like this:

cm_interpolate name_of_a_static_OBAN_that_corresponds_to_the_start_of_the_interpolated_sequence 0
cm_interpolate_block name_of_a_static_OBAN_that_corresponds_to_the_start_of_the_interpolated_sequence 300

The "_block" here is not actually needed (see below for an explanation), but it doesn't hurt. The 300 means 300 frames (5 seconds), which is the length of the interpolation in this case. The 0 just means that the camera interpolates from wherever it was to the new position as fast as it can, i.e., in 0 frames.

Anyway. Look for "cm_interpolate" in the original scripts if you want to see how it's done. What we are going to do now is create a new OBAN from scratch. Make an OBANshpadoinkle.xml file looking like this:

<?xml version="1.0" encoding="utf-8"?>
<Oni Version="0.9.27.0">
    <Instance id="0" type="OBAN" name="shpadoinkle">
        <Flags></Flags>
        <InitialTransform>1 0 0 0 1 0 0 0 1 0 0 0</InitialTransform>
        <BaseTransform>1 0 0 0 1 0 0 0 1 0 0 0</BaseTransform>
        <FrameLength>1</FrameLength>
        <FrameCount>1</FrameCount>
        <HalfStopFrame>0</HalfStopFrame>
        <KeyFrames>
            <OBANKeyFrame>
                <Rotation>0 0 0 1</Rotation>
                <Translation>0 0 0</Translation>
                
            </OBANKeyFrame>
        </KeyFrames>
    </Instance>
</Oni>

This is as simple as an OBAN gets. Both the initial/base matrices and the keyframe information (quaternion and 3-vector) correspond to the neutral transformation: stay at origin, don't rotate at all. Now create an OBANshpadoinkle.oni from that, using OniSplit.exe -create . OBANshpadoinkle.xml It should look like this:

 Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

00000000  1F 27 DC 33 DF BC 03 00 32 33 52 56 40 00 14 00  .'Ü3ß¼..23RV@...
00000010  10 00 08 00 01 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  60 00 00 00 A0 00 00 00 00 01 00 00 10 00 00 00  `... ...........
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  4E 41 42 4F 08 00 00 00 00 00 00 00 A0 00 00 00  NABO........ ...
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
00000080  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
00000090  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
000000A0  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
000000B0  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
000000C0  00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00  ..........€?....
000000D0  00 00 00 00 00 00 00 00 01 00 01 00 00 00 01 00  ................
000000E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3F  ..............€?
000000F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000100  4F 42 41 4E 73 68 70 61 64 6F 69 6E 6B 6C 65 00  OBANshpadoinkle.

Emphasized in bold and bold italics respectively are the 2 matrices. The first starts at byte 0x78 (0x98 at new onisplit versions), the second at byte 0xA8 (0xC8 at new onisplit versions). Both are 0x30=48 bytes long. Actually, as you've noticed, they're both exactly the same, and we'll just keep it that way to avoid confusion.

Now comes the actual authoring part. Grab THIS plugin (PC-only, because make_corpse is missing on the Mac) and put it in your GameDataFolder. Run Oni, enable the dev mode and shapeshift to the camera character. This character can only turn, not move, so you can just as well set chr_pin_character=1 or chr_nocollision 0 1, to allow the camera to float in the air at the desired location. Go to the location of your choice with the manual camera, then teleport the camera character there with the End hotkey. Now set the orientation by aiming around.

The main issue here is that the "player camera" will not exactly reflect the orientation of the "body" of the camera character. You'll obtain the best accuracy with the following settings:

cm_height=1
cm_distance=0.1
cm_canter_unarmed=0
cm_canter_weapon=0

Also note that the character's vertical aiming is unrestricted, so if you want to aim the camera extra-high or extra-low, the player camera won't be reflecting the actual elevation of the camera's "body": in that case, you may be better off going into manual camera mode 1 again, so as to look at the character a bit from the side, or roughly along the desired axis.

Once the orientation is correct, enter make_corpse shpadoinkle. This will create a file in the Oni folder called lvl_#_shpadoinkle_corpse.dat, that looks a bit like this (and a bit like CRSA, not surprisingly):

 Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

00000000  63 61 6D 65 72 61 00 00 00 00 00 00 00 00 00 00  camera..........
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000080  00 00 00 00 69 BE 40 BF 00 00 00 00 1F 7B 28 BF  ....i¾@¿.....{(¿
00000090  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 2B B8 24 3F  ?œ.>ÖHz?é."¾+¸$?
000000A0  D3 2B 57 BE C9 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾Ép<¿.ÀõD„K¥Á
000000B0  CC 8B 5C C4 65 BE 40 BF 05 CC 9D B3 23 7B 28 BF  Ì‹\Äe¾@¿.Ì.³#{(¿
000000C0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 2F B8 24 3F  ?œ.>ÖHz?é."¾/¸$?
000000D0  D3 2B 57 BE C5 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾Åp<¿.ÀõD„K¥Á
000000E0  CC 8B 5C C4 61 BE 40 BF 05 CC 1D B4 27 7B 28 BF  Ì‹\Äa¾@¿.Ì.´'{(¿
000000F0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 33 B8 24 3F  ?œ.>ÖHz?é."¾3¸$?
00000100  D3 2B 57 BE C1 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾Áp<¿.ÀõD„K¥Á
00000110  CC 8B 5C C4 5D BE 40 BF 08 B2 6C B4 2B 7B 28 BF  Ì‹\Ä]¾@¿.²l´+{(¿
00000120  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 37 B8 24 3F  ?œ.>ÖHz?é."¾7¸$?
00000130  D3 2B 57 BE BD 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾½p<¿.ÀõD„K¥Á
00000140  CC 8B 5C C4 65 BE 40 BF 05 CC 9D B3 23 7B 28 BF  Ì‹\Äe¾@¿.Ì.³#{(¿
00000150  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 2F B8 24 3F  ?œ.>ÖHz?é."¾/¸$?
00000160  D3 2B 57 BE C5 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾Åp<¿.ÀõD„K¥Á
00000170  CC 8B 5C C4 61 BE 40 BF 05 CC 1D B4 27 7B 28 BF  Ì‹\Äa¾@¿.Ì.´'{(¿
00000180  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 33 B8 24 3F  ?œ.>ÖHz?é."¾3¸$?
00000190  D3 2B 57 BE C1 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾Áp<¿.ÀõD„K¥Á
000001A0  CC 8B 5C C4 5D BE 40 BF 08 B2 6C B4 2B 7B 28 BF  Ì‹\Ä]¾@¿.²l´+{(¿
000001B0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 37 B8 24 3F  ?œ.>ÖHz?é."¾7¸$?
000001C0  D3 2B 57 BE BD 70 3C BF 02 C0 F5 44 84 4B A5 C1  Ó+W¾½p<¿.ÀõD„K¥Á
000001D0  CC 8B 5C C4 FA B6 24 BF D2 2B 57 3E D2 71 3C 3F  Ì‹\Äú¶$¿Ò+W>Òq<?
000001E0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 6C BF 40 BF  ?œ.>ÖHz?é."¾l¿@¿
000001F0  94 C8 A9 36 F5 79 28 BF 02 C0 F5 44 84 4B A5 C1  ”È©6õy(¿.ÀõD„K¥Á
00000200  CC 8B 5C C4 FE B6 24 BF D2 2B 57 3E CE 71 3C 3F  Ì‹\Äþ¶$¿Ò+W>Îq<?
00000210  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 68 BF 40 BF  ?œ.>ÖHz?é."¾h¿@¿
00000220  64 51 A7 36 F9 79 28 BF 02 C0 F5 44 84 4B A5 C1  dQ§6ùy(¿.ÀõD„K¥Á
00000230  CC 8B 5C C4 02 B7 24 BF D2 2B 57 3E CA 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Êq<?
00000240  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 64 BF 40 BF  ?œ.>ÖHz?é."¾d¿@¿
00000250  34 DA A4 36 FD 79 28 BF 02 C0 F5 44 84 4B A5 C1  4Ú¤6ýy(¿.ÀõD„K¥Á
00000260  CC 8B 5C C4 06 B7 24 BF D2 2B 57 3E C6 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Æq<?
00000270  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 60 BF 40 BF  ?œ.>ÖHz?é."¾`¿@¿
00000280  04 63 A2 36 01 7A 28 BF 02 C0 F5 44 84 4B A5 C1  .c¢6.z(¿.ÀõD„K¥Á
00000290  CC 8B 5C C4 06 B7 24 BF D2 2B 57 3E C6 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Æq<?
000002A0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 60 BF 40 BF  ?œ.>ÖHz?é."¾`¿@¿
000002B0  04 63 A2 36 01 7A 28 BF 02 C0 F5 44 84 4B A5 C1  .c¢6.z(¿.ÀõD„K¥Á
000002C0  CC 8B 5C C4 0A B7 24 BF D2 2B 57 3E C2 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Âq<?
000002D0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 5C BF 40 BF  ?œ.>ÖHz?é."¾\¿@¿
000002E0  D4 EB 9F 36 05 7A 28 BF 02 C0 F5 44 84 4B A5 C1  ÔëŸ6.z(¿.ÀõD„K¥Á
000002F0  CC 8B 5C C4 0E B7 24 BF D2 2B 57 3E BE 71 3C 3F  Ì‹\Ä.·$¿Ò+W>¾q<?
00000300  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 58 BF 40 BF  ?œ.>ÖHz?é."¾X¿@¿
00000310  A4 74 9D 36 09 7A 28 BF 02 C0 F5 44 84 4B A5 C1  ¤t.6.z(¿.ÀõD„K¥Á
00000320  CC 8B 5C C4 12 B7 24 BF D2 2B 57 3E BA 71 3C 3F  Ì‹\Ä.·$¿Ò+W>ºq<?
00000330  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 54 BF 40 BF  ?œ.>ÖHz?é."¾T¿@¿
00000340  74 FD 9A 36 0D 7A 28 BF 02 C0 F5 44 84 4B A5 C1  týš6.z(¿.ÀõD„K¥Á
00000350  CC 8B 5C C4 06 B7 24 BF D2 2B 57 3E C6 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Æq<?
00000360  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 60 BF 40 BF  ?œ.>ÖHz?é."¾`¿@¿
00000370  04 63 A2 36 01 7A 28 BF 02 C0 F5 44 84 4B A5 C1  .c¢6.z(¿.ÀõD„K¥Á
00000380  CC 8B 5C C4 0A B7 24 BF D2 2B 57 3E C2 71 3C 3F  Ì‹\Ä.·$¿Ò+W>Âq<?
00000390  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 5C BF 40 BF  ?œ.>ÖHz?é."¾\¿@¿
000003A0  D4 EB 9F 36 05 7A 28 BF 02 C0 F5 44 84 4B A5 C1  ÔëŸ6.z(¿.ÀõD„K¥Á
000003B0  CC 8B 5C C4 0E B7 24 BF D2 2B 57 3E BE 71 3C 3F  Ì‹\Ä.·$¿Ò+W>¾q<?
000003C0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 58 BF 40 BF  ?œ.>ÖHz?é."¾X¿@¿
000003D0  A4 74 9D 36 09 7A 28 BF 02 C0 F5 44 84 4B A5 C1  ¤t.6.z(¿.ÀõD„K¥Á
000003E0  CC 8B 5C C4 12 B7 24 BF D2 2B 57 3E BA 71 3C 3F  Ì‹\Ä.·$¿Ò+W>ºq<?
000003F0  3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE 54 BF 40 BF  ?œ.>ÖHz?é."¾T¿@¿
00000400  74 FD 9A 36 0D 7A 28 BF 02 C0 F5 44 84 4B A5 C1  týš6.z(¿.ÀõD„K¥Á
00000410  CC 8B 5C C4 02 80 F5 44 C2 A5 16 C2 CC 0B 5D C4  Ì‹\Ä.€õDÂ¥.ÂÌ.]Ä
00000420  02 00 F6 44 28 87 B3 C0 CC 0B 5C C4              ..öD(‡³ÀÌ.\Ä

The 48 bytes emphasized in bold, starting at 0x84, correspond to the transform matrix of the "pelvis" of our camera character. We want this matrix to replace both of the matrices in our OBANshpadoinkle.oni, making it look like this:

 Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 

00000000  1F 27 DC 33 DF BC 03 00 32 33 52 56 40 00 14 00  .'Ü3ß¼..23RV@...
00000010  10 00 08 00 01 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  60 00 00 00 E0 00 00 00 40 01 00 00 0D 00 00 00  `...à...@.......
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  4E 41 42 4F 08 00 00 00 00 00 00 00 E0 00 00 00  NABO........à...
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000070  00 00 00 00 00 00 00 00 69 BE 40 BF 00 00 00 00  ........i¾@¿....
00000080  1F 7B 28 BF 3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE  .{(¿?œ.>ÖHz?é."¾
00000090  2B B8 24 3F D3 2B 57 BE C9 70 3C BF 02 C0 F5 44  +¸$?Ó+W¾Ép<¿.ÀõD
000000A0  84 4B A5 C1 CC 8B 5C C4 69 BE 40 BF 00 00 00 00  „K¥ÁÌ‹\Äi¾@¿....
000000B0  1F 7B 28 BF 3F 9C 0D 3E D6 48 7A 3F E9 00 22 BE  .{(¿?œ.>ÖHz?é."¾
000000C0  2B B8 24 3F D3 2B 57 BE C9 70 3C BF 02 C0 F5 44  +¸$?Ó+W¾Ép<¿.ÀõD
000000D0  84 4B A5 C1 CC 8B 5C C4 01 00 01 00 00 00 01 00  „K¥ÁÌ‹\Ä........
000000E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3F  ..............€?
000000F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000100  4F 42 41 4E 73 68 70 61 64 6F 69 6E 6B 6C 65 00  OBANshpadoinkle.

Now import OBANshpadoinkle.oni in a level, say, level3 and enjoy your new "shpadoinkle" animation. In order to enjoy it you type this:

cm_interpolate shpadoinkle 0

and then you can interpolate to another camera OBAN, for example:

cm_interpolate shpadoinkle 0
dmsg "the camera instantly snaps to the shpadoinkle position and orientation"
cm_interpolate BomberCam01 300
dmsg "then starts interpolating towards BomberCam01, over 5 seconds"
cm_interpolate_block BomberCam02 300
dmsg "the third interpolation won't start until the second is done, and will also delay this message"

Basically, all you need to know is that cm_interpolate shpadoinkle 0 instantly places the camera at the position and orientation defined by OBANshpadoinkle. cm_interpolate shpadoinkle 300 will interpolate position and rotation linearly from the current ones, over 300 frames. As for cm_interpolate_block shpadoinkle 300, it will wait until all previous camera interpolations (if any) are done, and from then and there it will interpolate position and rotation linearly, over 300 frames. All script commands placed after cm_interpolate_block shpadoinkle 300 will be blocked until cm_interpolate_block shpadoinkle 300 starts, i.e., until all interpolations before cm_interpolate_block shpadoinkle 300 are done intrerpolating.

You can also use non-blocked interpolations, interrupting the previous ones, like this:

cm_interpolate shpadoinkle 600
dmsg "the camera starts moving to shpadoinkle, expecting to get there in 10 seconds"
sleep 300
dmsg "five seconds later..."
cm_interpolate BomberCam01 600
dmsg "the journey to shpadoinkle is interrupted and we start moving to BomberCam01"
dmsg "from wherever we were, that is, from halfway between out starting point and shpadoinkle"
dmsg "at such a speed that we expect to get to BomberCam01 in 10 seconds, counting from now"
dmsg "the now, of course, being five seconds later than when we started"
sleep 300
dmsg "another five seconds later"
cm_interpolate BomberCam01 600
dmsg "the journey to BomberCam01 is also interrupted and we take the short way to BomberCam02"
dmsg "from wherever we were, that is, from halfway between BomberCam01 and that other point"
dmsg "that other point being halfway between shpadoinkle and where we started"

That should get you started. A mini example featuring the DeLorean will be provided Soon Enough.