Put Components into a list or array? - c#

how can I collect the name of all of my Script(Generic) (in a list or array?)
Like below?
public class inputdata
{
public Component com;
public int index;
public inputdata(Component newcom,int newindex)
{
com = newcom;
index = newindex;
}
}
inputdata[] data = {new inputdata(component1, 1),inputdata(component2, 1),inputdata(component3, 1)}
foreach (inputdata ft in data)
{
movefunc <ft.com> (ft.index);
}
void movefunc <T> (int index){
gameobject.GetComponent<T>()
}
it 's shows 'component1' is a type, which is not valid in the given context.
component1 is my c# script, I use it for detecting raycast.
like this
hitInfo.collider.gameObject.GetComponent <component1 > ()

It seems not clear what you are trying to achieve. In case it helps you can achive a list of the components in a gameObject like this:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class GetComponets : MonoBehaviour {
public GameObject goWithComponets; //attach in the inspector
void Start() {
getComponets().ToList().ForEach(component => {
Debug.Log(component.GetType().ToString());
});
}
private IEnumerable<Component> getComponets() {
return goWithComponets.GetComponents<Component>().ToList(); ;
}
}
Your components wont be generic, they for sure will inherit from the Component type, so in case its needed you can grab all the components from the gameObject getting all the components from the base class Component, and if you need to handle specifics types, handle that later on with some code.

Sounds to me like you don't want a generic but rather something like e.g
public class inputdata
{
public Type Type;
public int Index;
public inputdata(Type type, int index)
{
Type = type;
Index = index;
}
}
inputdata[] data = new []
{
new inputdata(typeof(component1), 1),
new inputdata(typeof(component2), 1),
new inputdata(typeof(component3), 1)
};
and then
foreach (inputdata ft in data)
{
movefunc (ft.type, ft.index);
}
and
void movefunc (Type type, int index)
{
// ... whatever happens before and where you get the hitInfo from
if(hitInfo.collider.TryGetComponent(type, out var component))
{
// component of given type exists on the hit object
// "component" will contain a reference to it in the base type "Component" -> if you need it in its actual type you will need to cast
}
else
{
// there is no component of given type on the hit object
}
}

Related

UNITY, How can I get all properties from another C# class and put them into an enum

