Month: November 2014

A patch of user-requested features is available for the Dialogue System v1.3.9. It includes:

  • Improvements to the articy:draft converter. The converter now sets “Is Group” to true on Condition nodes, strips “{font-size:NN;}” from dialogue text in case you forget to turn off “Export Text Markup”, imports custom dialogue fragment fields, and adds a checkbox to use Stage Directions for Dialogue System sequences.
  • Improved PersistentPositionData component that remembers the actor’s last position in every level the actor has visited.
  • Updated Realistic FPS Prefab multi-scene example to use the new feature of PersistentPositionData.

The patch is available on the Pixel Crushers customer download site. If you need access, please contact us with your Asset Store invoice number.

 

Month: November 2014

The Dialogue System for Unity v1.3.9 has been released!

You can download it immediately on the Pixel Crushers customer download site. (Contact us with your invoice number if you need access.) It should be available on the Unity Asset Store in a few days.

The big addition is S-Inventory support. It also includes a special sub-package for S-Inventory + Realistic FPS Prefab. There’s finally a drop-in solution for dialogue + quests + inventory (+ first-person shooter)!

Version 1.3.9

  • Note: Rotorz Reorderable List has been removed, since UMA isn’t compatible with it. You can delete Dialogue System/DLLs/Rotorz in old v1.3.8 installations.
  • Note: Custom sequencer commands must now be in the PixelCrushers.DialogueSystem.SequencerCommands, PixelCrushers.DialogueSystem, or global namespace.

Core

  • Improved: In Selector and ProximitySelector, you can now specify a different Actor Transform to send OnUse.
  • Improved: Added LuaTableWrapper.Count property, support for one-dimensional arrays (as well as hashes).
  • Fixed: Dialogue entry passthrough works the same as Chat Mapper now.
  • Fixed: (Unity UI) Fixed null texture handling in subtitles.

Third Party Support

  • (Chat Mapper) Converter can now batch convert multiple Chat Mapper projects.
  • (S-Inventory) Added support, plus S-Inventory + Realistic FPS Prefab + Dialogue System integration.
  • (Action-RPG Starter Kit)
    • Fixed typo in test conversation command that prevented NPC from healing player.
    • Added DSGameOverC, and death prefab to example.
Month: November 2014

The Dialogue System for Unity v1.3.8 is now available on the customer download site and should be available on the Unity Asset Store in a few days!

 

Version 1.3.8

Core

  • Unity 5 Support
  • Added: Alert Trigger.
  • Added: Conversation-as-tutorial example.
  • Improved: Dialogue database asset fields are now reorderable in the Dialogue Editor.
  • Improved: Trigger editors (conversation, bark, etc.) now let you choose from popup menus if you don’t want to enter text directly.
  • Improved: Lua wizards now also handle Actors, Items, and Locations.
  • Changed: Lua code now runs on group nodes (dialogue entries with Is Group set True) if the group is used.
  • Fixed: Quest observers set to EveryUpdate weren’t triggering when quest states were changed through the QuestLog class.
  • Unity UI:
    • Updated to support Unity 4.6 RC1/2.
    • Added: Quest Tracker HUD.
    • Added: Option for template response buttons.
    • Changed: Unity UI portraits are now Images, not RawImages.
  • Unity GUI (Legacy):
    • Improved: Selector & ProximitySelector can now specify GUI style.

Third Party Support

  • Action-RPG Starter Kit: Improvements to DSSaveLoad.
  • Adventure Creator: Updated for AC v1.40; added ACCam() sequencer command.
  • AI for Mecanim: Added actions to get & set variables and quest states.
  • Chat Mapper: Fixed inconsistency with conversion of pipes to conform to Chat Mapper’s behaviour.
  • Cinema Director: Added support.
  • Core GameKit: CoreGameKitSynchroSpawn() now accepts min & max parameters to spawn a random number of entities.
  • plyGame: Updated for plyGame’s new item system.
Month: November 2014

The design of skill systems is a frequent topic on the Unity Scripting forum. This post describes one approach that follows the Unity philosophy of using prefabs and component-based design.

You can download the project here: Skill System Example. It includes a working example scene.

