Creating Plugin-Based Game Code - Sept 6th - Live From Epic HQ

I never tried it but I think you can just build it and delete the Source folder because the Plugin has all the binaries when you build it.

Run the automation tool as follows to build your plugin for distribution (RunUAT is a batch file in Engine/Build/BatchFiles):



RunUAT BuildPlugin -Plugin="<full path to your .uplugin>" -TargetPlatforms=Win64+Android+Whatever -Package="<full path to where you want the output>" -Rocket


Then you can delete the source folder from the output and distribute the rest. But, as far as I’m aware there is a longstanding issue that prevents binary-only plugins from working as a project level plugin in packaged builds. So if you want to distribute it to people and your plugin gets linked in to a packaged build (ie. it’s not an editor-only plugin) then you’ll need to tell people to install it to the Engine/Plugins directory, as opposed to their project Plugins directory.

For those who watched the stream, there were two issues that came up where we sort of glossed over them to keep things moving rather than diving in and debugging like we would if we weren’t broadcasting. Both of those will be addressed in the uploaded version of the project, but I thought I would mention them here:

  1. The compilation failure with “SM_State.h” not being found. This was a simple matter of needing the “StateMachine” module added to the “AdditionalDependencies” array in PluginDevelopment.uproject. The relevant chunk of the file should now look like this:


 "AdditionalDependencies": 
   "StateMachine",
   "Engine"
 ]

  1. The function that “looked wrong” on the stream did have some buggy code, but it was nothing that affected the use case we demonstrated. A simplified (and corrected) version of that function looks like this:

USM_State* USM_Branch::TryBranch(const UObject* RefObject, 
	const TArray<USM_InputAtom*>& DataSource, int32 DataIndex, int32 &OutDataIndex)
{
	OutDataIndex = DataIndex + 1;
	if (DataSource.IsValidIndex(DataIndex) && AcceptableInputs.Contains(DataSource[DataIndex]))
	{
		return bReverseInputTest ? nullptr : DestinationState;
	}
	return bReverseInputTest ? DestinationState : nullptr;
}

The previous check for an empty set of “AcceptableInputs” was not needed or correct. The OutDataIndex variable is now always advanced, regardless of whether the AcceptableInputs test passes. This is made necessary because of the presence of bReverseInputTest, meaning that failing the input test can still cause the branch to be taken, and thus input needs to be consumed in all cases.

Hey great presentation guys.

I just wanted to add that contrary to what was said in the video, Finite Automata do not necessarily have a guaranteed acceptance state for any arbitrary input sequence. Repeating states with cycles of different sizes may be perfectly valid (i.e. one may want a FA for its side effects only). The “Finite” term there refers to the number of states in the machine. What guarantees you termination is the general definition for Deterministic FAs which requires a non-empty set of acceptance states.

And without touching the proof by contradiction the most simple intuition behind the halting problem is not that you may trick the analyzer into returning the wrong result but that any analyzer would have to be able to evaluate all possible states and transitions of a program to determine if it would ever come to an end which is basically the same as to say the analyzer would have to run the original program itself. Since the hypothesis is that the original program may never end we get stuck back where we started…

Thanks, glad you enjoyed it!

That’s definitely true, and I hope I didn’t say that. Having a guaranteed acceptance state for any arbitrary input would mean that we can only build “yes” machines, using strict DFA construction rules.

Here, I differ a little bit here in my understanding of DFAs. There are two departures I have from this:

  1. FAs, as I understand them, require the set of acceptance states to be a subset of all states. But the empty set is a subset of every set, so this is OK as far as computer science theory goes. However, doing that means we made a “no” machine, which is as useless as a “yes” machine, so we probably shouldn’t do that in an engineering/game development context.
  2. I would say that what guarantees termination are the following rules: every transition in a DFA consumes one character from the (finite-length) input string, no state or transition can add characters back into the input string, the machine terminates immediately upon running out of input. This also lets me know that, given an input string of N length, the machine will definitely terminate within N steps.

I may have been wrong (or unclear) about some of what I said about NFAs, though. NFAs are equivalent to DFAs in terms of what they can do (any NFA can be converted to a DFA; any DFA technically is already an NFA) and therefore the same tasks can be performed by either. The reason we steered away from NFAs is that, although NFAs can often be written with fewer total nodes/transitions than equivalent DFAs, writing code to evaluate them can be more complex. I hope that’s what came across during the stream.

