I have implemented some logic for duplicating prefabs in my game, but it’s not working very well!
Has anyone else tried creating a Wrapping World at all? Is duplicating objects the correct way to go? I am really looking for confirmation that duplicating objects is the best way to go… If so, I will persevere for a bit, failing that I may have to simplify my game!
import { _decorator, Component, Node, Vec3, instantiate, Camera, view, js, RigidBody2D, UITransform, NodePool, Prefab } from 'cc';
import { GameConstants } from './constants';
const { ccclass, property } = _decorator;
import { GameObject } from './GameObject';
@ccclass('BoundaryDuplicator')
export class BoundaryDuplicator extends Component {
@property(Node)
player: Node | null = null;
@property(Camera)
camera: Camera | null = null;
@property({ type: [Prefab]})
prefabs: Prefab[] = [];
private clones: Map<Node, Node[]> = new Map();
private prefabPools: Map<Prefab, NodePool> = new Map();
onLoad() {
for(const prefab of this.prefabs) {
this.createPoolForPrefab(prefab);
}
}
lateUpdate(deltaTime: number) {
if (!this.player || !this.camera || !this.node) {
console.warn('Player, camera, or world node is missing!');
return;
}
const visibleBounds = this.getVisibleBounds();
const children = this.node.children.filter(child => !child.name.includes('_Clone'));
for (const child of children) {
const prefab = this.getPrefabForNode(child);
if(prefab) {
this.updateClones(child, visibleBounds, prefab);
}
}
}
private getVisibleBounds(): { min: Vec3; max: Vec3 } {
if (!this.camera) {
throw new Error("Camera is not set.");
}
const orthoHeight = this.camera.orthoHeight;
const aspectRatio = view.getVisibleSize().width / view.getVisibleSize().height;
const halfWidth = orthoHeight * aspectRatio;
const halfHeight = orthoHeight;
const cameraPosition = this.camera.node.position;
return {
min: new Vec3(cameraPosition.x - halfWidth, cameraPosition.y - halfHeight, 0),
max: new Vec3(cameraPosition.x + halfWidth, cameraPosition.y + halfHeight, 0),
};
}
private updateClones(node: Node, visibleBounds: { min: Vec3; max: Vec3 }, prefab: Prefab) {
const basePosition = node.position.clone();
if(!this.clones.has(node)) {
this.clones.set(node, []);
}
const nodeClones = this.clones.get(node);
const objectSize = this.getObjectSize(node);
const offsets = [
{ x: -GameConstants.WORLD.WIDTH, y: 0 }, // Left
{ x: GameConstants.WORLD.WIDTH, y: 0 }, // Right
{ x: 0, y: -GameConstants.WORLD.HEIGHT }, // Bottom
{ x: 0, y: GameConstants.WORLD.HEIGHT }, // Top
{ x: -GameConstants.WORLD.WIDTH, y: -GameConstants.WORLD.HEIGHT }, // Bottom-Left
{ x: -GameConstants.WORLD.WIDTH, y: GameConstants.WORLD.HEIGHT }, // Top-Left
{ x: GameConstants.WORLD.WIDTH, y: -GameConstants.WORLD.HEIGHT }, // Bottom-Right
{ x: GameConstants.WORLD.WIDTH, y: GameConstants.WORLD.HEIGHT }, // Top-Right
];
offsets.forEach((offset, index) => {
const clonePosition = new Vec3(
basePosition.x + offset.x,
basePosition.y + offset.y,
basePosition.z
);
const inView =
clonePosition.x + objectSize.width / 2 >= visibleBounds.min.x &&
clonePosition.x - objectSize.width / 2 <= visibleBounds.max.x &&
clonePosition.y + objectSize.height / 2 >= visibleBounds.min.y &&
clonePosition.y - objectSize.height / 2 <= visibleBounds.max.y;
if (inView) {
if (!nodeClones[index]) {
const clone = this.getOrCreateClone(prefab, node);
clone.name = node.name + '_Clone';
//const asteroidComponent = clone.getComponent(Asteroid);
//asteroidComponent.destroy();
this.node.addChild(clone);
clone.setPosition(clonePosition);
nodeClones[index] = clone;
} else {
nodeClones[index].setPosition(clonePosition);
}
} else if (nodeClones[index]) {
//nodeClones[index].destroy();
this.returnToPool(prefab, nodeClones[index]);
nodeClones[index] = null;
}
});
}
private createPoolForPrefab(prefab: Prefab) {
if(!this.prefabPools.has(prefab)) {
this.prefabPools.set(prefab, new NodePool());
}
}
private getOrCreateClone(prefab: Prefab, original: Node): Node {
const pool = this.prefabPools.get(prefab);
let clone: Node;
if(pool && pool.size() > 0) {
clone = pool.get();
} else {
clone = instantiate(prefab);
}
this.copyOrRemoveProperties(original, clone);
const gameObjectComponent = clone.getComponent(GameObject);
if (gameObjectComponent) {
gameObjectComponent.originalObject = original;
}
return clone;
}
private copyOrRemoveProperties(original: Node, clone: Node) {
clone.setRotation(original.getRotation());
clone.setScale(original.getScale());
const originalRigidBody = original.getComponent(RigidBody2D);
const cloneRigidBody = clone.getComponent(RigidBody2D);
if(originalRigidBody && cloneRigidBody) {
cloneRigidBody.linearVelocity = originalRigidBody.linearVelocity.clone();
cloneRigidBody.angularVelocity = originalRigidBody.angularVelocity;
}
}
private returnToPool(prefab: Prefab, node: Node) {
const pool = this.prefabPools.get(prefab);
if(pool) {
pool.put(node);
} else {
node.destroy();
}
}
private getPrefabForNode(node: Node): Prefab | null {
for (const prefab of this.prefabs) {
if(node.name.startsWith(prefab.data.name)) {
return prefab;
}
}
return null;
}
private getObjectSize(node: Node): { width: number; height: number } {
const uiTransform = node.getComponent(UITransform);
if (uiTransform) {
return {
width: uiTransform.contentSize.width,
height: uiTransform.contentSize.height,
};
}
return { width: 0, height: 0 };
}
}
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameObject')
export class GameObject extends Component {
originalObject: Node | null = null;
onDisable() {
if (this. originalObject) {
this.originalObject.removeFromParent();
this.originalObject.destroy();
this.originalObject = null;
}
}
onDestroy(): void {
if (this.originalObject) {
this.originalObject.removeFromParent();
this.originalObject.destroy();
this.originalObject = null;
}
}
}