This page is under contruction.
This tutorial is meant to demonstrate the basics needed to implement Monster class characters within a UT2004 environment. The Monster is a specific class introduced in UT2004 for the Invasion gametype. For a tutorial on using UT Monsters, a subclass of ScriptedPawn, see Basic ScriptedPawn Tutorial.
Contents
Prerequisites
This tutorial assumes you know how to Create A Subclass.
Overview
By default, Monsters in UT2004 use a MonsterController that implements a complex set of behaviors to hunt down and attack players. This is specifically designed for the Invasion gametype, where Monsters are spawned at random NavigationPoints around the map and attack the players in increasingly aggressive waves.
However, for other gametypes this default behavior may not be desirable to the mapper. As an example, placing UT2004 Monsters in a map designed for a TeamGame will result in a team imbalance, where the UnrealPawn PostNetBeginPlay() function attempts (and eventually fails) to place the Monster in an xTeamRoster. (RedTeam's Size is incremented although there's no valid player added and extra BlueTeam memebers are the result)
In cases where the mapper would like to get Monster class creatures to perform more complex tasks, such as Patrolling, Defending, Ambushing, just as UT was able to do, extra steps are needed.
ScriptedSequence
The ScriptedSequence is a subclass of AIScript designed to control pawns. This takes the place of the AI portion of UT's ScriptedPawn, which is not available in UT200x. With the ScriptedSequence, mappers can define a script using predefined ScriptedActions. The ScriptedSequence is designed to work in conjunction with an associated Pawn; one who's AI -> AIScriptTag matches the ScriptedSequence's Tag.
For example, one of the most useful ACTIONs is ACTION_SpawnActor. But, if no Pawn is associated to the ScriptedSequence, the script will be removed from the level at map start. If the mapper would like to spawn a Monster out of thin air, a special Pawn will have to be used.
The Dummy Pawn
The Dummy Pawn is a simple custom Pawn subclass that will act as the servant to our ScriptedSequence. Subclass Pawn and define the following default properties:
- (Advanced) bool bHidden
- true
- (Advanced) bool bNoDelete
- true
- (Collision) bool bUseCylinderCollision
- false
- (Display) EDrawType DrawType
- DT_Sprite
- (Display) Texture Texture
- Texture'Engine.S_Pawn'
- (Movement) EPhysics Physics
- PHYS_None
For each ScriptedSequence that does not already have an associated Pawn (Monster), a Dummy can be used instead. This will allow full use of ACTIONs, like ACTION_SpawnActor to create a kind of "MonsterFactory".
Useful Stock ACTIONs
The following stock ScriptedActions are available to use in conjunction with Monsters. This is just a basic list of useful ACTIONs to implement Monsters. For a full list of ACTIONs, see also ScriptedAction.
ACTION_SpawnActor
As long as a Pawn is already associated with this ScriptedSequence, this will spawn the provided Actor class and give it the provided Tag. A "MonsterFactory" is easily made with this action, but it should be noted that the Monster will automatically spawn it's own MonsterController, making it attack any player it finds.
ACTION_MoveToPoint
This is a latent ACTION that gives the Pawn a destination defined as a NavigationPoint with a particular Tag that matches the provided name. The Monster will animate, make sounds, navigate and move appropriately without any further ACTIONs.
ACTION_PlayAnim
This will play the provided animation sequence (if it exists for this character mesh) and allows options for timing, looping, etc.
ACTION_PlaySound
This will play the provided sound and allows options for pitch, volume, etc.
ACTION_IfCondition
This conditional is true if the associated TriggeredCondition actor is true. TriggeredCondition are a subclass of Trigger and can be activated or toggled on and off by another Trigger. TriggeredConditions act as a flag, a byte of memory that can store information about the map's current condition. Some examples are: Is a door open? Is an area occupied by a player? Has a flag been taken?
ACTION_IfRandomPct
This conditional is true if the random number generated, between 1 and 100, is greater than the provided threadhold. This is good to keep variety.
ACTION_EndSection
This is necessary to define the end of a conditional block of ACTIONs.
ACTION_GotoAction
This will send focus to another part of the script.
ACTION_TriggerEvent
This will trigger the provided Event and trigger those Actors with a matching Tag.
ACTION_WaitForEvent
This latent ACTION will pause the script's focus until the provided Event is triggered.
ACTION_WaitForTimer
This latent ACTION will pause the script's focus for the provided number of seconds.
ACTION_DestroyActor
This will delete the Actor(s) with a Tag that matches the provided name.
ACTION_ChangeScript
This will transfer control of the associate Pawn to the ScriptedSequence with a matching Tag.
Custom ACTIONs
Many of the stock ACTIONs are left over from UT and were never meant to effect the Monster's MonsterController. To perform complex behavior like Patrolling, we'll need some custom SciptedActions to take control of the Monster, give up control and detemine various aspects of this relatively new object class. For more on custom ScriptedActions, see also Creating And Using ScriptedActions.
ACTION_SpawnMonster
This will spawn the provided Monster class and automatically Possess it, unless otherwise configured. Other options include setting Tag for the Monster and it's MonsterController, if needed. This particular ScriptedAction is very useful in implementing Monsters in TeamGame maps. By spawning them after match start, as opposed to explicitly placing them in a map, you avoid the team imbalance problem described in the Overview above.
//============================================================================= // ACTION_SpawnMonster. // Spawn a Monster class and Possess unless bMonsterizeAtStart is true. // by SuperApe -- Dec 2005 //============================================================================= class ACTION_SpawnMonster extends ScriptedAction; var() class<Monster> MonsterClass< SEMI > var() name MonsterTag; var() bool bMonsterizeAtStart; var() name MonsterControllerTag; function bool InitActionFor( ScriptedController C ) { local Monster M; local MonsterController MC; M = C.spawn( MonsterClass, C, MonsterTag, C.Location, C.Rotation ); C.Pawn = M; if ( bMonsterizeAtStart ) M.Controller.Tag = MonsterControllerTag; else { M.Controller.Unpossess(); C.Possess( M ); M.Controller = C; M.AIScriptTag = C.Tag; } return false; }
ACTION_Monsterize
This spawns a MonsterController for the Monster Pawn and has the MonsterController Possess it. This is what gets the Monsters to begin to attack any player they see.
//============================================================================= // ACTION_Monsterize. // Spawn a MonsterController for this Pawn and have it possess the Pawn. // by SuperApe -- Dec 2005 //============================================================================= class ACTION_Monsterize extends ScriptedAction; var() name MonsterControllerTag; function bool InitActionFor( ScriptedController C ) { local MonsterController MC; MC = C.spawn( class'MonsterController', None ); MC.Tag = MonsterControllerTag; MC.Possess( C.Pawn ); return false; }
ACTION_PossessPawn
This Possesses the Monster Pawn matching the provided Tag, making it the currently associated Pawn. This will also destroy the previous Controller if it is a MonsterController. This is what stops the Monsters from attacking at will and puts them under the control of this ScriptedSequence.
//============================================================================= // ACTION_PossessPawn. // Possess the Pawn with the PawnTag. Destroy any previous MonsterController. // by SuperApe -- Dec 2005 //============================================================================= class ACTION_PossessPawn extends ScriptedAction; var() name PawnTag; var bool bPawnExists; function bool InitActionFor( ScriptedController C ) { local Pawn P; local MonsterController MC; bPawnExists = false; forEach C.DynamicActors( class'Pawn', P, PawnTag ) { bPawnExists = true; if ( P.Controller.IsA('MonsterController') ) MC = MonsterController( P.Controller ); P.Controller.Unpossess(); if ( P.Health > 0 ) { C.Possess( P ); P.Controller = C; P.AIScriptTag = C.Tag; } else P.AIScriptTag = ''; if ( MC != None ) MC.Destroy(); } if ( !bPawnExists ) Warn("No Pawn with tag "$PawnTag$" exists!"); return false; }
ACTION_IfMonsterHasEnemy
This conditional is true if the Monster Pawn's Controller.Enemy property is occupied. Useful for situations where you want to know if it's a good time to ACTION_PossessPawn, otherwise you may want to keep it "Monsterized" and wait until the Enemy is gone.
//============================================================================= // ACTION_IfMonsterHasEnemy. // Conditional on Pawn.Controller.Enemy != None // by SuperApe -- Dec 2005 //============================================================================= class ACTION_IfMonsterHasEnemy extends ScriptedAction; function ProceedToNextAction( ScriptedController C ) { if ( !C.Pawn.IsA('Monster') ) ProceedToSectionEnd( C ); C.ActionNum += 1; if ( C.Pawn.Controller.Enemy == None ) ProceedToSectionEnd( C ); } function bool StartsSection() { return true; }
ACTION_IfMonsterSeesEnemy
This conditional is true based on the property MonsterController.bEnemyIsVisible. Useful in the same way that ACTION_IfMonsterHasEnemy is, but this will only be true if the Enemy is visible. So an Enemy that's ducked out of sight will cause this conditional to be false.
//============================================================================= // ACTION_IfMonsterSeesEnemy. // Conditional on MonsterController.bEnemyIsVisible // by SuperApe -- Dec 2005 //============================================================================= class ACTION_IfMonsterSeesEnemy extends ScriptedAction; function ProceedToNextAction( ScriptedController C ) { if ( !C.Pawn.IsA('Monster') || MonsterController( C.Pawn.Controller ) == None ) ProceedToSectionEnd( C ); C.ActionNum += 1; if ( !MonsterController(C.Pawn.Controller).bEnemyIsVisible ) ProceedToSectionEnd( C ); } function bool StartsSection() { return true; }
ACTION_IfMonsterIsHurt
This conditional is true if the Pawn's Health is less than the provided HealthThreshold. This works whether the Monster is currently Monsterized or not. It's useful to determine if the ScriptedSequence should take control of a hurt Monster to move it to a safer place, etc.
//============================================================================= // ACTION_IfMonsterIsHurt. // Conditional on Pawn.Health < HealthThreshold // by SuperApe -- Dec 2005 //============================================================================= class ACTION_IfMonsterIsHurt extends ScriptedAction; var() int HealthThreshold; function ProceedToNextAction( ScriptedController C ) { if ( !C.Pawn.IsA('Monster') ) ProceedToSectionEnd( C ); C.ActionNum += 1; if ( C.Pawn.Health > HealthThreshold ) ProceedToSectionEnd( C ); } function bool StartsSection() { return true; }
ACTION_HealPawn
This gives the Pawn Health, up to it's MaxHealth, based on the provided HealAmount. Ironically, ACTION_HealActor doesn't work with Pawns. This is useful in situations where you'd like to keep the Monster around a bit longer. In conjunction with ACTION_IfMonsterIsHurt, this can be used to provide a kind of "Healing Station" for the Monster, giving it a sophisticated combat tactic: Retreat to Live and fight another day.
//============================================================================= // ACTION_HealPawn. // Calls Pawn.GiveHealth( HealAmount, HealthMax ). Does what HealActor can't. // by SuperApe -- Dec 2005 //============================================================================= class ACTION_HealPawn extends ScriptedAction; var() int HealAmount; function bool InitActionFor( ScriptedController C ) { C.Pawn.GiveHealth( HealAmount, int( C.Pawn.HealthMax ) ); return false; }
Further Development
The possibilities are endless. Some useful ACTIONs to make include setting specific Properties or switching to behavior States. Other custom ACTIONs should be appended here to this section.
ACTION_myCustomAction
Monster Tutorial
Now that we have the ScriptedActions available to affect Monsters, we can begin to construct a complex Monster behavior patrolling to guard an item in a map. We will start with a MonsterFactory. We will give our Monsters a path to follow while on patrol. We will keep track of the item so the Monsters know when it's been taken. We will also give our Monsters a safe "homebase" where they can heal themselves when they're hurt.
We will use a variety of actors to make this behavior happen. We could do it with one Dummy Pawn and one ScriptedSequence, however the ScriptedSequence script would be very long and complex. It's very easy to make a mistake in a script that long, so we will break it up into manageable chunks and spread the script around to several ScriptedSequences.
MonsterFactory
A MonsterFactory starts with a Dummy Pawn and a ScriptedSequence. The Dummy -> AIScript -> AIScriptTag will match the ScriptedSequence -> Tag. We will place the Dummy at the spot we would like our Monsters to spawn. We will keep the ScriptedSequence close by, just for organization.
This MonsterFactory script will consist of the following ScriptedActions:
- [0] ACTION_TriggerEvent
- An emitter actor will be set up here to give some simple effects for our spawning Monster. We will match it's Tag, "MonsterSpawnFX", to this ACTION's arguement.
- [1] ACTION_PlaySound
- A sound effect will also be played at this point so both a visual and audial queue will signal to any close player that a Monster is about to be spawned.
- [2] ACTION_SpawnMonster
- We will spawn a Krall and give it the MonsterTag, "KrallPatrol". We will keep it's control on this script rather than Monsterizing it at start.
- [3] ACTION_MoveToPoint
- A nearby PathNode with a Tag set to, "MonsterStart", will be designated as the first spot our Monsters will move. This will simply get them out of the way of other Monsters.
- [4] ACTION_TriggerEvent
- This action will signal the next ScriptedSequence that a Monster has been spawned and moved to the MonsterStart spot. This signal, Event "MonsterToPatrol", will start transferring control of the Monster to the next script.
- [5] ACTION_WaitForEvent
- At this point, our MonsterFactory has done its job. We will have it wait here for a signal to continue. Our signal will be the Event, "MonsterSpawn".
- [6] ACTION_GotoAction
- Once the signal is given, we will goto the Action 0 to start this script again and spawn a new Monster.
Patrolling
Here another ScriptedSequence will take over control of the Monsters from our MonsterFactory. A series of PathNodes will have Tags that we will use to define the Patrol points for our Monsters. Each Patrol point will have a corresponding ScriptedSequence. Our Patrolling scripts will perform checks to make sure our Monsters are Monsterized to attack visible enemies. At various points, we will also give the Monsters the opportunity to keep track of the item they are guarding and go to a safe healing place if they need it.
The Patrolling script will have the following ScriptedActions:
- [0] ACTION_WaitForEvent
- This script waits for the Event, "MonsterToPatrol", as a signal to take control of the Monster.
- [1] ACTION_PossessPawn
- This will take control of the Monster Pawn with the Tag, "KrallPatrol".
- [2] ACTION_MoveToPoint
- We will send this Monster to the first Patrol point, a PathNode with a Tag, "Patrol1".
- [3] ACTION_Monsterize
- We will give the Monster the opportunity to attack any player it sees. The MonsterControllerTag will be, "KrallMonster".
- [4] ACTION_WaitForTimer
- We will have the Monster wait 5 seconds while Monsterized.
- [5] ACTION_IfMonsterSeesEnemy
- This conditional gives the Monster a chance to tell us if they see an Enemy while at this Patrol point.
- [6] ACTION_GotoAction
- Send the script to Action 4, to continue the attack for another 5 seconds.
- [7] ACTION_EndSection
- This ends the conditional block for IfMonsterSeesEnemy.
- [8] ACTION_TriggerEvent
- This will signal to the next Patrol script to take control of this Monster and move it to the next Patrol point. The Event to trigger will be, "Patrol2".
- [9] ACTION_GotoAction
- This script has done its job and should return to the original state, at Action 0, waiting for the next Monster to take control of.
Now the next Patrol script and Patrol point will take over. They will look exactly like this one with the exception of some of the Tags. The script will be waiting for Event, "Patrol2". The Patrol point PathNode and ACTION_MoveToPoint will have the tag, "Patrol2". When the script has finished checking to see IfMonsterSeesEnemy, it will trigger the next Patrol script using Event, "Patrol3".
This series of Patrol points and scripts will loop around the Patrol path back to the first point, where the last Patrol script will be triggering, "MonsterToPatrol", to signal the first Patrol script to take over again.
Guarding An Item
In our case, we will have the Monsters guard a BombingRun xBombFlag at the xBombSpawn point. This will force players to get through the Monster patrol to get the BombFlag in order to score.
Healing
External Links
- UnrealPlayground Forum topic, Monster Experiments – Implementation of Monsters via custom ScriptedActions.
Related Topics
- Artificial Intelligence – A super topic for all AI (bots, NPCs, etc.)
- Monster Support – A hub for all Monster Support topics.
- Monster – The UT2004 parent class of all Monsters.
- MonsterController – The Controller class for Monsters.
- ScriptedPawn – The UT parent class for all Monsters and NPCs.
- AIScript
- ScriptedSequence
- UnrealScriptedSequence
- ScriptedTrigger
- ScriptedSequence
- Trigger Systems
Custom Content
- Old Skool Monsta Toolz – A mod/mapping toolset for implementing monsters and complex monster AI in any gametype for UT2004.
Discussion
SuperApe: Created. Working...
SuperApe: These are okay. It's beginning to work. There are a few key issues that need to be addressed:
- With these Actions, you can have the Monster attack or be controlled by the ScriptedSequence, not both. So, setting something akin to Alertness level is difficult.
- Technically, the MonsterController will "teleport" a Monster to a PlayerStart after several seconds if it hasn't encountered a player. Perhaps another controller should be used anyway. (like a subclass that at least removes that method)
- A daisy chain of ScriptedSequences should work, but I've been having problems. (I haven't figured out why, but it intermitently "misses" the Trigger for the next script.)
So, I'm beginning to think re-creating the HomeBase, AmbushPoint, AlarmPoint and PatrolPoint might be helpful in conjunction with a modified MonsterController.
Moofed: Any progress on this? I'll be learning monster scripting (very) soon and this page looks useful.. until your comment about it not working.
SuperApe: Your timing is impecable. :) I'm working on this now. It's not that it doesn't work, it's that there are serious limitations without further development, as I mention above. At the moment, I'm right on the edge of either continuing to use custom ScriptedActions (and making a lot more of them) or creating a custom MonsterController that will include much of the functionality needed. It's a bit of a mess to have both. I will be updating this soon.
Moofed: ... What I will be learning monster scripting for (is) civilian pedestrians and vehicles ... to recreate the GTA2 feel.
SuperApe: So, you're thinking more along the lines of an NPC? This could be done with ScriptedSequences, I suppose. I might be a little too much to do, or a little too simple an NPC. This page is specifically talking about the problems using Monsters with ScriptedSequences, so it's not gonna be directly applicable to what you want to do. Monsters are defined as a specific kind of NPC whose sole purpose is to hunt down and kill players. Civilians and pedestrians would be more along the lines of a general NPC. See NPC Support and describe for us what you'd like them to do. Perhaps we can offer suggestions. :)
Moofed: It seemed relevant to what I want, but you're probably right. A custom controller would be better. I'm making a personal page now to descibe my ideas for this.
SuperApe: Great. Be glad to help. :)
SuperApe: This page will be updated soon, but while I like being able to use (mostly) stock objects to implement UT2004 Monsters, there's some drastic limitations. It's no where near as cool as original ScriptedPawn Monsters used to be in UT. While I may keep this tutorial the same, I am beginning to recreate the older actors and code for the ScriptedPawn AI, with all their functionality. This will eventually include all the NavPoints, a new Monster parent class that mimics some ScriptedPawn functionality, as many individual Monsters as UT2004 has (with all their original functionality) and a new ScriptedMonsterController which will handle the bulk of what ScriptedPawn used to. I suppose that qualifies as a different project, a different page, but it related directly to and evolved from these experiments. In the meantime, I will try to finish up this tutorial as best I can using the custom work already described above.
SuperApe: This line of thinking has led to a new project, Old Skool Monsta Toolz, implementing UT-style monsters in UT2004. EDIT: It has been publicly released.
SuperApe: The current wiki page for the OSMT project is here: Old Skool Monsta Toolz.
Category:Legacy To Do – SuperApe is working on a tutorial. (This actually needs to be refactored, redone)