This is the fourth day of my participation in the August More text Challenge. For details, see:August is more challenging
preface
To the moon from the beginning,
Even if the failure
Maybe we can get a star.
— Clement Stone
introduce
Haven’t seen a firefly in a long time. Think of childhood, in the woods full of small fireflies, in the dark twilight, how happily they spread their wings! He is neither the sun nor the moon, and like the stars he brings endless joy.
Virtual joysticks are an important component of mobile games, especially RPGS. In this installment, we developed a virtual joystick to control the movement of fireflies. We will complete this scene by building the basic scene, drawing the background and firefly, and implementing the virtual joystick.
The development of
1. Set up basic scenarios
<style>
* {
padding: 0;
margin: 0;
}
html.body {
width: 100%;
height: 100vh;
overflow: hidden;
}
#canvas{
width: 100%;
height: 100%;
}
</style>
<body>
<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
</body>
Copy the code
Here we write the infrastructure first, using the Module pattern to import modules.
Let’s define rocker and introduce it.
/*rocker.js*/
class Rocker {
constructor(options) {}
render(ctx) {
return this; }}export default Rocker;
Copy the code
We first started to write the logic of the main scene. In fact, firefly should be written in a separate class and then instantiated to control. However, since the whole scene is relatively small and the focus of this time is on the virtual joystick part, it should be written together with the main scene.
/*app.js*/
import Rocker from "./js/rocker.js";
class Application {
constructor() {
this.canvas = null; / / the canvas
this.ctx = null; / / environment
this.w = 0; / / canvas width
this.h = 0; / / the canvas
this.r = 64; // The size of the firefly
this.x = 0; // The X-axis coordinates of the firefly
this.y = 0; // Firefly y coordinate
this.speed = 1; // Firefly moving speed
this.textures = new Map(a);/ / texture set
this.spriteData = new Map(a);// Sprite data
this.rocker = null; // The joystick object
this.angle = 0; // The current Angle
this.gradient = null; // Firefly glow gradient
this.init();
}
init() {
/ / initialization
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext("2d");
window.addEventListener("resize".this.reset.bind(this));
this.reset();
this.textures.set("rocker0".".. /assets/rocker0.png");
this.textures.set("rocker1".".. /assets/rocker1.png");
this.load().then(this.render.bind(this));
}
reset() {
// Screen change event
this.w = this.canvas.width = window.innerWidth;
this.h = this.canvas.height = window.innerHeight;
this.x = (this.w - this.r) / 2;
this.y = (this.h - this.r) / 2;
}
load(){
// Load the texture
const {textures, spriteData} = this;
let n = 0;
return new Promise((resolve, reject) = > {
for (const key of textures.keys()) {
let _img = new Image();
spriteData.set(key, _img);
_img.onload = () = > {
if(++n == textures.size) resolve(); } _img.src = textures.get(key); }})}render() {
/ / the main rendering
const {spriteData, w, h, ctx} = this;
this.rocker = new Rocker({
w,
h,
spriteData,
}).render(ctx);
this.step();
}
drawBackground(){
// Draw the background
}
drawBall(){
// Draw fireflies
}
step(delta) {
/ / redraw
const {w, h, ctx} = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0.0, w, h);
this.drawBackground();
this.drawBall();
this.rocker && this.rocker.draw(); }}window.onload = new Application();
Copy the code
We defined variables and render redraw events in the main scene logic, and first loaded the two images used by the virtual joystick.
The virtual joystick picture is divided into the outer ring and the inner joystick block, as shown in the figure below:
The render method instantiates the virtual joystick and then performs the redraw operation, rendering the image and the firefly and the virtual joystick inside the redraw.
2. Background and firefly drawing
// Draw the background
drawBackground() {
const {w, h, ctx} = this;
ctx.fillStyle = "RGB (24,13,50)";
ctx.fillRect(0.0, w, h);
}
Copy the code
// Draw fireflies
drawBall() {
const {x, y, ctx, r} = this;
ctx.save();
if (!this.gradient) {
this.setGradient()
}
ctx.fillStyle = this.gradient;
ctx.translate(x, y);
ctx.fillRect(0.0, r, r);
ctx.restore();
}
Copy the code
When we drew the firefly we expected the fill color to be a gradual glow from the inside out. So next, we’re going to write setGradient to change the gradient value to fillStyle
// Set the gradient
setGradient(n = 0) {
const {ctx, r} = this;
this.gradient = ctx.createRadialGradient(r / 2, r / 2.0, r / 2, r / 2, r / 2)
this.gradient.addColorStop(0.'rgba(255,255,128,1)');
this.gradient.addColorStop(0.2 - n, ` rgba (215215, 0,The ${0.7 - n * 2}) `);
this.gradient.addColorStop(0.5 - n * 2.` rgba (245245, 0,The ${0.3 - n}) `);
this.gradient.addColorStop(0.7 - n, 'rgba(255,255,255,0)');
}
Copy the code
We can’t write dead in gradient, because when the firefly flies, the flapping of its wings will affect the light area slightly, so we give it a value to simulate its state change when redrawing.
step(delta) {
const {w, h, ctx} = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0.0, w, h);
this.drawBackground();
this.drawBall();
this.rocker && this.rocker.draw();
// + Take mode and flash every 3
if (~~delta % 3= =0) {
this.setGradient(0.1)}else {
this.setGradient()
}
}
Copy the code
So here we have the picture, the fireflies flashing in the middle of the screen.
3. Realization of virtual joystick
We’re going to implement a class of virtual joysticks and then control the firefly to move through its instantiation.
class Rocker {
constructor(options) {
this.w = 0; // The width of the current scene
this.h = 0; // The height of the current scene
this.D = 180; // Diameter of outer ring
this.d = 60; // The slider diameter
this.spriteData = null; // Sprite image data
Object.assign(this, options);
this.x = 0; // The X-axis coordinates of the slider
this.y = 0; // The y axis of the slider
this.centerX = 0; // The x coordinate of the center point
this.centerY = 0; // The Y-axis coordinate of the center point
this.padding = 20; // The distance between the outer ring and the bottom
this.isActive = false; // Whether to trigger
this.angle = 0; // The current Angle
this.event = new Map(a);// Event dictionary
return this;
}
render(ctx) {
/ / the main rendering
const {d, h, D, padding} = this;
this.ctx = ctx;
this.x = this.centerX = padding + D / 2; // Calculate the x coordinate of the center point
this.y = this.centerY = h - padding - D / 2; // Calculate the y coordinate of the center point
this.draw();
let canvas = ctx.canvas; // Get the current scene canvas
// Determine the device environment
// 1. Touch events on the mobile terminal
if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
canvas.addEventListener("touchstart".this._mousedown.bind(this));
canvas.addEventListener("touchmove".this._mousemove.bind(this));
canvas.addEventListener("touchend".this._mouseup.bind(this));
}
// 2. Mouse event on the PC
else {
canvas.addEventListener("mousedown".this._mousedown.bind(this));
canvas.addEventListener("mousemove".this._mousemove.bind(this));
canvas.addEventListener("mouseup".this._mouseup.bind(this));
canvas.addEventListener("mouseout".this._mouseup.bind(this));
}
return this;
}
on(eventName, callback) {
// Fake subscription message
this.event.set(eventName, (. args) = >callback && callback(... args)) }_mousedown(e) {
// Input device press down
const {d} = this;
let x = e.offsetX || e.changedTouches[0].clientX;
let y = e.offsetY || e.changedTouches[0].clientY;
if ((x > this.x - d / 2 && x < this.x + d / 2) && (y > this.y - d / 2 && y < this.y + d / 2)) {
this.isActive = true; }}_mousemove(e) {
// The input device moves
if (this.isActive) {
let x = e.offsetX || e.changedTouches[0].clientX;
let y = e.offsetY || e.changedTouches[0].clientY;
this.limitToCircle({
x,
y
}, this.D / 2 - this.d / 2); }}_mouseup(e) {
// The input device lifts or leaves
this.isActive = false;
this.x = this.centerX;
this.y = this.centerY;
}
limitToCircle(pos, limitRadius) {
// Determine the sliding boundary
const {centerX, centerY} = this;
let dx = pos.x - centerX;
let dy = pos.y - centerY;
let _r = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
this.angle = Math.atan2(dy, dx);
if (this.event.has("change")) this.event.get("change") (this.angle, Math.min(_r,limitRadius) / limitRadius);
if (_r < limitRadius) {
this.x = pos.x;
this.y = pos.y;
} else {
this.x = centerX + Math.cos(this.angle) * limitRadius;
this.y = centerY + Math.sin(this.angle) * limitRadius; }}draw() {
/ / the main draw
this.drawOuter();
this.drawCenter();
}
drawOuter() {
// Draw the outer ring
const {ctx, spriteData, D, h, padding} = this;
ctx.save();
ctx.translate(padding, h - D - padding);
ctx.drawImage(spriteData.get("rocker0"), 0.0, D, D);
ctx.restore();
}
drawCenter() {
// Draw the center slider
const {ctx, spriteData, d, x, y} = this;
ctx.save();
ctx.translate(x - d / 2, y - d / 2);
ctx.drawImage(spriteData.get("rocker1"), 0.0, d, d); ctx.restore(); }}export default Rocker;
Copy the code
Above is the virtual joystick complete code.
Virtual joysticks go through the following processes:
- Draw: Without going into detail here, just put the loaded image and position it.
- Input events: Monitors the operation of the input device. The PC uses the mouse event to monitor, and the mobile end uses the touch event to monitor. It activates the slider that is currently clicked on the joystickisActiveMake a switch to judge. If you hold it down and move it, keep changing the coordinates to make it look like it’s dragging with the input device. Finally, it bounces offisActiveDeactivate it and return it to its original center.
- Sliding boundary with obtaining AngleWhen sliding, we don’t let the extreme value of the firefly be drawn at any time and if there is no Angle we don’t send to the firefly’s moving position. Here we set the maximum rangelimitRadius, the company uses the distance between two points to figure out the distance between the current point and the center of the circle, if the distance is greater thanlimitRadius. So we also want to find the extreme point of the circle in that direction. But I had to use it anywayMath.atan2Figure out the Angle at which the X-axis is dragging the current point. We can use this Angle to match the trigonometric functionslimitRadiusAs the radius, you can find the extreme point in the current range. And finally, if phi is greater thanlimitRadius, then return the extremum to him, and now slide the range.
- Release subscription: Here we for convenience, we only do a simple layer as the outside injectionchangeAfter the event we collected. When the slider to determine whether the change event exists, if so, the Angle and sliding force back to the outside world.
Finally, in the main logic, we modify the main rendering logic again to add the virtual joystick position change event listening.
render() {
const {spriteData, w, h, ctx} = this;
this.rocker = new Rocker({
w,
h,
spriteData,
}).render(ctx);
this.rocker.on("change".(angle, speed) = > {
this.angle = angle
this.speed = speed * 3;
})
this.step();
}
Copy the code
So we have the Angle and the velocity.
Next, we can change his coordinates on the firefly map.
drawBall() {
const {x, y, ctx, r, angle, speed} = this;
if (this.rocker && this.rocker.isActive) {
let vx = speed * Math.cos(angle);
let vy = speed * Math.sin(angle);
this.x += vx;
this.y += vy;
}
ctx.save();
if (!this.gradient) {
this.setGradient()
}
ctx.fillStyle = this.gradient;
ctx.translate(x, y);
ctx.fillRect(0.0, r, r);
ctx.restore();
}
Copy the code
It is simple to determine if the current virtual joystick is active, and use trigonometric functions to calculate the increment of x and y in its direction and velocity to make it displacement.
At this point, we’ve implemented a virtual joystick from zero and controlled the firefly’s movement through it, demonstrating it online
Expansion and extension
With the virtual joystick, you can make all kinds of mobile RPGS. But the math involved goes further and leads to more ideas, such as the detection of objects in range, or the dragging of items, or the movement of characters in war games.
In fact, this tutorial is supposed to write the first yamu tea air through the virtual rocker control air, but afraid of the street. I’ll be a firefly.