単純な往復運動であればsin関数を使って実装しますが、これだと等速にはなりません。
いや、数学の得意な人であれば、どうにかして等速にできるのかもしれないですが、今回は単純に区間を区切った式で等速往復を実装します。
DEMO
※ クリックすると動き出します
ソースコード(抜粋)
function Invader({ count, initX }) { const maxDistance = 400; // 折り返しを行う距離 const speed = 1; const size = 40; const [x, setX] = useState(0); useEffect(() => { setX(initX); }, []); useEffect(() => { const totalX = initX + count * speed; const level = 0 | totalX / maxDistance; // 折り返した数 const toRight = Boolean(level % 2); // 往路(to Left)か復路(to Right)か判定 let x = totalX % maxDistance; if (toRight) { // 復路の場合は位置を反転 x = maxDistance - x; } setX(x); }, [count]); return ( <div style={{ left: `${ x }px`, position: 'absolute', fontSize: `${size}px` }}>👾</div> ); }
愚直に実装しました。
往路と復路で条件分岐して、位置を反転させています。
次は動きをカクカクさせてみましょう。
DEMO
※ クリックすると動き出します
ソースコード(抜粋)
function Invader({ count, initX }) { const maxDistance = 400; const speed = 1; const size = 40; const [x, setX] = useState(0); useEffect(() => { setX(initX); }, []); useEffect(() => { const totalX = initX + count * speed; const level = 0 | totalX / maxDistance; const toRight = Boolean(level % 2); let x = (0 | totalX % maxDistance / size) * size; // 一度小数点以下を切り捨てることで段階的に変化させる if (toRight) { // 復路の場合は位置を反転 x = maxDistance - x; } setX(x); }, [count]); return ( <div style={{ left: `${ x }px`, position: 'absolute', fontSize: `${size}px` }}>👾</div> ); }
修正した箇所はコメントを入れた1箇所のみです。
小数点以下を切り捨てることでカクカク動くようになりました。
次は、端まで来た時に一歩前進させてみます。
DEMO
※ クリックすると動き出します
ソースコード(抜粋)
function Invader({ count, initX, initY }) { const maxDistance = 400; const speed = 1; const size = 40; const [x, setX] = useState(0); const [y, setY] = useState(0); useEffect(() => { setX(initX); setY(initY); }, []); useEffect(() => { const totalX = initX + count * speed; const level = 0 | totalX / maxDistance; const toRight = Boolean(level % 2); let x = (0 | totalX % maxDistance / size) * size; const y = level * size; // 折り返した数だけ前進させる if (toRight) { x = maxDistance - x; } setX(x); setY(y); }, [count]); return ( <div style={{ left: `${ x }px`, top: `${ y }px`, position: 'absolute', fontSize: `${size}px` }}>👾</div> ); }
前進するようになりました。
しかし、前進する時は斜め方向に動いているように見えます。
なので、等速で動かすことをやめて、端に来たらX方向の移動を一時停止してみます。
DEMO
ソースコード(抜粋)
function Invader({ count, initX, initY }) { const maxDistance = 400; const speed = 1; const size = 40; const [x, setX] = useState(0); const [y, setY] = useState(0); useEffect(() => { setX(initX); setY(initY); }, []); useEffect(() => { const totalX = initX + count * speed; const level = 0 | totalX / (maxDistance + size); // 一時停止分を考慮 const toRight = Boolean(level % 2); let x = (0 | Math.min(maxDistance, totalX % (maxDistance + size)) / size) * size; // 一時停止分を考慮 const y = level * size; if (toRight) { x = maxDistance - x; } setX(x); setY(y); }, [count]); return ( <div style={{ left: `${ x }px`, top: `${ y }px`, position: 'absolute', fontSize: `${size}px` }}>👾</div> ); }
折り返すまでの距離を若干伸ばしつつ、Xの限界値は伸ばした距離を考慮しないことで、両端での一時停止を実現しました。
今回は以上です。