HashSet of objects whose properties need to be the keys - c#

I have a Unity project in which various Cords are stretched between various Hooks. The player can grab one end of a cord from its hook, carry it, and attach it to another. As part of this functionality, the hook needs to keep track of which cords are attached to it, and when the player tries to attach a new one, check that doing so won't 'disappear' the cord (ie. they're not hooking both ends to the same place, and the cord won't merge with another by being attached to the same hooks at both ends).
Each Cord object has start and end properties that refer to the Hooks to which it's attached. Currently, I've been keeping track with a List:
public class Hook : MonoBehaviour {
List<Cord> attached = new List<Cord>();
public void Attach(Cord c) {
if (!ConflictTest(c))
attached.Add(c);
}
public void Detach(Cord c) {
attached.Remove(c);
}
bool ConflictTest(Cord toAttach) {
foreach (Cord c in attached) {
// canonically, a cord is grabbed by its 'end', so we only check its 'start'
if (c.start == toAttach.start || c.end == toAttach.start)
return true;
}
return false;
}
}
I realised that this linear search in ConflictTest(), which is actually executed at every OnMouseEnter() and OnMouseDown() isn't ideal. I would like to change the data structure of attached to a HashSet, but is there a way I can set things up so I can do the test in terms of the HashSet's Contains() method (ie. attached.Contains(toAttach.start))? Since I need to refer to the start and end properties of each member of the set, it seems I either need to a separate set for each property (but then how do I coordinate additions and removals?) or to somehow allow the properties to be keys. Any ideas?

One way to do this is to expand your List<Cord> into a Dictionary<Hook, Cord>, where the key represents the Hook end point of the value Cord that is not the current one. This Hook recorded is guaranteed to be unique since a Hook will only ever have at most one Cord validly connecting it to another Hook. Here's how this might look (extra commenting for clarity):
public class Hook : MonoBehaviour {
// Stores a Cord and its "other" Hook
Dictionary<Hook, Cord> attached = new Dictionary<Hook, Cord>();
// Add a Cord by retrieving its "other" Hook and using it as a key
public void Attach(Cord c) {
if (!ConflictTest(c)){
attached.Add(GetOtherHook(c), c);
}
}
// Remove a cord based on its "other" Hook
public void Detach(Cord c) {
attached.Remove(GetOtherHook(c));
}
// Determine whether current connections contain this Cord's "other" Hook
bool ConflictTest(Cord toAttach) {
return attached.ContainsKey(GetOtherHook(toAttach));
}
// Retrieves the "other" (non-current Hook) a cord is attached to
private Hook GetOtherHook(Cord c) {
return c.end != this ? c.end : c.start;
}
}
Hope this helps! Let me know if you have any questions.

Related

Doubst about Managin player actions and cooldown time

So i got this action object witch contains a button, when i press that button the action coroutine starts, sets the cooldown of the action and enabled=false the button.
Im trying to implement a cooldown system so every time the turn ends action.currentCooldown -=1.
Im yet to implement my turn manager and i must admit im a bit clueless about it, i guess it must have a state(allyTurn, enemyTurn) and a coroutine to update all action cooldown when the turn changes.
Also what i want to do is asing a List to each unit and then display each action buttons.
here are some screenshots of the code (keep in mind its just a first draft and im still learning the basics)
Hereis the action object
here is the code for the action
I apreciate all the help i can get
So there are a couple ways to implement what you are looking for. The way that I would want to go for is using events. You could create events for each state change (i.e. AllyTurnStart and EnemyTurnStart):
public class ExampleClass : MonoBehaviour
{
bool AllyTurn = true;
UnityEvent AllyTurnStart;
UnityEvent EnemyTurnStart;
BaseAction AllyAction;
BaseAction EnemyAction;
void Start()
{
// This will setup the event listeners that get called
if (AllyTurnStart == null)
AllyTurnStart = new UnityEvent();
if (EnemyTurnStart == null)
EnemyTurnStart = new UnityEvent();
// This is for example purposes but assigning actions to the events can happen anywhere
AllyAction = new BaseAction()
EnemyAction = new BaseAction()
AllyTurnStart.AddListener(AllyAction.StartOfTurnEvents);
EnemyTurnStart.AddListener(EnemyAction.StartOfTurnEvents);
}
//This method is meant to simulate switching turns back and forth.
void NextTurn()
{
if(AllysTurn)
{
AllyTurnStart.Invoke()
}
else
{
EnemyTurnStart.Invoke()
}
//This switches whos turn it is
AllysTurn = !AllysTurn;
}
}
public class BaseAction : Monobehaviour
{
int Cooldown;
public StartOfTurnEvents()
{
// Here you can reduce the cooldown, Check if it is ready, etc.
}
}
Hope this helps.

