Hi everyone, I’m happy to announce that my Low Poly Modular Medieval Buildings Pack is officially launching on August 25th, 2026! Wishlist now to get notified of the launch discount! Over 2,000 game-ready, modular medieval building assets , fully optimized for low-end devices and ready to use in your games or other projects. Create your own medieval houses, towers, sawmills, farms, and other buildings. Supports URP, HDRP, and Built-In render pipelines. Full trailer with much more information on launch day. This is the biggest and most thought out asset pack I’ve worked on. And I’m not proud of how long it took me to finally finish it and get the courage to release it to the public. I really hope someone finds this useful in their project/s. I worked on it for years, so you don’t have to. Have a nice day, LMHPOLY Justinas 1 post - 1 participant Read full topic
So, I’ll include the code down below as well, and if I missed anything. I had a video but because I’m a new user it won’t let me. I’m relatively new to unity. I’ve got experience with programming, but this is my first ‘game’ in 10 years. So I decided to go through a tutorial to re-familiarise myself with things, and learn the basics of the system. However, the tutorial I used was for an earlier version of Unity, and it’s possible I also made a mistake somewhere. Either way, for some reason the Dragable script, which I forgot to show in the video, doesn’t seem to detect the leftmost row in the inventory UI slots. I’ll link the files below, and all of that. It’s not a big huge thing since this is more for learning, but it’s been bugging me for two weeks now because I can’t figure out what’s causing this. Thanks for the help! ItemDragable: using UnityEngine; using UnityEngine.EventSystems; public class ItemDragable : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { Transform originalParent; CanvasGroup canvasGroup; public float minDropDistance = 2f; public float maxDropDistance = 3f; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { canvasGroup = GetComponent (); } public void OnBeginDrag(PointerEventData eventData) { originalParent = transform.parent; // Save Original Parent transform.SetParent(transform.root); //I think this means 'make item on top' canvasGroup.blocksRaycasts = false; // makes it clickable. In this case: You're already clicking. canvasGroup.alpha = 0.6f; // Semi transparrent during drag. } public void OnDrag(PointerEventData eventData) { transform.position = eventData.position; //Item moves where dragged. Could be useful for coding follower. } public void OnEndDrag(PointerEventData eventData) { canvasGroup.blocksRaycasts = true; // makes it clickable. In this case: so you can drag again. canvasGroup.alpha = 1f; Slot dropSlot = eventData.pointerEnter?.GetComponent (); // Question mark makes a variable able to be null. Useful! Slot originalSlot = originalParent.GetComponent (); if (dropSlot == null) { GameObject dropItem = eventData.pointerEnter; if (dropItem!=null) { dropSlot = dropItem.GetComponentInParent (); // Gets the item below's slot. Because something something grabbing the raycast? } } if (dropSlot != null) { if(dropSlot.currentItem != null) { dropSlot.currentItem.transform.SetParent(originalSlot.transform); originalSlot.currentItem = dropSlot.currentItem; dropSlot.currentItem.GetComponent ().anchoredPosition = Vector2.zero; // If there's an item in the slot, move the item to the previous slot. } else { originalSlot.currentItem = null; // If there isn't an item...empty old slot of item. } transform.SetParent(dropSlot.transform); dropSlot.currentItem = gameObject; //hotbarController.SetHotbarItem(saveData.InventorySaveData); // Moves the dragged item to the new slot. } else { //If where we're dropping is outside the menu, drop the item. if (!IsWithinInventory(eventData.position)) { DropItem(originalSlot); } else { transform.SetParent(originalParent); // Return to old slot. } } GetComponent ().anchoredPosition = Vector2.zero; // Center } bool IsWithinInventory(Vector2 mousePosition) { RectTransform inventoryRect = originalParent.parent.GetComponent (); return RectTransformUtility.RectangleContainsScreenPoint(inventoryRect, mousePosition); } ///throw new System.NotImplementedException(); void DropItem(Slot originalSlot) { originalSlot.currentItem = null; // Find player Transform playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; if (playerTransform == null) { Debug.LogError("Missing 'Player' tag."); return; } // Random drop position Vector2 dropOffset = Random.insideUnitCircle.normalized * Random.Range(minDropDistance, maxDropDistance); Vector2 dropPosition = (Vector2)playerTransform.position + dropOffset; //instantiate drop item and bounce GameObject dropItem = Instantiate(gameObject, dropPosition, Quaternion.identity); dropItem.GetComponent ().StartBounce(); //destroy the ui one Destroy(gameObject); } } Slot: using UnityEngine; public class Slot : MonoBehaviour { public GameObject currentItem; //The item that's held in this slot. } InventoryController: using UnityEngine; using System.Collections; using System.Collections.Generic; public class InventoryController : MonoBehaviour { private ItemDictionary itemDictionary; private ItemDragable itemdraggable; public GameObject inventoryPanel; public GameObject hotbarPanel; public GameObject slotPrefab; public int slotCount; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { //itemDictionary = FindObjectOfType (); itemDictionary = Object.FindAnyObjectByType (); // Creates the inventory slots. Removed as it's now done in SetInventoryItems. /* for (int i = 0; i (); if (i ().anchoredPosition = Vector2.zero; slot.currentItem = item; } } */ } public bool AddItem(GameObject itemPrefab) { foreach (Transform slotTransform in inventoryPanel.transform) { // For each slot in the inventory menu Slot slot = slotTransform.GetComponent (); if (slot != null && slot.currentItem == null) { GameObject newItem = Instantiate(itemPrefab, slotTransform); newItem.GetComponent ().anchoredPosition = Vector2.zero; slot.currentItem = newItem; return true; } } Debug.Log("Inventory is Full!"); return false; } public List GetInventoryItems() { List invData = new List (); foreach (Transform slotTransform in inventoryPanel.transform) { Slot slot = slotTransform.GetComponent (); if (slot.currentItem != null) { Item item = slot.currentItem.GetComponent (); invData.Add(new InventorySaveData {itemID = item.ID, slotIndex = slotTransform.GetSiblingIndex() }); } } return invData; } public void SetInventoryItem(List inventorySaveData) { // Clear the empty slots made on game start to make way for the new data. foreach (Transform child in inventoryPanel.transform) { Destroy(child.gameObject); } //Create New data slots for (int i = 0; i (); if (itemPrefab != null) { GameObject item = Instantiate(itemPrefab, slot.transform); item.GetComponent ().anchoredPosition = Vector2.zero; slot.currentItem = item; } } else { Debug.LogWarning($"Inventory amount {data.slotIndex} does not fit in slot amount."); } } } } And just in case, Item, and Item dictionary: using UnityEngine; using UnityEngine.UI; public class Item : MonoBehaviour { public int ID; public string Name; public int Amount = 1; public virtual void UseItem() { Debug.Log($"Using item {Name}."); } public virtual void Pickup() { Sprite itemIcon = GetComponent ().sprite; if (ItemPickupUIController.Instance != null) { //Stops it crashing if an item doesn't exist. ItemPickupUIController.Instance.ShowItemPickup(Name, itemIcon); } } } using System.Collections; using System.Collections.Generic; using UnityEngine; public class ItemDictionary : MonoBehaviour { public List itemPrefabs; private Dictionary itemDictionary; private void Awake() { itemDictionary = new Dictionary (); //Loads all items into the dictionary for (int i = 0; i 4 posts - 2 participants Read full topic
Introduction & Summary Hi! I am an independent sound designer and composer specializing in dark ambient soundscapes, industrial textures, and retro PS1-style horror audio. I am seeking freelance, contract, or game jam opportunities to create atmospheric audio and adaptive sound design for indie games. What Are You Looking For I am looking for contract or freelance work as a Sound Designer, Game Composer, or Audio Integrator. I am interested in horror, thriller, atmospheric, or retro/PS1-style indie projects, and I am available for per-asset work, short-term tasks, or full audio production. Areas of Expertise, Skills and Past Experiences Areas of Expertise: Seamless Ambient Loops & Audio Beds: Evolving void drones, industrial textures, lo-fi noise, and dark soundscapes inspired by classic psychological horror (Silent Hill, Cry of Fear). Custom SFX & Sound Layering: Multi-layered sound effects, metallic impacts, Foley, UI sounds, monster audio, and environmental ambiance. FMOD Studio Integration: Adaptive audio logic, parameter mapping, pitch/volume randomization to avoid repetitive sound effects, and export-ready .bank files built specifically for Unity integration. Portfolio & Audio Examples: YouTube Channel: https://www.youtube.com/@nullcatcher Industrial Horror Tense Ambient: https://www.youtube.com/watch?v=DmwCYM5N93U Dark Ambient Track: https://www.youtube.com/watch?v=dwa76feJYwo Contact Feel free to reach out via any of the following methods: Discord: nullcatcher Email: nulllcatcher@proton.me Unity Discussions PM: Yes, I accept and regularly check direct private messages here on Unity Discussions. 1 post - 1 participant Read full topic
My game has a rocket projectile that calls an explosion method I have whenever it hits a collider using OnTriggerEnter. There are certain places in any given level where a floor, wall, misc prop intersects with another or is so close that a rocket can touch both colliders at once and trigger multiple explosions. How can I make it so that only one explosion happens? 4 posts - 4 participants Read full topic
I have not been able to update my 2 week old game since 3 days after release cause the game freezes both in headset and in editor on runtime. I have updated unity editor, rebuilt library, reset project settings, and checked all the logs i could find, with nothing significant i could find. I would HIGHLY appreciate help, thank you. 3 posts - 3 participants Read full topic
Posted by Blair Harmon, Director of Product Management, Android Platform A great user experience is central to Android's mission, and delivering on that promise requires keeping devices fast, responsive, and reliable. This is why memory optimization is more critical than ever. Across the ecosystem, new devices are maintaining or even decreasing their physical memory capacity in response to memory price increases, yet users continue to expect the same seamless, high-performance app experience. In Android 17, we introduced per-app memory limits , starting with Pixel devices, to help protect the overall user experience from applications using excess memory and causing system-wide slowdowns. Over the coming year, an increasing number of manufacturers will leverage the Android per-app memory limits across their portfolio of device RAM configurations from 4GB to 16GB+ devices. If your app exceeds these limits, it will be slowed down and may be terminated. Optimizing your app's memory footprint is essential to preventing OS throttling and maintaining a seamless user experience. In this post, we’ll explore how these limits work under the hood, how to measure your memory footprint using new Android vitals metrics, and actionable steps to optimize your app or game. Understanding Memory Limits When your app exceeds its memory budget, Android takes progressive action to protect device responsiveness: zRAM Swapping: If your app reaches its allocated limit, the system forces your app's pages into zRAM (compressed RAM). While zRAM prevents immediate eviction, compressing and decompressing pages adds CPU overhead, which can result in noticeable UI jank and experience slowdowns. Process Termination: If your app continues to increase its memory usage beyond the zRAM threshold, it will be terminated by the system. To determine if your app session was impacted by these constraints in the field, you can call getDescription() within ApplicationExitInfo . If the system applied a limit, the exit reason is reported as REASON_OTHER and the description string will contain "MemoryLimiter:AnonSwap". You can also leverage trigger-based profiling using TRIGGER_TYPE_ANOMALY to automatically capture heap dumps when the memory limit is reached. To learn more about per-app memory limits and system enforcement, review the Android 17 App Memory Limits documentation . To test your application on different device configurations use the Memory Limiter adb commands . Monitoring and Diagnosing Memory Issues You can't optimize what you can't measure. Identifying memory leaks, excessive heap allocations, and Out-Of-Memory (OOM) crashes across the Android ecosystem requires leveraging complementary monitoring tools: Macro-level health with Android vitals: For broad, population-level visibility without additional overhead, Google Play Console’s Android vitals provides essential metrics like Memory Usage (Anonymous RSS + swap) and Bitmap Memory Usage . This gives you a clear snapshot of memory distribution across different process states (foreground, background, user-perceived services, and cached) and RAM class ranges, helping you spot memory outliers. Memory Limiter exits & OOM tracking with Firebase Crashlytics: To stay informed about severe memory degradation before it impacts your key metrics, Crashlytics version 20.1.0 introduces additional debug data to help you catch, prioritize, and fix Out-Of-Memory exceptions and memory limiter kills. Tracking these events alongside custom logs and key-value metadata gives you immediate context into process status when a memory failure occurs. In-field traces with ProfilingManager: For teams able to maintain a performance observability framework, the ProfilingManager API introduced in Android 15 (API level 35) allows your app to programmatically request and collect detailed memory debug artifacts such as Java heap dumps and heap profiles directly from production devices. You can also trigger heap dump captures based on specific system signals, such as TRIGGER_TYPE_OOM and TRIGGER_TYPE_ANOMALY . Read our documentation to learn more about other memory monitoring techniques. Summary & What's Next With Android broadening per-app memory limits across all RAM classes, now is the time to audit your memory footprint: Prioritize memory optimizations: Prevent your app from being impacted by app memory limits by using best practices . Monitor memory use: Monitor your app’s memory behavior to detect and resolve anomalous behavior. Optimize your game: Follow the latest guidance for games and complex multimedia apps to maximize memory savings across process states. Helpful Resources & References Android 17 Behavior Changes: App Memory Limits Android Vitals: Memory Usage (RSS + swap metric) and Bitmap Memory Usage Android Developers Blog: Prioritizing memory efficiency steps for Android 17 Developer Guide: Manage your app’s memory
Hello, I am trying to read in a JSON file that contains my dialogue for each customer in my game. I have looked at many tutorials and have gotten them to work, but when I try to change it to use nested lists, I cannot get it working. Here is a condensed version on what the JSON will look like. { { "timmy": [ "typeOfDialogue1": [ "version1": "heya1", "version2": "heya2", "version3": "heya3" ], "typeOfDialogue2": [ "version1": "heya4", "version2": "heya5", "version3": "heya6" ], "typeOfDialogue3": [ "version1": "heya7", "version2": "heya8", "version3": "heya9" ] ], "marge": [ "typeOfDialogue1": [ "version1": "hi1", "version2": "hi2", "version3": "hi3" ], "typeOfDialogue2": [ "version1": "hi4", "version2": "hi5", "version3": "hi6" ], "typeOfDialogue3": [ "version1": "hi7", "version2": "hi8", "version3": "hi9" ] ] } And here is what I currently have to read in each line of dialogue that is not working. using UnityEngine; using System; using System.Collections.Generic; public class ReadJSON : MonoBehaviour { [SerializeField] public TextAsset textJSON; [Serializable] public class Versions { public string version1; public string version2; public string version3; } [Serializable] public class TypeOfDialogue { public Versions typeOfDialogue1; public Versions typeOfDialogue2; public Versions typeOfDialogue3; } [Serializable] public class Customers { public TypeOfDialogue timmy; public TypeOfDialogue vanessa; } public Customers myCustomerList = new Customers(); void Start() { myCustomerList = JsonUtility.FromJson (textJSON.text); } } I was hoping to just be able to use this method and not have to install a JSON reader extension since I don’t really know how to do that. If that is the only option though, I will do that. Thank you for reading! 5 posts - 3 participants Read full topic
Posted by Ajesh R Pai, Developer Relations Engineer, Ulises Uriel Verduzco Diaz, Software Engineer, Tinder, and Tracy Agyemang, Product Marketing Manager Tinder is on a mission to power and inspire real connections by making meeting easy and fun for every new generation of singles. However, as their Android application codebase grew in size, so did its complexity. Prior to their latest optimization efforts, approximately 70% of the application was not optimized, carrying 17 dex files,including three dedicated just to startup. Although they had enabled R8, much of its optimization potential was blocked due to keep rules, and the team was unable to identify which specific rules were preventing optimization. To reduce startup time and decrease user-perceived Application Not Responding (ANR) errors, Tinder turned to the new R8 Configuration Analyzer to tackle these challenges. By utilizing the R8 Configuration Analyzer , Tinder successfully identified and removed unintentional optimization blockers. The results were immediate and impactful: Tinder achieved a 47% reduction in app cold starts, shrank their app download size by 28.98% (down to 61.5 MB), and reduced user-perceived ANRs by 28%. Configuration analyzer The R8 Configuration Analyzer shows R8 optimization by tracking shrinking, optimization, and obfuscation scores to show available refinement areas. It shows the broad, redundant, or obsolete keep rules, including those from external libraries so that you can analyse the keep rule impact and refine the keep rules. Key metrics shown in Configuration Analyzer include: Shrinking Score: Code percentage available for R8 shrinking. Optimization Score: Code percentage open to optimization (for example, method inlining, horizontal class merging). Obfuscation Score: Percentage of classes, methods and fields that can be renamed by R8 to decrease size. Use the analyzer to audit keep rules and their impacts: Find broad rules: Narrow the scope of package-wide rules that restrict R8 optimization, and identify the specific classes, methods, and fields excluded from shrinking, optimization, and obfuscation. Refine rules: Target only specific classes/methods requiring reflection to unlock optimization Remove redundant rules: Remove rules that match zero classes, methods, or fields in your current build. Identical rules: Identical keep rules means rules that target the same classes, fields, and methods or duplicate declarations of keep rule in same or across keep rule files. Find subsumed rules: Clean up specific rules already covered by broader configurations. Identify problematic libraries: Check the combined optimization impact of merged consumer keep rules from all libraries. R8 Configuration Analyzer report of a sample application To assist you in using the R8 Configuration Analyzer with agentic tools, we have published an R8 Analyzer skill . This skill optimizes automated development workflows by summarizing the R8 Configuration Analyzer report to display key metrics: optimization, obfuscation, and shrinking scores. It also highlights the five most impactful keep rules, giving you clear insight into what blocks code optimization. Pinpointing hidden optimization blockers Prior to integrating the R8 Configuration Analyzer, Tinder's Android app suffered from significant technical debt due to a heavily unoptimized codebase. This lack of optimization directly degraded the user experience, leading to users experiencing slow cold starts To resolve these issues, the Tinder team utilized the R8 Configuration Analyzer to comprehensively audit their R8 configuration. The analyzer showed the R8 optimization of the codebase was around 28% even with R8 full mode. With R8 Configuration Analyzer, Tinder identified that an in-house library was introducing a broad, unscoped keep rule. # Prevents optimization in all public classes along with all of their public and protected members -keep public class * { public protected *; } This "wide" rule unintentionally covered various dependencies across the entire app, preventing optimization in a large number of classes. Because the over-inclusive rule prevented runtime crashes, developers frequently missed adding new rules for new features that used reflection, allowing hidden issues to compound over time. By leveraging the insights provided by the R8 Configuration Analyzer, the team successfully traced and analyzed the specific classes affected by the broad keep rule from the library. The team immediately discovered that optimization was being blocked in larger, non-dynamically invoked classes where R8 could do optimization. Refining this specific keep rule allowed Tinder to unlock substantial optimization capabilities, untangle their legacy configurations, and drastically improve their overall optimization numbers, with R8 scores increasing from 28% to 50%, driving immediate performance gains across the application, and the Tinder team is actively working to further improve this figure. Faster Loading: The team achieved a 47% reduction on users experiencing slow cold starts of the app. Smaller Footprint: The App download size went from 86.6MB down to 61.5 MB (28.98% decrease). Improved Stability: User-perceived Application Not Responding (ANR) errors decreased from 0.35% to 0.28%, bringing them significantly closer to the peer median numbers Reduced Complexity: The total number of DEX files was cut down from 17 to 11, including just two startup files. Beyond these technical performance enhancements, the increased application optimization directly translated into tangible business growth and higher user engagement, particularly in resource-constrained markets. Regional Engagement: Countries where Low RAM devices take a huge portion of the market, presented the largest increase in engagement, and decreasing the ANR rates was key to improving engagement in this vast market. Engagement Growth: Engagement has increased 3% since the increase in app optimization. Safeguarding future performance with continuous integration Addressing code minification isn't just a one-time fix; it requires continuous vigilance. Inspired by the massive gains achieved through the R8 Configuration Analyzer, Tinder’s Android team proactively integrated optimization monitoring into their daily workflow to prevent regressions. Tinder’s team added a new job in their CI/CD pipeline to report changes in the optimization stats so everyone can see how their contribution is affecting optimization. When advising other developers considering R8 configuration integration, the team emphasizes the importance of auditing internal dependencies. While most popular third-party libraries come with well-defined rules, internal company projects that are considered "stable" might actually be introducing wide rules that negatively impact overall optimization. Key Takeaways Faced with a heavily unoptimized codebase and a high volume of DEX files, Tinder needed a way to cleanly audit their app’s minification rules. The R8 Configuration Analyzer provided the ideal tooling necessary to identify overly broad internal library rules, the classes affected by the keep rule, allowing the team to confidently optimize their codebase. As a result, Tinder successfully cut cold starts by nearly half, shrank their APK size by over 28%, and established a healthier, more performant foundation for their users, with the team actively working to further improve these numbers. How to Use R8 Configuration Analyzer The R8 Configuration Analyzer and its standalone features can be utilized based on your current Android Gradle Plugin (AGP) version: AGP 9.3 Release: The R8 Configuration Analyzer is fully integrated and released with AGP 9.3. When running an R8 release build, the report will be generated in the build/outputs/mapping/release/configanalyzer.html folder. Standalone Gradle Task: AGP 9.3 introduces a standalone Gradle task that allows you to generate the analyzer report without running a full release build, providing a much faster feedback loop when refining keep rules locally: ./gradlew :app:analyzeReleaseR8Config The report is generated at build/reports/r8/r8-config-analyzer-release.html . Usage on Older AGP Versions: If you are using a version below AGP 9.3, you do not need to migrate your entire AGP version to analyze your configuration. You can update the R8 version independently to 9.3.7-dev or higher by following the Replacing R8 in AGP instructions . To generate the report locally, run your build with the property specified: ./gradlew assembleRelease -Dcom.android.tools.r8.dumpkeepradiushtmltodirectory=<output_directory> To learn more, see the R8 Configuration Analyzer documentation.