Composing character from multiple body parts (as scenes)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By random_char

I’m trying to create a character, which consists from different body parts (body, head, arms, legs etc). There can be different types of same body part, e.g. normal arms, wooden arms, metal arms. The idea is to modify character parameters based on the type of each body part (lets say metal arms allow to pick heavy objects but reduce jumping height). I have a following setup (for simplicity lets just use character and arms entities)

  • BaseCharacter scene with BaseCharacter.gd script
  • BaseArms scene with BaseArms.gd script
  • MetalArms scene with MetalArms.gd script. MetalArms scene inherits BaseArms scene, same for scripts, MetalArms inherits BaseArms

Next, I want to instance character body parts from code (so later they can be changeable at runtime). I’ve got some programming background and at first I’ve decided to use Dependency Injection (DI). With DI I just modify character _init to have _arms: BaseArms parameter and then can create characters passing needed arms, which all inherit BaseArms.gd. That works just fine but only with scripts. Is there a way to use some kind of DI with scene instancing? Since if arms have not only behavior attached (scripts), but also some appearance (like BaseArms scene has Sprite and AnimationPlayer child nodes), then it doesn’t work like that. I’ve tried to resolve this issue by preloading body parts and instancing them in Character’s _init method, but then I have hard time using their behavior.

So the questions are:

  1. Can I instance scenes with their scripts (which inherit some base scenes and scripts) in another scenes and have access to both scene nodes and script properties/methods
  2. Is there a way to use DI like principle to instance scenes?
  3. If I use scene instancing like the following, how to get the attached scene script?
we are in character script, say _init method
var arms_scene = preload("res://MetalArms.tscn")
self.add_child(arms_scene.instance())
self.arms = arms_scene.script? how to get this to work

Thanks a lot!

:bust_in_silhouette: Reply From: magicalogic

When you instance a scene from code you get access to all its properties and methods.
For instance if you instance an arm which has property length and method hold(), you can access them as arm.length and arm.hold(). In short the script is part of the scene so with access to the scene you have access to the script (in terms of properties and methods).

How is that done via code?
To instance a scene I have to

var a = preload("res://src/Entity/BodyParts/SomeArms.tscn").instance()
a.do_arm_stuff()

This doesn’t work.
If I do

var a = SomeArms.new(...)
a.do_arm_stuff()

This works, but I cannot access a scene object. Can You please provide a code snippet to show how You do instancing of a scene to access its properties and methods also?

random_char | 2021-05-09 08:58

var arm = preload("res://arm.tscn").instance()
arm.do_something()

magicalogic | 2021-05-09 09:02

hmmm, weird, I receive an error with this approach
Invalid call. Nonexistent function 'do_something' in base 'Node2D'.

A note: though it may be caused by class declaration in related scripts - apparently not, still receiving this error :confused:

random_char | 2021-05-09 09:08

That was just an example which you should replace with your own methods.

magicalogic | 2021-05-09 13:06

Yes, I’ve changed the names. Seems like I’ve missed somewhere script and scene connection -__
Anyway, apparently it is not possible to solve my initial problem, but thanks to You I’ve found a suitable working solution just for now

random_char | 2021-05-09 21:20

:bust_in_silhouette: Reply From: random_char

Ok, for all who wonder, this is what seems to work and what cannot be done:

  1. It’s impossible (or really obscure) to call _init during scene creation in code, so lets just create simple init method to mimic its behavior. Ofc you’ll need to keep track of this by yourself now
  2. Create a base scene and attach a base script to it (e.g. BaseArms.tscn and BaseArms.gs). Make needed inherited scenes, attach new scripts, which inherit the base script (e.g. MetalArms.tscn with MetalArms.gd which inherit BaseArms scene and script respectively). I don’t know if this is necessary, but i’ve also changed MetalArms.tscn root node type to MetalArms (it will appear in Nodes list if you set class_name of your scripts)
  3. When you need to create body part scene, use something like this (say this is part of character script which has arms: BaseArms property
self.arms = preload(...).instance()
self.arms.name = "Arms"#to allow access to child node as $Arms 
self.arms.init(...)
self.add_child(self.arms)