I have the same scrolling script attached to multiple game objects. But I want them executed in sequence. At present they were executing, I can say randomly so they aren't able to achieve synchronisation with each other in order.
So I want them executed based on each gameobject's order rather then each one randomly selected.
You can define your own UpdateManually method and call it in the appropriate order yourself. For example:
public class ScrollScriptUpdater : MonoBehaviour
{
// set references according to desired update order
// this can be done in the editor or via script if appropriate
public List<ScrollScript> ScrollScripts;
void Update ()
{
// this updates in the natural order of the list,
// list item 0, 1, 2, ...
foreach (var script in ScrollScripts)
{
script.UpdateManually();
}
}
}
public class ScrollScript : MonoBehaviour
{
public void UpdateManually()
{
// do stuff
}
}
Related
While trying to make a script for building assets, I ran into an issue with unity's serialization. I have a class in which I store some arbitrary information, which is then stored in an array in a MonoBehaviour on a prefab. I cannot for the life of me get the array to save however, as when I make the object into a prefab it loses the list's values. I have tried using [System.Serializable] and ScriptableObject, but both seem to pose their own new issues.
For instance, using ScriptableObject would mean having to save the data objects as assets, which would become way too much since these objects can get to hundreds in number.
Am I making a mistake in my understanding of unity's serialization? Is there a way to get this working without the ScriptableObject approach of saving every ArbitraryInfo object in an asset?
Data object:
[System.Serializable]
public class ArbitraryInfo{
public int intValue;
public Vector3 vectorValue;
}
OR
public class ArbitraryInfo : ScriptableObject {
public int intValue;
public Vector3 vectorValue;
void OnEnable() {
hideflags = HideFlags.HideAndDontSave;
}
}
Behaviour:
public class MyBuilder : MonoBehaviour {
public ArbitraryInfo[] infoArray;
}
Editor:
[CustomEditor(typeof(MyBuilder))]
public class MyBuilderEditor : Editor {
private SerializedProperty infoArrayProperty;
void OnLoad() {
infoArrayProperty = serializedObject.FindProperty("infoArray");
}
void OnInspectorGUI() {
serializedObject.Update();
for (var i = 0; i < infoArrayProperty.arraySize; i++) {
if (i > 0) EditorGUILayout.Space();
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
EditorGUILayout.LabelField("Info " + i, EditorStyles.boldLabel);
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
}
serializedObject.ApplyModifiedProperties();
}
}
EDIT 1, Thank you derHugo
I changed my code to incorporate the changes. Now there are errors for ArbitraryInfo not being a supported pptr value.
Secondly, ArbitraryInfo no longer being a ScriptableObject poses the question of how to initialize it. An empty object can be added to infoArrayProperty through infoArrayProperty.arraySize++, but this new empty object seems to be null in my case. This might be due to the pptr issue mentioned above.
EDIT 2
The issue I was having was caused by another piece of code where I tried to check if infoArrayProperty.objectReferenceValue == null. I changed this to another check that did the same thing and everything worked!
No, no ScriptableObject needed.
But note that GetArrayElementAtIndex(i) returns a SerializedProperty. You can not simply parse it to your target class.
so instead of
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
and
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
you have to get the info's SerializedPropertys by using FindPropertyRelative:
var info = infoArrayProperty.GetArrayElementAtIndex(i);
var intValue = info.FindPropertyRelative("intValue");
var vectorValue = info.FindPropertyRelative("vectorValue");
than you can/should use PropertyFields
EditorGUILayout.PropertyField(intValue);
EditorGUILayout.PropertyField(vectorValue);
allways try to avoid using direct setters and use those SerializedProperties instead! This provides you with Undo/Redo functionality and marking the changed Behaviour/Scene as unsaved automatically. Otherwise you would have to tak care of that manually (... don't ^^).
Okay so I am making a custom feature for my OP-Prison server, one of the things that I need to do is get an integer from the players.yml file, check if it is >= one, if it is take away one, save it and then if it is still above one then they can repeat the action untill it's 0.
The issue comes with the fact that I have to restart the server for the file to change, and even when I do, it will only go down by one integer at a time, before having to reload it again.
GUI Creation code:
Main main = Main.getPlugin(Main.class);
#SuppressWarnings("unused")
private FileControl fc;
#SuppressWarnings("unused")
private FileControl playerfc;
public static String inventoryname = Utils.chat(Main.pl.getFileControl().getConfig().getString("Backpacks.White.InventoryName"));
public List<Player> WhiteOpened = new ArrayList<>();
public static Inventory whiteBackpack(Player player) {
Inventory whiteBackpack = Bukkit.createInventory(null, 27, (inventoryname));
UUID uuid = player.getUniqueId();
whiteBackpack.setItem(10,
new ItemCreator(Material.INK_SACK).setData(8)
.setDisplayname(Utils.chat("&fCommon Packages &8ยป &f&l" + Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common")))
.getItem());
return whiteBackpack;
}
Code for updating the config + item when the Commonpackage is clicked:
#EventHandler
public void whiteBackpackInteract(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
UUID uuid = player.getUniqueId();
ItemStack clicked = event.getCurrentItem();
String title = event.getInventory().getName();
if (title.equals(inventoryname)) {
// Making it so that the item cannot be moved
event.setCancelled(true);
if (clicked != null) {
if (event.getSlot() == 10) {
// Getting the user's common packages section in the config and checking if it is greater than or equal to 1.
if (Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common") >= 1) {
// Saving the user's common package section to 'currentCommon'
Integer currentCommon = Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common");
// Taking away one from 'currentCommon' and saving it to 'newCommon'
Integer newCommon = currentCommon - 1;
// Getting the 'players.yml' file
File file = new File(main.getDataFolder(), "players.yml");
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
// Checking if the current common keys is greater than or equal to 1
if (currentCommon >= 1) {
try {
//Now, Here's where the error lies.
//Gets the player's common package count and sets it to the 'newCommon' count
config.set("Users." + uuid + ".Packages.Common", newCommon);
//Saves the players.yml file
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
// Updates the inventory they're currently in (Atleast it's meant to...)
player.updateInventory();
// Sends them a message (This is just for testing purposes, making sure it's working.)
player.sendMessage(Utils.chat("&8(&9Vexil&8) &fCommon Package"));
}
}
}
}
}
}
If there is any other code that you need, just ask I'll happily provide it for you.
Right now, you need to restart the server for it to save the data to the file. This should not happen, since you are calling the method config.save(file). The following is simply speculation, but it's the only cause that I think can easily explain what is going on.
In the object that is returned by getPlayerFile().getConfig(), there is likely a variable that stores a FileConfiguration object. That variable houses all the data from the players.yml file. In your whiteBackpackInteract() method, you load the data all over again. You then continue on to write to this NEW FileConfiguration variable, rather than the one that is stored in getPlayerfile().getConfig(). Since you then proceed to save to the file directly, the variables stored in the getPlayerfile().getConfig() is never told that you changed some values around. To fix this, you need to change the following:
config.set("Users." + uuid + ".Packages.Common", newCommon);
config.save(file);
to this:
Main.pl.getPlayerFile().getConfig().set("Users." + uuid + ".Packages.Common", newCommon);
Main.pl.getPlayerFile().getConfig().save(file);
and then delete this line of code:
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
This should solve your problem entirely. If it does not, I would recommend not using your friend's custom config API and instead just use the ones that are built in. Using third party code that you don't properly understand can very often lead to problems such as this.
The following are not the bugs, but are suggestions to help improve your code:
You should be sure to put your comments ABOVE or to the RIGHT over the code they describe. People read from top to bottom, so the comments (before I made the suggested edit to your post) were all below the code they describe.
Typically, you want to try to make sure that if code doesn't need to be run, it isn't. Since the int newCommon is not used until inside that if statement, you should move it in there.
You are using Main.getPlugin();
Now while that doesn't seem like such a bad thing, your getting an unassigned variable, I have no idea how it is working but you're assigning Main to Main. There are 2 proper ways to actually get the main class.
The first, and generally best way, is to use dependency injection.
So basically,
public class Main extends JavaPlugin {
#Override
public void onEnable() {
BackpackListener listener new Backpacklistener(this);
getServer().getPluginManager().registerEvents(listener, this);
}
}
public class BackpackListener implements Listener {
private Main instance;
private BackpackUtil util;
public BackpackListener(Main instance) {
this.instance = instance;
util = new BackpackUtil();
}
#EventHandler
public void onClick(InventoryClickEvent event) {
//code
util.whiteBackpack(instance);
}
public class BackpackUtil {
public Inventory whiteBackpack(Main instance) {
FileConfiguration config = instance.getConfig();
//Do things
instance.saveConfig();
}
}
The next way you can do it is less optimal, and frowned upon, but still an easier option.
public class Main() {
public static Main instance;
#Override
public void onEnable() {
instance = this;
}
}
public class ConfigHelper() {
Main instance = Main.instance;
FileConfiguration config = instance.getConfig();
//Do things
instance.saveConfig();
}
It's good to get out of the habit of using the second method (It's called a singleton), because normally the main class will change, or have multiple instances, etc... but with Spigot there can only be one main instance and one thread.
So, without posting my entire project in here, I will sum it up as best I can:
class Program
{
static void Main(string[] args)
{
Thing one = new Thing();
one.addTimer(10);
one.addTimer(4);
one.addTimer(2);
one.addTimer(8);
}
}
class Counter
{
private int Seconds;
private int TimerNum;
public Counter(int SecondsX)
{
Seconds = (SecondsX * 1000);
}
public void TimerCall(){
Thread.sleep(Seconds);
CounterCallBack();
}
public void CounterCallBack()
{
Console.WriteLine("Timer " + TimerNum + " Done");
//Then the time is up the call back is executed
//The issue I am having is how do I trigger the next timer for the list timers to go from hear automatically. It would send back TimerNum to Thing.Continue
}
}
class Thing
{
List<int> timers = new List<int>();
public Thing()
{
}
public void addTimer(new Timer(int SecondsToAdd))
{
timers.Add(SecondsToAdd);
}
public void StartTimers(){
timers[0].TimerCall();
}
public void Continue(int LastRun){
if(timers.count()-1>= LastRun){
timers[LastRun].TimerCall();
}
}
}
So I need to access the Continue method from counter to kick off the next timer.
Or I need to find a way to do the same thing.
However, the user needs to be able to edit, add, and remove timers (Which happens from the Program class)
Remember that in my program (this is a simplified version) Counter is a timer Call and CallBack that runs asynchronously.
Is it even possible to do? Or do I need to scrap this approach and start from square one?
Also, I know this is rough, but this a project is for charity and I plan to clean it up once I get this prototype working. Also I am 16. So please, any help you can give would be well appreciated.
Okay It's a dirty answer but I am going to Use a dictionary to store the Object variables, and have an assessor method that is passed the ID of Correct set of timers, and the Index of the next timer to run. That then calls the next timer, and so on and so fort.
Dirty but functional for a Prototype.
Looking for help on how to setup "EventListenerCustom" class instance so that I can broadcast to all objects a unique trigger has occurred. In older versions of Cocos2D-X, there was CCNotification, where I could set a flag and all my objects could check to see what the flag value was and based on whichever flag that an object reacted too would take an action.
I have tried looking for example where I could put all the movement, rotation, firing logic in one class and my other objects would take its specific action based on the flag that one class could control and change.
I would do this by creating a vector that keeps lambda functions.
class EventListenerCustom : public cocos2d::Layer
{
public:
virtual bool init();
CREATE_FUNC(EventListenerCustom);
void startFire();
void addFiringEventListener(std::function<void ()> listener);
protected:
std::vector<std::function<void()>> _firingEvents;
};
void EventListenerCustom::addFiringEventListener(std::function<void ()> listener)
{
_firingEvents.push_back(listener);
}
void EventListenerCustom::startFire()
{
for( const auto &child : _firingEvents)
child();
}
Or you can create a CustomEvent class for holding the lambda function and the target node in order to check if the target node is not null.
I have a page using StackLayoutPanel.
In that 3 stacks requires separate CellTables.
Currently events I need to use are RangeChangeEvent, SelectionChangeEvent.
Now question is how to differentiate OnRangeChangeEvent from one another.
The source table is referenced by the event that you are catching. "event.getSource()"
always gives a reference to the object that caused the event.
For example, if you have three tables, and you attach the same handler as below:
RangeChangeEvent.Handler handler = new RangeChangeEvent.Handler() {
#Override
public void onRangeChange(RangeChangeEvent event) {
if(table1 == event.getSource()){
// first table
} else if (table2 == event.getSource()){
// second table
} else if (table3 == event.getSource()){
// third table
}
};
table1.addRangeChangeHandler(handler);
table2.addRangeChangeHandler(handler);
table3.addRangeChangeHandler(handler);
The above example assumes that there is no selection model specified on the tables. If there is, the selection model will be the source of the events.
Alternatively, you can just attach a table-specific handler to each table:
RangeChangeEvent.Handler handler1 = new RangeChangeEvent.Handler() {
#Override
public void onRangeChange(RangeChangeEvent event) {
// Handle stuff happening to table 1
}
};
RangeChangeEvent.Handler handler2 = new RangeChangeEvent.Handler() {
#Override
public void onRangeChange(RangeChangeEvent event) {
// Handle stuff happening to table 2
}
};
table1.addRangeChangeHandler(handler1);
table2.addRangeChangeHandler(handler2);
// And so on for any more tables
With this approach you won't need to worry about the event source, as you already know which handler corresponds to which table.
Were you setting the same event handlers for each CellTable? If so, don't. You can simply make a different handler for each CellTable. While it would theoretically be possible to detect the source as in filip-fku's example, it won't be if you are using SelectionModel.
Bottom line: you should not try to use the same handlers on multiple objects unless you absolutely have to.