[SOLVED] Dynamically Referencing a Script Without Name

Hello,

Apologies if this is a dumb question but I’ve been hitting my head against this for a while:

I’m trying to use a single raycast to activate different scripts based on what is hit by the raycast. Like a point-and-click adventure type game, where different objects do different things when clicked.

Here’s an example of what I mean:

Entity A has a script on it called “flyAwayIntoTheSun”

Entity B has a script on it called “explodeIntoPieces”

Both “flyAwayIntoTheSun” and “explodeIntoPieces” have functions named “onSelect” in them that fire off their individual different behaviors.

What I cannot figure out is how to get my raycast to call “onSelect” on these scripts (based on what object the raycast hits), unless I explicitly pass it the script name.

It’d be silly and unmaintainable to do a massive pile of conditional logic to call each script’s individual ‘onSelect’ function based on the name of the script. I’m also not sure that using events would be very maintainable or performant either…

I tried making a helper script just named “selectable” and have that script reference another script dynamically as an asset attribute, but I couldn’t get that to work.

Is there a way to programmatically access a script based on something other than its name?
Is there an easier way to do what I’m trying to do?

Thank you immensely for any help!

Not sure if it’s helpful because it’s extremely basic but here’s the raycast script:

var SelectItem = pc.createScript('selectItem');

SelectItem.attributes.add('cameraEntity', {type: 'entity'});

// initialize code called once per entity
SelectItem.prototype.initialize = function() {

     this._camera = this.cameraEntity.camera;

     // susbscribe mouseup event to raycast
     this.app.mouse.on(pc.EVENT_MOUSEUP, this.mouseUp, this);

     //subscribe on touch event
     if (this.app.touch) {
         this.app.touch.on(pc.EVENT_TOUCHSTART, this.touchStart, this);
     }

// remove handlers on destroy
this.on('destroy', function() {

    this.app.mouse.off(pc.EVENT_MOUSEUP, this.mouseUp, this);
    
    if (this.app.touch) {
        this.app.touch.off(pc.EVENT_TOUCHSTART, this.touchStart, this);
    }

     }, this);

};


SelectItem.prototype.mouseUp = function (e) {
     this.doRaycast(e);
};

SelectItem.prototype.touchStart = function (e) {
    if (e.touches.length == 1)
    {
        this.doRaycast(e.touches[0]);
    }
    e.event.preventDefault();
};

SelectItem.prototype.doRaycast = function (screenPosition) {

     var _from = this.cameraEntity.getPosition();

     var _to = this._camera.screenToWorld(screenPosition.x, screenPosition.y, this._camera.farClip);

     var _result = this.app.systems.rigidbody.raycastFirst(_from, _to);

// if we hit anything with a collider
if (_result) {

    var hitEntity = _result.entity;

    // if it has a script component at all
    if (hitEntity.script){

        // FIND THE SCRIPT I WANT SOMEHOW AND CALL ITS 'onSelect' FUNCTION
        
        }

    }


}

};

entity.script.scripts is an array of all the scripts. So you could loop trough each script, check if there is an onSelect function and then call it. eg entity.script.scripts[0].onSelect();

Or you could fire an event app.fire('selected', result.entity) and then in the event handler in your receiving script you can check if (selectedEntity === this.entity) { }

3 Likes

The events approach is so much more elegant!

3 Likes

Thank you, Kulodo!

Somehow I didn’t find that entity.script.scripts would let me access all the scripts as an array.

Events would be more elegant, but I was worried about the performance impact of so many event handlers… I think my fear may be unfounded though, after reading some more about them I dunno that I would hit the point where I had enough to make much of a problem.

Thanks again!

1 Like

Hello,
I am experiencing the exact same problem! You have asked the exact thing I have been struggling with. Have you managed to solve it? How did you manage to do it?
I will truly appreciate your help.

I would use event approach:
I would specify bunch of tags e.g door, window
If my ryacast would hit entity I would check the tag:
if(entity.tags.has(door) { this.app.fire('interact', tag) }

I would create InteractManager with references to all necessary scripts and I would listen to the event:

this.app.on('interact', function(tag) {
switch(tag) {
case DOOR:
this.doorScript.interact();
break;
case WINDOW:
this.windowScript.interact();
break
}
})

Thank you very much. I am not familiar with events, but I will definitely research them and give this a try. The problem is that I have a lot of different objects, and I do not want to end up with a long list of tags and if statements in my player object, or a lengthy switch condition in InteractManager. Is it possible to:

  • Trigger a single general event when a raycast hits an entity, and have only the script of the object that was hit start to function? OR
  • When a raycast hits something, can I start the script attached to the item you hit, without needing to mention the script name? (This is important because interactable objects have different scripts for different interactions with different script names)

The other way around is basically to create trigger volume on all of the interactable entities.
Check if the player is inside volume that solves the problem because you not just checking for one tag.
So, you can create script called triggerVolume and attach it to all entities and then call corresponding script when player is inside trigger volume

1 Like