I have two classes:
public class Stats : MonoBehaviour
{
// Primary Stats
public int strength;
public int agility;
public int intellect;
public int stamina;
public int spirit;
}
and
public class EquipmentProperties : ItemProperties
{
public Stats stats;
}
public enum Stats
{//variables from "Stats" class to be in this enum
}
I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them..
"I am trying to get all the variables from the Stats class to be in the stats enum without having to manually enter them"
Enums must be specified at compile time, you can't dynamically add enums during run-time. If you would like to stablish the fields of your enum dynamically with your class variables, guess that because the Stats class might change along the app's development, you would need to store that enum somewhere, because if not you would need to access the fields of the dynamic enum according to that generic way of setting the enumeration, in a kind of meta-programming templated way that would not make much sense.
So along with your question comes the question of how to store that enum to be used afterwards I guess. For that you can check EnumBuilder class.
Extending that example, you can build the enum according to the specific Stats class like this:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
public class Stats
{
// Primary Stats
public int strength;
public int agility;
public int intellect;
public int stamina;
public int spirit;
}
class Example
{
public static List<string> getFields(Type type) {
var propertyValues = type.GetFields();
var result = new Stats[propertyValues.Length];
var retStr = new List<string>();
for (int i = 0; i < propertyValues.Length; i++) {
retStr.Add(propertyValues[i].Name);
}
return retStr;
}
public static void Main() {
// Get the current application domain for the current thread.
AppDomain currentDomain = AppDomain.CurrentDomain;
// Create a dynamic assembly in the current application domain,
// and allow it to be executed and saved to disk.
AssemblyName aName = new AssemblyName("TempAssembly");
AssemblyBuilder ab = currentDomain.DefineDynamicAssembly(
aName, AssemblyBuilderAccess.RunAndSave);
// Define a dynamic module in "TempAssembly" assembly. For a single-
// module assembly, the module has the same name as the assembly.
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
// Define a public enumeration with the name "Elevation" and an
// underlying type of Integer.
EnumBuilder eb = mb.DefineEnum("Stats", TypeAttributes.Public, typeof(int));
int fieldCount = 0;
getProperties(typeof(Stats)).ForEach(field => {
eb.DefineLiteral(field, fieldCount);
fieldCount++;
});
// Define two members, "High" and "Low".
//eb.DefineLiteral("Low", 0);
//eb.DefineLiteral("High", 1);
// Create the type and save the assembly.
Type finished = eb.CreateType();
ab.Save(aName.Name + ".dll");
foreach (object o in Enum.GetValues(finished)) {
Console.WriteLine("{0}.{1} = {2}", finished, o, ((int)o));
}
Console.ReadLine();
}
}
Output:
Stats.strength = 0
Stats.agility = 1
Stats.intellect = 2
Stats.stamina = 3
Stats.spirit = 4
Prolog
This is of course not what you are asking directly since it is not automatic but I would suggest a Dictionary<Stats, int> and do e.g.
public class StatsComponent : MonoBehaviour
{
// Make these only assignable via the Inspector
[SerializeField] private int strength;
[SerializeField] private int agility;
[SerializeField] private int intellect;
[SerializeField] private int stamina;
[SerializeField] private int spirit;
public readonly Dictionary<Stats, int> stats = new Dictionary<Stats, int>();
private void Awake ()
{
// Initialize once with values from the Inspector
stats.Add(Stats.Strength, strength);
stats.Add(Stats.Agility, agility);
stats.Add(Stats.Intellect, intellect);
stats.Add(Stats.Stamina, stamina);
stats.Add(Stats.Spirit, spirit);
}
}
public enum Stats
{
Strength,
Agility,
Intellect,
Stamina,
Spirit
}
Of course there are ways to automatize that via reflection but I'm sure it will bring you more headaches and issues then it is solving - that's only an opinion of course.
Intermediate Solution
If you don't want to type things twice you could instead of an enum rather go by index or strings e.g. using SerializedDictionary you could simply have a
public SerializedDictionary<string, int> stats;
and fill it in the Inspector and not have your fields at all.
Enum Generator Window
However, if you still want at least automation with a minimal effort to build on top of this answer I made this a tool EditorWindow you can directly use within Unity.
Just place this script anywhere in your project.
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
public class EnumGeneratorWindow : EditorWindow
{
// This is of course optional but I thought it makes sense to filer for a specific field type
private enum FieldType
{
Int,
Float,
Bool,
// more could of course be added
}
private MonoScript sourceScript;
private MonoScript targetScript;
private FieldType fieldType;
private Type GetFieldType()
{
return fieldType switch
{
FieldType.Int => typeof(int),
FieldType.Float => typeof(float),
FieldType.Bool => typeof(bool),
// according to the enum add more cases
_ => null
};
}
[MenuItem("Window/ENUM GENERATOR")]
private static void Init()
{
var window = GetWindow<EnumGeneratorWindow>();
window.Show();
}
private void OnGUI()
{
EditorGUILayout.LabelField("ENUM GENERATOR", EditorStyles.boldLabel);
sourceScript = EditorGUILayout.ObjectField("Source", sourceScript, typeof(MonoScript), false) as MonoScript;
if (!sourceScript)
{
EditorGUILayout.HelpBox("Reference the script where to fetch the fields from", MessageType.None, true);
return;
}
var sourceType = sourceScript.GetClass();
if (sourceType == null)
{
EditorGUILayout.HelpBox("Could not get Type from source file!", MessageType.Error, true);
return;
}
targetScript = EditorGUILayout.ObjectField("Target", targetScript, typeof(MonoScript), false) as MonoScript;
if (!targetScript)
{
EditorGUILayout.HelpBox("Reference the script where write the generated enum to", MessageType.None, true);
return;
}
if (targetScript == sourceScript)
{
EditorGUILayout.HelpBox("The source and target script should probably rather not be the same file ;)", MessageType.Error, true);
return;
}
var targetType = targetScript.GetClass();
if (targetType == null)
{
EditorGUILayout.HelpBox("Could not get Type from target file!", MessageType.Error, true);
return;
}
fieldType = (FieldType)EditorGUILayout.EnumPopup("Field Type", fieldType);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
var fields = sourceType.GetFields().Where(f => f.FieldType == GetFieldType()).Select(f => f.Name).ToArray();
var fileContent = new StringBuilder("public enum ").Append(targetType.Name).Append(" { ");
for (var i = 0; i < fields.Length; i++)
{
if (i != 0)
{
fileContent.Append(", ");
}
fileContent.Append(fields[i]);
}
fileContent.Append(" }");
EditorGUILayout.LabelField(fileContent.ToString());
var color = GUI.color;
GUI.color = Color.red;
GUILayout.BeginVertical();
{
EditorGUILayout.LabelField("! DANGER ZONE !", EditorStyles.boldLabel);
EditorGUILayout.Space();
if (GUILayout.Button("GENERATE ENUM"))
{
var targetID = targetScript.GetInstanceID();
// e.g. Assets/SomeFolder/MyStats.cs
var targetAssetPath = AssetDatabase.GetAssetPath(targetID);
// just as a safety net
if (EditorUtility.DisplayDialog("Generate and overwrite with enum?", $"Attention\n\nThis will overwrite any content of {targetAssetPath} with the new content.\n\nAre you sure?", "Yes generate", "OMG NO! Cancel this!"))
{
// a bit of a hack but we need to convert the Unity asset path into a valid system path by erasing one duplicate "Assets"
var pathParts = targetAssetPath.Split('/').ToArray();
// overwrite the "Assets" with the full path to Assets
pathParts[0] = Application.dataPath;
// re-combine all path parts but this time use the according system file path separator char
var targetSystemPath = Path.Combine(pathParts);
// Write the content into the file via the normal file IO
File.WriteAllText(targetSystemPath, fileContent.ToString());
// trigger a refresh so unity re-loads and re-compiles
AssetDatabase.Refresh();
}
}
}
GUILayout.EndVertical();
GUI.color = color;
}
}
#endif
How it works:
Open the window via the header menu -> Window -> ENUM GENERATOR
Drag in your MonoBehaviour script into "Source"
Create a new empty script with the enum name you like
Drag your enum target script into "Target"
Select the type of fields we are looking for
Finally hit generate
Here is a little demo ;)
I just start of using Example.cs
public class Example : MonoBehaviour
{
public int someInt, anotherInt;
public float someFloat, anotherFloat, andOnMore;
public bool someBool, yetAnotherBool;
}
and ExampleEnum.cs
public enum ExampleEnum
{
}

