• Content Count

  • Joined

  • Last visited

Community Reputation

1,087 Excellent


About xEvilReeperx

  • Rank
    Junior Rocket Scientist

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Don't mess with the original models in GameDatabase. Instantiate a new GameObject using them instead or you'll blow things up later. I would be surprised if your first log was working; GetComponentInChildren only returns a component on active GameObjects (unless you use one of its overloads) and none of the model stuff should be active
  2. I've put together a bare-bones example that does what you want. Code: using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using KSPAssets; namespace CargoLoader { [DatabaseLoaderAttrib(new[] { "cargo" })] public class CargoLoader : DatabaseLoader<GameObject> { private readonly List<AssetBundle> _loadedBundles = new List<AssetBundle>(); private void Failed(string reason) { successful = false; Debug.LogError(reason); } public override IEnumerator Load(UrlDir.UrlFile urlFile, FileInfo file) { // load the AssetBundle var bundleRequest = AssetBundle.LoadFromFileAsync(file.FullName); yield return bundleRequest; var bundle = bundleRequest.assetBundle; if (bundle == null) { Failed("Unable to load: " + urlFile.url); yield break; } _loadedBundles.Add(bundle); // use manifest to determine URLs of models // // -> first we need to find it. It's probably named the same as the // bundle, but let's not rely on that in case it gets renamed var manifest = bundle.GetAllAssetNames().FirstOrDefault( assetName => assetName.EndsWith("_bundle.xml")); if (string.IsNullOrEmpty(manifest)) { Failed("Could not find manifest inside " + urlFile.url); foreach (var name in bundle.GetAllAssetNames()) { Debug.Log(" name: " + name); } yield break; } var manifestRequest = bundle.LoadAssetAsync<TextAsset>(manifest); yield return manifestRequest; if (manifestRequest.asset == null) { Failed("Failed to load manifest: " + manifest); yield break; } var xmlManifest = (TextAsset)manifestRequest.asset; var bundleDefinition = BundleDefinition.CreateFromText(xmlManifest.text); bundleDefinition.path = urlFile.fullPath; foreach (var def in bundleDefinition.GetAssetsWithType(typeof(GameObject))) { var assetRequest = bundle.LoadAssetAsync<GameObject>(def.name); yield return assetRequest; var asset = assetRequest.asset as GameObject; if (asset == null) { Debug.LogWarning(string.Format("Failed to load asset '{0}' from {1}", def.name, urlFile.url)); // don't quit here: maybe there are multiple assets in this bundle } // two options here: use the Unity path, or just take the name and generate // a URL using the path of the bundle. Latter option makes more sense to me // note: this means the name of the bundle is used within the part's url // example: GameData/myfolder/mybundle.cargo has two parts, part1 and part2, inside it // you would reference these parts in a config as myfolder/mybundle/part1 var name = Path.GetFileNameWithoutExtension(def.name); var url = urlFile.parent.url + "/" + urlFile.name + "/" + name; asset.name = url; asset.transform.parent = GameDatabase.Instance.transform; GameDatabase.Instance.databaseModel.Add(asset); // this next line might be a problem if you have multiple models in one bundle // unsure when and where it's used GameDatabase.Instance.databaseModelFiles.Add(urlFile); Debug.Log("Loaded " + url + " from " + urlFile.url); } } public override void CleanUp() { base.CleanUp(); foreach (var bundle in _loadedBundles) bundle.Unload(false); } } } This will load all prefab GameObjects from a KSP bundle created with PartTools (renamed with .cargo extension) and make them available for use from MODEL nodes inside part configs. It does nothing extra or fancy (no dependency resolution) but just cramming everything into one bundle is convenient and ensures resources are shared. The bundle I've provided contains two prefabs named Cube and Sphere. Cube uses a custom shader I slapped together. Sphere uses one of the shaders from PartTools. Both have a shader property driven by an animation. You refer to these in the same way that you would refer to a mu file, with the exception that the bundle is part of the path. For example, if you have the bundle at GameData/mystuff/mybundle.cargo, you would refer to the assets inside using a config file like this: PART { // ... MODEL { model = mystuff/mybundle/Sphere // ... } // ... }
  3. The game scans for all DatabaseLoaderAttrib-decorated types and uses them to load files from GameData directory. You can use this to have the game do stuff with your own files. I recommend you choose your own extension (it can be whatever you like that isn't already taken). It looks like ShadowMage needed to manipulate the materials, and uses that list to keep track of materials that have already been adjusted. My prototype didn't need to do this because I stored everything (textures and materials) inside the asset bundles themselves, so it all just works. RE your concern of multiple-named files: an advantage of asset bundles is that they can share this information already. That's why the XML definitions might be useful to you. You could put all the shared data into its own bundle, and as long as this bundle is loaded, textures and other assets will be shared. There isn't any technical reason you couldn't load lots of models or whatever else at a time from the bundle. That would definitely be the simplest, most straight-forward way to handle shared dependencies though: throw all your stuff into one, single file.
  4. Did you use the code from Shadowmage's post? In that version, the file extension should be 'smf' (although you should probably choose your own extension and edit the code accordingly). You also don't need to use the KSP asset compiler, although it shouldn't be a problem if you do
  5. You don't put the .mu model into the bundle. You put the actual GameObject that you've set up (as a prefab) in there instead, bypassing the .mu creation step entirely. This asset bundle is essentially a mu file now, except it uses Unity's serialization instead of Squad's hand-rolled stuff that is missing lots of features. Everything else behaves exactly the same as if you were using a mu file -- define your cfg in exactly the same way. By the time the PartLoader is creating parts out of the loaded models, all of that stuff has been run and shaders are in-place.
  6. That method will work (for your shader problem). What exactly is the issue? The Shader.Find API apparently requires that the shaders exist at project build time, which is why they aren't found when the PartLoader is creating materials. That's why I suggest you bypass PartLoader entirely. If you put your shader AND model into an asset bundle and then use the previously posted code to load that as though it was a model, you won't be restricted to the extremely limited stuff PartLoader does. The shaders are all in squadcore.ksp. squadcorefx contains TextMeshPro shaders. You can inspect these using the Asset Bundle Browser, a free Unity asset you can download from their store
  7. You shouldn't even need a loader for the shader. Make sure it's stored inside the AssetBundle itself and Unity should do the rest
  8. I haven't been up with the latest stuff so I'm not sure if it's still functional, but the limitations of the mu loader are exactly why I loaded certain assets as asset bundles instead. @Shadowmage has a version posted, in fact: If you make sure your textures are included in the bundle, you shouldn't even need any post-loading stuff
  9. It doesn't matter for the Canvas' children, but it does for the Canvas itself. Also, the Canvas appears to copy the camera's transform and rotation which means the position and orientation of the Image you're adding are relevant. Here's a fixed version: private void Start() { GameObject canvasGO = new GameObject("censorcanvas"); canvasGO.layer = 0; // must be in camera's culling mask canvasGO.AddComponent<RectTransform>(); canvas = canvasGO.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceCamera; // UI elements do not show up if we use the local space camera Camera flightCam = FlightCamera.fetch.mainCamera; canvas.worldCamera = flightCam; canvas.planeDistance = flightCam.nearClipPlane + 0.5f; canvas.pixelPerfect = true; CanvasScaler canvasScalar = canvasGO.AddComponent<CanvasScaler>(); canvasScalar.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize; canvasGO.AddComponent<GraphicRaycaster>(); GameObject panelGO = new GameObject("censorbar"); panelGO.layer = LayerMask.NameToLayer("UI"); panel = panelGO.AddComponent<RectTransform>(); panelGO.transform.SetParent(canvasGO.transform, false); // gets moved Image censorbar = panelGO.AddComponent<Image>(); Texture2D tex = GameDatabase.Instance.GetTexture("Harm/harm", false); censorbar.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); panel.anchorMin = new Vector2(0, 0); panel.anchorMax = new Vector2(0, 0); panel.pivot = new Vector2(0.5f, 0.5f); panel.sizeDelta = new Vector2(tex.width, tex.height); } private void LateUpdate() { // this is the nearer of the two local space cameras Camera cam = FlightCamera.fetch.mainCamera; // make image track kerbal position on screen Vector2 screenPoint = (Vector2)cam.WorldToScreenPoint(transform.position); panel.anchoredPosition = screenPoint; // eventual goal is to also put image just in front of kerbal so it gets occluded by other closer objects // canvas.planeDistance = Vector3.Dot((transform.position - cam.transform.position), cam.transform.forward) - 0.5; canvas.planeDistance = Vector3.Distance(transform.position, cam.transform.position); }
  10. But why? Here's what I meant: private class JellyListener : MonoBehaviour { private JellyfishDishMk2 _jellydish; // your PartModule here private void Awake() { _jellydish = GetComponentInParent<JellyfishDishMk2>(); if (_jellydish == null) Debug.LogError("Didn't find expected Jellyfish PM"); } private void FirstAnimationFrame(UnityEngine.Object param) { var pm = param as JellyfishDishMk2; if (pm != null) pm.PlayStartingSound(); } // alternative method: note the lack of argument private void LastAnimationFrame() { _jellydish.PlayEndingSound(); } } // add events at beginning and end jellyfish.clip.AddEvent(new AnimationEvent() { time = 0f, functionName = "FirstAnimationFrame", objectReferenceParameter = this // note that any UnityEngine.Object works here: we could pass a sound, texture, ScriptableObject, MonoBehaviour, ... }); jellyfish.clip.AddEvent(new AnimationEvent() { time = jellyfish.length, functionName = "LastAnimationFrame" // note didn't use objectRef here, see listener method });
  11. While you could do that (any UnityEngine.Object or derived class ... like PartModule or MonoBehaviour), you don't need to. You already know exactly where your data is. You could cache the PartModule or wherever your data is being kept in whatever script you have as an event listener and call methods directly
  12. This kind of error can be a real PITA to debug, but in this case it's correct. The specific problem (at least, in my test bench of just downloading KAS 1.2 + current version of KOS) is that SaveUpgradePipeline.KAS.PatchFilesProcessor fails to load. This happens because it doesn't implement an abstract method. Between 1.6 and 1.7, UpgradeScript.OnUpgrade's signature changed. I didn't check that other mod you mentioned but I imagine something similar is going on. I don't think there's a 1.7 version of KAS yet or else this would be caught already
  13. Are you 100% sure it exported successfully? That looks like it might be the result of an incomplete model. Delete, re-export, then examine Unity console for any exceptions.
  14. You don't need ref or out here. Just use isDragging directly in the lambda functions; C# will create a closure over it and you effectively have a reference to it (although secretly the compiler creates a private inner class which is how it pulls this off). By the way, you probably don't want to be inserting triggers in OnUpdate(): every time the slider value changes, you add another set of triggers
  15. Something sounds funny in your explanation. If you want everything to stay in one function, what are you outputting to? Why does the float need to be passed by reference? Changing it won't make any difference, you'll want to call a method on the slider itself to change it anyways or you might break state stuff. Assuming "which" slider is the problem (and you REALLY wanted to keep it all in one function [why?]), you could always do something like this: private enum WhichSlider // or however you want to identify it, right down to calling different functions { First, Second, Third } // embed which slider is clicked into another anon function private void Foo() { DialogGUISlider slider1; // = whatever DialogGUISlider slider2; DialogGUISlider slider3; // can make an inner func for logic... Action<float, WhichSlider> lambdaSliderChanged = (f, which) => { print("Slider " + which + " is now " + f); }; slider1.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.First); }); slider2.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.Second); }); slider3.slider.onValueChanged.AddListener(val => { lambdaSliderChanged(val, WhichSlider.Third); }); }