The key is to split out functionality into small, abstract pieces. MonoBehaviours are perfect for this. To make it abstract, instead of hard-coding, say, a single projectiles script with all of the different behaviors that can happen when the projectile hits, attach a simple MonoBehaviour whose only purpose is to do something on collisions. This way, your projectiles script doesn’t need to know anything about hit behavior. When the projectile hits, it just passes the message along to the hit handler. This lets you add new types of hit handlers without having to touch any code in the projectiles script. Then apply this principle to every part of a skill — how it targets, how the player triggers it, etc.

This makes the system data-driven and component-based. By data-driven, I mean that the scripts provide a general-purpose method for defining skills. But the actual skills are designed in the inspector by composing different parts (casting prerequisites, aiming, firing, etc.) and optionally saved as prefab. To add a new skill, you just use the inspector. You don’t have to modify code.

You generally want to avoid designs that require you to go back and modify existing code, since it’s easy to break existing functionality. For example, avoid enums and switch statements unless you’re absolutely sure you won’t ever add any new spells or skills. Otherwise you’ll have to go in and edit the code with those enum and switch statements. This is a very common source of bugs.

The example code in this post uses a handful of general-purpose, reusable scripts for each spell, and it uses SendMessage. Someone asked if this design has any impact on performance. In practice, no. If you’re unsure about that, just profile it. Few of the scripts have Update() methods, so they don’t do anything on a regular interval. And if you’re concerned about SendMessage (though you shouldn’t be, since it’s only called once when the character casts a spell, so it has no performance impact), you could always use C# delegates. But in this scenario it’s not worth the extra complexity.

In the example project, the player has a GameObject named Skillbar. Skills are defined as children:
[​IMG]

The skillbar and skills use scripts in these folders:
[​IMG]

Each skill has different scripts that, as a whole, define the skill’s behavior. The scripts are generic, nothing hard-coded for specific skills. The Launcher (rocket launcher) skill is below.

  • For aiming, it uses “AimCenter”, which aims at targets at the center of the screen.
  • For triggering, it uses “ClickToCast”, which casts the skill when the player mouse-clicks. This sends an OnFire message to the skill.
  • For shooting, it uses “SpawnWithVelocity”, which listens for OnFire and spawns a prefab with a velocity.

[​IMG]

The prefab that it spawns is a Rocket. The rocket has three behaviors:

  • “Temporary” despawns the rocket after 5 seconds.
  • “CollisionDamage” causes damage to whatever it hits, by sending an OnTakeDamage message.
  • “SpawnCollisionReplacement” destroys the rocket immediately and replaces it with an Explosion prefab. (The Explosion prefab also has a Temporary that despawns it after 1 second.)

[​IMG]

So you can see that none of the code is skill-specific. All skill-specific data is managed in the inspector. You can create prefabs of these skills and add and remove them as the player gains and loses abilities.

Here are some of the main scripts:

Skillbar.cs draws the skill bar and lets you select the active skill.

using UnityEngine;

public class Skillbar : MonoBehaviour {

	private Skill[] skills;

	private Skill activeSkill;

	void Start() {
		skills = GetComponentsInChildren<Skill>();
		SetActiveSkill((skills.Length > 0) ? skills[0] : null);
	}

	public void SetActiveSkill(Skill newActiveSkill) {
		activeSkill = newActiveSkill;
		foreach (var skill in skills) {
			skill.gameObject.SetActive(skill == activeSkill);
		}
	}

	void OnGUI() {
		GUILayout.BeginHorizontal();
		foreach (var skill in skills) {
			GUI.color = (skill == activeSkill) ? Color.yellow : Color.gray;
			if (GUILayout.Button(skill.name, GUILayout.Width(64), GUILayout.Height(64))) {
				SetActiveSkill(skill);
			}
		}
		GUILayout.EndHorizontal();
	}

}

Skill.cs is really just a placeholder in this example. The other scripts compose the actual functionality.

using UnityEngine;

public class Skill : MonoBehaviour {

	public GameObject target;

}

AimBase.cs provides basic targeting functionality.

using UnityEngine;

public class AimBase : MonoBehaviour {