c# getting and setting variables within static nested classes by their string names

So i wanted to store categories of data within my "player" class with static classes so that they are named variables and are callable by name. I recently found a solution for calling variables within a class by their string name to get and set them here: C# setting/getting the class properties by string name and i tried to call the static class via string by using "Type.GetType(pattern)": Get class by string value
I attempted to modify the object to also call the static class by a string but I am missing something because get and set doesn't work at all:
public class Player
{
//categories of variables within static classes
public static class Status { public static int health = 10, stamina = 10, mana = 10; };
public static class Immunity { public static bool fire = false, water = false, giantEnemyCrab = true; };
//paralell arrays
public string[] namelistStatus = { "health", "stamina", "mana" };
public string[] namelistImmunity = { "fire", "water", "giantEnemyCrab" };
//get and set Variables from nested classes
//('classname' is the name of the class,'propertyName' is the name of the variable)
public object this[string className, string propertyName]
{
//indirectly calls variables within static classes entirely via string
get
{
//i think the problem originates from "Type.GetType(className)" not being the correct way to call the static class by their string name
Type myType = Type.GetType(className);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
return myPropInfo.GetValue(this, null);
}
set
{
Type myType = Type.GetType(className);
PropertyInfo myPropInfo = myType.GetProperty(propertyName);
myPropInfo.SetValue(this, value, null);
}
}
//display variables
public void DisplayPlayerStatus()
{
for (int i = 0; i < namelistStatus.Length; i++)
{
Console.WriteLine(namelistStatus[i]+":"+this["Status", namelistStatus[i]]);
}
for (int i = 0; i < namelistStatus.Length; i++)
{
if (Convert.ToBoolean(this["Immunity", namelistStatus[i]]))
{
Console.WriteLine(namelistStatus[i] + " Immunity");
}
}
}
}
That was a simplification of the Player class I'm trying to organize with nested static classes, there are also some functions that are meant to set the variables in the status and immunity classes but here i only made an example function for 'getting' the nested static class variables via string but 'setting' doesn't work either.
Any suggestions of how i would be able to do this properly would be much appreciated.
Problems:
Your static inner classes are just that - nested Types inside your Player class - not properties of your Player class.
Your properties inside your static class are actually Fields.
You used the wrong name list for Immunities inside Player.DisplayPlayerStatus()
You could ( but you really should NOT , reconsider your design ) fix it like this
public class Player
{
public string[] namelistImmunity = { "fire", "water", "giantEnemyCrab" };
public string[] namelistStatus = { "health", "stamina", "mana" };
public object this[string className, string propertyName]
{
//indirectly calls variables within static classes entirely via string
get
{
var innerClass = GetType().GetNestedType(className);
var myFieldInfo = innerClass?.GetField(propertyName);
return myFieldInfo.GetValue(null);
}
set
{
var innerClass = GetType().GetNestedType(className);
var myFieldInfo = innerClass?.GetField(propertyName);
myFieldInfo?.SetValue(null, value);
}
}
public void DisplayPlayerStatus()
{
// why for and indexing - foreach is far easier
foreach (var s in namelistStatus)
Console.WriteLine(s + ":" + this["Status", s]);
// you used the wrong list of names here
foreach (var n in namelistImmunity)
if (Convert.ToBoolean(this["Immunity", n]))
Console.WriteLine(n + " Immunity");
}
public static class Immunity
{
// these are fields
public static bool fire = false,
water = false,
giantEnemyCrab = true;
};
public static class Status
{
// fields as well
public static int health = 10,
stamina = 10,
mana = 10;
};
}
Tested with:
using System;
internal class Program
{
public static void Main(string[] args)
{
Player p = new Player();
p.DisplayPlayerStatus();
Console.WriteLine();
p["Status", "health"] = 200;
p.DisplayPlayerStatus();
Console.ReadLine();
}
// the fixed stuff goes here
}
Output:
health:10
stamina:10
mana:10
giantEnemyCrab Immunity
health:200
stamina:10
mana:10
giantEnemyCrab Immunity
Your classes are currently very tighty coupled, if you add psyonicPower to your stats, you need to edit your Player, edit Player.Status, modify Player.namelistStatus.
You could autodiscover the static class fieldnames via reflection in a similar fashion like you access the field-values and get rid of both your namelistXXXX - but still, the design is bad.

