Godot C# setScript() with derived class - c#

I have a scene that is a Paddle (like the one in PONG game). But my paddles can be either horizontal either vertical.
So I made one parent abstract class "Paddle" that contains the common logic, and two derived classes that extends Paddle "PaddleHorizontal" and "PaddleVertical" where the movements are different (one go up and down, the other go left and right).
At the beginning, I want to create my paddles and attach correct script to each of them but I got this error
" Script inherits from native type 'KinematicBody2D', so it can't be instanced in object of type: 'PackedScene' "
My Main.cs is like so :
using Godot;
public class Main : Node2D
{
private PackedScene _paddleScene;
public override void _Ready()
{
base._Ready();
_paddleScene = GD.Load<PackedScene>("res://src/scenes/entities/paddle/Paddle.tscn");
var script = GD.Load<Reference>("res://src/scenes/entities/paddle/PaddleHorizontal.cs");
_paddleScene.SetScript(script);
this.InitPaddles();
}
private void InitPaddles()
{
this.AddPaddle(new Vector2(PaddlePositions.Top.x, PaddlePositions.Top.y));
this.AddPaddle(new Vector2(PaddlePositions.Bottom.x, PaddlePositions.Bottom.y));
}
private void AddPaddle(Vector2 paddlePosition)
{
KinematicBody2D paddleInstance = (KinematicBody2D)_paddleScene.Instance();
paddleInstance.Position = paddlePosition;
AddChild(paddleInstance);
}
}
// -- Paddle.cs --
using Godot;
public abstract class Paddle : KinematicBody2D
{
// common methods & properties
// the one method that is different and should be override
public abstract Vector2 GetMovement();
}
// -- PaddleHorizontal.cs --
using Godot;
public class PaddleHorizontal : Paddle
{
public override Vector2 GetMovement()
{
// different from PaddleVertical
}
}
I guess the error come from the fact that PaddleHorizontal don't extends KinematicBody2D directly but there is a lot of logic that will be in common between the 2 types of Paddle... .. If you guys have a solution or a workaround...
EDIT: A workaround has beed found by mxmissile in comments. Instead of setting the script of PackedScene, he proposed to set it on the Instance. And it works. Check https://github.com/godotengine/godot/issues/31994 for more details and DO READ the first comment in order to avoid another issue.

The solution suggested by #mxmissile looks like:
private void AddPaddle(Vector2 paddlePosition)
{
KinematicBody2D paddleInstance = (KinematicBody2D)_paddleScene.Instance();
ulong paddleInstanceId = paddleInstance.GetInstanceId();
Resource script = GD.Load("res://src/scenes/entities/paddle/PaddleHorizontal.cs");
paddleInstance.SetScript(script);
paddleInstance = (KinematicBody2D)GD.InstanceFromId(paddleInstanceId);
paddleInstance.Position = paddlePosition;
AddChild(paddleInstance);
}

The Error
Script inherits from native type 'KinematicBody2D', so it can't be instanced
in object of type: 'PackedScene'
This error is caused by setting the script of an Object to a script of a
different object hierarchy.
The same error will occur if you, for example, set the script of a Control node
to a script that inherits from Node2D.
The Solution
Instead of set_script() of the _paddleScene you should set_script() of the
paddleInstance which is the instance of KinematicBody2D.
KinematicBody2D paddleInstance = (KinematicBody2D)_paddleScene.Instance();
paddleInstance.SetScript(thePaddleScript);

Related

Using parent reference to call child methods

