After updating to PlayCanvas version 1.68.0, the picker framebuffer no longer consistently selects the correct entity when I click on it. This issue seems to occur after I move the camera or change the screen size. For example, when I attempt to select a capsule entity, the picker sometimes incorrectly identifies and selects a brick entity instead, even though the capsule was clearly clicked. This behavior is inconsistent, and the selected entity varies without apparent reason.
Steps to Reproduce:
Update PlayCanvas project to version 1.68.0.
Implement a basic picker framebuffer setup to select entities on click.
Move the camera or change the screen size.
Attempt to select an entity by clicking on it.
Observe that the wrong entity is sometimes selected.
and I cannot reproduce the error by changing the screen size. The example calls this each frame to handle it, is this something you do in your code as well?
// Make sure the picker is the right size, and prepare it, which renders meshes into its render target
if (picker) {
picker.resize(canvas.clientWidth * pickerScale, canvas.clientHeight * pickerScale);
picker.prepare(camera.camera, app.scene, pickerLayers);
}
Hello @mvaligursky , Thank you for the reply. below is my code.
it does not selects the correct entity when I click on it.
var PickerFramebuffer = pc.createScript('pickerFramebuffer');
PickerFramebuffer.attributes.add('pickAreaScale', {
type: 'number',
title: 'Pick Area Scale',
description: '1 is more accurate but worse performance. 0.01 is best performance but least accurate. 0.25 is the default.',
default: 0.25,
min: 0.01,
max: 1
});
PickerFramebuffer.attributes.add('layerNames', {
type: 'string',
title: 'Layers Names',
array: true,
description: 'Layer names from which objects will be picked from.',
default: ['World']
});
// initialize code called once per entity
PickerFramebuffer.prototype.initialize = function () {
// Create a frame buffer picker with a scaled resolution
var canvas = this.app.graphicsDevice.canvas;
var canvasWidth = parseInt(canvas.clientWidth, 10);
var canvasHeight = parseInt(canvas.clientHeight, 10);
this.picker = new pc.Picker(this.app, canvasWidth * this.pickAreaScale, canvasHeight * this.pickAreaScale);
this.layers = [];
for (var i = 0; i < this.layerNames.length; ++i) {
var layer = this.app.scene.layers.getLayerByName(this.layerNames[i]);
if (layer) {
this.layers.push(layer);
}
}
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
this.on('destroy', function () {
this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onSelect, this);
}, this);
};
PickerFramebuffer.prototype.onSelect = function (event) {
var canvas = this.app.graphicsDevice.canvas;
var canvasWidth = parseInt(canvas.clientWidth, 10);
var canvasHeight = parseInt(canvas.clientHeight, 10);
var camera = this.entity.camera;
var scene = this.app.scene;
var picker = this.picker;
if (picker) {
picker.resize(canvasWidth * this.pickAreaScale, canvasHeight * this.pickAreaScale);
picker.prepare(camera, scene, this.layers);
}
// Map the mouse coordinates into picker coordinates and
// query the selection
var selected = picker.getSelection(
Math.floor(event.x * (picker.width / canvasWidth)),
picker.height - Math.floor(event.y * (picker.height / canvasHeight))
);
if (selected.length > 0) {
// Get the graph node used by the selected mesh instance
var entity = selected[0].node;
console.log('selected entity :', entity);
}
};
I’m not certain if this is the most efficient approach, but it has proven effective for my needs.
// initialize code called once per entity
Picker.prototype.initialize = function () {
// Initialize the picker component
this.initPicker();
const selectedEntity = this.handleEntityPicking(event);
};
Picker.prototype.initPicker = function () {
try {
// Define layer names
this.layerNames = ['World', 'Front'];
// Destructure canvas dimensions directly for picker initialization
const { clientWidth, clientHeight } = this.app.graphicsDevice.canvas;
// Initialize the picker with full canvas resolution
this.picker = new pc.Picker(this.app, clientWidth, clientHeight);
// Filter valid layers directly, logging warnings for missing layers
this.layers = this.layerNames.reduce((validLayers, name) => {
const layer = this.app.scene.layers.getLayerByName(name);
if (!layer) {
if (debugLevel > 0) console.log(`Layer with name ${name} not found.`);
return validLayers;
}
validLayers.push(layer);
return validLayers;
}, []);
// Handle canvas resize events to adjust picker dimensions accordingly
this.app.graphicsDevice.on('resizecanvas', () => {
const { canvas } = this.app.graphicsDevice;
this.picker.resize(canvas.clientWidth, canvas.clientHeight);
}, this);
} catch (error) {
if (debugLevel > 0) console.log('Error initializing picker:', error);
}
};
Picker.prototype.handleEntityPicking = function (event) {
// Simplify the assignment of camera and scene for readability
const { camera } = this.entity;
const { scene } = this.app;
// Prepare the picker with necessary layers, if any
this.picker.prepare(camera, scene, this.layers);
// Get the selection based on the event's x and y coordinates
const selection = this.picker.getSelection(event.x, event.y);
// Check if there is a selection and the first item has a node
if (selection && selection[0]?.node) {
const { name, parent } = selection[0].node;
// Log the selected entity's name and its parent
// if (debugLevel > 0) console.log(`Selected entity: ${name}`);
// if (debugLevel > 0) console.log(`Selected parent:`, parent);
// Additional check to console if the selected entity has an object and has a node
if (parent && parent.node) {
if (debugLevel > 0) console.log('The selected entity\'s parent has a node');
}
return selection[0];
} else {
// Log when no entity is selected or the selected entity does not have a node
if (debugLevel > 0) console.log('No valid selection or the selected entity does not have a node');
// Return null when the selection doesn't meet the criteria
return null;
}
};