preface
H5 games have long been known to be cross-platform and low-play, largely due to early technical solutions that were immature and limited by the level of coding in H5 games. But today, Canvas and WebGL render performance is pretty good, and with proper coding, the experience is no different from native app games
Derived from wechat mini program and independent [wechat mini Game] is aimed at Web game rendering, representing this is a great direction of the future game production trend. Wechat mini game environment has removed BOM and DOM, which is an interesting solution, because it means that game developers have to draw game content with pure canvas, which is a huge improvement in game performance
Meanwhile, in order to retain the support for game engines and reduce the migration of a large number of H5 games, the official wechat mini games provide p-Adapter, which can be compatible with a lot of BOM or DOM apis through the official wechat mini games or self-developed adapters
Because the micro channel game platform has just been launched, a large number of micro channel games currently on the network, including the open source micro channel games on Github, are actually the web page version of micro channel programs, which is no different from the traditional page games. Limited by BOM and DOM, the performance and experience are not good. The purpose of this paper is to make a popular and fun game on wechat mini-games by starting from scratch and using the development method of pure Canvas.
demo
H5 mode demo version: cheneyweb. Making. IO/wxgame – elas…
H5 mode QR code, mobile phone scan experience (wechat scan, browser scan, etc.)
Micro channel small game mode demo version: need to open the micro channel developer tool import project directory
Train of thought
The core of the game lies in the real simulation of physical elastic movement and the collision and interaction of a large number of object elements, which is a very challenging game to make
Any game development development cannot leave the game engine, because of the pure native code production efficiency is very low, and difficult to maintain, so to do a good, must first sharpen his, in the development of play a 弾 】 【 at the same time, we also need to make a streamlined canvas game engine (called the game engine is not appropriate, Since we can’t develop a game engine in a short time, this is just an analogy for a few features of a game engine.
The essence of any game must contain one or more cycles to produce the animation effects we see. Here are the development ideas of playing a projetiles
- Unified definition of resources (including images, sound effects, music) and other resources
- Uniform resource loading (initial resource loading in memory)
- Unified state management (global variables, data maintenance, here said a digression, I really don’t like the global variables, such as state management, but in game development, this is a must and have to introduce, because the game programming for state changes demand is very big, the reasonable use of global variables can greatly improve the coding efficiency)
- Unified resource rendering, rendering rendering
- Global physics engine, responsible for simulating elastic collision implementation, implementation of the core logic of the game
- The object oriented development approach takes object elements as game content units and formulates the behavior and logic of each object element
The above points 1-4 are the simple and efficient simplified version of “game engine” that we need to make. With the foundation of 1-4, we can complete the production of “playing a projetiles” by introducing 5 and self-defining development of 6
What needs to be added here is the fifth point, physics engine. In order to develop “Playing a band”, I found and compared several JS physics engines. ** The current situation is that most JS physics engines have stopped development and maintenance, and several well-known JS physics engines have not been updated on Github for many years. ** Probably because of the high barrier of physics engines and the early failure of H5 games. But the physics engine is a core and important part of the game. Many of the biggest games on PC and Mobile experience well because they have a powerful physics engine behind them, but many of these big games’ physics engines are commercial versions, expensive and not open source
Fortunately, however, there is one JS physics engine that stands out, has great performance and functionality, and is currently under constant maintenance: mate.js. This physics engine is almost the only choice for me to make a ball. there are not many problems in my personal test, some problems can be solved through some modifications to the source code. It should be noted that the Matter physics engine is also a common choice for Laya and Egret
practice
The whole development process will be divided into seven steps. It should be noted that due to the limited space of the article, it is impossible to show all the codes, but all the core processes will be introduced. At the end of the article, I will attach the Github address of the project for your reference
1. Preparation of development environment
The development environment for H5 games is very simple and light compared to traditional game development, and instead of using a commercial game engine, we developed them purely native, so we only needed one key tool:
Wechat developer tools
Develop a lite version of the game engine
What does a super lite game engine need to do, and that’s render the game. So in theory all we need is a “brush class” that can draw what we want. Of course, in addition to the brushes, we also needed some other key components. We named a folder “Base” and put all the game base classes we needed in that folder
├─ Base Game Engine │ ├─ Body. Js Physical Elements Base class │ ├─ Datastore.js Global State Management class │ ├─ Resource-.js Unified Resource Definition class │ ├─ Resourceloader.js ├─ ├─ matter. Js Physics engineCopy the code
Resource. Js This is the unified Resource management class, which is very simple, because the entire game only needs two images and two sound effects
export const Resources = [
['background'.'res/background.png'],
['startButton'.'res/startbutton.png'],
['bgm'.'res/xuemaojiao.mp3'],
['launch'.'res/launch.mp3']]Copy the code
Resourceloader. js this is the unified resource loading class, equally simple, we only need to call back after the resource loading, because wechat small game picture and sound resources loading needs its official API, here and H5 native standard slightly different
// The resource file loader ensures that the image is not rendered until the resource is loaded
import { Resources } from './Resource.js'
export class ResourceLoader {
constructor() {
this.imageCount = 0
this.audioCount = 0
// Import resources
this.map = new Map(Resources)
for (let [key, src] of this.map) {
let res = null
if (src.split('. ') [1] = ='png' || src.split('. ') [1] = ='jpg') {
this.imageCount++
// H5 create image API
res = new Image()
// wechat create image API
// res = wx.createImage()
res.src = src
} else {
this.audioCount++
// H5 create audio API
res = new Audio()
// create audio API for wechat
// res = wx.createInnerAudioContext()
res.src = src
}
this.map.set(key, res)
}
}
// The load completes the callback
onload(cb) {
let loadCount = 0
for (let res of this.map.values()) {
// Make this point to the current ResourceLoader
res.onload = (a)= > {
loadCount++
if (loadCount >= this.imageCount) {
cb(this.map)
}
}
}
}
}
Copy the code
Sprite.js is the ordinary object rendering brush class. At present, we only need to encapsulate the picture drawing of the underlying canvas
import { DataStore } from './DataStore.js'
export class Sprite {
constructor(ctx, img, x = 0, y = 0, w = 0, h = 0, srcX = 0, srcY = 0, srcW = 0, srcH = 0, ) {
this.ctx = ctx
this.img = img
this.srcX = srcX
this.srcY = srcY
this.srcW = srcW
this.srcH = srcH
this.x = x
this.y = y
this.w = w
this.h = h
}
/** ** draw the Image * img passes the Image object * srcX the initial X coordinate to crop * srcY the initial Y coordinate to crop * srcW the width to crop * srcH the height to crop * X the X coordinate to place * Y the Y coordinate to place * w the width to use * h The height to use */
draw(img = this.img,
x = this.x, y = this.y, w = this.w, h = this.h,
srcX = this.srcX, srcY = this.srcY, srcW = this.srcW, srcH = this.srcH) {
this.ctx.drawImage(img, srcX, srcY, srcW, srcH, x, y, w, h)
}
static getImage(key) {
return DataStore.getInstance().res.get(key)
}
}
Copy the code
Body.js is the base class for the physical object element, which currently only needs to be implemented by introducing physics engine instances
// Object base class
export class Body {
constructor(physics) {
this.physics = physics
}
}
Copy the code
3. Code the main logic of the game
App.js is the entrance of the game and also the whole game application class. Only canvas instance and extended physics engine instance are required as input parameters to instantiate the game application
import { ResourceLoader } from './src/base/ResourceLoader.js'
import { DataStore } from './src/base/DataStore.js'
import { Director } from './src/Director.js'
/** * game entry */
export class App {
constructor(canvas, options) {
this.canvas = canvas / / the canvas
this.physics = { ... options,ctx: this.canvas.getContext('2d')}// Physics engine
this.director = new Director(this.physics) / / director
this.dataStore = DataStore.getInstance()
// Resource loading
new ResourceLoader().onload(res= > {
// Persist resources
this.dataStore.res = res
// Load sprites
this.director.spriteLoad(res)
// Run the game
this.run()
})
}
/** * Run the game */
run() {
// Register events
this.registerEvent()
// Physical render
this.director.physicsDirect()
// Sprite render
this.director.spriteDirect()
// Play music
this.dataStore.res.get('bgm').autoplay = true
}
/** * reload the game */
reload() {
// Physical render
this.director.physicsDirect(true)
// Sprite render
this.director.spriteDirect(true)}/** * Register event */
registerEvent() {
// Mobile device touch event, use => to make this point to the Main class
this.canvas.addEventListener('touchstart', e => {
// Mask event bubbling
e.preventDefault()
// If the game is over, start again
if (this.dataStore.isGameOver) {
// reinitialize
this.dataStore.isGameOver = false
this.reload()
}
})
// PC device click event
this.canvas.addEventListener('mousedown', e => {
// Mask event bubbling
e.preventDefault()
// If the game is over, start again
if (this.dataStore.isGameOver) {
// reinitialize
this.dataStore.isGameOver = false
this.reload()
}
})
}
}
Copy the code
Director. Js This is the game Director class, responsible for the scheduling and deployment of the main logic of the game, as well as the game screen rendering work
// Sprite object
import { BackGround } from './sprite/BackGround.js'
import { StartButton } from './sprite/StartButton.js'
import { Score } from './sprite/Score.js'
// The physics engine draws objects
import { Block } from './body/Block.js'
import { Border } from './body/Border.js'
import { Bridge } from './body/Bridge.js'
import { Aim } from './body/Aim.js'
// Data management
import { DataStore } from './base/DataStore.js'
/** * Control the logic of the game */
export class Director {
constructor(physics) {
this.physics = physics
this.dataStore = DataStore.getInstance()
}
// Load Sprite objects
spriteLoad() {
this.sprite = new Map(a)this.sprite['score'] = new Score(this.physics)
this.sprite['startButton'] = new StartButton(this.physics)
this.sprite['background'] = new BackGround(this.physics)
}
// Frame by frame
spriteDirect(isReload) {
if(isReload){
this.dataStore.scoreCount = 0
}
// Check collision before drawing
// this.check()
// The game is not over
if (!this.dataStore.isGameOver) {
// Draw the game content
this.sprite['score'].draw()
// this.sprite['background'].draw()
// Adapt the browser frame rate to improve performance
this.animationHandle = requestAnimationFrame((a)= > this.spriteDirect())
}
// Game over
else {
// Stop the physics engine
this.physics.Matter.Engine.clear(this.physics.engine)
this.physics.Matter.World.clear(this.physics.engine.world)
this.physics.Matter.Render.stop(this.physics.render)
// Stop drawing
cancelAnimationFrame(this.animationHandle)
// End the interface
this.sprite['score'].draw()
this.sprite['startButton'].draw()
}
}
// Physical rendering
physicsDirect(isReload) {
this.physics.Matter.Render.run(this.physics.render)
if(! isReload) {new Aim(this.physics).draw().event()
// new Bridge(this.physics).draw()
}
new Block(this.physics).draw().event().upMove()
new Border(this.physics).draw()
}
}
Copy the code
4. Render base object elements
From this point on, background.js has started to formally design and draw the game content using the built game framework. Here, we take the simplest BackGround class as an example. This basic object is very simple and only does one thing, that is, draw the game BackGround. The remaining basic objects are the scorer and the game start button. Limited by space, I will not expand this project. The github open source project address will be included at the end of this article
import { Sprite } from '.. /base/Sprite.js'
/** * Background class */
export class BackGround extends Sprite {
constructor(physics) {
const image = Sprite.getImage('background')
super(
physics.ctx, image,
(physics.canvas.width - image.width) / 2,
(physics.canvas.height - image.height) / 2.5,
image.width, image.height,
0.0,
image.width, image.height
)
}
}
Copy the code
5. Introduce physics engines
In order to make the physics engine matter.js suitable for the development of the game, we needed to modify it to add the ability to Render text, so we chose the uncompressed version of matter.js in the render.bodies method of matter.js. Follow c.globalalpha = 1; After that, add the extension code
c.globalAlpha = 1;
// Add custom render TEXT
if (part.render.text) {
// 30px is default font size
var fontsize = 30;
// arial is default font family
var fontfamily = part.render.text.family || "Arial";
// white text color by default
var color = part.render.text.color || "#FFFFFF";
// text maxWidth
var maxWidth = part.render.text.maxWidth
if (part.render.text.size)
fontsize = part.render.text.size;
else if (part.circleRadius)
fontsize = part.circleRadius / 2;
var content = "";
if (typeof part.render.text == "string")
content = part.render.text;
else if (part.render.text.content)
content = part.render.text.content;
c.textBaseline = "middle";
c.textAlign = "center";
c.fillStyle = color;
c.font = fontsize + 'px ' + fontfamily;
if (part.bounds) {
maxWidth = part.bounds.max.x - part.bounds.min.x;
}
c.fillText(content, part.position.x, part.position.y, maxWidth);
}
Copy the code
After game.js made some adjustments to the Matter physics engine, we could introduce it into the entry file of wechat mini games and initialize the “play a projetiles” game instance
// require('./src/base/weapp-adapter.js')
const Matter = require('./src/base/matter.js')
import { App } from './App.js'
// Compatible with H5 mode and wechat mini game mode
const canvas = typeof wx == 'undefined' ? document.getElementById('app') : wx.createCanvas()
// H5 web game mode
if (typeof wx == 'undefined') {
canvas.width = 375
canvas.height = 667
}
// wechat mini game mode
else {
window.Image = (a)= > wx.createImage()
window.Audio = (a)= > wx.createInnerAudioContext()
}
// Initialize the physics engine
const engine = Matter.Engine.create({
enableSleeping: true
})
const render = Matter.Render.create({
canvas: canvas,
engine: engine,
options: {
width: canvas.width,
height: canvas.height,
background: './res/background.png'.// transparent
wireframes: false.showAngleIndicator: false
}
})
Matter.Engine.run(engine)
// Matter.Render.run(render)
// Initialize the game
const physics = { Matter, engine, canvas, render }
new App(canvas, physics)
Copy the code
6. Render physical object elements
Border-.js After the basic object rendering and the introduction of the physics engine are completed, we can start to draw the physical object elements we need by using the physics engine. In the game “Zha Yi Tiles”, there were three kinds of physical objects in total, namely walls, marbles and blocks
Here, the simplest wall is taken as an example, while the other complex marbles and squares have long codes, which are not expanded here due to space limitations. At the end of this article, there will be the github address of this project, which is open source, for further understanding
/ / boundary
import { Body } from '.. /base/Body.js'
export class Border extends Body {
constructor(physics) {
super(physics)
}
draw() {
const physics = this.physics
let bottomHeight = 10
let leftWidth = 10
const borderBottom = physics.Matter.Bodies.rectangle(
physics.canvas.width / 2, physics.canvas.height - bottomHeight / 2,
physics.canvas.width - leftWidth * 2, bottomHeight, {
isStatic: true.render: {
visible: true}})const borderLeft = physics.Matter.Bodies.rectangle(
leftWidth / 2, physics.canvas.height / 2,
leftWidth, physics.canvas.height, {
isStatic: true.render: {
visible: true}})const borderRight = physics.Matter.Bodies.rectangle(
physics.canvas.width - leftWidth / 2, physics.canvas.height / 2,
leftWidth, physics.canvas.height, {
isStatic: true.render: {
visible: true
}
})
physics.Matter.World.add(physics.engine.world, [borderBottom, borderLeft, borderRight])
}
}
Copy the code
7. Completion of the game and project overview
So far, the entire play a 弾 WeChat 】 little game production is completed, in fact, looking back comb the entire process, also is fluent, also not complex, but most of the time, all things are difficult before they are easy in the beginning I really met a lot of problems, including the introduction of physics engine, reasonable arrangement of game logic, even some challenges, but these problems are solved, Hence this article
Of course, there are still some problems THAT I have not solved perfectly, such as the problem of “going through the wall” caused by too fast ball speed, which is actually a problem of Matter. Js physics engine. In the discussion about this problem on Github, the author also established a branch of CCD algorithm to try to solve it. This issue is still not solved in Matter. Js, if readers have ideas to solve it, they can also contact me, thank you very much
In addition, I have completed the core interaction logic of “Balltiles” so far, but compared with “Balltiles” on wechat, I haven’t done many details, such as the improvement of art style, recovery of marbles, as well as various blocks and props. If I have time, I will further improve these in the future
Personally, I’m a big fan of minimalism and expansion. Here is the engineering catalog structure of playing a projetiles
│ ├─ App.js │ ├─ Game.js │ ├─ game.json │ ├─ project.config.json │ ├─ background.png │ ├─ Launch. Mp3 │ ├ ─ ─ startbutton. PNG │ └ ─ ─ xuemaojiao. Mp3 └ ─ ─ the SRC ├ ─ ─ Director. The Director of js ├ ─ ─ base compact edition game engine │ ├ ─ ─ Body. Js physical elements base class object │ ├ ─ ─ DataStore. Js global state management class │ ├ ─ ─ the Resource. The js uniform Resource definition class │ ├ ─ ─ ResourceLoader. Js uniform Resource loading class │ ├ ─ ─ Sprite. Js common object rendering brush │ └ ─ ─ ├── Body Physical Elements │ ├─ ├─ Block. Js Barrier Block │ ├─ Border. Js Border Wall ├── Body Physical Elements │ ├─ Body Physical Elements │ ├─ ├─ body Physical Elements │ ├─ Block ├─ score.js └─ StartButtonCopy the code
Afterword.
For the development of “Tianyi tiles”, I chose the pure Canvas program. On the one hand, it was suitable for wechat small game platform, and on the other hand, it was compatible with H5 web pages. At the same time, it had good performance. But building made a concise mini game development framework, so I didn’t use WeChat official adapter weapp – adapter, which can save the 53 KB, both to improve code execution efficiency, faster and faster And, of course, I have described in the full production lite version of the game engine, in fact than the current mainstream commercial game engine, It’s just the tip of the iceberg, and it’s designed to give more beginner players a taste of the game engine. Real commercial game engines such as Laya and Egret are very powerful, and I will write an article in the future to rework them using such commercial game engines. Judging from the development of wechat mini-games today, we can see that pure Web games like H5 will likely occupy a large share of the game market in the future. Making game development truly cross platform, hot update, and high performance
Thank you for reading and hope you found this article helpful 🙂
Author: CheneyXu
Making: wxgame – elastic
About: XServer official website