Announcement

Collapse
No announcement yet.

TUTORIAL - How To Make A Zombie / Wandering / Roaming Monster

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    TUTORIAL - How To Make A Zombie / Wandering / Roaming Monster

    TUTORIAL - HOW TO MAKE A ZOMBIE / WANDERING / ROAMING MONSTER

    **UPDATE*********

    I've updated this tutorial with a link to the game my team and I worked on in which every monster AI is based off of the code found in this tutorial.
    You can find the game in the User Maps and Mods forum. It's called Welcome to Wilshire Heights: http://forums.epicgames.com/showthread.php?t=588111

    If you leave the monsters alone long enough, you'll see that they roam around by themselves randomly along with all the other behavior I detail here in this tutorial.

    I hope that anyone who is having trouble visualizing this tutorial will be able to take some time and look at the game to see how I implemented this code (it's a big download!).


    *****************


    "Who cares if everyone wants to make zombie knock-offs. Zombies never get old, because they are never dead. GET IT?"

    "Because they are UNDEAD!!!!"


    Summary:

    What does this do?

    It makes a zombie that will wander around a certain area, when it sees the player it will move toward them and try to attack. If the player runs away and the zombie can no longer see them, it will go back to its wandering state.

    Step 1: Create a custom monster pawn class extending off Monster class

    Step 2: Create a base custom monster controller extending off MonsterController

    Step 3: Create another custom monster controller extending your base controller (this is the one that controls the actual roaming)

    Step 4: Create the zombie itself

    Code (just fill in all the stuff between the *stars* to work with your stuff):

    Step 1 Code:

    Code:
    //=====================================================
    // You only need to edit one line of code here, but this is important
    // so that when you spawn your zombies that they know to use your
    // controller instead of MonsterController
    //=====================================================
    
    class *YOUR_ZOMBIE_PAWN_CLASS* extends Monster;
    
    
    defaultproperties
    {
    ControllerClass=Class'*YOUR_BASE_ZOMBIE_CONTROLLER*'
    }
    Step 2 Code:
    Code:
    class *YOUR_BASE_ZOMBIE_CONTROLLER* extends MonsterController;
    
    var float lastDistance;
    //  this function will get the node furthest from an actor
    //  It is used by the run state in order to have a destination to run to
    var array<PathNode> nodeArray;
    var int myArrayIndex;
    
    function getNodes()
    {
      local int newArrayLength;
      local PathNode arrayNode;
      local int arrayIndex;
    
      newArrayLength = 0;
      arrayIndex = 0;
    
      foreach AllActors (class'PathNode', arrayNode)
      {
        newArrayLength++;
      }  // end for
      nodeArray.Length = newArrayLength;
    
      // now populate the array
      foreach AllActors (class'PathNode', arrayNode)
      {
        nodeArray[arrayIndex] = arrayNode;
        arrayIndex++;
      }  // end for
    }  // end getNodes
    
    function PathNode closestNode(Actor mySelf)
    {
      local PathNode farNode;
      local PathNode pathNode;
      local float thisDistance;
      local float holdingDistance;
      local float xDistance;
      local float yDistance;
    
      // now get the first distance to use as an example
      thisDistance = 10000;
    
      // in the for loop, get the distance from the node the actor
      // if the distance is greatest so far, set that pathNode
      // equal to the furthest node
      foreach AllActors (class'PathNode', pathNode)
      {
        xDistance = abs(mySelf.Location.X - pathNode.Location.X);
        yDistance = abs(mySelf.Location.Y - pathNode.Location.Y);
        holdingDistance = xDistance+yDistance;
    
        if((holdingDistance < thisDistance)&&(holdingDistance>0)&&(holdingDistance!=lastDistance))
        {
          thisDistance = holdingDistance;
          farNode = pathNode;
        }  // end else if
      }  // end for
      lastDistance = thisDistance;
      return farNode;
    }  // end getClosestNode
    // a function that will return whether the bot it close to the node
    function bool isClose(Actor closeActor)
    {
      local float holdingDistance;
      local float xDistance;
      local float yDistance;
    
      xDistance = abs(Pawn.Location.X - closeActor.Location.X);
      yDistance = abs(Pawn.Location.Y - closeActor.Location.Y);
      holdingDistance = sqrt((xDistance*xDistance)+(yDistance*yDistance));
      if(holdingDistance <= 30)
        return true;
      else
        return false;
    }  // end isClose
    
    //  this function will get the node furthest from an actor
    //  It is used by the run state in order to have a destination to run to
    function PathNode furthestNode(Actor mySelf)
    {
      local PathNode farNode;
      local PathNode pathNode;
      local float thisDistance;
      local float holdingDistance;
      local float xDistance;
      local float yDistance;
    
      // now get the first distance to use as an example
      thisDistance = 0;
    
      // in the for loop, get the distance from the node the actor
      // if the distance is greatest so far, set that pathNode
      // equal to the furthest node
      foreach Region.Zone.ZoneActors (class'PathNode', pathNode)
      {
        xDistance = abs(mySelf.Location.X - pathNode.Location.X);
        yDistance = abs(mySelf.Location.Y - pathNode.Location.Y);
        holdingDistance = xDistance+yDistance;
    
        if((holdingDistance > thisDistance))
        {
          thisDistance = holdingDistance;
          farNode = pathNode;
        }  // end else if
      }  // end for
      return farNode;
    }  // end getClosestNode
    state Dead
    {
    ignores SeePlayer, EnemyNotVisible, HearNoise, ReceiveWarning, NotifyLanded, NotifyPhysicsVolumeChange,
    		NotifyHeadVolumeChange,NotifyLanded,NotifyHitWall,NotifyBump;
    
    	function WhatToDoNext(byte CallingByte)
    	{
    	}
    
    	function Celebrate()
    	{
    	}
    
    	function SetAttractionState()
    	{
    	}
    
    	function EnemyChanged(bool bNewEnemyVisible)
    	{
    	}
    
    	function WanderOrCamp(bool bMayCrouch)
    	{
    	}
    
    	function Timer() {}
    
    	function BeginState()
    	{
    		Destroy();
    		return;
    	}
    
    Begin:
    	if ( Level.Game.bGameEnded )
    		GotoState('GameEnded');
    	Sleep(0.2);
    TryAgain:
    	if ( UnrealMPGameInfo(Level.Game) == None )
    		destroy();
    	else
    	{
    		Sleep(0.25 + UnrealMPGameInfo(Level.Game).SpawnWait(self));
    		LastRespawnTime = Level.TimeSeconds;
    		Goto('TryAgain');
    	}
    
    MPStart:
    	Sleep(0.75 + FRand());
    	Goto('TryAgain');
    }
    
    defaultproperties
    {
    PawnClass=Class'*YOUR_ZOMBIE_PAWN_CLASS*' // you need to make sure that your controller and your custom pawn class link up
    }
    Step 3 Code:
    Code:
    class *YOUR_EXTENDED_CONTROLLER* extends *YOUR_BASE_ZOMBIE_CONTROLLER*;
    
    // global variables.
    var NavigationPoint destinationNode;        // the node that the zombie will run to
    var NavigationPoint RoamDestination; // the Destination for Roaming
    
    var *YOUR_PLAYER_CONTROLLER* victim;    // this is the victim's / player's controller. Whatever class you want the zombie to target.
    
    
    function Tick(float DeltaTime)
    {
      super.Tick(DeltaTime);
    }
    
    function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum)
    {
      if ( Pawn.Health <= 0 )
         return;
    }
    
    function Destroyed()
    {
    	Super.Destroyed();
    }
    
    
    function PostBeginPlay()
    {
    	Super.PostBeginPlay();
    	InitializeEnemy();
        GotoState('Roaming','Begin');
    }
    
    function Restart()
    {
             Super.Restart();
             InitializeEnemy();
        GotoState('Roaming','Begin');
    }
    
    event SeePlayer(Pawn SeenPlayer)
    {
      Enemy = SeenPlayer;
    }
    
    
    //  make sure that the controller is controlling your zombie
    
    function TakeControlOf(Pawn aPawn)
    {
        if ( Pawn != aPawn )
        {
            aPawn.PossessedBy(self);
            Pawn = aPawn;
        }
        //numberHits = 0;
        GotoState('Roaming','Begin');
    }  // end TakeControlOf
    
    
    
    function SetPawnClass(string inClass, string inCharacter)
    {
        local class<Pawn> pClass;
        pClass = class'*YOUR_ZOMBIE_PAWN_CLASS*'; // if you don't have a custom one just use the class 'Monster'
        if ( pClass != None )
            PawnClass = pClass;
    }
    
    function InitializeEnemy()
    {
        local *YOUR_PLAYER_CONTROLLER* v;
    
        Enemy = none;
    
        foreach AllActors(class'*YOUR_PLAYER_CONTROLLER*', v) {
            victim = v;
            Enemy = victim.Pawn;
            break;
        }
    }
    
    
    /*
    *  AI states
    *
    */
    
    state Roaming
    {
        ignores EnemyNotVisible, SeePlayer, HearNoise;
    
        function Tick( float DeltaTime ) {
      
           if (victim == none)
              return;     
    
           if ( Enemy == none || Enemy != victim.Pawn ) {
              Enemy = victim.Pawn;
              return;
           }
    
           if ( FastTrace( Enemy.Location, self.Location ) ) {
              GoToState('StakeOut','WaitforAnim');
           }
        }
    
        event bool NotifyBump(actor Other)
    	{
    		if (Other == Enemy)
    		{
    		    GoToState('StakeOut','WaitforAnim');
    			return false;
    		}
            return true;
    	}
    
    Begin:
      destinationNode = closestNode(self);
      MoveTarget = FindPathToward(destinationNode);
      MoveToward(MoveTarget);
      sleep(0.5);
      GotoState('Roaming','Begin');
    }
    
    defaultproperties
    {
    
    }
    Step 4 Code:
    Code:
    //====================================================
    // This is just a generic monster based off Krall, modified to be a melee fighter
    // All that is important here is that in the PostBeginPlay()
    // function of your monster that you tell it to go the roaming state
    //====================================================
    
    class *YOUR_ZOMBIE* extends *YOUR_ZOMBIE_PAWN_CLASS*
          placeable;
          
    var bool bAttackSuccess;
    var bool bLegless;
    var bool bSuperAggressive;
    var name MeleeAttack[5];
    
    
    function PostBeginPlay()
    {
        Super.PostBeginPlay();
    
        bSuperAggressive = (FRand() < 0.5);
    
        Controller.GotoState('Roaming', 'Begin'); // Zombie roams and waits for player to come into view
    }
    
    function RangedAttack(Actor A)
    {
    	MeleeDamageTarget(50, 21000 * Normal(Controller.Target.Location - Location));  // adjust parameters for whatever damage you want. "50" is how much damage is inflicted right now
    }
    
    //======================================================================================
    // Copy and pasted Krall code, nothing in this section has been changed at all
    //======================================================================================
    
    replication
    {
        unreliable if( Role==ROLE_Authority )
    		bLegless;
    }
    
    function StrikeDamageTarget()
    {
    	if (MeleeDamageTarget(20, 21000 * Normal(Controller.Target.Location - Location)))
    		PlaySound(sound'hit2k',SLOT_Interact);
    }
    
    function vector GetFireStart(vector X, vector Y, vector Z)
    {
        return Location + 0.9*X - 0.5*Y;
    }
    
    function SpawnShot()
    {
    	FireProjectile();
    }
    
    function PlayTakeHit(vector HitLocation, int Damage, class<DamageType> DamageType)
    {
    	local rotator r;
    
    	if ( bLegless )
    		return;
    		
    	if ( (Health > 30) || (Damage < 20) || (HitLocation.Z > Location.Z) )
    	{
    		Super.PlayTakeHit(HitLocation, Damage, DamageType);
    		return;
    	}
    	r = rotator(Location - HitLocation);
    	CreateGib('lthigh',DamageType,r);
    	CreateGib('rthigh',DamageType,r);
    	
    	bWaitForAnim = false;
    	SetAnimAction('LegLoss');
    }
    
    simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
    {
    	Super.PlayDying(DamageType,HitLoc);
        
        if ( bLegless )
    		PlayAnim('LeglessDeath',0.05);
    }
    
    simulated event SetAnimAction(name NewAction)
    {
    	local int i;
    	
    	if ( NewAction == 'LegLoss' )
    	{
    		bWaitForAnim = false;
    		GroundSpeed = 100;
    		bCanStrafe = false;
    		bMeleeFighter = true;
    		bLegless = true;
    		SetCollisionSize(CollisionRadius,16);
    		PrePivot = vect(0,0,1) * (Default.CollisionHeight - 16);
    
    		for ( i=0; i<3; i++ )
    		{
    			MovementAnims[i] = 'Drag';
    			SwimAnims[i] = 'Drag';
    			CrouchAnims[i] = 'Drag';
    			WalkAnims[i] = 'Drag';
    			AirAnims[i] = 'Drag';
    			TakeOffAnims[i] = 'Drag';
    			LandAnims[i] = 'Drag';
    			DodgeAnims[i] = 'Drag';
    		}
    		IdleWeaponAnim = 'Drag';
    		IdleHeavyAnim = 'Drag';
    		IdleRifleAnim = 'Drag';
    		IdleRestAnim = 'Drag';
    		IdleCrouchAnim = 'Drag';
    		IdleSwimAnim = 'Drag';
    		AirStillAnim = 'Drag';
    		TakeoffStillAnim = 'Drag';
    		TurnRightAnim = 'Drag';
    		TurnLeftAnim = 'Drag';
    		CrouchTurnRightAnim = 'Drag';
    		CrouchTurnLeftAnim = 'Drag';
    	}
    	Super.SetAnimAction(NewAction);
    }
    
    function PlayVictory()
    {
    	if ( bLegless )
    		return;
    	Controller.bPreparingMove = true;
    	Acceleration = vect(0,0,0);
    	bShotAnim = true;
        PlaySound(sound'staflp4k',SLOT_Interact);	
    	SetAnimAction('Twirl');
    	Controller.Destination = Location;
    	Controller.GotoState('TacticalMove','WaitForAnim');
    }
    
    function ThrowDamageTarget()
    {
    	bAttackSuccess = MeleeDamageTarget(30, vect(0,0,0));
    	if ( bAttackSuccess )
    		PlaySound(sound'hit2k',SLOT_Interact);
    }
    
    function ThrowTarget() 
    {
    	if ( bAttackSuccess && (VSize(Controller.Target.Location - Location) < CollisionRadius + Controller.Target.CollisionRadius + 1.5 * MeleeRange) )
    	{
    		PlaySound(sound'hit2k',SLOT_Interact);
    		if (Pawn(Controller.Target) != None)
    		{
    			Pawn(Controller.Target).AddVelocity( 
    				(50000.0 * (Normal(Controller.Target.Location - Location) + vect(0,0,1)))/Controller.Target.Mass);
    		}
    	}
    }
    
    //==============================
    // End copy and pasted Krall code
    //==============================
    
    
    defaultproperties
    {
    
         Health = 100    // whatever health
         bMeleeFighter = true;  // so it doesn't shoot projectiles
         bCanStrafe=true
    
         DrawScale=1
         Mesh=VertMesh'SkaarjPack_rc.KrallM'
         
         MovementAnims(0)="RunF"
         MovementAnims(1)="RunF"
         MovementAnims(2)="RunF"
         MovementAnims(3)="RunF"
         MeleeAttack(0)="Strike1"
         MeleeAttack(1)="Strike2"
         MeleeAttack(2)="Strike3"
         MeleeAttack(3)="Strike1"
         MeleeAttack(4)="Strike1"
         AirAnims(0)="RunF"
         AirAnims(1)="RunF"
         AirAnims(2)="RunF"
         AirAnims(3)="RunF"
         TakeoffAnims(0)="RunF"
         TakeoffAnims(1)="RunF"
         TakeoffAnims(2)="RunF"
         TakeoffAnims(3)="RunF"
    }
    FINAL NOTE:

    YOU NEED PATHNODES IN YOUR MAP! If you have no pathnodes or "apple roads" (one of my instructors' terms for them) set up in your map, then the zombies won't roam much. The more pathnodes you set up, the more control you can have over how they roam.

    I made this by modifying cknight52's code from this thread:

    http://forums.epicgames.com/showthre...ghlight=zombie

    Unfortunately the thread was never completed and it's about 3 years old.

    #2
    Have you tested this code?
    Does the zombie "teleport" after not seeing a player or playerBot for 15 seconds or so?
    Does this have a true wandering state, or does it automatically know where a player enemy is? (It appears to determine the first player controller in the level and head for it)
    Do you have a "runAway" state for when it is hit?
    IMHO, I think there's room for improvement here.

    And, just for the record, I have helped people do exactly this with a toolset called OSMT.

    Comment


      #3
      Hey,
      Thanks for pointing out the runaway state, that actually is a remnant of cknight52's script and should not be there, so I edited the post above to fix that.

      I just fixed another mistake too actually, I forgot to mention linking up the monster pawn and controller classes but I just added it as an extra step above, so it's fixed now too.

      I extracted and cleaned up this code from the mod I am working on right now, so there might be (hopefully not) a few typos here and there but for the most part this is the code that drives it all.

      For our mod we wanted a horde of zombies to be wandering around and attack the player as soon as they entered a certain area, and if the player hides behind a wall, or leaves the zombies' view, they stop chasing and just start wandering around again.

      So yeah, it should work because it's working like it should in my mod, unless something got lost in the translation from my code to this board (like the runaway state).

      As far as I can tell, the zombies have not teleported on me at all. As I've gone through debugging the mod they always just kind of hang out and wait for me.

      In the wandering state, they just move back and forth along the pathnodes that are nearest to them when they spawn. When a player enters their vicinity (generally somewhere where the player can be seen by them) they switch to the stakeout state, which means that they automatically attack because the player is already in view.

      So instead of them just standing around waiting in stakeout state, they now wander around waiting and when the player comes near them they attack.

      And yeah I checked out your OSMT yesterday actually and I have it installed now on my version of Unreal. Honestly though, I didn't look at it long enough to be absolutely sure, but from what I could tell it didn't have exactly what I was looking for because I wanted to be able to use my own custom monsters and have them wander around, with what I saw with your OSMT is that I had to use one of the classes you had made and set them up in the map editor, whereas I wanted to work it all out through code. But again, I didn't look very closely at the wiki page or the readme's, so if I missed something obvious (which I usually do), I apologize

      I even looked over your code to see if I could somehow make it work for me, but there were just too many things referencing each other that I gave up :P

      I also looked at the Zombies of Nightmare code and the Killing Floor code. Whoever made Zombies of Nightmare tried to implement a wandering state for zombies, but it didn't seem to work properly, as even when you run the mod the zombies just stand around and don't do anything if you are invisible or too far away.

      The Killing Floor code didn't work for me either because the zombies in that game just rush the player like regular monsters.

      After digging through all that code I decided that cknight52's code was the closest to what I wanted, so yeah, I just tweaked it around to make it work for my mod.

      And you're right, there's definitely room for improvement, especially since I'm still new to this.

      Comment


        #4
        Yup, on the wiki page it mentions using OSMT as a base, (specifically subclassing ScriptedMonster so you can take advantage of the toolset) and just adjusting the properties (groundspeed, etc) and adding the functions that make the most sense for custom creatures.

        But, if you want to go from scratch, I can understand that. Good luck.

        Comment


          #5
          yeah... This code checks out! It's pretty cool!

          Comment


            #6
            That's suprising to me. I don't see anything in the above code to prevent the Invasion Monster class' tendency to teleport if player hasn't been seen in a while, nor do I see code to prevent this creature from causing a Team Roster imbalance in team games. I have a feeling that those problems exist here, and that's why I made my suggestions above.

            Comment


              #7
              Thanks for the tut, yappari! I'll give it a try sometime. I have a vehicle that spawns monsters. It'd be nice to have them wander around their spawn point instead of actively hunting for players.

              Comment


                #8
                Custom characters

                How would you go about using this code for a new character with custom skeleton and animations? I can't seem to figure out which code I should modify to get this happenning.

                For that matter, even after scouring all the Monster and MonsterController classes and super classes, I can't even find the code that chooses and plays animations. I've spent hours trying to get my head around this. Help anyone?

                Comment


                  #9
                  thats a pretty sweet tut! me likes...

                  hey yappari maybe you would know the answer one of my monster related questions.

                  i made a gametype that spawns monsters all over the map via portals, the problem is that some times the monsters attack eachother (generally after one accidentally shoots another monster).

                  so do you know how to make them never attack their own team.

                  Comment


                    #10
                    Try this example function, found from the Invasion source, from the skaarj monster.

                    Code:
                    function bool SameSpeciesAs(Pawn P)
                    {
                    	return ( (Monster(P) != None) && (P.IsA('Skaarj') || P.IsA('WarLord')) );
                    }
                    This tell the monster what not to attack. Actually, I just think this stops the monster from taking damage from those particular monsters, so they don't get provoked in the first place. Note that specifying skaarj also means any other monster that subclasses it (the Ice and Fire Skaarj for example)

                    Comment


                      #11
                      Originally posted by someOther View Post
                      How would you go about using this code for a new character with custom skeleton and animations? I can't seem to figure out which code I should modify to get this happenning.

                      For that matter, even after scouring all the Monster and MonsterController classes and super classes, I can't even find the code that chooses and plays animations. I've spent hours trying to get my head around this. Help anyone?
                      To get animations to work with your a mesh, if you are doing it the way I did in the tutorial, you need to specify all the animation actions you want to change in the default properties of your monster. For my zombies, I've kept them with really basic animations so I replaced certain types of animations with the same instances of a single one. You can find the animation list by checking xPawn.uc.

                      If you want to use your own custom animations, you need to know what you called them in your animation package first and then load the file in your code and specify which animation goes with what action.

                      It would look something like this:

                      Code:
                      class *YOUR_ZOMBIE* extends *YOUR_ZOMBIE_PAWN_CLASS*
                            placeable;
                      
                      #exec OBJ LOAD FILE=..\Animations\YourAnimationPackage.ukx
                      #exec OBJ LOAD FILE=..\Textures\YourTexturePackage.utx
                      And then your default properties:

                      Code:
                      defaultproperties
                      {
                           DrawScale=1
                          
                           WalkAnims(0)="WalkF"
                           WalkAnims(1)="WalkF"
                           WalkAnims(2)="WalkF"
                           WalkAnims(3)="WalkF"
                           MovementAnims(0)="RunF"
                           MovementAnims(1)="RunF"
                           MovementAnims(2)="RunF"
                           MovementAnims(3)="RunF"
                           MeleeAttack(0)="Strike1"
                           MeleeAttack(1)="Strike2"
                           MeleeAttack(2)="Strike3"
                           MeleeAttack(3)="Strike1"
                           MeleeAttack(4)="Strike1"
                           SwimAnims(0)="Swim"
                           SwimAnims(1)="Swim"
                           SwimAnims(2)="Swim"
                           SwimAnims(3)="Swim"
                           IdleSwimAnim="Swim"
                      
                           bMeleeFighter = true;
                           bCanStrafe=true
                           JumpZ=0  // this is just to disable their jumping
                           
                           Mesh=SkeletalMesh'MyAnimationPackage.MyZombieMesh'
                      
                           Skins(0)=Texture'YourTexturePackage.Characters.head_texture'
                           Skins(1)=Texture'YourTexturePackage.Characters.body_texture'
                         
                      }

                      Originally posted by Cody Lavery View Post
                      thats a pretty sweet tut! me likes...

                      hey yappari maybe you would know the answer one of my monster related questions.

                      i made a gametype that spawns monsters all over the map via portals, the problem is that some times the monsters attack eachother (generally after one accidentally shoots another monster).

                      so do you know how to make them never attack their own team.
                      Actually I'm not sure how to make them not attack each other. In my mod they do the same thing. If one accidentally hits the other then they attack each other. For me it wasn't a problem since I thought it was funny that they do that and just left it. For my more important monsters I made it so that if they do get attacked by something other than the player, they don't take damage. I did that by just giving monsters a custom damage type, and in the custom zombie pawn class I modified the PlayHit function.

                      Code:
                      function PlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum)
                      {
                          Super.PlayHit(Damage,InstigatedBy,HitLocation,DamageType,Momentum);
                      
                          if(DamageType==class'MyCodePackage.MyDamType')
                          {
                              Health += 40;
                          }
                      }
                      Mine is just a crazy work around where they heal each other to cancel out the damage done to them.

                      Comment


                        #12
                        hey thanks 9ades and yappari! yeah i've seen that samespecies function before didnt realize what it did... that helps me alot!

                        about the whole ' if( P.IsA('Skaarj')) ' couldnt you just do ' if (P.IsA('Monster')) ' cause if it returns true for any 'descendants' of monster wouldnt that cover all of them?

                        i read through your tut again and noticed that you assign enemy=SeenPlayer, im wondering if monsters have a playerReplicationInfo, so that i could give them a team, and make them do a check first

                        Code:
                        event SeePlayer(Pawn SeenPlayer)
                        {
                        
                        if(SeenPlayer.Controller.PlayerReplicationInfo.Team.TeamIndex != MonsterTeamNum)
                          Enemy = SeenPlayer;
                        }
                        i have always wanted hordes of monsters battling it out during a CTF or ONS... you know just to give that greater feeling of WAR!!!

                        im going to test this stuff out tonight, if all goes good ill make my monsters finally work together without killing eachother.

                        Comment


                          #13
                          Hello everyone, sorry for bringing this old topic up. Anyways, I'm new to coding unreal, and I've looked everywhere for a good tutorial. So, I figured this would be a good start in creating my first custom monsters for the invasion gametype, but problem is...im having difficulty with steps 1-3. I'm not sure exactly what im supposed to do to set up a monster pawn class and what not. any help, or links to tutorials with help?

                          Comment


                            #14
                            Originally posted by yappari View Post
                            TUTORIAL - HOW TO MAKE A ZOMBIE / WANDERING / ROAMING MONSTER

                            First, I just want to say that it's pretty sad that nobody on this board has made a tutorial nor has been able to explain how to make one of the most simplest monsters in the existence of all video games.

                            It's even sadder because we all know that about 70% of the people posting now on this board are students like me looking for help on their homework and school projects. And it's even SADDER that we all know that most of these students want to make some sort of a zombie / wandering / roaming monster knock-off. Yet nobody has even bothered to go out of their way to make one of these to help everyone else out.

                            Hey, I'm just telling it how it is. Who cares if everyone wants to make zombie knock-offs. Zombies never get old, because they are never dead. GET IT?

                            Because they are UNDEAD!!!!

                            Summary:

                            What does this do?

                            It makes a zombie that will wander around a certain area, when it sees the player it will move toward them and try to attack. If the player runs away and the zombie can no longer see them, it will go back to its wandering state.

                            Step 1: Create a custom monster pawn class extending off Monster class

                            Step 2: Create a base custom monster controller extending off MonsterController

                            Step 3: Create another custom monster controller extending your base controller (this is the one that controls the actual roaming)

                            Step 4: Create the zombie itself

                            Code (just fill in all the stuff between the *stars* to work with your stuff):

                            Step 1 Code:

                            Code:
                            //=====================================================
                            // You only need to edit one line of code here, but this is important
                            // so that when you spawn your zombies that they know to use your
                            // controller instead of MonsterController
                            //=====================================================
                            
                            class *YOUR_ZOMBIE_PAWN_CLASS* extends Monster;
                            
                            
                            defaultproperties
                            {
                            ControllerClass=Class'*YOUR_BASE_ZOMBIE_CONTROLLER*'
                            }
                            Step 2 Code:
                            Code:
                            class *YOUR_BASE_ZOMBIE_CONTROLLER* extends MonsterController;
                            
                            var float lastDistance;
                            //  this function will get the node furthest from an actor
                            //  It is used by the run state in order to have a destination to run to
                            var array<PathNode> nodeArray;
                            var int myArrayIndex;
                            
                            function getNodes()
                            {
                              local int newArrayLength;
                              local PathNode arrayNode;
                              local int arrayIndex;
                            
                              newArrayLength = 0;
                              arrayIndex = 0;
                            
                              foreach AllActors (class'PathNode', arrayNode)
                              {
                                newArrayLength++;
                              }  // end for
                              nodeArray.Length = newArrayLength;
                            
                              // now populate the array
                              foreach AllActors (class'PathNode', arrayNode)
                              {
                                nodeArray[arrayIndex] = arrayNode;
                                arrayIndex++;
                              }  // end for
                            }  // end getNodes
                            
                            function PathNode closestNode(Actor mySelf)
                            {
                              local PathNode farNode;
                              local PathNode pathNode;
                              local float thisDistance;
                              local float holdingDistance;
                              local float xDistance;
                              local float yDistance;
                            
                              // now get the first distance to use as an example
                              thisDistance = 10000;
                            
                              // in the for loop, get the distance from the node the actor
                              // if the distance is greatest so far, set that pathNode
                              // equal to the furthest node
                              foreach AllActors (class'PathNode', pathNode)
                              {
                                xDistance = abs(mySelf.Location.X - pathNode.Location.X);
                                yDistance = abs(mySelf.Location.Y - pathNode.Location.Y);
                                holdingDistance = xDistance+yDistance;
                            
                                if((holdingDistance < thisDistance)&&(holdingDistance>0)&&(holdingDistance!=lastDistance))
                                {
                                  thisDistance = holdingDistance;
                                  farNode = pathNode;
                                }  // end else if
                              }  // end for
                              lastDistance = thisDistance;
                              return farNode;
                            }  // end getClosestNode
                            // a function that will return whether the bot it close to the node
                            function bool isClose(Actor closeActor)
                            {
                              local float holdingDistance;
                              local float xDistance;
                              local float yDistance;
                            
                              xDistance = abs(Pawn.Location.X - closeActor.Location.X);
                              yDistance = abs(Pawn.Location.Y - closeActor.Location.Y);
                              holdingDistance = sqrt((xDistance*xDistance)+(yDistance*yDistance));
                              if(holdingDistance <= 30)
                                return true;
                              else
                                return false;
                            }  // end isClose
                            
                            //  this function will get the node furthest from an actor
                            //  It is used by the run state in order to have a destination to run to
                            function PathNode furthestNode(Actor mySelf)
                            {
                              local PathNode farNode;
                              local PathNode pathNode;
                              local float thisDistance;
                              local float holdingDistance;
                              local float xDistance;
                              local float yDistance;
                            
                              // now get the first distance to use as an example
                              thisDistance = 0;
                            
                              // in the for loop, get the distance from the node the actor
                              // if the distance is greatest so far, set that pathNode
                              // equal to the furthest node
                              foreach Region.Zone.ZoneActors (class'PathNode', pathNode)
                              {
                                xDistance = abs(mySelf.Location.X - pathNode.Location.X);
                                yDistance = abs(mySelf.Location.Y - pathNode.Location.Y);
                                holdingDistance = xDistance+yDistance;
                            
                                if((holdingDistance > thisDistance))
                                {
                                  thisDistance = holdingDistance;
                                  farNode = pathNode;
                                }  // end else if
                              }  // end for
                              return farNode;
                            }  // end getClosestNode
                            state Dead
                            {
                            ignores SeePlayer, EnemyNotVisible, HearNoise, ReceiveWarning, NotifyLanded, NotifyPhysicsVolumeChange,
                            		NotifyHeadVolumeChange,NotifyLanded,NotifyHitWall,NotifyBump;
                            
                            	function WhatToDoNext(byte CallingByte)
                            	{
                            	}
                            
                            	function Celebrate()
                            	{
                            	}
                            
                            	function SetAttractionState()
                            	{
                            	}
                            
                            	function EnemyChanged(bool bNewEnemyVisible)
                            	{
                            	}
                            
                            	function WanderOrCamp(bool bMayCrouch)
                            	{
                            	}
                            
                            	function Timer() {}
                            
                            	function BeginState()
                            	{
                            		Destroy();
                            		return;
                            	}
                            
                            Begin:
                            	if ( Level.Game.bGameEnded )
                            		GotoState('GameEnded');
                            	Sleep(0.2);
                            TryAgain:
                            	if ( UnrealMPGameInfo(Level.Game) == None )
                            		destroy();
                            	else
                            	{
                            		Sleep(0.25 + UnrealMPGameInfo(Level.Game).SpawnWait(self));
                            		LastRespawnTime = Level.TimeSeconds;
                            		Goto('TryAgain');
                            	}
                            
                            MPStart:
                            	Sleep(0.75 + FRand());
                            	Goto('TryAgain');
                            }
                            
                            defaultproperties
                            {
                            PawnClass=Class'*YOUR_ZOMBIE_PAWN_CLASS*' // you need to make sure that your controller and your custom pawn class link up
                            }
                            Step 3 Code:
                            Code:
                            class *YOUR_EXTENDED_CONTROLLER* extends *YOUR_BASE_ZOMBIE_CONTROLLER*;
                            
                            // global variables.
                            var NavigationPoint destinationNode;        // the node that the zombie will run to
                            var NavigationPoint RoamDestination; // the Destination for Roaming
                            
                            var *YOUR_PLAYER_CONTROLLER* victim;    // this is the victim's / player's controller. Whatever class you want the zombie to target.
                            
                            
                            function Tick(float DeltaTime)
                            {
                              super.Tick(DeltaTime);
                            }
                            
                            function NotifyTakeHit(pawn InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum)
                            {
                              if ( Pawn.Health <= 0 )
                                 return;
                            }
                            
                            function Destroyed()
                            {
                            	Super.Destroyed();
                            }
                            
                            
                            function PostBeginPlay()
                            {
                            	Super.PostBeginPlay();
                            	InitializeEnemy();
                                GotoState('Roaming','Begin');
                            }
                            
                            function Restart()
                            {
                                     Super.Restart();
                                     InitializeEnemy();
                                GotoState('Roaming','Begin');
                            }
                            
                            event SeePlayer(Pawn SeenPlayer)
                            {
                              Enemy = SeenPlayer;
                            }
                            
                            
                            //  make sure that the controller is controlling your zombie
                            
                            function TakeControlOf(Pawn aPawn)
                            {
                                if ( Pawn != aPawn )
                                {
                                    aPawn.PossessedBy(self);
                                    Pawn = aPawn;
                                }
                                //numberHits = 0;
                                GotoState('Roaming','Begin');
                            }  // end TakeControlOf
                            
                            
                            
                            function SetPawnClass(string inClass, string inCharacter)
                            {
                                local class<Pawn> pClass;
                                pClass = class'*YOUR_ZOMBIE_PAWN_CLASS*'; // if you don't have a custom one just use the class 'Monster'
                                if ( pClass != None )
                                    PawnClass = pClass;
                            }
                            
                            function InitializeEnemy()
                            {
                                local *YOUR_PLAYER_CONTROLLER* v;
                            
                                Enemy = none;
                            
                                foreach AllActors(class'*YOUR_PLAYER_CONTROLLER*', v) {
                                    victim = v;
                                    Enemy = victim.Pawn;
                                    break;
                                }
                            }
                            
                            
                            /*
                            *  AI states
                            *
                            */
                            
                            state Roaming
                            {
                                ignores EnemyNotVisible, SeePlayer, HearNoise;
                            
                                function Tick( float DeltaTime ) {
                              
                                   if (victim == none)
                                      return;     
                            
                                   if ( Enemy == none || Enemy != victim.Pawn ) {
                                      Enemy = victim.Pawn;
                                      return;
                                   }
                            
                                   if ( FastTrace( Enemy.Location, self.Location ) ) {
                                      GoToState('StakeOut','WaitforAnim');
                                   }
                                }
                            
                                event bool NotifyBump(actor Other)
                            	{
                            		if (Other == Enemy)
                            		{
                            		    GoToState('StakeOut','WaitforAnim');
                            			return false;
                            		}
                                    return true;
                            	}
                            
                            Begin:
                              destinationNode = closestNode(self);
                              MoveTarget = FindPathToward(destinationNode);
                              MoveToward(MoveTarget);
                              sleep(0.5);
                              GotoState('Roaming','Begin');
                            }
                            
                            defaultproperties
                            {
                            
                            }
                            Step 4 Code:
                            Code:
                            //====================================================
                            // This is just a generic monster based off Krall, modified to be a melee fighter
                            // All that is important here is that in the PostBeginPlay()
                            // function of your monster that you tell it to go the roaming state
                            //====================================================
                            
                            class *YOUR_ZOMBIE* extends *YOUR_ZOMBIE_PAWN_CLASS*
                                  placeable;
                                  
                            var bool bAttackSuccess;
                            var bool bLegless;
                            var bool bSuperAggressive;
                            var name MeleeAttack[5];
                            
                            
                            function PostBeginPlay()
                            {
                                Super.PostBeginPlay();
                            
                                bSuperAggressive = (FRand() < 0.5);
                            
                                Controller.GotoState('Roaming', 'Begin'); // Zombie roams and waits for player to come into view
                            }
                            
                            function RangedAttack(Actor A)
                            {
                            	MeleeDamageTarget(50, 21000 * Normal(Controller.Target.Location - Location));  // adjust parameters for whatever damage you want. "50" is how much damage is inflicted right now
                            }
                            
                            //======================================================================================
                            // Copy and pasted Krall code, nothing in this section has been changed at all
                            //======================================================================================
                            
                            replication
                            {
                                unreliable if( Role==ROLE_Authority )
                            		bLegless;
                            }
                            
                            function StrikeDamageTarget()
                            {
                            	if (MeleeDamageTarget(20, 21000 * Normal(Controller.Target.Location - Location)))
                            		PlaySound(sound'hit2k',SLOT_Interact);
                            }
                            
                            function vector GetFireStart(vector X, vector Y, vector Z)
                            {
                                return Location + 0.9*X - 0.5*Y;
                            }
                            
                            function SpawnShot()
                            {
                            	FireProjectile();
                            }
                            
                            function PlayTakeHit(vector HitLocation, int Damage, class<DamageType> DamageType)
                            {
                            	local rotator r;
                            
                            	if ( bLegless )
                            		return;
                            		
                            	if ( (Health > 30) || (Damage < 20) || (HitLocation.Z > Location.Z) )
                            	{
                            		Super.PlayTakeHit(HitLocation, Damage, DamageType);
                            		return;
                            	}
                            	r = rotator(Location - HitLocation);
                            	CreateGib('lthigh',DamageType,r);
                            	CreateGib('rthigh',DamageType,r);
                            	
                            	bWaitForAnim = false;
                            	SetAnimAction('LegLoss');
                            }
                            
                            simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
                            {
                            	Super.PlayDying(DamageType,HitLoc);
                                
                                if ( bLegless )
                            		PlayAnim('LeglessDeath',0.05);
                            }
                            
                            simulated event SetAnimAction(name NewAction)
                            {
                            	local int i;
                            	
                            	if ( NewAction == 'LegLoss' )
                            	{
                            		bWaitForAnim = false;
                            		GroundSpeed = 100;
                            		bCanStrafe = false;
                            		bMeleeFighter = true;
                            		bLegless = true;
                            		SetCollisionSize(CollisionRadius,16);
                            		PrePivot = vect(0,0,1) * (Default.CollisionHeight - 16);
                            
                            		for ( i=0; i<3; i++ )
                            		{
                            			MovementAnims[i] = 'Drag';
                            			SwimAnims[i] = 'Drag';
                            			CrouchAnims[i] = 'Drag';
                            			WalkAnims[i] = 'Drag';
                            			AirAnims[i] = 'Drag';
                            			TakeOffAnims[i] = 'Drag';
                            			LandAnims[i] = 'Drag';
                            			DodgeAnims[i] = 'Drag';
                            		}
                            		IdleWeaponAnim = 'Drag';
                            		IdleHeavyAnim = 'Drag';
                            		IdleRifleAnim = 'Drag';
                            		IdleRestAnim = 'Drag';
                            		IdleCrouchAnim = 'Drag';
                            		IdleSwimAnim = 'Drag';
                            		AirStillAnim = 'Drag';
                            		TakeoffStillAnim = 'Drag';
                            		TurnRightAnim = 'Drag';
                            		TurnLeftAnim = 'Drag';
                            		CrouchTurnRightAnim = 'Drag';
                            		CrouchTurnLeftAnim = 'Drag';
                            	}
                            	Super.SetAnimAction(NewAction);
                            }
                            
                            function PlayVictory()
                            {
                            	if ( bLegless )
                            		return;
                            	Controller.bPreparingMove = true;
                            	Acceleration = vect(0,0,0);
                            	bShotAnim = true;
                                PlaySound(sound'staflp4k',SLOT_Interact);	
                            	SetAnimAction('Twirl');
                            	Controller.Destination = Location;
                            	Controller.GotoState('TacticalMove','WaitForAnim');
                            }
                            
                            function ThrowDamageTarget()
                            {
                            	bAttackSuccess = MeleeDamageTarget(30, vect(0,0,0));
                            	if ( bAttackSuccess )
                            		PlaySound(sound'hit2k',SLOT_Interact);
                            }
                            
                            function ThrowTarget() 
                            {
                            	if ( bAttackSuccess && (VSize(Controller.Target.Location - Location) < CollisionRadius + Controller.Target.CollisionRadius + 1.5 * MeleeRange) )
                            	{
                            		PlaySound(sound'hit2k',SLOT_Interact);
                            		if (Pawn(Controller.Target) != None)
                            		{
                            			Pawn(Controller.Target).AddVelocity( 
                            				(50000.0 * (Normal(Controller.Target.Location - Location) + vect(0,0,1)))/Controller.Target.Mass);
                            		}
                            	}
                            }
                            
                            //==============================
                            // End copy and pasted Krall code
                            //==============================
                            
                            
                            defaultproperties
                            {
                            
                                 Health = 100    // whatever health
                                 bMeleeFighter = true;  // so it doesn't shoot projectiles
                                 bCanStrafe=true
                            
                                 DrawScale=1
                                 Mesh=VertMesh'SkaarjPack_rc.KrallM'
                                 
                                 MovementAnims(0)="RunF"
                                 MovementAnims(1)="RunF"
                                 MovementAnims(2)="RunF"
                                 MovementAnims(3)="RunF"
                                 MeleeAttack(0)="Strike1"
                                 MeleeAttack(1)="Strike2"
                                 MeleeAttack(2)="Strike3"
                                 MeleeAttack(3)="Strike1"
                                 MeleeAttack(4)="Strike1"
                                 AirAnims(0)="RunF"
                                 AirAnims(1)="RunF"
                                 AirAnims(2)="RunF"
                                 AirAnims(3)="RunF"
                                 TakeoffAnims(0)="RunF"
                                 TakeoffAnims(1)="RunF"
                                 TakeoffAnims(2)="RunF"
                                 TakeoffAnims(3)="RunF"
                            }
                            FINAL NOTE:

                            YOU NEED PATHNODES IN YOUR MAP! If you have no pathnodes or "apple roads" (one of my instructors' terms for them) set up in your map, then the zombies won't roam much. The more pathnodes you set up, the more control you can have over how they roam.

                            I made this by modifying cknight52's code from this thread:

                            http://forums.epicgames.com/showthre...ghlight=zombie

                            Unfortunately the thread was never completed and it's about 3 years old.
                            This may seem like a stupid queston, but how do you make a zombie play "possom" and as soon as a player gets close, it come to life? and how do u make it react to sound/light?

                            Comment


                              #15
                              Do you really have to quote an entire HUGE post just to ask a question?

                              Comment

                              Working...
                              X