I am a beginner in Unity and I am currently making a simple game. I have a problem managing the flow of my minigame. The minigame is simply finding an object, when I found and tap on the item name below will be shaded or marked or there can be animation just to indicate that the item is found.
What I want to do is to get the name of the objects that need to be found and set them randomly in the three (3) item names below. like every time this minigame opens the names of the items are randomly placed in the 3 texts. And when the item is found the name below will be marked or shaded or anything that will indicate it is found, but for now I will just set it inactive for easier indication. How can I properly do this whole process?
The objects inside the scene are button for them to have onCLick() events
Correction: the term choices are wrong because they just display the name of the items that you need to find, just in case you get confused with the term choice in my minigame. I will fix it.
Here is the visuals for the minigame:
The script I currently have for when the objects was clicked:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
[SerializeField] Button pillowBtn, pcBtn, lampBtn;
// [SerializeField] TextMeshProUGUI choice1, choice2, choice3;
// FROM THE SOLUTION
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] List<TextMeshProUGUI> textBoxes;
// METHOD NAMES IS TEMPORARY
public void pillowClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void desktopClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
public void lampClicked()
{
Debug.Log("you found the " + EventSystem.current.currentSelectedGameObject.name);
}
}
You asked for a lot and I hope I understood your intention.
first, if you want to randomly choose an amount of game objects from your game, I think the best way to do it is by adding the refernece of all the game objects you want to choose from radomly inside a list of game objects and then randomly take a game object of the list and make it a child of another game object I call "fatherGoTranform" on my code like that:
[SerializeField] List<GameObject> gameObjectWanted;
[SerializeField] float numOfGO = 4;
[SerializeField] Transform fatherGoTranform;
void Start()
{
for(int i=0;i<numOfGO;i++)
{
int index = Random.Range(0, gameObjectWanted.Count-1);
GameObject currentGO = gameObjectWanted[index ];
currentGO.transform.parent = fatherGoTranform;
gameObjectWanted.RemoveAt(index);
}
}
and then to click on a game object and the do with what you want try this:
void Update()
{
//Check for mouse click
if (Input.GetMouseButtonDown(0))
{
RaycastHit raycastHit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out raycastHit, 100f))
{
if (raycastHit.transform != null)
{
//Our custom method.
CurrentClickedGameObject(raycastHit.transform.gameObject);
}
}
}
}
I have not checked the code so if there is an error tell me and I will fix it
This is the solution that worked for my problem. I randomly placed numbers to the list according to childCount and every index indicate the index of the text that I want to put them on which I get as a Transform child.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using TMPro;
public class ClickObject : MonoBehaviour
{
// [SerializeField] Button pillowBtn, pcBtn, lampBtn;
[SerializeField] GameObject choices, v1, finishPanel;
// RANDOM NUMBER HOLDER FOR CHECKING
private int randomNumber, foundCount;
// RANDOMIZED NUMBER LIST HOLDER
public List<int> RndmList = new List<int>();
private void Awake()
{
foundCount = 0;
RndmList = new List<int>(new int[v1.transform.childCount]);
for (int i = 0; i < v1.transform.childCount; i++)
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
while (RndmList.Contains(randomNumber))
{
randomNumber = UnityEngine.Random.Range(0, (v1.transform.childCount) + 1);
}
RndmList[i] = randomNumber;
// Debug.Log(v1.transform.GetChild(randomNumber-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text = v1.transform.GetChild(randomNumber - 1).name;
}
}
public void objectFound()
{
string objectName = EventSystem.current.currentSelectedGameObject.name;
Debug.Log("you found the " + objectName);
for (int i = 0; i < choices.transform.childCount; i++)
{
if (objectName == choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().text)
{
// Debug.Log(i);
// choices.transform.GetChild(i).gameObject.SetActive(false);
// choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
if(RndmList.Contains(i+1))
{
// Debug.Log(i);
// Debug.Log(v1.transform.GetChild(RndmList[i]-1).name);
choices.transform.GetChild(i).GetComponentInChildren<TextMeshProUGUI>().color = Color.gray;
v1.transform.GetChild(RndmList[i]-1).GetComponent<Button>().enabled = false;
foundCount++;
}
}
}
if(foundCount == v1.transform.childCount)
{
finishPanel.SetActive(true);
}
}
}
I have several cities, and when I click on on a specific city I want that the next page shows the model from that city. As each city contains alot of models, I have a seperate class for each models in the city.
List<City> cities = City.fetchAll();
for (i = 0; i < cities.length; i++) {
if (cityID == cities[i].id) {
// returns 'Paris' when paris is clicked on
String city = cities[i].name;
}
}
// Then I want to call the class TestParis and fetch the the list from that model:
final test = TestParis.fetchAll();
// or it could be another city:
final test = TestMadrid.fetchAll();
//
final test = 'Test${city}'.toClass.fetchAll();
So basically convert string to initate a class. How can I do that?
Not possible in Flutter because reflactions not available in flutter ... in dart only there is the mirror package ... but here you can only build a map with constructor functions something like that
final test = {
'TestParis' : TestParis.fetchAll
}['Test${city}'].call() ;
I want to change the order of the rows in a list that retrieves objects from the core data. Moving rows works, but the problem is that I can't save the changes. I don't know how to save the changed Index of the CoreData Object.
Here is my Code:
Core Data Class:
public class CoreItem: NSManagedObject, Identifiable{
#NSManaged public var name: String
}
extension CoreItem{
static func getAllCoreItems() -> NSFetchRequest <CoreItem> {
let request: NSFetchRequest<CoreItem> = CoreItem.fetchRequest() as! NSFetchRequest<CoreItem>
let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
request.sortDescriptors = [sortDescriptor]
return request
}
}
extension Collection where Element == CoreItem, Index == Int {
func move(set: IndexSet, to: Int, from managedObjectContext: NSManagedObjectContext) {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
List:
struct CoreItemList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: CoreItem.getAllCoreItems()) var CoreItems: FetchedResults<CoreItem>
var body: some View {
NavigationView{
List {
ForEach(CoreItems, id: \.self){
coreItem in
CoreItemRow(coreItem: coreItem)
}.onDelete {
IndexSet in let deleteItem = self.CoreItems[IndexSet.first!]
self.managedObjectContext.delete(deleteItem)
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
}
.onMove {
self.CoreItems.move(set: $0, to: $1, from: self.managedObjectContext)
}
}
.navigationBarItems(trailing: EditButton())
}.navigationViewStyle(StackNavigationViewStyle())
}
}
Thank you for help.
Caveat: the answer below is untested, although I used parallel logic in a sample project and that project seems to be working.
There's a couple parts to the answer. As Joakim Danielson says, in order to persist the user's preferred order you will need to save the order in your CoreItem class. The revised class would look like:
public class CoreItem: NSManagedObject, Identifiable{
#NSManaged public var name: String
#NSManaged public var userOrder: Int16
}
The second part is to keep the items sorted based on the userOrder attribute. On initialization the userOrder would typically default to zero so it might be useful to also sort by name within userOrder. Assuming you want to do this, then in CoreItemList code:
#FetchRequest( entity: CoreItem.entity(),
sortDescriptors:
[
NSSortDescriptor(
keyPath: \CoreItem.userOrder,
ascending: true),
NSSortDescriptor(
keyPath:\CoreItem.name,
ascending: true )
]
) var coreItems: FetchedResults<CoreItem>
The third part is that you need to tell swiftui to permit the user to revise the order of the list. As you show in your example, this is done with the onMove modifier. In that modifier you perform the actions needed to re-order the list in the user's preferred sequence. For example, you could call a convenience function called move so the modifier would read:
.onMove( perform: move )
Your move function will be passed an IndexSet and an Int. The index set contains all the items in the FetchRequestResult that are to be moved (typically that is just one item). The Int indicates the position to which they should be moved. The logic would be:
private func move( from source: IndexSet, to destination: Int)
{
// Make an array of items from fetched results
var revisedItems: [ CoreItem ] = coreItems.map{ $0 }
// change the order of the items in the array
revisedItems.move(fromOffsets: source, toOffset: destination )
// update the userOrder attribute in revisedItems to
// persist the new order. This is done in reverse order
// to minimize changes to the indices.
for reverseIndex in stride( from: revisedItems.count - 1,
through: 0,
by: -1 )
{
revisedItems[ reverseIndex ].userOrder =
Int16( reverseIndex )
}
}
Technical reminder: the items stored in revisedItems are classes (i.e., by reference), so updating these items will necessarily update the items in the fetched results. The #FetchedResults wrapper will cause your user interface to reflect the new order.
Admittedly, I'm new to SwiftUI. There is likely to be a more elegant solution!
Paul Hudson (Hacking With Swift) has quite a bit more detail. Here is a link for info on moving data in a list. Here is a link for using core data with SwiftUI (it involves deleting items in a list, but is closely analogous to the onMove logic)
Below you can find a more generic approach to this problem. The algorithm minimises the number of CoreData entities that require an update, to the contrary of the accepted answer. My solution is inspired by the following article: https://www.appsdissected.com/order-core-data-entities-maximum-speed/
First I declare a protocol as follows to use with your model struct (or class):
protocol Sortable {
var sortOrder: Int { get set }
}
As an example, assume we have a SortItem model which implements our Sortable protocol, defined as:
struct SortItem: Identifiable, Sortable {
var id = UUID()
var title = ""
var sortOrder = 0
}
We also have a simple SwiftUI View with a related ViewModel defined as (stripped down version):
struct ItemsView: View {
#ObservedObject private(set) var viewModel: ViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.items) { item in
Text(item.title)
}
.onMove(perform: viewModel.move(from:to:))
}
}
.navigationBarItems(trailing: EditButton())
}
}
extension ItemsView {
class ViewModel: ObservableObject {
#Published var items = [SortItem]()
func move(from source: IndexSet, to destination: Int) {
items.move(fromOffsets: source, toOffset: destination)
// Note: Code that updates CoreData goes here, see below
}
}
}
Before I continue to the algorithm, I want to note that the destination variable from the move function does not contain the new index when moving items down the list. Assuming that only a single item is moved, retrieving the new index (after the move is complete) can be achieved as follows:
func move(from source: IndexSet, to destination: Int) {
items.move(fromOffsets: source, toOffset: destination)
if let oldIndex = source.first, oldIndex != destination {
let newIndex = oldIndex < destination ? destination - 1 : destination
// Note: Code that updates CoreData goes here, see below
}
}
The algorithm itself is implemented as an extension to Array for the case that the Element is of the Sortable type. It consists of a recursive updateSortOrder function as well as a private helper function enclosingIndices which retrieves the indices that enclose around a certain index of the array, whilst remaining within the array bounds. The complete algorithm is as follows (explained below):
extension Array where Element: Sortable {
func updateSortOrder(around index: Int, for keyPath: WritableKeyPath<Element, Int> = \.sortOrder, spacing: Int = 32, offset: Int = 1, _ operation: #escaping (Int, Int) -> Void) {
if let enclosingIndices = enclosingIndices(around: index, offset: offset) {
if let leftIndex = enclosingIndices.first(where: { $0 != index }),
let rightIndex = enclosingIndices.last(where: { $0 != index }) {
let left = self[leftIndex][keyPath: keyPath]
let right = self[rightIndex][keyPath: keyPath]
if left != right && (right - left) % (offset * 2) == 0 {
let spacing = (right - left) / (offset * 2)
var sortOrder = left
for index in enclosingIndices.indices {
if self[index][keyPath: keyPath] != sortOrder {
operation(index, sortOrder)
}
sortOrder += spacing
}
} else {
updateSortOrder(around: index, for: keyPath, spacing: spacing, offset: offset + 1, operation)
}
}
} else {
for index in self.indices {
let sortOrder = index * spacing
if self[index][keyPath: keyPath] != sortOrder {
operation(index, sortOrder)
}
}
}
}
private func enclosingIndices(around index: Int, offset: Int) -> Range<Int>? {
guard self.count - 1 >= offset * 2 else { return nil }
var leftIndex = index - offset
var rightIndex = index + offset
while leftIndex < startIndex {
leftIndex += 1
rightIndex += 1
}
while rightIndex > endIndex - 1 {
leftIndex -= 1
rightIndex -= 1
}
return Range(leftIndex...rightIndex)
}
}
First, the enclosingIndices function. It returns an optional Range<Int>. The offset argument defines the distance for the enclosing indices left and right of the index argument. The guard ensures that the complete enclosing indices are contained within the array. Further, in case the offset goes beyond the startIndex or endIndex of the array, the enclosing indices will be shifted to the right or left, respectively. Hence, at the boundaries of the array, the index is not necessarily located in the middle of the enclosing indices.
Second, the updateSortOrder function. It requires at least the index around which the update of the sorting order should be started. This is the new index from the move function in the ViewModel. Further, the updateSortOrder expects an #escaping closure providing two integers, which will be explained below. All other arguments are optional. The keyPath is defaulted to \.sortOrder in conformance with the expectations from the protocol. However, it can be specified if the model parameter for sorting differs. The spacing argument defines the sort order spacing that is typically used. The larger this value, the more sort operations can be performed without requiring any other CoreData update except for the moved item. The offset argument should not really be touched and is used in the recursion of the function.
The function first requests the enclosingIndices. In case these are not found, which happens immediately when the array is smaller than three items or either inside one of the recursions of the updateSortOrder function when the offset is such that it would go beyond the boundaries of the array; then the sort order of all items in the array are reset in the else case. In that case, if the sortOrder differs from the items existing value, the #escaping closure is called. It's implementation will be discussed further below.
When the enclosingIndices are found, both the left and right index of the enclosing indices not being the index of the moved item are determined. With these indices known, the existing 'sort order' values for these indices are obtained through the keyPath. It is then verified if these values are not equal (which could occur if the items were added with equal sort orders in the array) as well as if a division of the difference between the sort orders and the number of enclosing indices minus the moved item would result in a non-integer value. This basically checks whether there is a place left for the moved item's potentially new sort order value within the minimum spacing of 1. If this is not the case, the enclosing indices should be expanded to the next higher offset and the algorithm run again, hence the recursive call to updateSortOrder in that case.
When all was successful, the new spacing should be determined for the items between the enclosing indices. Then all enclosing indices are looped through and each item's sorting order is compared to the potentially new sorting order. In case it changed, the #escaping closure is called. For the next item in the loop the sort order value is updated again.
This algorithm results in the minimum amount of callbacks to the #escaping closure. Since this only happens when an item's sort order really needs to be updated.
Finally, as you perhaps guessed, the actual callbacks to CoreData will be handled in the closure. With the algorithm defined, the ViewModel move function is then updated as follows:
func move(from source: IndexSet, to destination: Int) {
items.move(fromOffsets: source, toOffset: destination)
if let oldIndex = source.first, oldIndex != destination {
let newIndex = oldIndex < destination ? destination - 1 : destination
items.updateSortOrder(around: newIndex) { [weak self] (index, sortOrder) in
guard let self = self else { return }
var item = self.items[index]
item.sortOrder = sortOrder
// Note: Callback to interactor / service that updates CoreData goes here
}
}
}
Please let me know if you have any questions regarding this approach. I hope you like it.
Had a problem with Int16 and solved it by changing it to #NSManaged public var userOrder: NSNumber? and in the func: NSNumber(value: Int16( reverseIndex ))
As well I needed to add try? managedObjectContext.save() in the func to actually save the new order.
Now its working fine - thanks!
I'm not sure using a CoreData NSManagedObject for a view model object is the best approach, but if you do below is a sample for moving items in a SwiftUI List and persisting an object value based sort order.
An UndoManager is used in the event an error occurs during the move to rollback any changes.
class Note: NSManagedObject {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Note> {
return NSFetchRequest<Note>(entityName: "Note")
}
#NSManaged public var id: UUID?
#NSManaged public var orderIndex: Int64
#NSManaged public var text: String?
}
struct ContentView: View {
#Environment(\.editMode) var editMode
#Environment(\.managedObjectContext) var viewContext
#FetchRequest(sortDescriptors:
[NSSortDescriptor(key: "orderIndex", ascending: true)],
animation: .default)
private var notes: FetchedResults<Note>
var body: some View {
NavigationView {
List {
ForEach (notes) { note in
Text(note.text ?? "")
}
}
.onMove(perform: moveNotes)
}
.navigationTitle("Notes")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
}
func moveNotes(_ indexes: IndexSet, _ i: Int) {
guard
1 == indexes.count,
let from = indexes.first,
from != i
else { return }
var undo = viewContext.undoManager
var resetUndo = false
if undo == nil {
viewContext.undoManager = .init()
undo = viewContext.undoManager
resetUndo = true
}
defer {
if resetUndo {
viewContext.undoManager = nil
}
}
do {
try viewContext.performAndWait {
undo?.beginUndoGrouping()
let moving = notes[from]
if from > i { // moving up
notes[i..<from].forEach {
$0.orderIndex = $0.orderIndex + 1
}
moving.orderIndex = Int64(i)
}
if from < i { // moving down
notes[(from+1)..<i].forEach {
$0.orderIndex = $0.orderIndex - 1
}
moving.orderIndex = Int64(i)
}
undo?.endUndoGrouping()
try viewContext.save()
}
} catch {
undo?.endUndoGrouping()
viewContext.undo()
// TODO: something with the error
// set a state variable to display the error condition
fatalError(error.localizedDescription)
}
}
}
if do like this
.onMove {
self.CoreItems.move(set: $0, to: $1, from: self.managedObjectContext)
try? managedObjectContext.save()
I'm using Reflection to get all the fields of my class in c#, but now I want to get the GC Generation of each variable in my class. How can I do this?
CSkyclass
{
float time = 0;
}
Sky = new CSkyclass();
void GetGeneration()
{
FieldInfo[] FieldArray = typeof(CSkyclass).GetFields(flags);
foreach(System.Reflection.FieldInfo Field in FieldArray)
{
string name = Field.Name; //"time"
int g = GC.GetGeneration(name); //should = GC.GetGeneration(Sky.time);
}
}
Is this even possible?
Thanks
You're trying to get the generation of the field's value:
GC.GetGeneration(field.GetValue(someInstance));
I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}