	public LayerMask layerMask = 1;
	public Texture2D reticle;

	private Skill skill;

	void Awake() {
		skill = GetComponent<Skill>();
	}

	public virtual Ray GetAimRay() {
		return Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2f, Screen.height / 2f));
	}

	void Update() {
		RaycastHit hit;
		if (Physics.Raycast(GetAimRay(), out hit, 100f, layerMask)) {
			skill.target = hit.collider.gameObject;
		}
	}

	void OnGUI() {
		DrawReticle();
		DrawTargetName();
	}

	public virtual void DrawReticle() {
		if (reticle == null) return;
		var rect = new Rect((Screen.width - reticle.width) / 2, (Screen.height - reticle.height) / 2, reticle.width, reticle.height);
		GUI.DrawTexture(rect, reticle);
	}

	public virtual void DrawTargetName() {
		if (skill.target == null) return;
		var size = GUI.skin.label.CalcSize(new GUIContent(skill.target.name));
		var rect = new Rect((Screen.width - size.x) / 2, ((Screen.height - size.y) / 2) - 50, size.x, size.y);
		GUI.Label(rect, skill.target.name);
	}

}

AimCenter.cs is really just another name for AimBase, since AimBase by default aims in the center of the screen.

using UnityEngine;

public class AimCenter : AimBase {
}

AimCursor.cs overrides the targeting position and the reticle GUI position. This is for skills that aim where the mouse cursor is pointing.

using UnityEngine;

public class AimCursor : AimBase {
	
	public override Ray GetAimRay() {
		return Camera.main.ScreenPointToRay(Input.mousePosition);
	}

	public override void DrawReticle() {
		if (reticle == null) return;
		var rect = new Rect(Input.mousePosition.x - (reticle.width / 2), (Screen.height - Input.mousePosition.y) - (reticle.height / 2), reticle.width, reticle.height);
		GUI.DrawTexture(rect, reticle);
	}

}

ClickToCast.cs implements one way to cast the skill (that is, send “OnFire”).

using UnityEngine;

public class ClickToCast : MonoBehaviour {

	void Update() {
		if (Input.GetMouseButtonDown(0)) {
			SendMessage("OnFire");
		}
	}
}

SpawnWithVelocity.cs responds to OnFire by firing a projectile.

using UnityEngine;

public class SpawnWithVelocity : MonoBehaviour {

	public GameObject prefab;
	public GameObject origin;
	public float force = 500f;

	void OnFire() {
		var projectile = Instantiate(prefab) as GameObject;
		projectile.transform.position = origin.transform.position;
		projectile.transform.rotation = origin.transform.rotation;
		projectile.GetComponent<Rigidbody>().velocity = origin.transform.TransformDirection(Vector3.forward * force);
	}

}

If you were to add RaycastEffect.cs or SpawnAtCursor.cs instead, it would change the way the skill handles OnFire.

CollisionDamage.cs gets added to projectiles, not to skills. When it collides, it sends “OnTakeDamage”. You could make this more generic by allowing the designer to specify the message that it sends, instead of hard-coding “OnTakeDamage”.

using UnityEngine;

public class CollisionDamage : MonoBehaviour {

	public float damage = 1;

	void OnCollisionEnter(Collision collision) {
		collision.collider.SendMessage("OnTakeDamage", damage, SendMessageOptions.DontRequireReceiver);
	}

}

In the example scene, the rocket has CollisionDamage.cs and SpawnCollisionReplacement.cs, which replaces the rocket with another prefab — in the rocket’s case, an explosion. It also has Temporary.cs, which despawns the rocket after 5 seconds in case it doesn’t hit anything.

The StormGust skill uses AimCursor and SpawnAtCursor. It spawns a Storm prefab, which has a Temporary to despawn it after 5 seconds.

Creating new skills is now just a matter of creating new GameObjects (or prefabs) and assigning the right components, without having to edit any existing code. You could create an area-of-effect Heal skill using the same components as StormGust that could spawn a healing prefab instead of the Storm prefab. Defining them as prefabs lets you add and remove skills as the character gains and loses abilities simply by instantiating the skill’s prefab.