I think you make a good point here, so thanks for making it! That’s true, I did gloss over that in the stream. I think you’re right that the most naive approach would be to construct a “watcher” that simply observes the machine being tested, and this “watcher” would fail to detect halts for just the reason you said. The second step would be to construct an “analyzer”, which is subject to being “tricked” as I said. I just skipped right to the “analyzer” and for a first-time introduction to state machines (some viewers may have been new to the concept), walking through the “watcher” scenario first would have been better. It’s interesting to see the different approaches people take to these kinds of questions, and I like how clearly you laid out that part of the problem. Thanks for contributing your explanation!

Anyway, it’s been a while since I just discussed raw CS theory stuff outside of “how will this make a game project better”, so thank you again for bringing it up. I hope the community benefits from this kind of discussion and maybe some people explore and apply different areas of computer science to their game development work.

[MENTION=2247][/MENTION]: Would you be able to address the questions I posted above, or pass them onto someone? It’s nigh on impossible to get answers on technical questions from Epic staff (with a couple of exceptions) if they’re not part of a bug report, so a related stream is about the only I have.

It seems Q1. has been shown to be the case, but I’d be interested in why it’s this way. I think a lot of people would be surprised to discover their project is packaging, for example, Paper2D related code, if they never did anything to explicitly enable it.

Hi, kamrann. I’m not an expert in that area, and I’m not actually sure who is, but I’ll ask around for you.

Thanks , that would be appreciated.

  1. Yes. Though you can black list / disable the enabled by default plugins to not have them linked. Some plugins don’t get linked in shipping builds, e.g. Developer Plugins.
  2. Maybe, it’s on the list of important things to solve with plugins; as is making it possible for plugins to have a Shader directory.
  3. Because of the large engine header files. On the build machines we share PCHs to avoid the obj being stuffed into each lib, is my understanding. If you were building the whole engine from source & the plugins you should also see a similar size.

Thanks for the answers Nick.

With number 3, I don’t really understand why the libs would need to be so big when built independently - presumably this stuff ends up getting dropped when linking anyway, rather than duplicated in the exe. But I won’t pretend to have any idea what I’m talking about. Cheers.

Hi,

Thank you for giving us the ability to make plugins more easily than it was following the existing tutorials. They’re kind of incomplete and confusing (though fully utilized and appreciated).

First, I was wondering about the other plugin options (in the wizard). There are a number of different plugin types, but I am interested in the one that links-in external libs. Right now, I link my lib in through an engine module, but I’d really like to be able to do it on the project level. I created a plugin of this type and it spat out a bunch of stuff that confuses me. I was hoping that there was some information on how to take the example library and substitute my own. Additionally, I’d like to know more about the “Is Engine Plugin” option.

Second, I was hoping to be able to provide two sets of c++ base classes. The two plugins (or sub-modules) would define the same classes and then the developer would decide which one they were using in their project. The two would be mutually exclusive, of course, so the same project could be built with either of my plugins being active. I tried doing this but UAT doesn’t like the same class being defined twice – even though only one of the plugins is enabled. This sounds a bit like the comments about plugins being included in packaging even though they aren’t used/enabled. I was hoping that someone might have some ideas or that this capability was added in the future.

Thanks again,

i have a simple question about adding cpp file to plugin using editor
as in the vedio,there is a option like this:

and in my project,it’s like this:

so,whit’s the tick to set up these thing.
now,i just using editor add file to my project and then manually copy file to the plugin diretory,and it’s very very unconvienent and easy to make things wrong!
please help~~~

I just wanted to post my work-around. First, to be clear, I am trying to get the Client and Server to build with different implementations of the same classes. Here’s what I did.

I put both sets of classes in the same plugin. In the plugin ModuleRules class (*.Build.cs), I added a check of Target.Type. If it is either TargetRules.TargetType.Game or TargetRules.TargetType.Client, I call Definitions.Add() with a preprocessor definition. If not, I define a different one. Then I wrap all my classes with #ifdef or #ifndef statements that look for those defines.

It works well enough, but it is still desirable that the build tools can’t even see the code of plugins which are disabled.

Cheers,