Is there an easy way to get out of this IF loop?

I'm learning how to program and I feel that I'm always trapped with this kind of loops problems.
the question is, what would be the best way to get out of the if when the grouped bool is always true on Update (every frame). I need to execute the EUREKA only once.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroupedWordsEyes : MonoBehaviour
{
void Update()
{
//Check if words are grouped
bool grouped = CompareTags_Word1.colliding == true;
if (grouped)
{
Debug.Log("EUREKAAAA!");
//get out of the loop!
}
}
}
It sounds like you want that Update is only called until the condition matches the first time.
You can simply disable that component by setting enabled to false.
This way Update is no longer called by Unity.
public class GroupedWordsEyes : MonoBehaviour
{
void Update()
{
//Check if words are grouped
if (CompareTags_Word1.colliding)
{
Debug.Log("EUREKAAAA!");
enabled = false;
}
}
}
It seems like you want the code within the if to execute only when "colliding" changes.
Then you need to remember the previous value and check for changes:
EDIT changed code based on comments by OP
public class GroupedWordsEyes : MonoBehaviour
{
private bool previousGrouped = false; // assume that at first there is no collision
void Update()
{
//Check if words are grouped
bool grouped = CompareTags_Word1.colliding == true;
if (grouped != previousGrouped )
{
// always remember a change
previousGrouped = grouped;
// only when just collided
if (grouped)
{
Debug.Log("EUREKAAAA!");
}
}
}
}
EDIT: Updating answer as I misunderstood the question.
You've got several options and it entirely depends on how you've architected.
One option is to deactivate (or even completely destory) the component. After you've done the one thing you need to do, call this.enabled = false - this will disable the component and no scripts will be run. This depends on architecture as this will also disable any other functions or child components attached to this one. You can mitigate this by ensuring this script only contains specific pieces that you want to be able to disable.
Create a flag that indicates whether the action has already been done.
if (grouped && !hasDoneEureka)
{
// do the thing
hasDoneEureka = true;
}
Note that this will ensure you only run what you need to once, but probably isn't optimal since while it won't "do the thing" every time, it will CHECK every frame.

Unity Editor - C#: Why does my list reset to null OnEnable?

