rapier2d示例

This commit is contained in:
janing
2025-11-30 20:56:51 +08:00
parent e8a07c695e
commit a0220bdbed
41 changed files with 2112 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
function padStart(value: string | number, length: number, padChar: string): string {
let result = String(value);
while (result.length < length) {
result = padChar + result;
}
return result;
}
class Clogger {
private prefix: string;
constructor(prefix: string = '') {
this.prefix = prefix;
}
// 将参数转换为字符串并输出到控制台
private formatAndLog(...args: any[]): void {
const now = new Date();
const hours = padStart(now.getHours(), 2, '0');
const minutes = padStart(now.getMinutes(), 2, '0');
const seconds = padStart(now.getSeconds(), 2, '0');
const milliseconds = padStart(now.getMilliseconds(), 3, '0');
const timestamp = `${hours}:${minutes}:${seconds}.${milliseconds}`;
console.log(
this.prefix +'|'+ timestamp +'|'+
args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))).join(' ')
);
}
debug(...args: any[]): void {
this.formatAndLog('DEBUG', ...args);
}
log(...args: any[]): void {
this.formatAndLog('LOG', ...args);
}
warn(...args: any[]): void {
this.formatAndLog('WARN', ...args);
}
error(...args: any[]): void {
this.formatAndLog('ERROR', ...args);
}
}
export const logger = new Clogger('SOIDA');

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ff0e1b17-5af0-4e18-8357-b98382f3911c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,100 @@
import { _decorator, Component, Graphics, Color } from 'cc';
const { ccclass, property } = _decorator;
// import RAPIER from '@dimforge/rapier2d';
export const RAPIER_PTM_RATIO:number = 32.0;
@ccclass('RapierDebugRenderer')
export class RapierDebugRenderer extends Component {
@property(Graphics)
graphics: Graphics = null!;
// 可选:可调整的线宽
@property
lineWidth: number = 1.0;
// 可选:坐标缩放系数
@property
scale: number = RAPIER_PTM_RATIO;
@property
onoff: boolean = false;
private _world: any = null; // RAPIER.World 类型
/**
* 设置物理世界引用
* @param world RAPIER物理世界实例
*/
public setWorld(world: any) {
this._world = world;
}
/**
* 在每帧更新中调用以渲染调试图形
*/
update() {
if (!this._world || !this.graphics) return;
this.onoff && this.render(this._world);
}
clear(){
this.graphics&&this.graphics.clear();
}
/**
* 渲染物理世界的调试信息
* @param world RAPIER物理世界
*/
render(world: any) {
// 获取调试渲染数据
const { vertices, colors } = world.debugRender();
// 清除之前的绘制内容
this.graphics.clear();
// const g = this.graphics;
// g.lineWidth = 10;
// g.fillColor.fromHEX('#ff0000ff');
// g.moveTo(-40, 0);
// g.lineTo(0, -80);
// g.lineTo(40, 0);
// g.lineTo(0, 80);
// g.close();
// g.stroke();
// g.fill();
// 遍历所有线段进行绘制
for (let i = 0; i < vertices.length / 4; i++) {
// 从颜色数组中获取当前线段的颜色
const r = Math.floor(colors[i * 8] * 255); // 转换为 0-255 范围
const g = Math.floor(colors[i * 8 + 1] * 255);
const b = Math.floor(colors[i * 8 + 2] * 255);
const a = colors[i * 8 + 3] * 255; // alpha 直接使用 0-255 范围
// 设置线条样式
this.graphics.lineWidth = this.lineWidth;
this.graphics.strokeColor = new Color(r, g, b, a);
// 获取线段的起点和终点坐标
// 注意Cocos Creator 使用左下角为原点的坐标系y轴向上为正
// 而 RAPIER 可能使用不同的坐标系,因此这里做适当调整
// 获取原始坐标
let x1 = vertices[i * 4] * this.scale;
let y1 = vertices[i * 4 + 1] * this.scale;
let x2 = vertices[i * 4 + 2] * this.scale;
let y2 = vertices[i * 4 + 3] * this.scale;
// 执行绘制
this.graphics.moveTo(x1, y1);
this.graphics.lineTo(x2, y2);
this.graphics.close();
this.graphics.stroke();
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "020358be-0b18-47a4-ad46-a6972ff0ed99",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,87 @@
import { _decorator, Color, Component, instantiate, Label, Node, Prefab, Sprite } from 'cc';
import * as cc from 'cc';
import { logger } from './Clogger';
const { ccclass, property } = _decorator;
@ccclass('SceneMain')
export class SceneMain extends Component {
@property(Node)
node_fps:Node|null = null;
@property(Node)
node_counts:Node|null = null;
@property(Prefab)
prefab_item:Prefab|null = null;
@property(Node)
container_balls:Node|null = null;
@property(cc.Node)
node_switchscene:cc.Node|null = null;
private _timeAccumulator: any;
private _countAcc: any;
private _listBalls: Node[] = [];
protected onLoad(): void {
logger.log('dballs onLoad');
let p = cc.PhysicsSystem2D.instance;
p.positionIterations = 4;
p.velocityIterations = 2;
}
start() {
logger.log('dballs start');
// let n= this.node.getChildByName('ballitem')
// let r = n.getComponent(cc.CircleCollider2D).radius
// logger.log('radius:', r);
this.node_switchscene.on('click', (b:cc.Button) => {
cc.director.loadScene('scene_rapier2d');
});
}
update(dt: number) {
if (!this._timeAccumulator) {
this._timeAccumulator = 0;
}
this._timeAccumulator += dt;
if (this._timeAccumulator >= 0.1) {
this.node_fps.getComponent(Label)!.string = `FPS: ${Math.floor(1 / dt)}`;
this._timeAccumulator = 0;
}
if(Math.floor(1 / dt) < 30){
return;
}
if (!this._countAcc) {
this._countAcc = 0;
}
// this._countAcc += 1;
//每秒实例化添加5个node_item 到场景中,并且显示当前场景中的node_item数量
// if (this._countAcc >= 60/60) {
for (let i = 0; i < 4; i++) {
let node = instantiate(this.prefab_item);
node.getComponent(Sprite).color = new Color(Math.random()*255, Math.random()*255, Math.random()*255);
this.container_balls.addChild(node);
let x = (Math.random()*2-1)*cc.view.getVisibleSize().width/2;;
node.setPosition(x, 400);
this._listBalls.push(node);
}
this.node_counts.getComponent(Label)!.string = `BALLS: ${this._listBalls.length}`;
this._countAcc = 0;
// }
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "22878c0e-0345-4a10-896a-6c1520d22df5",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,180 @@
import { _decorator, Color, Component, instantiate, Label, Node, Prefab, Sprite } from 'cc';
import * as cc from 'cc';
import RAPIER, { IntegrationParameters } from '@dimforge/rapier2d-compat';
import { RAPIER_PTM_RATIO, RapierDebugRenderer } from './RapierDebugRenderer';
import { logger } from './Clogger';
const { ccclass, property } = _decorator;
@ccclass('SceneRapier2d')
export class SceneRapier2d extends Component {
@property(Node)
node_fps:Node|null = null;
@property(Node)
node_counts:Node|null = null;
@property(Prefab)
prefab_item:Prefab|null = null;
@property(Node)
container_balls:Node|null = null;
@property(cc.Node)
node_switch:cc.Node|null = null;
@property(cc.Node)
node_switchscene:cc.Node|null = null;
private _timeAccumulator: any;
private _countAcc: any;
private world: RAPIER.World;
private gravity = new RAPIER.Vector2(0, -10);
private _initComplete: boolean = false;
private _rigidBody: RAPIER.RigidBody;
private _debugRenderer: RapierDebugRenderer;
//将一个node和一个RigidBody绑定
private _mapNodeBalls: Map<Node, RAPIER.RigidBody> = new Map<Node, RAPIER.RigidBody>();
protected async onLoad(): Promise<void> {
logger.log('dballs onLoad');
RAPIER.init().then(() => {
logger.log('RAPIER.init success');
this._initComplete = true;
// 创建物理世界
this.world = new RAPIER.World(this.gravity);
this.fillWorld();
this._debugRenderer = this.getComponent(RapierDebugRenderer)!;
this._debugRenderer.setWorld(this.world);
this.afterInit();
});
}
afterInit() {
this.node_switch.children[0]!.getComponent(Label).string = this._debugRenderer.onoff ? 'debug:ON' : 'debug:OFF';
this.node_switch.on('click', (b:cc.Button) => {
this._debugRenderer.onoff = !this._debugRenderer.onoff;
this._debugRenderer.clear();
b!.node.children[0]!.getComponent(Label).string = this._debugRenderer.onoff ? 'debug:ON' : 'debug:OFF';
});
}
start() {
logger.log('dballs start');
this.node_switchscene.on('click', (b:cc.Button) => {
cc.director.loadScene('scene_box2d');
});
// this.schedule((dt:number)=>{
// if(!this._initComplete){
// return;
// }
// this.world.step();
// this._mapNodeBalls.forEach((rigidBody, node)=>{
// let position = rigidBody.translation();
// node.setPosition(position.x*RAPIER_PTM_RATIO, position.y*RAPIER_PTM_RATIO);
// node.active = true;
// });
// }, 1/60, cc.macro.REPEAT_FOREVER, 0);
}
fillWorld(){
let world = this.world;
let viewSize = cc.view.getVisibleSize();
let groundRigidBody = world.createRigidBody(new RAPIER.RigidBodyDesc(RAPIER.RigidBodyType.Fixed));
world.createCollider(RAPIER.ColliderDesc.cuboid(viewSize.width/2, 0.5).setTranslation(0,-viewSize.height/(2*RAPIER_PTM_RATIO)).setRestitution(1).setFriction(0.0), groundRigidBody);
let groundRigidBody1 = world.createRigidBody(new RAPIER.RigidBodyDesc(RAPIER.RigidBodyType.Fixed));
world.createCollider(RAPIER.ColliderDesc.cuboid(0.5,viewSize.height/2 ).setTranslation(-viewSize.width/(2*RAPIER_PTM_RATIO),0).setRestitution(1).setFriction(0.0), groundRigidBody1);
let groundRigidBody2 = world.createRigidBody(new RAPIER.RigidBodyDesc(RAPIER.RigidBodyType.Fixed));
world.createCollider(RAPIER.ColliderDesc.cuboid(0.5,viewSize.height/2 ).setTranslation(viewSize.width/(2*RAPIER_PTM_RATIO),0).setRestitution(1).setFriction(0.0), groundRigidBody2);
// let rigidBody = world.createRigidBody(new RAPIER.RigidBodyDesc(RAPIER.RigidBodyType.Dynamic).setTranslation(0.0, 5.0).setLinearDamping(0).setAngularDamping(0));
// let collider = world.createCollider(RAPIER.ColliderDesc.ball(0.5).setDensity(1.0).setRestitution(1).setFriction(0.0), rigidBody);
// this._rigidBody = rigidBody;
}
update(dt: number) {
if (!this._timeAccumulator) {
this._timeAccumulator = 0;
}
this._timeAccumulator += dt;
// if (this._timeAccumulator >= 0.1) {
this.node_fps.getComponent(Label)!.string = `FPS: ${Math.floor(1 / dt)}`;
// this._timeAccumulator = 0;
// }
if(Math.floor(1 / dt) < 30){
return;
}
if (!this._countAcc) {
this._countAcc = 0;
}
//物理系统同步
if(!this._initComplete){
return;
}
this.world.step();
this._mapNodeBalls.forEach((rigidBody, node)=>{
let position = rigidBody.translation();
node.setPosition(position.x*RAPIER_PTM_RATIO, position.y*RAPIER_PTM_RATIO);
node.active = true;
});
// this._countAcc += 1;
// if (this._countAcc >= 60/60) {
for (let i = 0; i < 4; i++) {
let node = instantiate(this.prefab_item);
node.getComponent(Sprite).color = new Color(Math.random()*255, Math.random()*255, Math.random()*255);
node.active = false;
this.container_balls.addChild(node);
let x = (Math.random()*2-1)*cc.view.getVisibleSize().width/2;
let y = 400;
let r = (node.getComponent(cc.UITransform).width-2)/2 ;
let rigidBody = this.world.createRigidBody(new RAPIER.RigidBodyDesc(RAPIER.RigidBodyType.Dynamic).setTranslation(x/RAPIER_PTM_RATIO, y/RAPIER_PTM_RATIO).setLinearDamping(0).setAngularDamping(0).setCanSleep(false));
this.world.createCollider(RAPIER.ColliderDesc.ball(r/RAPIER_PTM_RATIO).setDensity(1.0).setRestitution(0.5).setFriction(0.0), rigidBody);
this._mapNodeBalls.set(node, rigidBody);
// node.setPosition(x, 400);
// this._listBalls.push(node);
}
this.node_counts.getComponent(Label)!.string = `BALLS: ${this.container_balls.children.length}`;
// this._countAcc = 0;
// }
}
}

View File

@@ -0,0 +1 @@
{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"188ce8c7-8772-4533-8d62-ad630ea294eb","files":[],"subMetas":{},"userData":{}}