-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<script setup lang="ts"> | ||
import { TresCanvas } from '@tresjs/core' | ||
import { OrbitControls, Line2, AnimatedSprite } from '@tresjs/cientos' | ||
import { TresLeches, useControls } from '@tresjs/leches' | ||
import '@tresjs/leches/styles' | ||
import { TexturePackerFrameDataArray } from '../../../../src/core/abstractions/AnimatedSprite/Atlas' | ||
const { anchorX, anchorY, fps } = useControls({ | ||
anchorX: { value: 0.5, min: 0, max: 1, step: 0.1 }, | ||
anchorY: { value: 0.5, min: 0, max: 1, step: 0.1 }, | ||
fps: {value:5, min:0, max:30, step:1} | ||
Check failure on line 11 in docs/.vitepress/theme/components/AnimatedSpriteAnchorDemo.vue
|
||
}) | ||
const anchorDemoAtlas: TexturePackerFrameDataArray = { frames: [] } | ||
const anchorDemoImgData = (() => { | ||
const NUM_ROWS_COLS = 64 | ||
const rects: { x: number, y: number, w: number, h: number }[] = [] | ||
Check failure on line 17 in docs/.vitepress/theme/components/AnimatedSpriteAnchorDemo.vue
|
||
let h = 0 | ||
for (let r = 0; r < NUM_ROWS_COLS; r += h) { | ||
let w = 0 | ||
h++ | ||
if (r + h >= NUM_ROWS_COLS) { | ||
continue | ||
} | ||
for (let c = 0; c < NUM_ROWS_COLS; c += w) { | ||
w++ | ||
if (c + w >= NUM_ROWS_COLS) { | ||
continue | ||
} | ||
rects.push({ x: c, y: r, w, h }) | ||
} | ||
} | ||
const canvas = document.createElement('canvas') | ||
const IMG_SIZE = 2048 | ||
const COL_SIZE = IMG_SIZE / NUM_ROWS_COLS | ||
const ROW_SIZE = IMG_SIZE / NUM_ROWS_COLS | ||
canvas.width = IMG_SIZE | ||
canvas.height = IMG_SIZE | ||
document.body.append(canvas) | ||
const ctx = canvas.getContext('2d')! | ||
const EDGE_ANCHOR_SIZE = 6 | ||
rects.forEach((rect, i) => { | ||
const frame = { x: rect.x * COL_SIZE, y: rect.y * ROW_SIZE, w: rect.w * COL_SIZE, h: rect.h * ROW_SIZE } | ||
const { x, y, w, h } = frame | ||
anchorDemoAtlas.frames.push({ filename: 'rect_' + i.toString().padStart(4, '0'), frame }) | ||
ctx.fillStyle = `#222` | ||
ctx.fillRect(x, y, w, h) | ||
ctx.fillStyle = '#999' | ||
ctx.fillRect(x + w * 0.5 - EDGE_ANCHOR_SIZE * 0.5, y + h * 0.5 - EDGE_ANCHOR_SIZE * 0.5, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x, y, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x + w * 0.5 - EDGE_ANCHOR_SIZE * 0.5, y, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x + w - EDGE_ANCHOR_SIZE, y, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x, y + h * 0.5 - EDGE_ANCHOR_SIZE * 0.5, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x + w - EDGE_ANCHOR_SIZE, y + h * 0.5 - EDGE_ANCHOR_SIZE * 0.5, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x, y + h - EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x + w * 0.5 - EDGE_ANCHOR_SIZE * 0.5, y + h - EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
ctx.fillRect(x + w - EDGE_ANCHOR_SIZE, y + h - EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE, EDGE_ANCHOR_SIZE) | ||
}) | ||
const imgData = canvas.toDataURL() | ||
canvas.parentElement?.removeChild(canvas) | ||
return imgData | ||
})() | ||
</script> | ||
|
||
<template> | ||
<TresLeches style="position:absolute; left:10px; top:10px;" /> | ||
<TresCanvas clear-color="#82DBC5"> | ||
<TresPerspectiveCamera :position="[5, 1, 5]" :look-at="[-2, 0, 0]" /> | ||
<OrbitControls /> | ||
<TresGroup :position-x="2"> | ||
<Line2 :points="[[-0.25, 0, 0], [0.25, 0, 0]]" :line-width="1" color="#FF0000" /> | ||
<Line2 :points="[[0, -0.25, 0], [0, 0.25, 0]]" :line-width="1" color="#00FF00" /> | ||
<Line2 :points="[[0, 0, -0.25], [0, 0, 0.25]]" :line-width="1" color="#0000FF" /> | ||
<Suspense> | ||
<AnimatedSprite :image="anchorDemoImgData" :atlas="anchorDemoAtlas" animation="rect" | ||
:anchor="[anchorX.value, anchorY.value]" :fps="fps.value" /> | ||
Check warning on line 81 in docs/.vitepress/theme/components/AnimatedSpriteAnchorDemo.vue
|
||
</Suspense> | ||
<TresGridHelper :args="[10, 10]" /> | ||
</TresGroup> | ||
</TresCanvas> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<script setup lang="ts"> | ||
import { TresCanvas } from '@tresjs/core' | ||
import { AnimatedSprite, OrbitControls } from '@tresjs/cientos' | ||
</script> | ||
|
||
<template> | ||
<TresLeches /> | ||
<TresCanvas clear-color="#82DBC5"> | ||
<OrbitControls /> | ||
<TresPerspectiveCamera :position="[0, 0, 5]"/> | ||
<Suspense> | ||
<AnimatedSprite | ||
image="https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/namedAnimationsTexture.png" | ||
atlas="https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/namedAnimationsAtlas.json" | ||
animation="yes" :fps="10" :loop="true" /> | ||
</Suspense> | ||
</TresCanvas> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<script setup lang="ts"> | ||
import { TresCanvas } from '@tresjs/core' | ||
import { AnimatedSprite } from '@tresjs/cientos' | ||
</script> | ||
|
||
<template> | ||
<TresCanvas clear-color="#82DBC5"> | ||
<TresPerspectiveCamera :position="[0, 0, 5]"/> | ||
<Suspense> | ||
<AnimatedSprite | ||
image="https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/textureWithoutAtlas.png" | ||
:atlas="16" | ||
:animation="[0,15]" | ||
:fps="10" | ||
/> | ||
</Suspense> | ||
</TresCanvas> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# AnimatedSprite | ||
|
||
<DocsDemo> | ||
<AnimatedSpriteDemo /> | ||
</DocsDemo> | ||
|
||
`<AnimatedSprite />` allows you to use 2D animations defined in a [texture atlas](https://en.wikipedia.org/wiki/Texture_atlas). A typical `<AnimatedSprite />` will use: | ||
|
||
* an image containing multiple sprites | ||
* a JSON atlas containing the coordinates of the image | ||
|
||
## Usage | ||
|
||
<<< @/.vitepress/theme/components/AnimatedSpriteDemo.vue{3,11-16} | ||
|
||
::: warning Suspense | ||
`<AnimatedSprite />` loads resources asynchronously, so it must be wrapped in a `<Suspense />`. | ||
::: | ||
|
||
## Props | ||
|
||
<CientosPropsTable | ||
component-path="src/core/abstractions/AnimatedSprite/component.vue" | ||
:fields="['name', 'description', 'default', 'required']" | ||
:on-format-value="({valueFormatted, propName, fieldName, getFieldFormatted})=> { | ||
if (fieldName === 'description') { | ||
let type = getFieldFormatted('type') | ||
if (type.indexOf('TSFunctionType') !== -1 && propName.startsWith('on')) { | ||
type = '<code>(frameName:string) => void</code>' | ||
} | ||
return type + ' – ' + valueFormatted | ||
} | ||
}" | ||
/> | ||
|
||
## `animation` | ||
|
||
The `animation` prop holds either the name of the currently playing animation or a range of frames to play, or a frame number to display. | ||
|
||
### Named animations | ||
|
||
Frames are automatically grouped into named animations, if you use either of the following naming conventions for your source images: | ||
|
||
* `[key][frame number].[file_extension]` | ||
* `[key]_[frame number].[file_extension]` | ||
|
||
The `<AnimatedSprite />` will automatically make all [key] available for playback as named animations. | ||
|
||
#### Example | ||
|
||
Here are some example source image names, to be compiled into an image texture and atlas. | ||
|
||
* heroIdle00.png | ||
* heroIdle01.png | ||
* heroIdle02.png | ||
* heroRun00.png | ||
* heroRun01.png | ||
* heroRun02.png | ||
* heroRun03.png | ||
* heroHeal00.png | ||
* heroHeal01.png | ||
|
||
When the resulting image texture and atlas are provided to `<AnimatedSprite />`, "heroIdle", "heroRun", and "heroHeal" will be available as named animations. Animation names can be used as follows: | ||
|
||
```vue{3} | ||
<AnimatedSprite | ||
atlas="..." image="..." | ||
animation="heroRun" | ||
/> | ||
``` | ||
|
||
### Ranges | ||
|
||
A `[number, number]` range can be supplied as the `animation` prop. The numbers correspond to the position of the frame in the `atlas` `frames` array, starting with `0`. The first `number` in the range represents the start frame of the animation. The last `number` represents the end frame. | ||
|
||
### Single frame | ||
|
||
To display a single animation frame, a `number` can be supplied as the `animation` prop. The `number` corresponds to the position of the frame in the `atlas` `frames` array, starting with `0`. | ||
|
||
## `anchor` | ||
|
||
The `anchor` allow you to control how differently sized source images "grow" and "shrink". Namely, they "grow out from" and "shrink towards" the anchor. `[0, 0]` places the anchor at the top left corner of the `<AnimatedSprite />`. `[1,1]` places the anchor at the bottom right corner. By default, the anchor is placed at `[0.5, 0.5]` i.e., the center. | ||
|
||
Below is a simple animation containing differently sized source images. The anchor is visible at world position `0, 0, 0`. | ||
<DocsDemo> | ||
<AnimatedSpriteAnchorDemo /> | ||
</DocsDemo> | ||
|
||
::: warning | ||
Changing the anchor from the default can have unpredictable results if `asSprite` is `true`. | ||
::: | ||
|
||
## `definitions` | ||
|
||
For each [named animation](#named-animations), you can supply a "definition" that specifies frame order and repeated frames (delays). For the [named animation example above](#named-animations), the `definitions` prop might look like this: | ||
|
||
```vue | ||
<AnimatedSprite | ||
atlas="..." image="..." | ||
:definitions="{ | ||
'heroIdle':'0(5),1-2', // repeat frame 0 five times, then play frames 1-2 | ||
'heroRun':'0-3,2-1', // play frames 0-3, then 2-1 | ||
// no "heroHeal" definition; the default frame order will be used: 0-1 | ||
}" | ||
/> | ||
``` | ||
|
||
## Compiling an atlas | ||
|
||
In typical usage, `<AnimatedSprite/>` requires both the URL to a texture of compiled sprite images and a JSON atlas containing information about the sprites in the texture. | ||
|
||
* [example compiled texture](https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/namedAnimationsTexture.png) | ||
* [example JSON atlas](https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/namedAnimationsAtlas.json) | ||
|
||
Compiling source images into a texture atlas is usually handled by third-party software. You may find [TexturePacker](https://www.codeandweb.com/texturepacker) useful. | ||
|
||
## Without an atlas | ||
|
||
There may be cases where you don't want to supply a generated JSON atlas as an `atlas` prop. This is possible if you compile your source images in a single row of equally sized columns *and* set the `atlas` prop to the number of columns. | ||
|
||
### Example | ||
|
||
This image is comprised of 16 source images, compiled into a single image, in a single row: | ||
|
||
<img src="https://raw.githubusercontent.com/Tresjs/assets/6c0b087768a0a2b76148c99fc87d7e6ddc3c6d66/textures/animated-sprite/textureWithoutAtlas.png" /> | ||
|
||
<DocsDemo> | ||
<AnimatedSpriteNoAtlasDemo /> | ||
</DocsDemo> | ||
|
||
<<< @/.vitepress/theme/components/AnimatedSpriteNoAtlasDemo.vue{12,13} |