I'm trying to make a custom unity editor and for some reason every time I close the window and open it again, my list resets to null.
I'm trying to save data from a dictionary by separating the keys and values into 2 separate lists OnDisable, and then re-creating the dictionary OnEnable by combining the lists. But every time OnEnable is called I get a null from my lists...
Here's an example of what the code looks like.
public Dictionary<string, string> myDictionary = new Dictionary<string, string>();
[SerializeField]
public List<string> listOfDictionaryKeys;
private void OnEnable()
{
// this always returns null
Debug.Log(listOfDictionaryKeys.Count);
}
private void OnDisable()
{
// this successfully saves the keys to the list
listOfDictionaryKeys = (myDictionary.Keys).ToList();
}
Does anyone have any ideas why I could be losing my list data? I'm not setting any values in an inspector, they're all being set and saved by code.
UnityEditor is a (great tool) tricky beast, that runs its own callback loop, and serialisation and deserialisation within the editor can be a bit messy.
Sometimes unity will igoner whatever constructor of an object did, and override it with default values, on the next update. I imagine that the inspector of an object initializes it when it finds null where a list is expected, but if you don't save the new serialized form with your scene, it will remain null, and will be null when OnEnable happens in the bulld (after deserialisation), unless its saved with the scene.
I do not have a full grasp of the process but that's how I imagine it.
For a quick workaround do:
private void OnEnable()
{
if (listOfDictionaryKeys==null) listOfDictionaryKeys=new List<string>();
// this always returns null
Debug.Log(listOfDictionaryKeys.Count);
}
this way you won't accidentally erase it in case it exists
I don't really understand from your question whether OnEnable and OnDisable are part of your editor script or the component itself.
If the later:
OnEnable is called when the component goes from disabled to enabled state, not when it gains focus in the inspector. The same way OnDisabled is called when the component or according GameObject is disabled, not if it loses focus.
If what you want is reacting to gaining and loosing focus in the inspector it would have to be OnEnable and OnDisable of the Inspector script itself. E.g.
[CustomEditor(typeof(XY)]
public class XYEditor : Editor
{
XY _target;
SerializedProperty list;
// Called when the class gains focus
private void OnEnable()
{
_target = (XY) target;
//Link the SerializedProperty
list = serializedObject.FindProperty("listOfDictionaryKeys");
}
public override void OnInpectorGUI()
{
//whatever your inspector does
}
// Called when the component looses focus
private void OnDisable()
{
serializedObjet.Update();
// empty list
list.ClearArray;
// Reading access to the target's fields is okey
// as long as you are sure they are set at the moment the editor is closed
foreach(var key in _target.myDoctionary.keys)
{
list.AddArrayElementAtIndex(list.arraySize);
var element = list.GetArrayElementAtIndex(list.arraySize - 1);
element.stringValue = key;
}
serialzedObject.ApplyModifiedProperties;
}
}
I also don't see where you populate the dictionary since you say it is not happening in the inspector. This might be a problem when mixonbSerializedProperty with direct access to fields.

How to notify UI about action done in API?

I'm writing a simple game, like tic tac toe (only mine is bigger). The game play is simple: my turn, check if won, bot's turn, check if won. I have simple UI and API that uses Domain Entites (that's not important now). So when user's moves, API will update the board, will know that next move is bot's move, so will do it and... has to notify UI. Here is my problem.
My question is:
How to notify UI about bot's move? I mean, to keep it simple but stick to the best programming practices.
My first thought was to create an event in GameAPI class. Is that good idea? Today will all new stuff, C# 6, etc.. I'm not sure:/ Right now UI is WinForms, but I would like to use this API in other UIs, like WPF or even mobile. Here is my simplified code of UI:
EDIT: Right now I'm talking about single player game. Both UI and API is a Client. There will be multiplayer through central server in next step, but right now, single player.
public partial class Form1 : Form
{
private GameAPI api;
public Form1()
{
InitializeComponent();
api = new GameAPI();
}
private void boardClick(object sender, EventArgs e)
{
Field field = GetClickedField(e);
MoveResult result = api.MakeMove(clickedColumn);
if (result != null && result.Row >= 0)
{
MessageBox.Show(result.Row + "," + clickedColumn);
if (result.IsConnected)
{
MessageBox.Show("Success!");
}
}
}
}
and API:
public class GameAPI
{
public IGame CurrentGame { get; set; }
public void CreateGame(GameType type)
{
CurrentGame = new SinglePlayerGame();
}
public Result Move(int column)
{
if (CurrentGame == null) return null;
Player player = CurrentGame.GetNextPlayer();
if (player.Type == PlayerType.Human) return CurrentGame.Move(column, player.Id);
}
public Result MoveBot()
{
// Simulate Bot's move...
}
}
My first thought was to create an event in GameAPI class. Is that good idea?
Yes, why not? Let take for example the modern UI frameworks data binding. The key point of making data binging work is providing a property change notification (read - event) when some property value of the object is modified. Usually that's implemented via IPropertyNotifyChanged interface, which is simply a polymorphic way of declaring support for PropertyChanged event. This way, if you set the object property via code, the UI updates automatically. Hope you see the similarity with your case - the API does something and raises an event, UI (being attached handler to that event as some earlier point) receives the event and updates accordingly.

How can I check if the clicked object is the topmost in C# XNA?

I'm trying to reproduce the simple window interface objects in C# XNA like labels, listboxes, textboxes and panels. All objects consequentially derive from basic abstract class (XNAInterfaceObject) that draws an object and updates it. In update method it interacts with a mouse and keyboard and raises various events.
The problem is when two interface objects are one over another (e.g. popup context menu over listbox) and both have non-null events - the code fires both events, when I just need the event of the topmost object. How can I check which object is the topmost? Or make somehow the top object overlap the lower. I thought about global variable which would keep the reference for the last clicked object, and other objects would check if this variable is null to proceed with their events, but I think it is a rough solution and there exists far more elegant one.
Sorry for my language, I'm not a native English-speaker.
I would probably break this issue down into two components:
Determining the order of interface objects.
Only triggering the events on the top-most object when there's an overlap.
Addressing part one is simple. Include a 'layer' field/property in the base class that specifies the depth of the object. In most game node classes I include this regardless, as it's useful in drawing. You may want a separate layering system for interface ordering if things get a bit more complex, and the downside to this approach is that you can get overlaps in which the layers are the same.
As #Attila has suggested, you can otherwise manage a Z-Ordered list of interface elements. In this case ordering is managed by index, and it's easy to manage but you can't also use this information for drawing without some additional processing and it won't be as quick as a simple value comparison.
Property
public class InterfaceComponent
{
// Class members...
private float layer;
public float Layer { get { return layer; } set { layer = Math.Abs(value); } }
public bool InFrontOf(InterfaceComponent other) { return this.Layer < other.Layer; }
}
Z-Ordered List
public class InterfaceComponent
{
private static List<InterfaceComponent> zOrder = new List<InterfaceComponent>();
// Class Members....
public InterfaceComponent()
{
// Construct class...
zOrder.Add(this);
}
private void SetZOrder(int order)
{
if (order < 0 || order >= zOrder.Count)
return;
zOrder.Remove(this);
zOrder.Insert(order, this);
// There are more efficient ways, but you get the idea.
}
public void SendBack() { SetZOrder(zOrder.indexOf(this) + 1); }
public void SendToFront() { SetZOrder(0); }
// etc...
}
Part Two
There are multiple ways to approach part two. The most obvious is to run a check against all interface components for intersection and layer property, or in the case of a Z-Ordered list, all components higher up the list (approaching 0 in my example) for intersection.
This can end up being pretty expensive, even if you use screens to make the list smaller. Instead you can manage a list of raised events and process them after you handle input. For example...
public static class InterfaceEvents
{
public static List<EventData> List = new List<EventData>();
public static void Resolve()
{
while (List.Count > 0)
{
for (int i = List.Count - 1; i > 0; i--)
{
if (List[i].EventType == List[0].EventType && List[i].Sender.Intersects(List[0].Sender))
{
if (List[i].Sender.Layer < List[0].Layer) // However you choose to manage it.
{
List[0] = List[i];
List.RemoveAt(i);
}
else
List.RemoveAt(i);
}
}
// Toggle event from List[0]
List.RemoveAt(0);
}
}
}
public struct EventData
{
public InterfaceComponent Sender;
public int EventType;
}
Anyway, those are my thoughts. It's pretty late at night, so I hope everything's remained sensible and there are no glaring mistakes.
Usually in GUI there is a list of visibility ordering (z-order) that maintains what is on top of what. Using this technique (assigning a z order to each of your component) you can check if there is anything more toward the top of a clicked component that also includes the clicked coordinates -- if there is, do not handle the click (som other component is on top, that will handle it); otherwise this component is the topmost one to handle the click
A simple solution is creating a list in your Game class:
List<XNAInterfaceObject> controls;
You can then use the order of the list for your problem. Think of the first element in your list as the control that is at the front. In the GetControlAt() method of your game, you can loop through the controls from front to back:
protected override void Update(GameTime gameTime)
{
...
MouseState ms = Mouse.GetState();
if (ms.LeftButton == ButtonState.Pressed)
{
XNAInterfaceObject control = GetControlAt(ms.X, ms.Y);
if (control != null)
control.MouseClickMethod(ms);
}
...
}
private XNAInterfaceObject GetControlAt(int x, int y)
{
for (int i = 0; i < controls.Count; i++)
{
if (controls[i].Rectangle.Contains(x, y)
{
return controls[i];
}
}
return null;
}
This means that a XNAInterfaceObject should have a Rectangle property and a MouseClickMethod(). Keep in mind that when drawing your controls, you have to loop through the list backwards.

Categories

Resources