I am writing a spawning system for my android game in Unity and for each type of enemy there is a different controller class, which by the way are not MonoBehaviours, these controller classes are controlled by the WaveController which is controlled by the Master enemy controller which is a MonoBehaviour (this doesn't make any difference, it's just easier to control the data flow).
Every controller inherits from the Controller abstract class, but also from the IController interface which takes in 5 generic arguments. You might already guessed it, but the data and functionality is split up since this was the whole spawning process of an enemy is a 5 step pipeline:
The Master controller determines the wave data
The Wave controller determines which controllers (since each enemy type has it's own controller) should be used and how
The controllers determine how and where enemies are spawned
Enemy objects are pulled from an object pool and spawned
Enemies are spawned with specific spawn data which is funneled through a scriptable object that is assigned to the Master controller
Without getting any more deeper into how this system works, my problem is that I have no way of updating a specific controllers data without separately referencing each type of controller data which would get really messy since like I said: the data referencing is inherited from the IController interface which requires 5 generic arguments :P
It's really a waste of my time to write the same code for each of controllers, and later even forget to add new controllers to the function.
PS all generic references are constrained by abstract classes.
I tried referencing each controller data separately, but this was too messy.
I also tried to abstract the ControllerData class, and having each controller have it's own ControllerData class that also inherits from an IControllerData interface for the generic referencing, but this didn't work since one of the 5 generic references also requires 2 generic references, BUT you can't cast nested generic arguments >:(
I just wish C# had wildcards like Java...
public class ObstacleControllerData<ObstacleType, ObstacleScriptableObject, SpawnScriptableObject, SpawnMetricsScriptableObject, SpawnArguments>
where ObstacleType : Obstacle
where ObstacleScriptableObject : ObstacleSO
where SpawnScriptableObject : SpawnSO
where SpawnMetricsScriptableObject : SpawnMetricsSO<ObstacleScriptableObject, SpawnScriptableObject>
where SpawnArguments : SpawnArgs
{
public float LastSpawnTime;
public SpawnMetricsScriptableObject SpawnMetrics { get; private set; }
public readonly ObstaclePool<ObstacleType, ObstacleScriptableObject, SpawnScriptableObject, SpawnArguments> Pool;
public ObstacleControllerData(SpawnMetricsScriptableObject spawn_metrics, ObstacleController controller)
{
LastSpawnTime = Time.time;
SpawnMetrics = spawn_metrics;
Pool = new(spawn_metrics.Data, controller, spawn_metrics.MaxActiveObstacles);
}
public void UpdateData(SpawnMetricsScriptableObject spawn_metrics)
{
Debug.Log("test update data");
}
}
public abstract class ObstacleController
{
public void TrySpawn()
{
if (CanSpawn())
Spawn();
}
protected abstract void Spawn();
protected abstract bool CanSpawn();
}
public class MissileController : ObstacleController, IObstacleController<Missile, MissileSO, MissileSpawnSO, MissileSpawnMetricsSO, MissileSpawnArgs>
{
private ObstacleControllerData<Missile, MissileSO, MissileSpawnSO, MissileSpawnMetricsSO, MissileSpawnArgs> _data;
public ObstacleControllerData<Missile, MissileSO, MissileSpawnSO, MissileSpawnMetricsSO, MissileSpawnArgs> ControllerData { get { return _data; } }
private int _spawnRandomTarget;
private List<int> _randomNumbers;
protected override bool CanSpawn()
{
if (_data.Pool.ActiveObstaclesControlled == _data.SpawnMetrics.MaxActiveObstacles)
return false;
if (_data.LastSpawnTime > 0f && Time.time - _data.LastSpawnTime < _data.SpawnMetrics.Interval)
return false;
if (SpawnChanceSuccessful())
{
_data.LastSpawnTime = Time.time;
return true;
}
else if (Time.time - _data.LastSpawnTime >= _data.SpawnMetrics.Interval)
{
_data.LastSpawnTime = Time.time;
return false;
}
return false;
}
protected override void Spawn()
{
MissileDirection direction = RandomDirection;
MissileSpawnArgs spawn_args = new MissileSpawnArgs(RandomSpawnPosition(direction), direction);
_data.Pool.SpawnObstacle(_data.SpawnMetrics.GetRandomSpawnData(), spawn_args);
}
public MissileController(MissileSpawnMetricsSO spawn_metrics)
{
_randomNumbers = new();
_spawnRandomTarget = Random.Range(0, 100);
_data = new(spawn_metrics, this);
}
}
public interface IObstacleController<Obstacle, ObstacleScriptableObject, SpawnScriptableObject, SpawnMetricsScriptableObject, SpawnArguments>
where Obstacle : JumpMaster.Obstacles.Obstacle
where ObstacleScriptableObject : ObstacleSO
where SpawnScriptableObject : SpawnSO
where SpawnMetricsScriptableObject : SpawnMetricsSO<ObstacleScriptableObject, SpawnScriptableObject>
where SpawnArguments : SpawnArgs
{
public ObstacleControllerData<Obstacle, ObstacleScriptableObject, SpawnScriptableObject, SpawnMetricsScriptableObject, SpawnArguments> ControllerData { get; }
}
I need to call the UpdateData function from a reference of the ObstacleController
The answer was actually really simple, but the array of forums and videos were quite confusing to me for some reason. I just used the dynamic keyword like:
dynamic controller = controllers[0];
controller.ControllerData.UpdateData(data);
To elaborate, the dynamic keyword is used to define an object of unknown type and property. What ever function you call, or property use, no matter if the the name syntax is correct there will be no compile error.
If there is an error in the code it wont be detected by the compiler, rather it will be noticed at runtime which is the only downside to using the dynamic keyword. Use it with care and only when you absolutely need it and know that there will be no error.
Since this is a Unity related question, the dynamic keyword works only if you set your projects .NET compatibility to 4.x

Unity, C#, pointer workaround?

I have a field of type Color, let's call it objectColor. I want objectColor "point" to a gameobject that has a color field. For example, the background color of the camera. Or the color of a sprite renderer. When I try "objectColor = mainCamera.backgroundColor" for example, it copies the main cameras background color at that time, and is not linked. I am used to pointers and C++. If I was in C++ I would just make a pointer of Color type, make it point to what mainCamera.backgroundColor is pointing to, and then change the color that way. Any suggestions?
No simple way to do that in C#. You could either hold reference to the class, that owns Color field, but this will prevent you from changing colors of different object types (not sure it is bad actually). Or you could make class wrappers around all objects with color changing functionality and use them through common interface.
interface IColorChange {
void SetColor(Color color);
}
class CameraWrapper : IColorChange {
public void SetColor(Color color){
m_camera.backgroundColor = color;
}
}
Actually, if you think about it, storing member pointer in c++ is also not such a great idea. Class instance could die any time and you will be left with a dangling pointer without any way to know about it.
One somewhat unusual way (but not necessarily bad if documented well) would be to use "properties". Those are variables which automatically call their custom setter and getter methods when accessed. You cannot avoid separately keeping a reference to the camera instance with them either however.
Here an unity-independent example: https://dotnetfiddle.net/oxBqXV
using System;
public class Camera
{
public int color = 20;
}
public class Foo // most likely should inherit from MonoBehavior
{
Camera _camera;
public int cam_color_ptr // property
{
get
{
return _camera.color;
} // get method
set
{
_camera.color = value;
} // set method
}
public void Start()
{
// of course you should acutally get the reference here
// or have _camera a serializable field to assign in the editor.
_camera = new Camera();
}
public void Print()
{
Console.WriteLine("The camera color is: " + cam_color_ptr);
}
}
public class Program
{
public static void Main()
{
Foo foo = new Foo();
foo.Start();
foo.Print();
}
}

Unity : Singleton ScriptableObjects resets after play

I have a singleton class that contains general information about my game.
public class GeneralGameData : ScriptableObject
{
private static GeneralGameData _currentGeneralGameData;
public static GeneralGameData CurrentGeneralGameData
{
get
{
if (_currentGeneralGameData == null)
{
_currentGeneralGameData = CreateInstance<GeneralGameData>();
}
DontDestroyOnLoad(_currentGeneralGameData);
return _currentGeneralGameData;
}
}
public string GameName;
public string GameVersion;
}
This class has no presents in the scene.
I also have a window to show and change that
public class GeneralGameDataMenuItem : EditorWindow
{
[MenuItem("CoreMaker/GeneralGameData")]
private static void _generalGameData()
{
GetWindow<GeneralGameDataMenuItem>("GeneralGameData");
}
void OnGUI()
{
GeneralGameData.CurrentGeneralGameData.GameName = EditorGUILayout.TextField("GameName", GeneralGameData.CurrentGeneralGameData.GameName);
GeneralGameData.CurrentGeneralGameData.GameVersion = EditorGUILayout.TextField("GameVersion", GeneralGameData.CurrentGeneralGameData.GameVersion);
EditorUtility.SetDirty(GeneralGameData.CurrentGeneralGameData);
}
}
The problem is that it wont save my changes after i hit play or restart unity.
any solutions??
A ScriptableObject is intended to be saved as an asset. Add the line [CreateAssetMenu(menuName = "MyGame/GeneralGameData")] above your ScriptableObject class declaration, then right click in the project pane, click Create > MyGame > GeneralGameData. Fill in all the fields you need. Any script that needs to reference it can just add a public field of type GeneralGameData and add that asset in the inspector.
This is the fixed code based on Nailuj29s answer but you do not need to get reference to it by having a public field , instead you just need to use GeneralGameData.CurrentGeneralGameData.
public class GeneralGameData : ScriptableObject
{
private static GeneralGameData _currentGeneralGameData;
public static GeneralGameData CurrentGeneralGameData
{
get
{
if (_currentGeneralGameData == null)
{
if (AssetDatabase.FindAssets("GeneralGameData", new []{ "Assets/CoreMaker" }).Length != 1)
{
_currentGeneralGameData = CreateInstance<GeneralGameData>();
if (!AssetDatabase.IsValidFolder("Assets/CoreMaker"))
{
AssetDatabase.CreateFolder("Assets", "CoreMaker");
}
AssetDatabase.CreateAsset(_currentGeneralGameData,"Assets/CoreMaker/GeneralGameData.asset");
}
}
_currentGeneralGameData = AssetDatabase.LoadAssetAtPath<GeneralGameData>("Assets/CoreMaker/GeneralGameData.asset");
return _currentGeneralGameData;
}
}
public string GameName;
public string GameVersion;
}
Keep in mind that when you reference GeneralGameData.CurrentGeneralGameData it is going to create and asset , if you delete that asset you are going to lose you data.
The reason you lose your data is because there are 2 ways people use ScriptableObject for singletons. The key difference is how the static value is set. (e.g. the _currentGeneralGameData = CreateInstance<GeneralGameData>() line in your code)
From an ASSET RESOURCE:
This is a shared, "actual" object/file that exists in your project Resources. When you change it, the changes are "permanent" (saved to that actual object), which I believe is what you're looking for. In your case, this would mean simply grabbing a reference to your existing asset resource, instead of creating a new instance, something along the lines of:
_currentGeneralGameData = Resources.Load<GeneralGameData>("GeneralGameData");
or
_currentGeneralGameData = Resources.FindObjectsOfTypeAll<GeneralGameData>().FirstOrDefault();
Note: Skipping verification/error handling code for clarity.
Important: Runtime (standalone) versus editor will get different behavior: you may need to put an explicit reference to the ScriptableObject asset in your scene to make sure it is not optimized out. More details at: https://baraujo.net/unity3d-making-singletons-from-scriptableobjects-automatically/
From an INSTANCE of an asset:
This is your current code. Instead of grabbing a shared asset, you create a FRESH instance, disconnected from what is saved on disk, which means you will not be saving any of the changes you make at runtime.
This is perfectly fine, and possibly preferable in some cases, but if you do want to keep changes then you should use the asset resource method.

Can I load an abstract class at runtime in Unity?

I'm making a card game where I assign random effects to cards, so I need to load the effect's code at runtime with just the class name.
I don't know if my abstract class and child are done properly, and I also don't exactly know how to get the class needed from a path.
I know Resouces.Load won't work but I'll leave it there to convey what I wanna do more easily.
public class GameManager : MonoBehaviour
{
public Effect effect;
...
effect = Resources.Load<Effect>("Card/Effects/" + c.cardActual.effect1);
if (effect.Execution())
{
StartCoroutine(TargetAndCastSpell(c,p));
}
This is the code for my abstract class
public abstract class Effect : MonoBehaviour
{
public string targetType;
public List<int> availableTargets;
public int effectTier;
public PlayerHolder playerTarget;
public CardPhysicalInstance minionTarget;
public PlayerHolder caster;
public void EffectX(PlayerHolder PlayerTarget, CardPhysicalInstance MinionTarget)
{
}
public bool Execution()
{
return false;
}
}
And lastly the child I want to load in runtime
class Spark : Effect
{
string targetType = "any";
//Deal 1 damage to any target
public bool Execution ()
{
bool canTarget = false;
caster = GameManager.singleton.currentPlayer;
availableTargets = SpellHelper.AvailableTargets();
if (targetType == "any") //Placeholder check
{
canTarget = true;
caster.playerState = GameManager.PlayerState.targeting;
}
return canTarget;
}
...
Any help is deeply appreciated, thanks and sorry about my clear lack of understanding of abstract classes.
Based on comments, I think Overriding is the Droid you are looking for. With Polymorphy there is two ways different Implementations can be resolved.
hiding is possibly by default. However, it is also pretty much useless. It is one of those things we thought we need and now everyone adds it to their OOP language. But aside from not using hiding when I wanted to overwrite, I have never had any use for it.
Overriding is the important thing. However, overriding has to be allowed for a function in the base class that first added it.
In Effect:
//Stil work how it does, but now can be overridden
public virtual bool Execution()
{
return false;
}
In Spark:
//We are actually overriding - not just hiding - Effect.Execution() here
public override bool Execution ()
{
bool canTarget = false;
caster = GameManager.singleton.currentPlayer;
availableTargets = SpellHelper.AvailableTargets();
if (targetType == "any") //Placeholder check
{
canTarget = true;
caster.playerState = GameManager.PlayerState.targeting;
}
return canTarget;
}
You can assign a Spark to a Effect variable, call Execution() and Polymorphy will deal with calling the version of Spark.
Add anotehr Effect sub-class? As long as it also overrides Execution() it works the same.
The Effect version could be empty/turned abstract. Or be kept as a default version for all subclasses.
With hiding you would have to cast it back to Spark to get access to it's variant of the Method. Wich is just extra work with no apparent advantage.

How to pass an array to a C# method?

I have my main program which contains an array called setOfBalls[i] which stores ellipses. The ellipses have three properties:
setOfBalls[i].velocity;
setOfBalls[i].direction;
setOfBalls[i].mass;
Then I have a collision class and need to be able to access the arrays with direction, velocity and mass in the collision class called Collisions.cs.
I need to be able to detect a two body collision out of my three balls but I do not know how to get the array from main program to my collision class?
public Boolean twoBodyCollision()
{
}
Consider adding parameters to your method and send the array as an argument; e.g.
public boolean HasCollidingObjects(Ball[] listOfBalls) { // ... }
Here I assume you're using a Ball class, but it's only an example. Use your class instead.
Also, you should use C# naming conventions instead of Java's. You can check the MSDN pages for information on what these are.
To pass an object into a class, you can use a constructor:
private string[] setofBalls;
public ClassName(string[] setBalls)
{
setofBalls = setBalls;
}
public void DoSomething()
{
foreach (string ball in setofBalls) { ...... }
}
More on Object Oriented Programming with C#: https://msdn.microsoft.com/en-us/library/dd460654.aspx
You can create an instance of the class you have your method in then call if from your "main program"
public void MethodInMainProgram()
{
Collision collision = new Collision();
collision.TwoBodyCollision(setOfBalls);
}
public class Collision
{
public void TwoBodyCollision(Ellipse[] ellipses)
{
//logic that detects collisions
}
}
more info on creating instances here

Categories

Resources