Are you using 4.13? If so then it appears that the plugin was not set up correctly.

Cheers,

oh,i was just using 4.12.
i update to 4.13 and check it today! and it works in 4.13!
i dont kown it is a new feature in 4.13.
anyway,thanks a lot!

So this has nothing to do with the plug-in but what if you did want to have a quest like kill 50 wolves? I mean, I guess I could have a state for each wolf killed but that just seems silly

Yes, making a quest with fifty “Killed A Wolf” states would not be the best way to do it. Especially if there’s anything else you can do during the wolf-killing part. What I would probably do is make a small subsystem for detecting repeated actions. So I might make a “Repeated Action Counter” class that looks like this (rough outline):


// This is off the top of my head, it might not even compile and definitely isn't complete.
class URepeatedActionCounter : public UDataAsset
{
GENERATED_BODY()
protected:
/** This is the InputAtom that increases the counter. */
UPROPERTY(EditAnywhere)
USM_InputAtom* Action;

/** This is the amount of times Action must take place to cause Result. Every time the correct InputAtom is given, this value decreases by 1. */
UPROPERTY(EditAnywhere)
int32 ActionsRemaining;

/** This is the InputAtom that results when Action has happened ActionsRemaining times. */
UPROPERTY(EditAnywhere)
USM_InputAtom* Result;

public:
// Possibly add something here to turn this on or off, like a bool member variable or a virtual bool function.

// A virtual function that receives InputAtoms. If the InputAtom given matches Action, ActionsRemaining is decreased by 1. When ActionsRemaining hits 0, this function returns the Result InputAtom.
};

So that’s the class, then I’d have an array of these in the PlayerController or GameInstance or wherever you decide to put your quest stuff. Go to whenever a new InputAtom gets added and you loop through adding that InputAtom to all your quests. Wherever that takes place, you can loop through all RACs and add any InputAtoms they give back to you onto the end of your input stream.

You might also want to consider a base class for this as you go on, depending on how complex you want to get. For example, this is fine for things that happen one at a time, like “kill fifty wolves”, but not great for things that happen in large quantities, like “gain 5000 experience points” (you don’t want to add 100 “got an experience” atoms for every 100-point monster you kill), and unusable for things that can go up or down during the quest, like “turn a 10,000 gold profit at the mining outpost” where the miners and soldiers you hire to extract and defend the gold are paid with that very same gold (and thus your “profit” value rises and falls constantly). So your function that takes InputAtoms would probably also want an int32 quantity parameter to accommodate that.

Or, maybe you have a “kill fifty wolves” quest, but your game actually has “regular wolf”, a “dire wolf”, and a “werewolf”, and rather than making all wolves give you two input atoms on death (a “killed any wolf” atom, plus another atom for the specific type of wolf), you would rather just make your quest accept each acceptable type of specific wolf atom. In that case, the Action member variable should be a TArray<USM_InputAtom*>. OK, now let’s say dire wolves and werewolves count double. Now that TArray<USM_InputAtom*> should be a TArray<FInputAtomCredit>, where FInputAtomCredit is a USTRUCT (you could also make another UDataAsset subclass for this) that designates a set of atoms (TArray<USM_InputAtom*>, as before) and also says how many points those atoms give (int32). So now you’d have two entries, one with just the regular wold atom at 1 point, and one with the dire wolf and werewolf atoms at 2 points.

As you can see, the functionality here can expand based on what your game needs. This is what makes a good argument for having a versatile, flexible base class. If you want to see how to make a base class retroactively and without ruining your existing functionality (AKA backwards compatible), watch the third stream in this series, where I do exactly that as I expand to fighting game input processing, changing how the state machine works, but still keep things in working order for the Quest game.

I hope that helps to explain how you’d use this system in a few more cases. Thanks for your question!

Thanks bro, i’ll try that out

So after correcting my code in Quest.h and Quest.cpp I successfully build solution but I did not get the “KnightsQuest” item?

Hey guys,

First off, I really enjoyed the content and found it very informative. Keep it up.

I would suggest, however, that in the future you start with a working project and go through it function for function rather than copying and pasting from a word doc. This would end the endless amount of frustration that I endured and probably many others when after 2 hours the final bit of code wouldn’t compile. Additionally it should go a bit faster allowing for more content or discussion on theory.