Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Prefabs with custom code

Discussion in 'Scripting' started by Noctys, Sep 8, 2020.

  1. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    I am creating a game that uses same GameObjects (sprites, sounds, background images, etc) for the story scenes as it does for the game... Think visual novel (I'm not making a visual novel, but the UI for the stories is similar).

    I already have everything setup to do what I need to do during the game, sprites go to the correct places, sounds play at the right time, etc.

    When a story plays I want to use those same commands to move everything around. If I was programming this outside of Unity I want to do is just create an IStory interface and a separate class for each story run through that class as the player hits next or makes choices, so there would be an IntroStory.cs, ScaryMonsterStory.cs, LoveStory.cs, etc..

    Lastly I also want to allow for assets to be loaded in later that would add in new story lines. Maybe player made assets, future DLC, Mods that contain story elements, etc. My understanding is that you would need to do this with either Prefabs in Asset Packs or directly importing the data in from something like a Json document. I don't think either of these will work for importing .cs files.

    How can I accomplish something like this in Unity -- custom C# scripts that are loaded into the game at runtime?
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    Any reason you think you can't do that in Unity? MonoBehaviours can have interfaces. (I would probably derive from a common intermediate class rather than use an interface, but an interface would work just as well)

    You can't import new .cs files via an AssetBundle, which is what you'd use for this. But, you can use any c# script that was in the version of the code you built the app with. If you come out with new functionality that requires actual new code, you'll have to put out a new version of the app with the code changes. So it will behoove you to write small, reusable scripts as much as possible, since these can be recombined any way you see fit when you release new content.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
  4. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    @StarManta - The only reason I wouldn't want to use interfaces, is for the same reason as your 2nd answer -- because Unity won't let me import any classes derived from those interface that were created by other people... As I am typing this though it makes me think about how it's probably not a good idea to let just anyone create new classes to be imported into my game. o_O

    @Kurt-Dekker - Thanks, I'll check out TimeLine and see if it will work for me... I am guessing not.

    What I am wanting to do it have something like a DoNext command in the class:

    DoNext()
    {
    switch(currentStep)
    {
    case 1:{
    Player.MoveTo(5,10);
    SoundPlay("Smack")'
    }
    case 2:{
    Character.Show("BadGuy");
    Dialog.Say("BadGuy", "I have you now");
    }
    }
    }


    Doing something like that through a json script sounds overly complicated, and trying to setup some type of behavior class that could become a Prefab does too. Looking for suggestions on how to handle this so users could submit asset bundles that can handle these type of stories.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    Well you've got about 3 separate Very Hard Problems(tm) going on here.

    1. you want to come up with something that does what animations and scripting (and possibly Timeline) already do. I suggest you find out exactly what they do before proceeding to design your own thing. It's at least a professional approach to understand ones competition before trying to beat them.

    2. you want to come up with a purpose-built dialog state machine running language... In the year 2020 I highly recommend you try using just plain old C# (the lingua-franca of Unity3D today) to implement some stuff, see how it goes. If you REALLY must tinker with another language, something like MoonSharp LUA might be a good starting point.

    finally 3. you want to have user-developed content... which means coming up with a system others can use, an engine that tolerates their misuse in a constructive way (gives them a helpful error message, etc.), and this third one is not even remotely an easy task.

    I suggest you start with square one and make a tiny adventure story with perhaps 3 rooms, 3 inventory items, and at least two possible outcomes: win and lose. Once you have accomplished that in plain old Unity, then you might be ready to begin tackling any one of the three above Very Hard Problems(tm).
     
  6. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    Created by other people? Yeah, that's a terrible idea. Not only does it open you up to insane stability and security vulnerabilities, it would be flat out rejected by all of the app stores (because it opens you up to insane stability and security vulnerabilities).

    Even if you were the only one coding this, a switch statement for every step of the way would be... well, really annoying. There's a ton of ways to go about doing this in a more reasonable and extensible way.

    Here's what I'd do:
    Create a "StoryAction" base class and derive all of your dialog, sound, etc stuff from that, which has an "Execute" abstract method. Create a "StoryStep" class which has an array of StoryActions, and calls Execute() on them all.
    Code (csharp):
    1. [System.Serializable] public abstract class StoryAction {
    2. public abstract void Execute();
    3. }
    4.  
    5. // in another file
    6.  
    7. [System.Serializable] public class DialogAction : StoryAction {
    8. public string characterName;
    9. public string line;
    10.  
    11. public override void Execute() {
    12. Dialog.Say(characterName, line);
    13. }
    14. }
    The derived classes should all have public members that describe their functionality, like characterName and line in the DialogAction above. Make sure you only use members composed of primitive data types here - no textures, models, etc. If you need to refer to any of those assets, store them as a string and load them by path from the AssetBundle. (You can use Resources.Load during dev, it's more straightforward & it's easy to port that code to use AssetBundles later)

    You should be able to do everything through these classes that you are currently doing in that switch statement, but these StoryActions are now in a form that can be saved into a plain old JSON file. I recommend using Newtonsoft's Json.NET here, and I believe you'll have to set it to store class type information when you save so that it can load the correct classes. (Unity's own JsonUtility doesn't support derived classes like this so it's no good)

    This should be enough to get you started on a good, sustainable path for what you're trying to accomplish.
     
    Last edited: Sep 8, 2020
    Noctys likes this.
  7. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    Thanks @StarMatra - That's some detailed advice. So let's pretend I've create this collection of DialogActions, my intro/sample story has 24 of these all together. How would you suggest I string them together?

    My initial thought is that each DialogAction has a NextAction associated with it, and when they click next it runs that NextAction -- or if there is a choice it has multiple next actions. So a DialogAction, DialogActionWithResponses

    Does that sound like the right path to go down?
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    To be clear, a StoryAction would essentially replace one of your lines of code in the sample you posted. I briefly mentioned a StoryStep class, which would contain a StoryAction[] array - that would be your main organizing unit.

    For chaining these steps together (I assume that's what you mean you have 24 of) - I'd give each StoryStep something to reference it by (probably a name, but any unique identifier works), and anytime you need to reference other StorySteps, use this name. That will prevent you from accidentally creating a StoryStep with a child that's a StoryStep with a child that's a StoryStep etc.

    Code (csharp):
    1. [System.Serializable]public class StoryStep {
    2. public StoryAction[] actions;
    3. public string identifier;
    4. public string nextStepIdentifier;
    5.  
    6. public void Execute() {
    7. foreach (var action in actions) {
    8. action.Execute();
    9. }
    10. YourStorySystem.GetStoryStepByIdentifier(nextStepIdentifier).Execute();
    11. }
    12. }
    (You probably wouldn't actually execute the next story step immediately, that's just an example)

    A StoryStep might not have a nextStepIdentifier of it own, but you may have its final StoryAction be a ProceedToStepStoryAction (which takes a StoryStep identifier as its parameter). That would allow you the freedom to create branching choices with a different StoryAction derivative and not be committed to one linear path through the story. And rather than immediately proceeding to the next Step, it would probably store that next Step somewhere where it would get activated when the user presses a button or something.
     
    Noctys likes this.
  9. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    @StarManta - Awesome, thanks so much - that is pretty much what I had setup based on your previous comment - I probably wouldn't have used the identifier/nextStepIdentifier so thanks for that.
     
  10. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    @StarManta - One more question -- I hope.

    From what I am seeing as I program all this in (and help my son with dividing fractions) there is no way to use these directly in a prefab. So if I wanted to do this straight from Unity without using Json for quick testing, or if I wanted to store the main story line in prefabs, I would need to duplicate all these classes with something like UnityStoryAction, UnityDialogAction, etc... And just have a reference to the StoryAction and Execute() directly from there.

    And later build some Story Builder application that helps with creating the Json (or just do it the old fashioned way).

    Is that correct? Am I missing something? Is there a better way? Thanks Again!
     
  11. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    Hm, the main challenge here is that Unity's serialization doesn't support inherited types, so you couldn't put "public StoryStep x;" in the inspector and be able to create StoryActions of different types. You could maybe use something like Odin Inspector from the asset store to edit inherited types in the inspector. The other option would be writing your own story editing tool. It sounds like writing your own editing tool is in the plans for the future, so it may be that that would be the next step - at least a basic editing interface.

    You could edit JSON files directly, that would certainly work, but I would definitely start off with a code-generated JSON file as your template so that you don't have to guess-and-check with the JSON syntax. So like, create one or two of these StorySteps in code:
    Code (csharp):
    1. public void GenerateJSONExample() {
    2. StoryStep[] myStorySteps = new StoryStep[2];
    3. myStorySteps[0] = new StoryStep();
    4. myStorySteps[0].actions = new StoryAction[2];
    5. myStorySteps[0].actions[0] = new DialogAction() { characterName = "Bob", line = "bob says what" };
    6. // and so on
    7. File.WriteAllText(somePath, JsonConvert.SerializeObject(myStorySteps) );
    8. }
    ...and then use that JSON file as a base for the rest of your JSON files
     
    Noctys likes this.
  12. Noctys

    Noctys

    Joined:
    Aug 23, 2013
    Posts:
    37
    Thanks again for the assistance - I was able to get everything working using the techniques you suggested.
    Decided to go with small step classes -- ShowCharacter, HideCharacter, MoveCharacter -- etc. There will be a ton of Action classes when I am all done, but it makes the Json for each of those tiny and easy to manage.
     
    StarManta likes this.