遊戲引擎六大能力展示
用 SVG 動畫展示遊戲引擎的六項核心功能:會跑、會跳、會打人、按鍵反應、碰撞偵測、畫面更新,每張卡片依序彈入。
遊戲開發引擎SVG動畫卡片展示
提示詞(可直接修改內容)
import React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
} from "remotion";
const colors = {
background: "#0A0E14",
backgroundGradient: "linear-gradient(135deg, #0A0E14 0%, #131A24 100%)",
text: "#FFFFFF",
accent: "#00D4AA",
accentSecondary: "#4DA3FF",
warning: "#FFB547",
danger: "#FF6B6B",
dimmed: "rgba(255, 255, 255, 0.6)",
cardBg: "rgba(255, 255, 255, 0.05)",
border: "rgba(0, 212, 170, 0.3)",
};
const fonts = { main: "'Inter', 'Noto Sans TC', sans-serif" };
const RunningFigure: React.FC<{ frame: number; progress: number }> = ({ frame, progress }) => {
const runCycle = Math.sin(frame * 0.4) * 12;
const armSwing = Math.sin(frame * 0.4 + Math.PI) * 20;
const bobY = Math.abs(Math.sin(frame * 0.4)) * 5;
const moveX = interpolate(progress, [0, 1], [-40, 40]);
const opacity = interpolate(progress, [0, 0.05, 0.9, 1], [0, 1, 1, 0.8]);
return (
<g transform={`translate(${moveX}, ${-bobY})`} opacity={opacity}>
<circle cx="0" cy="-30" r="8" fill={colors.accent} />
<line x1="0" y1="-22" x2="0" y2="5" stroke={colors.accent} strokeWidth="3" strokeLinecap="round" />
<line x1="0" y1="-15" x2={-12 + armSwing * 0.3} y2={-5 + Math.abs(armSwing) * 0.15} stroke={colors.accent} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="-15" x2={12 - armSwing * 0.3} y2={-5 + Math.abs(armSwing) * 0.15} stroke={colors.accent} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2={runCycle * 0.8} y2="25" stroke={colors.accent} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2={-runCycle * 0.8} y2="25" stroke={colors.accent} strokeWidth="2.5" strokeLinecap="round" />
{[0, 1, 2].map((i) => (
<line key={i} x1={-20 - i * 6} y1={-20 + i * 12} x2={-28 - i * 6} y2={-20 + i * 12} stroke={colors.accent} strokeWidth="1.5" strokeLinecap="round" opacity={0.4 + Math.sin(frame * 0.3 + i) * 0.3} />
))}
</g>
);
};
const JumpingFigure: React.FC<{ progress: number }> = ({ progress }) => {
const jumpY = interpolate(progress, [0, 0.15, 0.5, 0.85, 1], [0, 0, -45, -45, 0]);
const squash = progress < 0.15 ? interpolate(progress, [0, 0.15], [1, 0.8]) : progress > 0.85 ? interpolate(progress, [0.85, 1], [0.8, 1]) : 1;
const stretch = progress > 0.15 && progress < 0.5 ? interpolate(progress, [0.15, 0.35], [1, 1.2]) : progress >= 0.5 && progress < 0.85 ? interpolate(progress, [0.5, 0.85], [1.2, 1]) : 1;
const armsUp = progress > 0.15 && progress < 0.85;
const opacity = interpolate(progress, [0, 0.05, 0.95, 1], [0, 1, 1, 0.8]);
return (
<g transform={`translate(0, ${jumpY}) scale(${squash}, ${stretch})`} opacity={opacity}>
<circle cx="0" cy="-30" r="8" fill={colors.warning} />
<line x1="0" y1="-22" x2="0" y2="5" stroke={colors.warning} strokeWidth="3" strokeLinecap="round" />
<line x1="0" y1="-15" x2={armsUp ? -14 : -12} y2={armsUp ? -30 : -5} stroke={colors.warning} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="-15" x2={armsUp ? 14 : 12} y2={armsUp ? -30 : -5} stroke={colors.warning} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2={-8} y2={armsUp ? 15 : 25} stroke={colors.warning} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2={8} y2={armsUp ? 15 : 25} stroke={colors.warning} strokeWidth="2.5" strokeLinecap="round" />
</g>
);
};
const PunchingFigure: React.FC<{ progress: number; frame: number }> = ({ progress, frame }) => {
const punchExtend = progress > 0.3 && progress < 0.7 ? interpolate(progress, [0.3, 0.45, 0.55, 0.7], [0, 1, 1, 0]) : 0;
const opacity = interpolate(progress, [0, 0.05, 0.9, 1], [0, 1, 1, 0.8]);
const impactFlash = punchExtend > 0.8;
return (
<g opacity={opacity}>
<circle cx="0" cy="-30" r="8" fill={colors.danger} />
<line x1="0" y1="-22" x2="0" y2="5" stroke={colors.danger} strokeWidth="3" strokeLinecap="round" />
<line x1="0" y1="-15" x2={12 + punchExtend * 25} y2={-15 + punchExtend * 2} stroke={colors.danger} strokeWidth="3" strokeLinecap="round" />
<line x1="0" y1="-15" x2="-12" y2="-5" stroke={colors.danger} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2="-8" y2="25" stroke={colors.danger} strokeWidth="2.5" strokeLinecap="round" />
<line x1="0" y1="5" x2="10" y2="22" stroke={colors.danger} strokeWidth="2.5" strokeLinecap="round" />
{impactFlash && (
<g transform={`translate(40, -16)`}>
{[0, 45, 90, 135].map((angle) => (
<line key={angle} x1="0" y1="0" x2={Math.cos((angle * Math.PI) / 180) * 12} y2={Math.sin((angle * Math.PI) / 180) * 12} stroke={colors.warning} strokeWidth="2.5" strokeLinecap="round" />
))}
</g>
)}
</g>
);
};
const ButtonPress: React.FC<{ progress: number; frame: number }> = ({ progress, frame }) => {
const pressed = progress > 0.3 && progress < 0.6;
const btnY = pressed ? 4 : 0;
const ripple = progress > 0.4 ? interpolate(progress, [0.4, 0.8], [0, 1], { extrapolateRight: "clamp" }) : 0;
const opacity = interpolate(progress, [0, 0.1, 0.9, 1], [0, 1, 1, 0.8]);
return (
<g opacity={opacity}>
<rect x="-28" y="4" width="56" height="28" rx="8" fill="rgba(0,0,0,0.3)" />
<rect x="-28" y={btnY} width="56" height="28" rx="8" fill={pressed ? colors.accent : colors.accentSecondary} stroke={colors.accent} strokeWidth="2" />
<text x="0" y={16 + btnY} textAnchor="middle" fontSize="14" fontWeight="bold" fontFamily="monospace" fill="#fff">A</text>
<g transform={`translate(0, ${pressed ? -16 : -22})`}>
<circle cx="0" cy="0" r="6" fill={colors.dimmed} opacity={0.6} />
<line x1="0" y1="-6" x2="0" y2="-18" stroke={colors.dimmed} strokeWidth="3" strokeLinecap="round" opacity={0.5} />
</g>
{ripple > 0 && (
<>
<circle cx="0" cy="14" r={20 + ripple * 30} stroke={colors.accent} strokeWidth="2" fill="none" opacity={(1 - ripple) * 0.6} />
<circle cx="0" cy="14" r={10 + ripple * 20} stroke={colors.accent} strokeWidth="1.5" fill="none" opacity={(1 - ripple) * 0.4} />
</>
)}
</g>
);
};
const CollisionEffect: React.FC<{ progress: number; frame: number }> = ({ progress, frame }) => {
const approach = interpolate(progress, [0, 0.35], [0, 1], { extrapolateRight: "clamp" });
const bounce = progress > 0.35 ? interpolate(progress, [0.35, 0.7, 1], [0, 1, 1]) : 0;
const leftX = interpolate(approach, [0, 1], [-50, -12]) + (bounce > 0 ? -bounce * 30 : 0);
const rightX = interpolate(approach, [0, 1], [50, 12]) + (bounce > 0 ? bounce * 30 : 0);
const impactFlash = progress > 0.33 && progress < 0.5;
const opacity = interpolate(progress, [0, 0.05, 0.9, 1], [0, 1, 1, 0.8]);
return (
<g opacity={opacity}>
<rect x={leftX - 12} y="-12" width="24" height="24" rx="4" fill={colors.accentSecondary} opacity={0.9} />
<circle cx={rightX} cy="0" r="12" fill={colors.warning} opacity={0.9} />
{impactFlash && (
<g>
{[0, 60, 120, 180, 240, 300].map((angle) => (
<circle key={angle} cx={Math.cos((angle * Math.PI) / 180) * 10} cy={Math.sin((angle * Math.PI) / 180) * 10} r="2.5" fill={colors.warning} opacity={0.8} />
))}
<circle cx="0" cy="0" r="8" fill="#fff" opacity={0.4} />
</g>
)}
</g>
);
};
const ScreenRefresh: React.FC<{ progress: number; frame: number }> = ({ progress, frame }) => {
const scanlineY = interpolate((frame * 3) % 60, [0, 60], [-30, 30]);
const fpsCount = progress > 0.2 ? Math.floor(interpolate(progress, [0.2, 0.5], [0, 60], { extrapolateRight: "clamp" })) : 0;
const opacity = interpolate(progress, [0, 0.1, 0.9, 1], [0, 1, 1, 0.8]);
return (
<g opacity={opacity}>
<rect x="-35" y="-25" width="70" height="50" rx="4" fill="rgba(255,255,255,0.05)" stroke={colors.accent} strokeWidth="2" />
<line x1="-33" y1={scanlineY} x2="33" y2={scanlineY} stroke={colors.accent} strokeWidth="1" opacity={0.3} />
{[-15, -5, 5, 15].map((yPos, i) => (
<rect key={i} x="-25" y={yPos - 2} width={interpolate(progress, [0.1 + i * 0.05, 0.3 + i * 0.05], [0, 50 - i * 8], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })} height="4" rx="2" fill={i % 2 === 0 ? colors.accent : colors.accentSecondary} opacity={0.5} />
))}
<g transform="translate(25, -18)">
<rect x="-14" y="-8" width="28" height="16" rx="4" fill={colors.accent} opacity={0.9} />
<text x="0" y="4" textAnchor="middle" fontSize="10" fontWeight="bold" fontFamily="monospace" fill="#000">{fpsCount}</text>
</g>
</g>
);
};
const AbilityCard: React.FC<{ children: React.ReactNode; label: string; progress: number; color: string }> = ({ children, label, progress, color }) => {
const scale = interpolate(progress, [0, 0.5, 1], [0, 1.1, 1]);
const opacity = interpolate(progress, [0, 0.2], [0, 1], { extrapolateRight: "clamp" });
return (
<div style={{ transform: `scale(${scale})`, opacity, display: "flex", flexDirection: "column", alignItems: "center", gap: 18 }}>
<div style={{ width: 420, height: 330, borderRadius: 20, background: colors.cardBg, border: `1.5px solid ${color}30`, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: `0 0 30px ${color}15` }}>
{children}
</div>
<div style={{ fontSize: 45, fontWeight: 700, fontFamily: fonts.main, color, letterSpacing: 3, textShadow: `0 0 12px ${color}40` }}>{label}</div>
</div>
);
};
export const Scene03-EngineAbilities: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const runProgress = interpolate(frame, [30, 100], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const jumpProgress = interpolate(frame, [55, 125], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const punchProgress = interpolate(frame, [80, 145], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const runCardSpring = spring({ frame: Math.max(0, frame - 28), fps, config: { damping: 12, stiffness: 100 } });
const jumpCardSpring = spring({ frame: Math.max(0, frame - 53), fps, config: { damping: 12, stiffness: 100 } });
const punchCardSpring = spring({ frame: Math.max(0, frame - 78), fps, config: { damping: 12, stiffness: 100 } });
const buttonProgress = interpolate(frame, [110, 175], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const collisionProgress = interpolate(frame, [135, 200], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const refreshProgress = interpolate(frame, [160, 230], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const buttonCardSpring = spring({ frame: Math.max(0, frame - 108), fps, config: { damping: 12, stiffness: 100 } });
const collisionCardSpring = spring({ frame: Math.max(0, frame - 133), fps, config: { damping: 12, stiffness: 100 } });
const refreshCardSpring = spring({ frame: Math.max(0, frame - 158), fps, config: { damping: 12, stiffness: 100 } });
const fadeOut = interpolate(frame, [240, 270], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ background: colors.backgroundGradient }}>
<AbsoluteFill style={{ display: "flex", alignItems: "center", justifyContent: "center", opacity: fadeOut }}>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 420px)", gridTemplateRows: "repeat(2, auto)", gap: "75px 120px", justifyContent: "center", alignContent: "center" }}>
<AbilityCard label="會跑" progress={runCardSpring} color={colors.accent}>
<svg width="300" height="255" viewBox="-60 -45 120 90"><RunningFigure frame={frame} progress={runProgress} /></svg>
</AbilityCard>
<AbilityCard label="會跳" progress={jumpCardSpring} color={colors.warning}>
<svg width="300" height="255" viewBox="-60 -50 120 100"><JumpingFigure progress={jumpProgress} /></svg>
</AbilityCard>
<AbilityCard label="會打人" progress={punchCardSpring} color={colors.danger}>
<svg width="300" height="255" viewBox="-60 -45 120 90"><PunchingFigure progress={punchProgress} frame={frame} /></svg>
</AbilityCard>
<AbilityCard label="按鍵反應" progress={buttonCardSpring} color={colors.accentSecondary}>
<svg width="300" height="255" viewBox="-60 -45 120 90"><ButtonPress progress={buttonProgress} frame={frame} /></svg>
</AbilityCard>
<AbilityCard label="碰撞偵測" progress={collisionCardSpring} color={colors.warning}>
<svg width="300" height="255" viewBox="-60 -30 120 60"><CollisionEffect progress={collisionProgress} frame={frame} /></svg>
</AbilityCard>
<AbilityCard label="畫面更新" progress={refreshCardSpring} color={colors.accent}>
<svg width="300" height="255" viewBox="-60 -35 120 70"><ScreenRefresh progress={refreshProgress} frame={frame} /></svg>
</AbilityCard>
</div>
</AbsoluteFill>
</AbsoluteFill>
);
};登入後查看完整程式碼