跳到主要内容

Camera

Motion Canvas 内置了一个简单的正交相机,允许你平移、缩放和旋转场景的视口,而无需转换场景中的单个对象。

Click to preview animation

import {Camera, Circle, makeScene2D, Rect} from '@motion-canvas/2d';
import {all, createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();
const rect = createRef<Rect>();
const circle = createRef<Circle>();

view.add(
<>
<Camera ref={camera}>
<Rect
ref={rect}
fill={'lightseagreen'}
size={100}
position={[100, -50]}
/>
<Circle
ref={circle}
fill={'hotpink'}
size={120}
position={[-100, 50]}
/>
</Camera>
</>,
);

yield* all(
camera().centerOn(rect(), 3),
camera().rotation(180, 3),
camera().zoom(1.8, 3),
);
yield* camera().centerOn(circle(), 2);
yield* camera().reset(1);
});

Basic usage

最快入门的方法是将场景包裹在 Camera 组件中并获取对它的引用。

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();

view.add(
<Camera ref={camera}>
<Rect size={100} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);
});

然后你可以使用相机引用在场景中操作相机。

默认情况下,Camera 组件是完全透明的。在开始动画之前,它不会对场景产生任何可见效果。

Moving the camera

有两种移动相机的方法。你可以像其他组件一样直接使用相机的 position signal。

Click to preview animation

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();

view.add(
<Camera ref={camera}>
<Rect size={100} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);

yield* camera().position([-100, -30], 1);
yield* camera().position([100, -30], 1);
yield* camera().position(0, 1);
});

或者,你可以使用 centerOn 方法将相机居中于特定位置或对象。如果直接将对象传递给 centerOn,相机将居中于对象的位置。

Click to preview animation

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();
const rect = createRef<Rect>();
const circle = createRef<Circle>();

view.add(
<Camera ref={camera}>
<Rect
ref={rect}
size={100}
fill={'lightseagreen'}
position={[-100, -30]}
/>
<Circle ref={circle} size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);

yield* camera().centerOn(rect(), 1);
yield* camera().centerOn(circle(), 1);
});

你也可以直接将位置传递给 centerOn。在这种情况下,相机将居中于指定位置在世界空间中

Click to preview animation

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();

view.add(
<Camera ref={camera}>
<Rect size={100} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);

yield* camera().centerOn([-200, 50], 1);
yield* camera().centerOn([150, -30], 1.5);
yield* camera().centerOn(0, 1);
});

Zooming

要放大或缩小相机,你可以使用 zoom 方法。

Click to preview animation

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();

view.add(
<Camera ref={camera}>
<Rect size={100} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);

yield* camera().zoom(2, 1);
yield* camera().zoom(0.5, 1.5);
yield* camera().zoom(1, 1);
});

你不应该直接操作相机的 scale 属性。这是因为 Camera 组件在内部做了一些额外的工作,以确保在与其他相机动画结合时,相机缩放级别的动画能够正常工作。

你也可以使用 to 方法来流畅地链接多个动画。

- yield* camera().zoom(2, 1);
- yield* camera().zoom(0.5, 1.5);
- yield* camera().zoom(1, 1);
+ yield* camera().zoom(2, 1).to(0.5, 1.5).to(1, 1);

Rotating the camera

由于 Camera 是一个常规组件,你可以像其他组件一样使用 rotation signal 来更改其旋转。

Click to preview animation

import {makeScene2D, Camera, Rect, Circle} from '@motion-canvas/2d';
import {createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();

view.add(
<Camera ref={camera}>
<Rect size={100} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={80} fill={'hotpink'} position={[100, 30]} />
</Camera>,
);

yield* camera().rotation(50, 1).to(-120, 2).to(0, 1);
});

请注意,这使用相机的当前位置作为旋转中心。

Resetting the camera

要将相机的位置、缩放级别和旋转重置为默认值,你可以使用 reset 方法。

// 将相机的位置重置为 [0, 0],缩放重置为 1,旋转重置为 0。
camera().reset(1);

Moving along a path

你可以使用 followCurve 方法让相机沿着路径移动。该方法接受任何扩展 Curve 类的对象。Motion Canvas 附带的许多形状,如 RectCirclePolygonSpline 都扩展了 Curve 类,可以用作路径。

下面的示例显示了相机沿着二次贝塞尔曲线定义的路径移动。请注意,路径不必是可见的,甚至不必是场景的一部分。这只是为了演示目的。

Click to preview animation

import {makeScene2D, Camera, QuadBezier} from '@motion-canvas/2d';
import {createRef, linear} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera = createRef<Camera>();
const path = createRef<QuadBezier>();

