you-engine
v0.2.0
Published
A lightweight, pluggable 2D Canvas game framework
Downloads
262
Maintainers
Readme
You Engine
一个轻量级、可插拔的 2D Canvas 游戏框架。
简介
You Engine 是一个现代化的 TypeScript 游戏引擎,专为 2D Canvas 游戏设计。它采用模块化的系统架构,让你可以只引入需要的功能,保持项目精简。
特性
- 轻量级: 核心模块仅几 KB,按需加载
- TypeScript 原生: 完整的类型支持,智能提示
- ECS 架构: 基于 miniplex 的轻量级实体组件系统
- 可插拔系统: 输入、渲染、物理、音频、粒子等系统按需使用
- 手柄支持: 支持 Xbox、PlayStation、Switch 等多种控制器
- 物理引擎: 内置简单碰撞和 Matter.js 集成
- 动画系统: 基于 tween.js 的缓动动画
- 音频系统: 基于 Howler.js 的音效管理
- 粒子系统: 内置多种预设特效
- 等距视角系统: 2.5D 等距渲染,适用于 RTS/ARPG 游戏
安装
pnpm add you-engine快速开始
import { Engine, InputSystem, RenderSystem, CameraSystem } from 'you-engine';
// 创建引擎
const engine = new Engine({
canvas: '#gameCanvas',
width: 1600,
height: 900,
backgroundColor: '#1a1a2e'
});
// 注册系统
engine
.use(InputSystem)
.use(CameraSystem)
.use(RenderSystem);
// 创建实体
const player = engine.spawn({
transform: { x: 400, y: 300, rotation: 0, scaleX: 1, scaleY: 1 },
velocity: { x: 0, y: 0 },
sprite: { width: 32, height: 32, color: '#4ecdc4', alpha: 1, visible: true },
collider: { type: 'circle', radius: 16 }
});
// 启动游戏
engine.start();核心概念
Engine(引擎)
引擎是游戏的核心,负责管理游戏循环、系统和场景。
const engine = new Engine({
canvas: HTMLCanvasElement | string, // Canvas 元素或选择器
width?: number, // 设计宽度,默认 1600
height?: number, // 设计高度,默认 900
backgroundColor?: string, // 背景色,默认 '#000'
autoScale?: boolean, // 自动缩放,默认 true
targetFPS?: number, // 目标帧率,默认 60
debug?: boolean // 调试模式,默认 false
});Entity(实体)
实体是游戏对象,由多个组件组成。
interface GameEntity {
id?: string; // 实体 ID
transform?: Transform; // 变换组件
velocity?: Velocity; // 速度组件
sprite?: Sprite; // 精灵组件
collider?: Collider; // 碰撞体组件
lifecycle?: Lifecycle; // 生命周期组件
tags?: Tags; // 标签组件
onUpdate?: (dt: number) => void; // 自定义更新
onRender?: (ctx: CanvasRenderingContext2D) => void; // 自定义渲染
onDestroy?: () => void; // 销毁回调
}Component(组件)
组件是数据容器,定义实体的属性。
Transform(变换)
interface Transform {
x: number;
y: number;
rotation: number; // 弧度
scaleX: number;
scaleY: number;
}
// 辅助函数
import { createTransform } from 'you-engine';
const transform = createTransform(100, 200); // x, y, rotation=0, scaleX=1, scaleY=1Velocity(速度)
interface Velocity {
x: number;
y: number;
angularVelocity?: number; // 角速度
damping?: number; // 阻尼 (0-1)
}
// 辅助函数
import { createVelocity } from 'you-engine';
const velocity = createVelocity(5, 0); // x, ySprite(精灵)
interface Sprite {
texture?: string; // 纹理(暂未实现)
width: number;
height: number;
color?: string; // 填充颜色
alpha: number; // 透明度 (0-1)
visible: boolean;
render?: (ctx, entity) => void; // 自定义渲染
}
// 辅助函数
import { createSprite } from 'you-engine';
const sprite = createSprite({ width: 32, height: 32, color: '#ff0000' });Collider(碰撞体)
interface Collider {
type: 'circle' | 'rect';
radius?: number; // 圆形半径
width?: number; // 矩形宽度
height?: number; // 矩形高度
layer?: number; // 碰撞层(位掩码)
mask?: number; // 碰撞掩码
isTrigger?: boolean; // 是否为触发器
offsetX?: number; // X 偏移
offsetY?: number; // Y 偏移
}
// 辅助函数
import { createCollider } from 'you-engine';
const circleCollider = createCollider('circle', { radius: 16 });
const rectCollider = createCollider('rect', { width: 32, height: 32 });Tags(标签)
标签用于对实体进行分类和查询。
interface Tags {
values: string[]; // 标签数组
}
// 使用辅助函数
import { hasTag, addTag } from 'you-engine';
// 创建带标签的实体
const player = engine.spawn({
transform: createTransform(100, 100),
tags: { values: ['player', 'movable'] }
});
// 检查标签
if (hasTag(player, 'player')) {
// ...
}
// 添加标签
addTag(player, 'invincible');
// 按标签查询实体
const players = engine.world.entities.filter(
e => e.tags?.values.includes('player')
);System(系统)
系统是逻辑处理器,负责更新实体和处理游戏逻辑。
import { System } from 'you-engine';
class MySystem extends System {
static priority = 0; // 优先级,越小越先执行
onCreate(): void {
// 系统初始化
}
onUpdate(dt: number): void {
// 每帧更新,dt 是毫秒
for (const entity of this.engine.world.entities) {
// 处理实体
}
}
onRender(ctx: CanvasRenderingContext2D): void {
// 渲染
}
onDestroy(): void {
// 系统销毁
}
}
// 注册系统
engine.use(MySystem);Scene(场景)
场景用于组织游戏关卡和状态。
import { Scene } from 'you-engine';
class GameScene extends Scene {
onCreate(): void {
// 场景创建时调用(只调用一次)
}
onEnter(): void {
// 进入场景时调用
const player = this.spawn({
transform: createTransform(100, 100),
sprite: createSprite({ width: 32, height: 32, color: '#fff' })
});
}
onUpdate(dt: number): void {
// 每帧更新
}
onExit(): void {
// 离开场景时调用
}
}
// 注册场景
engine.addScene('game', GameScene);
// 切换场景
engine.goto('game');内置系统
InputSystem(输入系统)
处理键盘和手柄输入。
import { InputSystem, GamepadButton } from 'you-engine';
engine.use(InputSystem);
// 获取系统实例
const input = engine.system(InputSystem);
// 检查动作
if (input.isPressed('jump')) {
// 跳跃键刚按下
}
if (input.isHeld('fire')) {
// 开火键按住中
}
if (input.isReleased('dash')) {
// 冲刺键刚释放
}
// 获取轴值 (-1 到 1)
const moveX = input.axisX(); // 水平移动
const moveY = input.axisY(); // 垂直移动
// 手柄震动
input.vibrate(0, { strong: 0.5, weak: 0.3, duration: 200 });
// 获取手柄类型
const type = input.getGamepadType(0); // 'xbox' | 'playstation' | 'switch' | 'generic'
// 获取按钮显示名称
const buttonName = input.getButtonName(GamepadButton.A, 0); // Xbox: 'A', PS: '✕', Switch: 'B'
// 自定义输入映射
input.setMapping('attack', {
keyboard: ['KeyJ', 'Space'],
gamepadButton: [0, 2]
});RenderSystem(渲染系统)
渲染带有 sprite 组件的实体。
import { RenderSystem } from 'you-engine';
engine.use(RenderSystem);
const render = engine.system(RenderSystem);
// 显示碰撞体调试
render.showColliders = true;
render.showDebug = true;
// 静态绘图方法
RenderSystem.drawCircle(ctx, x, y, radius, { fill: '#fff', stroke: '#000' });
RenderSystem.drawRect(ctx, x, y, w, h, { fill: '#fff', centered: true });
RenderSystem.drawLine(ctx, x1, y1, x2, y2, { color: '#fff', lineWidth: 2 });
RenderSystem.drawText(ctx, 'Hello', x, y, { font: '24px Arial', color: '#fff' });CameraSystem(摄像机系统)
摄像机控制,支持跟随、震屏、缩放等效果。
import { CameraSystem } from 'you-engine';
engine.use(CameraSystem);
const camera = engine.system(CameraSystem);
// 设置位置
camera.setPosition(400, 300);
// 平滑移动
camera.moveTo(800, 600);
// 跟随实体
camera.follow(player, { smoothing: 0.1, offsetX: 0, offsetY: -50 });
// 停止跟随
camera.unfollow();
// 震屏效果
camera.shake({ intensity: 20, decay: 0.9 });
// 闪屏效果
camera.flash({ color: '#fff', duration: 100, alpha: 0.8 });
// 缩放
camera.zoomTo(1.5);
// 设置边界
camera.setBounds(0, 0, 3200, 1800);
// 坐标转换
const worldPos = camera.screenToWorld(mouseX, mouseY);
const screenPos = camera.worldToScreen(entity.transform.x, entity.transform.y);
// 检查是否在视口内
if (camera.isInView(x, y, margin)) {
// 在视口内
}PhysicsSystem(物理系统)
简单的碰撞检测和物理模拟。
import { PhysicsSystem } from 'you-engine';
engine.use(PhysicsSystem);
const physics = engine.system(PhysicsSystem);
// 设置重力
physics.gravity = { x: 0, y: 980 };
// 碰撞检测
physics.onCollision((pair) => {
console.log('碰撞:', pair.a, pair.b);
// pair.normalX, pair.normalY - 碰撞法线
// pair.depth - 穿透深度
});
// 分离碰撞体
physics.separate(pair, 0.5); // 各分离 50%
// 弹性碰撞响应
physics.bounce(pair, 0.8); // 弹性系数
// 射线检测
const hit = physics.raycast(startX, startY, dirX, dirY, maxDistance);
if (hit) {
console.log('击中:', hit.entity, hit.point, hit.distance);
}
// 点检测
if (physics.pointInEntity(mouseX, mouseY, entity)) {
// 点在实体内
}MatterPhysicsSystem(Matter.js 物理)
完整的 2D 物理模拟,基于 Matter.js。
import { MatterPhysicsSystem } from 'you-engine';
engine.use(MatterPhysicsSystem);
const matter = engine.system(MatterPhysicsSystem);
// 为实体添加物理体
matter.addBody(entity, {
type: 'dynamic', // 'dynamic' | 'static' | 'kinematic'
shape: 'circle', // 'circle' | 'rect' | 'polygon'
radius: 20,
mass: 1,
friction: 0.1,
restitution: 0.8, // 弹性
fixedRotation: false
});
// 设置重力
matter.gravity = { x: 0, y: 1 };
// 施加力
matter.applyForce(entity, { x: 100, y: 0 });
// 施加冲量
matter.applyImpulse(entity, { x: 50, y: -100 });
// 设置速度
matter.setVelocity(entity, { x: 10, y: 0 });
// 创建约束(连接两个物体)
const constraint = matter.createConstraint(entityA, entityB, {
stiffness: 0.9,
damping: 0.1
});
// 碰撞事件
engine.on('matter:collisionStart', ({ a, b }) => {
console.log('开始碰撞:', a, b);
});
engine.on('matter:collisionEnd', ({ a, b }) => {
console.log('结束碰撞:', a, b);
});AudioSystem(音频系统)
音效和背景音乐管理,基于 Howler.js。
import { AudioSystem } from 'you-engine';
engine.use(AudioSystem);
const audio = engine.system(AudioSystem);
// 加载音效
await audio.loadSound('explosion', { src: '/sounds/explosion.mp3' });
await audio.loadSound('coin', { src: ['/sounds/coin.mp3', '/sounds/coin.ogg'] });
// 加载音乐
await audio.loadMusic('bgm', { src: '/music/bgm.mp3', loop: true });
// 批量加载
await audio.loadAll({
sounds: {
jump: { src: '/sounds/jump.mp3' },
hit: { src: '/sounds/hit.mp3' }
},
music: {
menu: { src: '/music/menu.mp3' },
game: { src: '/music/game.mp3' }
}
});
// 播放音效
audio.playSound('explosion', { volume: 0.8, rate: 1.2 });
// 播放音乐
audio.playMusic('bgm', { fadeIn: 1000 });
// 停止音乐
audio.stopMusic({ fadeOut: 500 });
// 暂停/恢复
audio.pauseMusic();
audio.resumeMusic();
// 音量控制
audio.masterVolume = 0.8; // 主音量
audio.sfxVolume = 1.0; // 音效音量
audio.musicVolume = 0.5; // 音乐音量
// 静音
audio.muted = true;
// 解锁音频(移动端需要用户交互)
await audio.unlock();TweenSystem(缓动系统)
动画和缓动效果,基于 @tweenjs/tween.js。
import { TweenSystem, Easing } from 'you-engine';
engine.use(TweenSystem);
const tween = engine.system(TweenSystem);
// 基本动画
tween.to(entity.transform, { x: 500, y: 300 }, {
duration: 1000,
easing: Easing.QuadOut,
onUpdate: () => console.log('更新中'),
onComplete: () => console.log('完成')
});
// 便捷方法
tween.moveTo(entity.transform, 500, 300, 1000);
tween.scaleTo(entity.transform, 2, 2, 500);
tween.rotateTo(entity.transform, Math.PI, 800);
// 精灵动画
tween.fadeIn(entity.sprite, 500);
tween.fadeOut(entity.sprite, 500);
// 特效
tween.bounce(entity.transform, 0.2, 300); // 弹跳
tween.shake(entity.transform, 10, 500); // 抖动
tween.pulse(entity.transform, 1.2, 300); // 脉冲
// 链式动画
tween.chain(entity.transform, [
{ target: { x: 100 }, options: { duration: 500 } },
{ target: { y: 200 }, options: { duration: 500 } },
{ target: { x: 0, y: 0 }, options: { duration: 1000, easing: Easing.BounceOut } }
]);
// 延迟执行
await tween.delay(1000);
// 数值动画
tween.animate(0, 100, 1000, (value) => {
healthBar.width = value;
});
// 可用的缓动函数
// Linear, QuadIn/Out/InOut, CubicIn/Out/InOut, QuartIn/Out/InOut
// QuintIn/Out/InOut, SineIn/Out/InOut, ExpoIn/Out/InOut, CircIn/Out/InOut
// ElasticIn/Out/InOut, BackIn/Out/InOut, BounceIn/Out/InOutParticleSystem(粒子系统)
粒子效果,内置多种预设。
import { ParticleSystem } from 'you-engine';
engine.use(ParticleSystem);
const particles = engine.system(ParticleSystem);
// 创建发射器
const emitter = particles.createEmitter({
x: 400,
y: 300,
rate: 20, // 每秒发射数
maxParticles: 100,
life: [500, 1000], // 生命周期(毫秒)
speed: [50, 150], // 速度范围
angle: [0, Math.PI * 2], // 发射角度范围
startSize: [5, 10], // 初始大小
endSize: [0, 2], // 结束大小
startColor: ['#ff0', '#f80'], // 初始颜色(随机选择)
endColor: '#f00', // 结束颜色
gravity: 200, // 重力
drag: 0.1, // 阻力
blendMode: 'lighter' // 混合模式
});
// 更新发射器位置
emitter.config.x = player.transform.x;
emitter.config.y = player.transform.y;
// 停止发射器
particles.stopEmitter(emitter);
particles.stopEmitter(emitter, true); // 立即清除所有粒子
// 预设特效
particles.explode(x, y); // 爆炸
particles.smoke(x, y); // 烟雾
particles.fire(x, y); // 火焰
particles.sparkle(x, y); // 闪光
particles.trail(x, y); // 轨迹
// 自定义预设参数
particles.explode(x, y, {
burst: 50,
startColor: ['#00f', '#0ff'],
gravity: 100
});
// 全局重力
particles.gravity = { x: 0, y: 100 };
// 统计
console.log('粒子数:', particles.getParticleCount());
console.log('发射器数:', particles.getEmitterCount());IsometricSystem(等距视角系统)
2.5D 等距渲染,适用于 RTS、ARPG、模拟经营等游戏(如红警、暗黑、帝国时代)。
import {
IsometricSystem,
IsometricRenderSystem,
createIsometricTransform,
createIsometricSprite
} from 'you-engine';
engine.use(IsometricSystem);
engine.use(IsometricRenderSystem);
const iso = engine.system(IsometricSystem);
const isoRender = engine.system(IsometricRenderSystem);
// 配置等距参数
iso.setConfig({
tileWidth: 64, // 地块宽度
tileHeight: 32, // 地块高度(通常是宽度的一半)
heightScale: 32, // 高度单位像素值
depthSortEnabled: true, // 启用深度排序
shadow: {
enabled: true,
color: 'rgba(0, 0, 0, 0.3)',
heightFade: 0.02 // 阴影随高度淡化
}
});
// 创建等距实体
const unit = engine.spawn({
transform: createIsometricTransform(5, 3, 0), // x, y, z(高度)
sprite: createIsometricSprite({
width: 64,
height: 96,
color: '#4ecdc4',
anchorX: 0.5, // 锚点 X(中心)
anchorY: 1, // 锚点 Y(底部)- 等距游戏通常锚点在脚底
castShadow: true
})
});
// 坐标转换
const screen = iso.worldToScreen(5, 3, 0); // 世界坐标 -> 屏幕坐标
const world = iso.screenToWorld(mouseX, mouseY); // 屏幕坐标 -> 世界坐标
const tile = iso.screenToTile(mouseX, mouseY); // 屏幕坐标 -> 格子坐标
// 摄像机控制
iso.setCameraPosition(10, 10); // 设置摄像机位置(世界坐标)
iso.followEntity(player, 0.1); // 跟随实体
iso.cameraZoom = 1.5; // 缩放
iso.moveCamera(dx, dy); // 移动摄像机
// 绘制等距方块
iso.drawIsometricBox(ctx, x, y, z, width, depth, height, {
topColor: '#888',
leftColor: '#666',
rightColor: '#555'
});
// 绘制等距圆柱体(适合角色)
iso.drawIsometricCylinder(ctx, x, y, z, radius, height, {
topColor: '#888',
bodyColor: '#666',
bodyGradient: true
});
// 绘制地面网格
iso.drawGrid(ctx, 0, 0, 10, 10, {
lineColor: 'rgba(255, 255, 255, 0.2)',
highlightTile: { x: 5, y: 3 },
highlightColor: 'rgba(255, 255, 0, 0.3)'
});
// 绘制阴影
iso.drawShadow(ctx, x, y, z, radius);
// 检查是否在屏幕内
if (iso.isOnScreen(screenX, screenY, margin)) {
// 渲染实体
}
// 渲染系统设置
isoRender.showGrid = true; // 显示网格
isoRender.showDebug = true; // 显示调试信息(坐标、深度值)
// 便捷渲染方法
isoRender.drawBox(ctx, x, y, z, size, height, '#ff6600');
isoRender.drawCharacter(ctx, x, y, z, radius, height, '#4ecdc4');数学工具
Vec2(2D 向量)
import { Vec2 } from 'you-engine';
const v1 = new Vec2(10, 20);
const v2 = Vec2.from({ x: 5, y: 5 });
// 运算
v1.add(v2); // 加法
v1.sub(v2); // 减法
v1.scale(2); // 缩放
v1.normalize(); // 归一化
v1.rotate(Math.PI/4); // 旋转
// 属性
v1.length; // 长度
v1.lengthSq; // 长度平方
v1.angle; // 角度
// 静态方法
Vec2.distance(v1, v2);
Vec2.dot(v1, v2);
Vec2.cross(v1, v2);
Vec2.lerp(v1, v2, 0.5);
Vec2.reflect(v1, normal);MathUtils(数学工具)
import { clamp, lerp, smoothstep, degToRad, radToDeg, randomFloat, randomInt, randomChoice } from 'you-engine';
// 范围限制
clamp(value, min, max);
// 线性插值
lerp(a, b, t);
// 平滑插值
smoothstep(a, b, t);
// 角度转换
degToRad(90); // 1.5707...
radToDeg(Math.PI); // 180
// 随机数
randomFloat(0, 100);
randomInt(1, 10);
randomChoice(['a', 'b', 'c']);
// 阻尼
damp(current, target, smoothing, dt);
// 循环
wrap(value, min, max);
// 来回
pingPong(t, length);事件系统
// 发送事件
engine.emit('player:death', { playerId: 0 });
// 监听事件
const subscription = engine.on('player:death', (data) => {
console.log('玩家死亡:', data.playerId);
});
// 取消监听
subscription.unsubscribe();
// 一次性监听
engine.once('game:start', () => {
console.log('游戏开始');
});对象池
用于优化频繁创建/销毁的对象。
import { ObjectPool } from 'you-engine';
// 创建对象池
const bulletPool = new ObjectPool({
create: () => ({ x: 0, y: 0, vx: 0, vy: 0, active: false }),
reset: (bullet) => { bullet.x = 0; bullet.y = 0; },
initialSize: 100,
maxSize: 500
});
// 或简单用法
const pool = new ObjectPool(() => ({ value: 0 }));
// 获取对象
const bullet = bulletPool.acquire();
bullet.x = player.x;
bullet.y = player.y;
// 归还对象
bulletPool.release(bullet);
// 统计
console.log('活跃:', bulletPool.activeCount);
console.log('可用:', bulletPool.availableCount);完整示例
import {
Engine, Scene,
InputSystem, RenderSystem, CameraSystem, PhysicsSystem,
ParticleSystem, AudioSystem, TweenSystem,
createTransform, createVelocity, createSprite, createCollider,
Vec2, Easing
} from 'you-engine';
// 创建引擎
const engine = new Engine({
canvas: '#game',
width: 1280,
height: 720,
backgroundColor: '#1a1a2e'
});
// 注册系统
engine
.use(InputSystem)
.use(PhysicsSystem)
.use(CameraSystem)
.use(TweenSystem)
.use(ParticleSystem)
.use(AudioSystem)
.use(RenderSystem);
// 定义游戏场景
class GameScene extends Scene {
player: any;
async onEnter() {
const audio = this.getSystem(AudioSystem);
await audio.loadAll({
sounds: { jump: { src: '/sounds/jump.mp3' } }
});
// 创建玩家
this.player = this.spawn({
transform: createTransform(640, 360),
velocity: createVelocity(),
sprite: createSprite({ width: 48, height: 48, color: '#4ecdc4' }),
collider: createCollider('circle', { radius: 24 })
});
// 摄像机跟随
const camera = this.getSystem(CameraSystem);
camera.follow(this.player, { smoothing: 0.08 });
}
onUpdate(dt: number) {
const input = this.getSystem(InputSystem);
const { transform, velocity } = this.player;
// 移动
velocity.x = input.axisX() * 300;
velocity.y = input.axisY() * 300;
// 跳跃/动作
if (input.isPressed('jump')) {
const audio = this.getSystem(AudioSystem);
audio.playSound('jump');
const particles = this.getSystem(ParticleSystem);
particles.explode(transform.x, transform.y + 24);
}
}
}
// 注册并启动
engine.addScene('game', GameScene);
engine.start('game');构建
# 开发
pnpm dev
# 构建
pnpm build
# 预览
pnpm preview许可证
MIT License
