Skip to content

Commit

Permalink
Add remaining Fish Pond tutorial steps
Browse files Browse the repository at this point in the history
  • Loading branch information
bbazukun123 committed Jan 13, 2024
1 parent cd53671 commit 4965624
Show file tree
Hide file tree
Showing 14 changed files with 826 additions and 117 deletions.
28 changes: 17 additions & 11 deletions src/tutorials/v8.0.0/fishPond/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import code3 from '!!raw-loader!./step3-code';
import completedCode3 from '!!raw-loader!./step3-completed-code';
import code4 from '!!raw-loader!./step4-code';
import completedCode4 from '!!raw-loader!./step4-completed-code';
import code5 from '!!raw-loader!./step5-code';
import completedCode5 from '!!raw-loader!./step5-completed-code';
import code6 from '!!raw-loader!./step6-code';
import type { TutorialStep } from '../..';
import content1 from './step1-content.md';
import content2 from './step2-content.md';
import content3 from './step3-content.md';
import content4 from './step4-content.md';
import content5 from './step5-content.md';
import content6 from './step6-content.md';

export const fishPondTutorialSteps: TutorialStep[] = [
{
Expand All @@ -32,19 +37,20 @@ export const fishPondTutorialSteps: TutorialStep[] = [
completedCode: completedCode3,
},
{
header: 'Adding Water Surface',
header: 'Adding Water Overlay',
Content: content4,
code: code4,
completedCode: completedCode4,
},
// {
// header: 'Add Displacement Effect',
// Content: content4,
// code: code4,
// },
// {
// header: 'You did it!',
// Content: content4,
// code: code4,
// },
{
header: 'Adding Displacement Effect',
Content: content5,
code: code5,
completedCode: completedCode5,
},
{
header: 'You did it!',
Content: content6,
code: code6,
},
];
3 changes: 2 additions & 1 deletion src/tutorials/v8.0.0/fishPond/step2-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const app = new Application();
{
await setup();
await preload();
this.addBackground();

addBackground();
})();

async function setup()
Expand Down
11 changes: 6 additions & 5 deletions src/tutorials/v8.0.0/fishPond/step2-completed-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const app = new Application();
{
await setup();
await preload();
this.addBackground();

addBackground();
})();

async function setup()
Expand Down Expand Up @@ -57,10 +58,10 @@ function addBackground()
}
else
{
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
background.height = app.screen.height;
background.scale.x = background.scale.y;
}
Expand Down
18 changes: 11 additions & 7 deletions src/tutorials/v8.0.0/fishPond/step3-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import { Application, Assets, Sprite, Container } from 'pixi.js';
// Create a PixiJS application.
const app = new Application();

// Store an array of fish sprites for animation.
const fishes = [];

// Asynchronous IIFE
(async () =>
{
await setup();
await preload();
this.addBackground();
this.addFishes();

addBackground();
addFishes();

// Add the fish animation callback to the application's ticker.
app.ticker.add(this.animateFishes);
app.ticker.add(animateFishes);
})();

async function setup()
Expand Down Expand Up @@ -61,10 +65,10 @@ function addBackground()
}
else
{
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
background.height = app.screen.height;
background.scale.x = background.scale.y;
}
Expand Down
15 changes: 8 additions & 7 deletions src/tutorials/v8.0.0/fishPond/step3-completed-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const fishes = [];
{
await setup();
await preload();
this.addBackground();
this.addFishes();

addBackground();
addFishes();

// Add the fish animation callback to the application's ticker.
app.ticker.add(this.animateFishes);
app.ticker.add(animateFishes);
})();

async function setup()
Expand Down Expand Up @@ -64,10 +65,10 @@ function addBackground()
}
else
{
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
/**
* If the preview is square or portrait, then fill the height of the screen instead
* and apply the scaling to the horizontal scale accordingly.
*/
background.height = app.screen.height;
background.scale.x = background.scale.y;
}
Expand Down
4 changes: 3 additions & 1 deletion src/tutorials/v8.0.0/fishPond/step3-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,6 @@ fishes.forEach((fish) =>
fish.y -= boundHeight;
}
});
```
```

They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic.
31 changes: 26 additions & 5 deletions src/tutorials/v8.0.0/fishPond/step4-code.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Application, Assets, Container, Sprite } from 'pixi.js';
import { Application, Assets, Container, Sprite, Texture, TilingSprite } from 'pixi.js';

// Create a PixiJS application.
const app = new Application();

// Store an array of fish sprites for animation.
const fishes = [];

// Reference to the water overlay.
let overlay;

// Asynchronous IIFE
(async () =>
{
await setup();
await preload();
this.addBackground();
this.addFishes();

// Add the fish animation callback to the application's ticker.
app.ticker.add(this.animateFishes);
addBackground();
addFishes();

addWaterOverlay();
animateWaterOverlay();

// Add the animation callbacks to the application's ticker.
app.ticker.add((time) =>
{
animateFishes(time);
animateWaterOverlay(time);
});
})();