Cannot add an item to a List in a constructor of a user control's base class

I seem to be getting a null reference error when I try to add an item to a list in the constructor of my CustomCheckoutProcess class. This class is inherited by a user control's class, so its default constructor is called when the control's constructor is called (I believe that's how it works).
Here's the relevant parts of the class definition for the checkout process (error seems to be line 12 - marked with a comment on line 11):
public class CustomShoppingCart : ShoppingCart {
private List<CheckoutProcessArgument> checkoutProcessArguments = List<CheckoutProcessArgument>();
public CustomShoppingCart()
{
GetBaseCartSteps();
CheckoutProcessArgument stepTwoArg = new CheckoutProcessArgument("Step2", new Guid("12345678-1234-1234-1234-123456789012"));
// when I comment this line out, everything works fine
checkoutProcessArguments.Add(stepTwoArg);
BuildCheckoutProcess();
}
private void GetBaseCartSteps()
{
baseCartSteps = new Dictionary<string, int>();
// iterate through base cart's steps and grab name/index pairs
foreach (CheckoutProcessStepInfo step in this.CheckoutProcessSteps)
{
baseCartSteps.Add(step.Name, step.StepIndex);
}
}
private bool CartContains(Guid productGuid)
{
for (int i = 0; i < ShoppingCartInfoObj.CartItems.Count; i++)
{
if (ShoppingCartInfoObj.CartItems[i].SKU.SKUGUID == productGuid)
{
return true;
}
}
return false;
}
private void BuildCheckoutProcess()
{
// create a list of ints to dynamically add or remove the indexes of the base cart's steps
List<int> steps = new List<int>();
if (checkoutProcessArguments != null)
{
// add steps if they are specified in the arguments list
foreach (CheckoutProcessArgument argument in checkoutProcessArguments)
{
if (CartContains(argument.ProductGUID))
{
int stepIndexToAdd;
baseCartSteps.TryGetValue(argument.StepName, out stepIndexToAdd);
steps.Add(stepIndexToAdd);
}
}
}
// sort the steps so that they'll be in the correct order
steps.Sort();
customSteps = new int[steps.Count];
customSteps = steps.ToArray();
}
}
And here's the struct that I'm using to create arguments for a custom checkout process:
public struct CheckoutProcessArgument
{
private string stepName;
private Guid productGUID;
public string StepName { get { return stepName; } }
public Guid ProductGUID { get { return productGUID; } }
public CheckoutProcessArgument(string stepName, Guid productGuid)
{
this.stepName = stepName;
this.productGUID = productGuid;
}
}
Here's the stack trace for the error I recieve:
Message: Object reference not set to an instance of an object.
Stack Trace:
at CustomShoppingCart..ctor()
...
Problem is, I can't seem to see what could possibly be null. Any ideas? I'm guessing the problem isn't that I'm adding something to a List in a constructor, but that I've bungled something else somewhere, but for the life of me I can't figure out what.
Try instantianting the list in the constructor immediately before you attempt to access it. Also, if the list is to not be assigned to again you can use the readonly keyword. Further note that we can instantiate the list and add stepTwoArg to it in a single line of code.
private readonly List<CheckoutProcessArgument> checkoutProcessArguments;
public CustomShoppingCart()
{
GetBaseCartSteps();
CheckoutProcessArgument stepTwoArg = new CheckoutProcessArgument("Step2", new Guid("12345678-1234-1234-1234-123456789012"));
checkoutProcessArguments = new List<CheckoutProcessArgument> { stepTwoArg };
BuildCheckoutProcess();
}

How to bind an object to a list?

This is not a windows form app, I'm not sure I'm using the correct terminology. I'm trying to bind an object to a list so when the object is modified outside of the list, those changes are reflected in the list. I'm not entirely sure how to start, my searches just keep returning "winform" answers to a datasource but this is not what I want. Here is what I have so far:
You can copy this code into a console app if you wish to test it. Notice the foreach that loops through go.Getcomponents() does not show the names because I don't think the object modified is still referenced when pulled out of the list. Essentially I'm trying to modify the object outside the list but when that object is modified the object in the list is also modified.
It is important that it can be serialized because the GameObject will be transferred across a network and data within it will be read by a server.
class Test
{
public void TestStart()
{
GameObject go = new GameObject(); //create GameObject
Dog dog = go.AddComponent<Dog>(); //Add a Dog component to the GameObject
dog.name = "Fluffy"; //name the dog fluffy, this should be reflected in the GenericComponent list of GameObject
Dog dog2 = go.AddComponent<Dog>();
dog2.name = "Fuzzy";
//loop through all dog components in GameObject go, doesn't print dog names :(
foreach (Dog dg in go.GetComponents<Dog>())
{
Console.WriteLine(dg.name);
}
Console.ReadLine();
}
}
[Serializable]
public class GameObject
{
List<GenericComponent<Object>> componentList = new List<GenericComponent<Object>>();
//returns first found in list.
public T GetComponent<T>()
{
return (T)componentList.Find(c => c.component.GetType() == typeof(T)).component;
}
//add a component to component list.
public T AddComponent<T>()
{
GenericComponent<Object> newComponent = new GenericComponent<Object>();//;(T)Activator.CreateInstance(typeof(T));
newComponent.component = (T)Activator.CreateInstance(typeof(T));
componentList.Add(newComponent);
return (T)Activator.CreateInstance(typeof(T));
}
//returns all components of type T
public List<T> GetComponents<T>()
{
List<T> r = new List<T>();
for (int i = 0; i < componentList.Count; i++)
{
if (componentList[i].component.GetType() == typeof(T))
{
r.Add((T)componentList[i].component);
}
}
return r;
}
}
[Serializable]
public class GenericComponent<T> where T : new()
{
public T component;
public GenericComponent()
{
component = new T();
}
}
[Serializable]
public class Dog
{
public string name = "";
public Dog() { }
public Dog(string name)
{
this.name = name;
}
}
In your AddComponent method, you are adding one component and then returning another one. Instead, return the same one you added:
public T AddComponent<T>()
{
GenericComponent<Object> newComponent = new GenericComponent<Object>();
newComponent.component = (T)Activator.CreateInstance(typeof(T));
componentList.Add(newComponent);
return (T)newComponent.component;
}

Get access to my derived class members

I have several classes that inhabit from this class:
public abstract class Class1
{
private string _protocol;
private static List<Plus> _class1Objects;
public string Protocol
{
get { return _protocol; }
set { _protocol = value; }
}
public static List<Plus> Class1Objects
{
get { return _class1Objects; }
set { _class1Objects = value; }
}
}
And the derive class:
public class Class2 : Plus
{
public bool name;
public int id;
}
public Webmail(string name, int id)
{
if (Class1Objects == null)
Class1Objects = new List<class1>();
.....
Class1Objects.Add(this);
}
And after my list is full of Class1Objects:
for (int i = 0; i < Class1.Class1Objects.Count; i++)
{
if (Class1.Class1Objects[i].GetType() == typeof(Class2))
}
(Class2)Class1.Class1Objects[i].
}
}
Here after (Class2)Class1.Class1Objects[i]. i cannot see my Class2 memners
You need one additional paranthese:
((Class2)Class1.Class1Objects[i]).
At the moment it is read as the following:
(Class2)(Class1.Class1Objects[i].) //<= at the '.' it is still a class1
BUT as David said in his comment: If all are of type Class2 it should be a collection of that type and if not you should check the type, altogether with foreach:
foreach(var item in Class1.Class1Objects)
{
if(item is Class2)
((Class2)Class1.Class1Objects[i]).
}
It would be cleaner to use as:
for (int i = 0; i < Class1.Class1Objects.Count; i++)
{
var c2 = Class1.Class1Objects[i] as Class2;
if (c2!=null)
}
c2.<whatever was meant to come after the .>
}
}
You might also want to consider switching to foreach unless there's a specific reason you want to manually extract each element from the List, e.g. if you're actually storing new values back into the list.
The correct syntax would be:
((Class2)Class1.Class1Objects[i]).name;
Because in your case, when you type something like this:
(Class2)Class1.Class1Objects[i].name;
You try to access the member name of Class1.Class1Objects[i], and only after that you try to cast it to Class2.
Also, the whole loop would be much simpler if you used foreach:
using System.Linq;
foreach(Class2 c in Class1.Class1Objects.OfType<Class2>())
{
Console.WriteLine(c.name); // or whatever you need to do with it
}

Categories

Resources