Ruo
Ruo

Reputation: 127

I'm trying sprite animations in React for the first time and my character keeps teleporting to its starting position

It's my first time messing around with sprites in ReactJs and I wanted to make something for fun. There is a canvas where a character randomly walks left/right. My sprite sheet has 3 columns and 6 rows, where the rows are respectively: walk left, walk right, dangling left, dangling right, fall left, fall right and the spritesheet is 384 × 768 pixels. I have this code, which so far allows the character to randomly walk left/right and when it is clicked, swaps to the dangling animation. However, the character doesn't walk far from it's starting position before teleporting back to the starting location. I'd be grateful if someone could help sort out my code.

import styled, { ThemeProvider } from 'styled-components'
import defaultTheme, { Theme } from '../styles/theme'
import React, { useEffect, useRef, useState } from 'react'
import pokemon from '../assets/pokemon.png'

type AnimationProps = {
  theme?: Theme
}

const SpriteCanvas = styled.canvas`
  width: 100%;
  height: 100%;
  background-color: transparent;
  color: black;
`

export default function AnimationComponent(props: AnimationProps) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null)
  const [isDragging, setIsDragging] = useState(false)
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
  const [animationType, setAnimationType] = useState<
    'walkLeft' | 'walkRight' | 'pause' | 'danglingLeft' | 'danglingRight'
  >('walkLeft')

  useEffect(() => {
    const canvas = canvasRef.current
    if (!canvas) return

    const ctx = canvas.getContext('2d')
    if (!ctx) return

    const animations = {
      walkLeft: { row: 0, frameCount: 3 },
      walkRight: { row: 1, frameCount: 3 },
      pause: { row: 0, frameCount: 1 },
      danglingLeft: { row: 2, frameCount: 3 },
      danglingRight: { row: 3, frameCount: 3 },
    } as const

    let frameIndex = 0
    const frameWidth = 128
    const frameHeight = 128

    let x = canvas.width / 2 - frameWidth / 2 // Start character in the center (adjusted for sprite width)
    let y = canvas.height - frameHeight
    const speed = 1

    let frameCounter = 0

    const updateFrame = () => {
      const anim = animations[animationType]
      const frameDelay = 10

      frameCounter += 1
      if (frameCounter >= frameDelay) {
        frameIndex = (frameIndex + 1) % anim.frameCount
        frameCounter = 0
      }

      if (!isDragging) {
        if (animationType === 'walkLeft') x -= speed
        if (animationType === 'walkRight') x += speed

        // Ensure the character stays within canvas bounds
        const canvasWidth = canvas.width
        if (x < 0) x = 0
        if (x + frameWidth > canvasWidth) x = canvasWidth - frameWidth
      }
    }

    const drawFrame = () => {
      const anim = animations[animationType]
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.drawImage(
        spriteSheet,
        frameIndex * frameWidth,
        anim.row * frameHeight,
        frameWidth,
        frameHeight,
        x,
        y,
        frameWidth,
        frameHeight
      )
    }

    const spriteSheet = new Image()
    spriteSheet.src = pokemon

    const loop = () => {
      updateFrame()
      drawFrame()
      requestAnimationFrame(loop)
    }

    spriteSheet.onload = () => {
      const containerWidth = canvas.offsetWidth
      const containerHeight = canvas.offsetHeight
      canvas.width = containerWidth
      canvas.height = containerHeight
      y = canvas.height - frameHeight

      loop()
    }

    // Mouse event handlers for dragging
    const handleMouseDown = (e: MouseEvent) => {
      const mouseX = e.clientX - canvas.offsetLeft
      const mouseY = e.clientY - canvas.offsetTop

      if (
        mouseX >= x &&
        mouseX <= x + frameWidth &&
        mouseY >= y &&
        mouseY <= y + frameHeight
      ) {
        setIsDragging(true)
        setDragOffset({ x: mouseX - x, y: mouseY - y })
        setAnimationType(
          animationType == 'walkLeft'  ? 'danglingLeft' : 'danglingRight'
        )
      }
    }

    const handleMouseMove = (e: MouseEvent) => {
      if (isDragging) {
        const mouseX = e.clientX - canvas.offsetLeft
        const mouseY = e.clientY - canvas.offsetTop

        x = mouseX - dragOffset.x
        y = mouseY - dragOffset.y

        // Keep character within canvas bounds while dragging
        if (x < 0) x = 0
        if (x + frameWidth > canvas.width) x = canvas.width - frameWidth
        if (y < 0) y = 0
        if (y + frameHeight > canvas.height) y = canvas.height - frameHeight
      }
    }

    const handleMouseUp = () => {
      setIsDragging(false)
      setAnimationType('walkLeft')
    }

    // Add event listeners for mouse interactions
    canvas.addEventListener('mousedown', handleMouseDown)
    canvas.addEventListener('mousemove', handleMouseMove)
    canvas.addEventListener('mouseup', handleMouseUp)

    // Cleanup event listeners
    return () => {
      canvas.removeEventListener('mousedown', handleMouseDown)
      canvas.removeEventListener('mousemove', handleMouseMove)
      canvas.removeEventListener('mouseup', handleMouseUp)
    }
  }, [isDragging, dragOffset, animationType])

  // Random animation change every few seconds
  useEffect(() => {
    if (isDragging) return

    const randomizeAnimation = () => {
      const rand = Math.random()
      if (rand < 0.33) {
        setAnimationType('walkLeft')
      } else if (rand < 0.66) {
        setAnimationType('walkRight')
      } else {
        setAnimationType('pause')
      }
    }

    const interval = setInterval(randomizeAnimation, 1000)

    return () => clearInterval(interval)
  }, [isDragging])

  return (
    <ThemeProvider theme={props.theme ?? defaultTheme}>
      <SpriteCanvas ref={canvasRef} />
    </ThemeProvider>
  )
}

Upvotes: 0

Views: 21

Answers (0)

Related Questions