This is the sixth day of my participation in the August Text Challenge.More challenges in August
preface
Who are blessed with special blessings,
But also beware of good fortune.
– Seneca
introduce
You are still depressed about the lack of money, you are still suffering from black face, come here to learn this, so that you can get all kinds of cards ~
The theme of the cold rice king, oh no, is king of games, that is the memory of 80,90 generation, small shop to buy cards together to find partners Versailles their own rare card, and then imagine two people with animation in the picture, is also a section of ting in two memories.
The task we did this time was very simple, using pure Canvas instead of CSS3 and without game engine and plug-in, to make the card flat to fit the landscape screen, and then click and reverse to work together.
This time, we will focus on how the cards are arranged horizontally, how the clicks are detected, and how to make the 3D effect flip, which will be roughly expanded from the infrastructure, card horizontal coordinate processing, card class implementation.
PS: The game is based on the canvas implementation, can not use CSS3, in this way to implement it, so this is only to provide an idea of how to deal with it, if doing small activity page, still suggest using CSS3 preserve-3D mode to change the rotateY of the element is the most convenient.
Set out
Chapter 1: Infrastructure
We still write the HTML first, in which we use module mode, after the aspect of the module load
<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
Then, we write the structure of the card class first, and the aspects are introduced in the main logic.
/*Card.js*/
class Card {
constructor(options) {
this.name = "";
this.x = 0;
this.y = 0;
return this;
}
render(ctx) {}
draw(){}}export default Card;
Copy the code
/*app.js*/
import Card from "./js/Card.js";
Copy the code
Next we need to prepare some card material, as fake data implementation.
/*app.js*/
const cardData = [{
name: "back".src: "./assets/back.png"
}, {
name: "Broken 壊 Shin Ho In fact Dipped ゴ".src: "./assets/ broken 壊 God in fact penetrated ゴ. PNG"
}, {
name: "White Dragon by Blue Eyes".src: "./assets/ White dragon. PNG"
}, {
name: "Raijin-mode · プ Mode of transportation" "Raijin-mode · プ Mode of transportation".src: "./assets/ raikmode · プ Mode migration. PNG"
}, {
name: "V · HERO Hiatus ィ · チ · Mode".src: "./ Assets /V · HERO hiatus チ · Fukushima.png"
}, {
name: "エ レ ベ 'タ' degrees degrees".src: "./ Assets /エ レ ス マ ス chance · Nitd random ー Spoon."
}, {
name: "A · O · J (I'm playing) デ fair トロ".src: "./assets/A · O · J (I'm doing something) デ fair トロ - PNG"}, {name:"Great God Bird by Rosy Clouds and Valleys".src: "./assets/ Giant orator.png"}, {name:Conceived by Matuchi matuchi for the chemical industry..src: "./assets/ uchi sprung sprung by uchi chi. PNG"}, {name:"Inda チ roundtable · raijin-raid second".src: List_second "./assets/ inda チ roundtable · raid_enjoyable. PNG"}, {name:"チ hikaristic rotational rotational rotational".src: "./assets/チ hikari · Can be rescued. PNG"
}];
Copy the code
Mobile game cards are actually returned to the foreground data through the back-end algorithm, and then displayed, so we directly write dead here.
With the resource, we can write the main logic, let the main logic load the resource, and then do the feedback:
class Application {
constructor() {
this.canvas = null; / / the canvas
this.ctx = null; / / environment
this.w = 0; / / canvas width
this.h = 0; / / the canvas
this.textures = new Map(a);/ / texture set
this.spriteData = new Map(a);// Sprite data
this.cardList = []; // Card array
this.progress = 0; // Load progress [0-1]
this.init();
}
init() {
/ / initialization
this.canvas = document.getElementById("canvas");
this.ctx = this.canvas.getContext("2d");
window.addEventListener("resize".this.reset.bind(this));
this.reset();
cardData.forEach(card= > {
this.textures.set(card.name, card.src);
})
this.load().then(this.render.bind(this));
}
reset() {
// Screen changes events
this.w = this.canvas.width = this.ctx.width = window.innerWidth;
this.h = this.canvas.height = this.ctx.height = window.innerHeight;
if (this.progress >= 1) this.render();
}
render() {
/ / the main rendering
this.cardList.length = 0;
this.step();
}
load() {
// Load the texture
const { textures, spriteData } = this;
let n = 0;
return new Promise((resolve, reject) = > {
if (textures.size == 0) {
this.progress = 1;
resolve();
}
for (const key of textures.keys()) {
let _img = new Image();
spriteData.set(key, _img);
_img.onload = () = > {
this.progress += (1 / textures.size);
if (++n == textures.size) {
this.progress = 1; resolve(); } } _img.src = textures.get(key); }})}drawCard() {
// Draw the cards in the card array
this.cardList.forEach(card= >{ card.draw(); })}drawBackground() {
// Draw the background
const { w, h, ctx} = this;
ctx.fillStyle = "# 000";
ctx.fillRect(0.0, w, h);
}
step(delta) {
/ / redraw
const { w, h, ctx } = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0.0, w, h);
this.drawBackground();
this.drawCard()
}
}
window.onload = new Application();
Copy the code
Just like before, we put the image data into texture set inside, and then load them one by one, added progress variables to see the loading schedule, because of the screen to change the situation, we will empty card set, to map the position of the card, is that the progress is the premise of doing these 1 is full loaded resources will make a change.
After loading, we go to the main render, where we will later deal with the generation of cards into the deck. We first draw a black background, walk through the deck to draw each card, and then redraw the event continuously.
· Card layout
We expect cards to be horizontal and wrap if they exceed the screen width. This works fine if you do elastic or float in CSS, but how does this work in Canvas?
We first add the card generation content to the main logic
render() {
const { ctx, spriteData, w } = this;
this.cardList.length = 0;
let scale = 0.225;
let n = 1;
while (n < cardData.length) {
let item = cardData[n];
let img = spriteData.get(item.name);
let size = Math.max(1.Math.floor(w / (img.width * scale + 20)));
let i = n - 1;
let x = 20 * (i % size + 1) + (i % size) * img.width * scale;
let y = 20 * Math.ceil(n / size) + Math.floor(i / size) * img.height * scale;
let card = new Card({
name: item["name"],
scale,
x,
y,
img,
back: spriteData.get("back")
}).render(ctx)
this.cardList.push(card);
n++;
}
this.step();
}
Copy the code
The material image that we’ve got is actually quite large, so we’re going to have to scale it, and we’re going to scale it in card later. But the implementation has to define the scaling factor. Next, the variable n as image resources use count, because starting from 1 0 is card back pictures, each all have, so we traverse is he different positive, we expect that the incoming his name x y coordinate positive figure on the back of the zoom level, this also is we can think of is at present normal logic, let his rendering. Instantiated.
Now the key question is how do we get his x and y coordinates?
Size = (width of canvas)/(width of image after scaling + white space). At the same time, ensure that there is at least one number.
Then we can use it to calculate his abscissa in turn, that is, the sum of the white space and the sum of the width of the picture after scaling. Then we can use the size just calculated to take the modulus, let it break the line and calculate from the beginning. Similarly, the y-coordinate does the same thing, but we just divide to get the current number of rows.
Chapter 3 · Card category
/*Card.js*/
class Card {
constructor(options) {
this.name = ""; / / name
this.x = 0; // X coordinates
this.y = 0; // Y coordinates
this.back = null; // The back of the card
this.img = null; // Card front
this.speed = 0.02; // Flip speed
this.scale = 1; // Scale
Object.assign(this, options);
this.ctx = null; / / environment
this.timer = null; / / timer
this.scaleX = this.scale; // x scale
this.scaleY = this.scale; // y-scale
this.isActive = false // Whether to activate
return this;
}
render(ctx) {
/ / the main rendering
if(! ctx)return;
this.ctx = ctx;
this.draw();
return this;
}
show() {
/ / show
if(this.isActive) return;
this.isActive = true;
this.timer = setInterval(() = > {
this.scaleX -= this.speed;
if (this.scaleX <= 0) {
this.isShow = true;
}
if (this.scaleX <= -this.scale ) {
this.scaleX = -this.scale;
clearInterval(this.timer);
this.timer = null; }},1000 / 60)}draw() {
/ / to draw
if(this.isShow)
this.drawCard();
else
this.drawBack();
}
drawCard() {
// Draw the front side
const {ctx, img, x, y, scaleX, scaleY,scale} = this;
ctx.save();
ctx.translate(x+img.width*(scale-scaleX)/2, y);
ctx.scale(scaleX, scaleY);
ctx.drawImage(img, 0.0);
ctx.restore();
}
drawBack() {
// Draw the back side
const {ctx, x, y, scaleX, scaleY, back, img,scale} = this;
ctx.save();
ctx.translate(x+img.width*(scale-scaleX)/2, y);
ctx.scale(scaleX, scaleY);
ctx.drawImage(back, 0.0); ctx.restore(); }}Copy the code
So what we do is we use isShow to determine whether to draw heads or tails, and isActive is to determine whether it’s already active, and once it’s active, it can’t be changed, it’s a one-way state.
We’re going to talk a lot about how to draw it, and we probably started with the rotate of the canvas. However, this form only works for 2d horizontal rotation. And we do the card flip in 3D. You can’t do that, and at this point, we’re reminded of another algorithm, which is the perspective algorithm, where you zoom in and out to simulate what it looks like.
So the following operations are written:
/*Card.js*/
show() {
if(this.isActive) return;
this.isActive = true;
this.timer = setInterval(() = > {
this.scaleX -= this.speed;
if (this.scaleX <= 0) {
this.isShow = true;
}
if (this.scaleX <= -this.scale ) {
this.scaleX = -this.scale;
clearInterval(this.timer);
this.timer = null; }},1000 / 60)}Copy the code
Since we are rotating along the y axis, we need to change the scale of the x axis appropriately so that the back side is scaled to 0 and the front side is restored to the original opposite value.
But, it’s not enough just to change this, because it’s just going to flip around the zero point, and what we want is the center point, but the most important thing for the center point of the x axis is to keep calculating the difference between the current size and the original size, and keep shifting it by that distance, and trick the naked eye into thinking that it’s not moving, it’s flipped around in its original position.
/*Card.js*/
ctx.translate(x+img.width*(scale-scaleX)/2, y);
Copy the code
Final chapter · Capture cards
We have written, now only how can let the mouse trigger his flip effect ~
/*app.js*/
init() {
+ canvas.addEventListener("click".this.clickHandle.bind(this), false)}clickHandle(e) {
let offsetX = e.offsetX;
let offsetY = e.offsetY;
let card = this.cardList.find(card= > {
const { img, x, y, scale } = card;
return (offsetX > x && offsetX < x + img.width * scale) && (offsetY > y && offsetY < y + img.height * scale)
})
card && card.show();
}
Copy the code
First, we need to monitor his click event, bind should method, and then judge the current coordinate after each mouse click. Here, we use the enclosing rectangle determination method.
Peripheral rectangle determination method: refers to that if the detected object is a rectangle or approximate rectangle, we can abstract the object into a rectangle, and then use the method of judging whether the two rectangles collide to detect. In simple terms, we treat the object as a rectangle.
Our card is a rectangle and we match its size according to its horizontal and vertical coordinates, whether the mouse point is in the range or not, and then we sift through the clicked card and flip it.
So we’re done, isn’t it that easy? You’re done, online demo
Expansion and extension
The flip that we do in pseudo-3D is actually an extension of perspective, if you want to do, say, a 3D placement game, and you want to simulate it with a 2D canvas. Of course, if there is a lot of demand, or obediently use some game engine to develop, we do not need to calculate coordinates and anchor points frequently.
Here only to provide ideas, more creative and ideas need to use their own hands to achieve ~
Finally, to the previous students in Versailles, what you bought is pirated ~ haha ~~