一、背景介绍
很长时间没更新了,我这里也是发现,用Dom元素对页面进行游戏操作,是非常消耗性能的,所以也随之抛弃了原来的写法,转行一些比较成熟的框架。
经过一系列的H5游戏框架背景调研,发现pixi是对于一个react前端开发工程师最友好的游戏开发工具。
- 性能好
- 社区健全
- demo简单明了
- 开发方式最接近前端业务开发
| 框架 | pixi | eva | tiny | cocos | egret | unity |
|---|---|---|---|---|---|---|
| 背景 | 敏捷开发 | 敏捷开发,淘宝开发的游戏引擎,基于pixi | 敏捷开发,支付宝开发的游戏引擎,基于pixi | 引擎开发 | 引擎开发 | 引擎开发 |
| 官网 | pixijs.com/ | eva-engine.gitee.io/ | tinyjs.net/ | www.cocos.com/ | egret.com/about | unity.cn/ |
| web工程师上手难度 | 简单 | 简单 | 简单 | 难 | 中等 | 难 |
| 2d性能 | 🌟🌟🌟🌟 | 🌟🌟 | 🌟🌟 | 🌟🌟🌟🌟🌟 | 🌟🌟🌟🌟 | 🌟🌟🌟🌟🌟 |
| 3d性能 | 🌟🌟 | 🌟 | 🌟 | 🌟🌟🌟 | 🌟🌟🌟🌟 | 🌟🌟🌟🌟🌟 |
| 支持物理 | 🌟 | 🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟🌟🌟 | 🌟🌟🌟🌟 | 🌟🌟🌟🌟 |
| 支持语言 | js | js、rax | js | c、c#、python、js等 | js | c、c#、python、js等 |
| 社区 | 🌟🌟🌟 | 🌟 | 🌟 | 🌟🌟🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟🌟🌟🌟 |
上述评分仅个人调研结果,如对评分有疑问、或者更好的建议,非常欢迎提出来一起探讨。
最终决定使用pixi进行游戏开发,接下来的游戏文章,也将都是基于react+pixi进行分享,直到发现更好的web前端游戏框架。
二、前置准备
首先用create-react-app快速搭建一个react项目
0. 安装create-react-app
$ [sudo] cnpm i create-react-app -g
我这里采用了typescript模版,不会用ts的小伙伴,先听我一句劝强烈建议用上ts,实在不能接受可以去掉后面的--template typescript
1. 新建一个名为my-react-pixi-demo1的项目
$ create-react-app my-react-pixi-demo1 --template typescript
2. 下一步 进入项目,安装依赖 安装pixi.js
$ cd my-react-pixi-demo1
$ cnpm i
$ cnpm i pixi.js --save
3. 启动项目
$ npm run start
当项目启动 出现这个像原子核的小图标,就说明基本项目已经搭建成功了
三、快速开始 coding!
1. 进入App.tsx,删掉原来的代码,写下以下代码,画布就出现了
import { useEffect, useRef } from 'react';
import { Application, ICanvas } from 'pixi.js';
import './App.css';
function App() {
const myRef = useRef<HTMLDivElement>(null)
useEffect(() => {
// 新建一个画布
const app = new Application({ background: '#1099bb' });
// @ts-ignore 此处app.view的类型是ICanvas,pixi官方未将其定义为React原生Node的拓展,引发TS报错,可忽略
myRef?.current?.appendChild<ICanvas>(app.view);
}, [])
return (
<div className="App" >
<div ref={myRef} />
</div>
);
}
export default App;
2. 给画布添加一些元素
import { useEffect, useRef } from 'react';
import { Application, ICanvas, Sprite, Container, Texture } from 'pixi.js';
import logo from './logo.svg';
import './App.css';
function App() {
const myRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const app = new Application({ background: '#1099bb' });
// @ts-ignore 此处app.view: ICanvas未被定义为Node,引发TS报错,可忽略
myRef?.current?.appendChild<ICanvas>(app.view);
const container = new Container();
app.stage.addChild(container);
// Create a new texture
const texture = Texture.from(logo);
const bunny = new Sprite(texture);
bunny.anchor.set(0.5);
container.addChild(bunny);
// Move container to the center
container.x = app.screen.width / 2;
container.y = app.screen.height / 2;
// Center bunny sprite in local container coordinates
container.pivot.x = container.width / 2;
container.pivot.y = container.height / 2;
// Listen for animate update
app.ticker.add((delta) => {
// rotate the container!
// use delta to create frame-independent transform
container.rotation -= 0.01 * delta;
});
}, [])
return (
<div className="App" >
<div ref={myRef} />
</div>
);
}
export default App;
上面这段代码来自pixi官方的第一个demo:pixijs.io/examples/#/… 。我小改了一下,让它可以在react项目中运行。官方有很多有意思的demo,同学们都可以把代码原封不动的复制过来玩玩。看看效果吧~
四、进阶:代码优化
1. 游戏画布hooks
上面的代码,只能说是可以运行,但并不是我们理想中的“好代码”,我们需要把创建画布和动画代码分开,让代码逻辑更加清晰,也更好去复用。
我定义了一个叫做useGame的hooks
import { useEffect, useState } from "react";
import { Application } from 'pixi.js'
import type { IApplicationOptions, ICanvas } from 'pixi.js'
export interface IUseGameOptions extends IApplicationOptions {
myRef: React.RefObject<HTMLDivElement>;
}
const useGame = ({ myRef, ...rest }: IUseGameOptions) => {
const [app, setApp] = useState<Application<ICanvas>>()
useEffect(() => {
// 获取容器的宽高
const width = rest?.width || myRef?.current?.clientWidth;
const height = rest?.height || myRef?.current?.clientHeight;
const myApp = new Application({ ...rest, width, height });
setApp(myApp)
// @ts-ignore 此处app.view: ICanvas未被定义为Node,引发TS报错,可忽略
myRef?.current?.appendChild(myApp.view);
}, [])
return {
app,
}
}
export default useGame;
useGame该如何使用?
- App.tsx
import { useEffect, useRef } from 'react';
import useGame from './hooks/useGame';
import './App.css';
function App() {
const myRef = useRef<HTMLDivElement>(null)
const { app } = useGame({ myRef: myRef, background: '#1099bb' })
useEffect(() => {
// 此处可以用app编写游戏代码
}, [app])
return (
<div className="App" >
<div className='game1' ref={myRef} />
</div>
);
}
export default App;
- App.css
.App {
display: flex;
justify-content: center;
padding: 20px;
}
.game1 {
width: 300px;
height: 300px;
}
- 很轻松的定义了一块画布,画布的位置和大小随css而定,非常方便。
- 添加内容,责任分明后,代码更加清晰了,也非常好懂
import { useEffect, useRef } from 'react';
import { Container, Sprite, Texture } from 'pixi.js';
import useGame from './hooks/useGame';
import logo from './logo.svg';
import './App.css';
function App() {
const myRef = useRef<HTMLDivElement>(null)
const { app } = useGame({ myRef: myRef, background: '#1099bb' })
const addContainer = () => {
if (!app) {
return;
}
const container = new Container();
app.stage.addChild(container);
// Create a new texture
const texture = Texture.from(logo);
const bunny = new Sprite(texture);
bunny.anchor.set(0.5);
container.addChild(bunny);
// Move container to the center
container.x = app.screen.width / 2;
container.y = app.screen.height / 2;
// Center bunny sprite in local container coordinates
container.pivot.x = container.width / 2;
container.pivot.y = container.height / 2;
// Listen for animate update
app.ticker.add((delta) => {
// rotate the container!
// use delta to create frame-independent transform
container.rotation -= 0.01 * delta;
});
}
useEffect(() => {
addContainer()
}, [app])
return (
<div className="App" >
<div className='game1' ref={myRef} />
</div>
);
}
export default App;
2. 定义游戏元素
一个游戏、无非就是若干个元素的组合,只要提前定义好游戏元素,想用的时候拿来用就好了。
import { useEffect, useRef } from 'react';
import { Application, Container, ICanvas, Sprite, Texture } from 'pixi.js';
import useGame from './hooks/useGame';
import logo from './logo.svg';
import './App.css';
// 定义一个游戏元素
const gameNode = ({ app, x, y }: { app: Application<ICanvas>, x?: number, y?: number }) => {
const container = new Container();
// Create a new texture
const texture = Texture.from(logo);
const bunny = new Sprite(texture);
bunny.anchor.set(0.5);
container.addChild(bunny);
container.x = x || 0;
container.y = y || 0;
let speedX = 1, speedY = 1
app.ticker.add((delta) => {
// rotate the container!
// use delta to create frame-independent transform
container.rotation -= 0.01 * delta;
if (container.x < 0 || container.x > app.screen.width) {
speedX = -speedX
}
if (container.y < 0 || container.y > app.screen.width) {
speedY = -speedY
}
container.x = container.x + speedX
container.y = container.y + speedY
});
return container
}
function App() {
const myRef = useRef<HTMLDivElement>(null)
const { app } = useGame({ myRef: myRef, background: '#1099bb' })
useEffect(() => {
if (!app) {
return;
}
// 定义不同的位置
[[50, 50], [50, 250], [250, 250], [250, 50]].map((item) => {
app.stage.addChild(gameNode({ app: app, x: item[0], y: item[1] }));
})
}, [app])
return (
<div className="App" >
<div className='game1' ref={myRef} />
</div>
);
}
export default App;
看看效果吧
五、后续更新
后续会继续更新pixi.js小游戏的demo