view.add(
<Camera ref={camera}>
<QuadBezier
ref={path}
lineWidth={6}
stroke={'lightseagreen'}
p0={[-200, 0]}
p1={[0, 200]}
p2={[200, 0]}
/>
</Camera>,
);

yield* camera().followCurve(path(), 2.5, linear);
});

Using multiple cameras in a scene

Motion Canvas 还提供了一种将同一场景渲染到多个相机的方法。这对于创建分屏视图或从不同角度渲染同一场景非常有用。

将同一场景渲染到多个相机需要稍微不同的设置。

首先,不是将我们要渲染的场景直接添加到视图中,而是将其存储在变量中。

const scene = (
<Node>
<Rect size={70} fill={'lightseagreen'} position={[-100, -30]} />
<Circle size={50} fill={'hotpink'} position={[100, 30]} />
</Node>
);

接下来,对于我们想要显示的每个相机,我们将一个 Camera.Stage 组件添加到视图中,并将场景传递给 scene prop。

const scene = /* ... */

const camera1 = createRef<Camera>();
const camera2 = createRef<Camera>();

view.add(
<>
<Camera.Stage
cameraRef={camera1}
size={[300, 200]}
position={[-180, 0]}
scene={scene}
/>
<Camera.Stage
cameraRef={camera2}
size={[300, 200]}
position={[180, 0]}
scene={scene}
/>
</>,
);

cameraRef prop 提供了对舞台相机的引用。然后你可以使用这些引用来独立操作每个相机。

Camera.Stage 需要单个顶级节点作为其 scene prop。使用片段 (<></>) 定义场景是一个常见的错误源。

以下场景在传递给 Camera.Stage 时会导致错误:

const scene = (
<>
<Circle />
<Rect />
</>
);

相反,像这样将场景包裹在单个节点中:

const scene = (
<Node>
<Circle />
<Rect />
</Node>
);

Camera 组件不同,Camera.Stage 需要设置显式大小。舞台将自动将场景裁剪为指定大小。

Click to preview animation

import {makeScene2D, Node, Camera, Rect, Circle} from '@motion-canvas/2d';
import {all, createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera1 = createRef<Camera>();
const camera2 = createRef<Camera>();
const rect = createRef<Rect>();
const circle = createRef<Circle>();

const scene = (
<Node>
<Rect
ref={rect}
size={70}
fill={'lightseagreen'}
position={[-100, -30]}
/>
<Circle ref={circle} size={50} fill={'hotpink'} position={[100, 30]} />
</Node>
);

view.add(
<>
<Camera.Stage
cameraRef={camera1}
scene={scene}
size={[300, 200]}
position={[-180, 0]}
fill={'#ccc'}
radius={10}
smoothCorners
/>
<Camera.Stage
cameraRef={camera2}
scene={scene}
size={[300, 200]}
position={[180, 0]}
fill={'#ccc'}
radius={10}
smoothCorners
/>
</>,
);

yield* all(camera1().centerOn(rect(), 1), camera2().centerOn(circle(), 1));
yield* all(camera1().centerOn(circle(), 1), camera2().centerOn(rect(), 1));
yield* all(camera1().zoom(1.5, 1), camera2().zoom(0.5, 1));
yield* all(camera1().reset(1), camera2().reset(1));
});

与简单地渲染同一场景两次相比,这样做的好处是对场景所做的任何更改都将在两个相机中反映出来。

Click to preview animation

import {makeScene2D, Node, Camera, Rect, Circle} from '@motion-canvas/2d';
import {all, createRef} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
const camera1 = createRef<Camera>();
const camera2 = createRef<Camera>();
const rect = createRef<Rect>();
const circle = createRef<Circle>();

const scene = (
<Node>
<Rect
ref={rect}
size={70}
fill={'lightseagreen'}
position={[-100, -30]}
/>
<Circle ref={circle} size={50} fill={'hotpink'} position={[100, 30]} />
</Node>
);

view.add(
<>
<Camera.Stage
cameraRef={camera1}
scene={scene}
size={[300, 200]}
position={[-180, 0]}
fill={'#ccc'}
radius={10}
smoothCorners
/>
<Camera.Stage
cameraRef={camera2}
scene={scene}
size={[300, 200]}
position={[180, 0]}
fill={'#ccc'}
radius={10}
smoothCorners
/>
</>,
);

yield* all(
camera1().centerOn(rect(), 1),
camera2().centerOn(circle(), 1),
rect().fill('lightcoral', 1),
circle().fill('steelblue', 1),
);
yield* all(camera1().reset(1), camera2().reset(1));
yield* all(rect().fill('lightseagreen', 1), circle().fill('hotpink', 1));
});