async function setup()
Expand Down Expand Up @@ -165,3 +176,13 @@ function animateFishes(time)
}
});
}

function addWaterOverlay()
{
/** -- INSERT CODE HERE -- */
}

function animateWaterOverlay(time)
{
/** -- INSERT CODE HERE -- */
}
47 changes: 42 additions & 5 deletions src/tutorials/v8.0.0/fishPond/step4-completed-code.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Application, Assets, Container, Sprite } from 'pixi.js';
import { Application, Assets, Container, Sprite, Texture, TilingSprite } from 'pixi.js';

// Create a PixiJS application.
const app = new Application();

// Store an array of fish sprites for animation.
const fishes = [];

// Reference to the water overlay.
let overlay;

// Asynchronous IIFE
(async () =>
{
await setup();
await preload();
this.addBackground();
this.addFishes();

// Add the fish animation callback to the application's ticker.
app.ticker.add(this.animateFishes);
addBackground();
addFishes();

addWaterOverlay();
animateWaterOverlay();

// Add the animation callbacks to the application's ticker.
app.ticker.add((time) =>
{
animateFishes(time);
animateWaterOverlay(time);
});
})();

async function setup()
Expand Down Expand Up @@ -165,3 +176,29 @@ function animateFishes(time)
}
});
}

function addWaterOverlay()
{
// Create a water texture object.
const texture = Texture.from('overlay');

// Create a tiling sprite with the water texture and specify the dimensions.
overlay = new TilingSprite({
texture,
width: app.screen.width,
height: app.screen.height,
});

// Add the overlay to the stage.
app.stage.addChild(overlay);
}

function animateWaterOverlay(time)
{
// Extract the delta time from the Ticker object.
const delta = time.deltaTime;

// Animate the overlay.
overlay.tilePosition.x -= delta;
overlay.tilePosition.y -= delta;
}
92 changes: 17 additions & 75 deletions src/tutorials/v8.0.0/fishPond/step4-content.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,30 @@
# Adding Water Surface
# Adding Water Overlay

What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life.
At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface.

## Create and Setup Fish Sprites
## Create and Setup Tiling Sprite

Let's encapsulate all the following setup within the `addFishes` function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation.
Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage.

```javascript
const fishContainer = new Container();
const texture = Texture.from('overlay');

app.stage.addChild(fishContainer);
```

Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases.

```javascript
const fishCount = 20;
const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];
```

Instead of creating each of the fish individually, which will be super tedious, we will use a simple `for` loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like `direction`, `speed` and `turnSpeed` which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE.

```javascript
for (let i = 0; i < fishCount; i++)
{
const fishAsset = fishAssets[i % fishAssets.length];
const fish = Sprite.from(fishAsset);

fish.anchor.set(0.5);

fish.direction = Math.random() * Math.PI * 2;
fish.speed = 2 + Math.random() * 2;
fish.turnSpeed = Math.random() - 0.8;

fish.x = Math.random() * app.screen.width;
fish.y = Math.random() * app.screen.height;
fish.scale.set(0.8 + Math.random() * 0.3);

this.fishContainer.addChild(fish);
this.fishes.push(fish);
}
overlay = new TilingSprite({
texture,
width: app.screen.width,
height: app.screen.height,
});
app.stage.addChild(overlay);
```

## Animate Fishes

It's time to give the fishes some movements! Another function `animateFishes` has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls.

We will declare a few variables to help us with the animation. We extract `deltaTime` from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a `fish.x = 0`, half of the fish's width is still apparent on the screen.

```javascript
const delta = time.deltaTime;

const stagePadding = 100;
const boundWidth = app.screen.width + stagePadding * 2;
const boundHeight = app.screen.height + stagePadding * 2;
```
## Animate Overlay

We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound.
Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the `animateWaterOverlay` function.

```javascript
fishes.forEach((fish) =>
{
fish.direction += fish.turnSpeed * 0.01;
fish.x += Math.sin(fish.direction) * fish.speed;
fish.y += Math.cos(fish.direction) * fish.speed;
fish.rotation = -fish.direction - Math.PI / 2;

if (fish.x < -stagePadding)
{
fish.x += boundWidth;
}
if (fish.x > app.screen.width + stagePadding)
{
fish.x -= boundWidth;
}
if (fish.y < -stagePadding)
{
fish.y += boundHeight;
}
if (fish.y > app.screen.height + stagePadding)
{
fish.y -= boundHeight;
}
});
elapsed += time.deltaTime;
overlay.tilePosition.x = elapsed * -1;
overlay.tilePosition.y = elapsed * -1;
```

They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic.
Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!
Loading

0 comments on commit 4965624

Please sign in to comment.