sega 500 creating a new game jeff “ezeikeil” giles [email protected] jgiles

79
Sega 500 Creating a New Game Jeff “Ezeikeil” Giles [email protected]

Upload: kelly-harold-ryan

Post on 13-Dec-2015

219 views

Category:

Documents


1 download

TRANSCRIPT

Sega 500

Creating a New Game

Jeff “Ezeikeil” [email protected]://gamestudies.cdis.org/~jgiles

So far…

Ok, we’ve looked at how to access properties of the game to influence how it looks and plays by modifying the HUD and passing around the Udamage.

In short, we looked at the pieces…not the whole.

Today…

We’re going to build on what we learnt so far and further modify the gametype rules and objects to create a complete new game.

That’s right…a complete gametype.

So here’s the plan

We’re going to run with the idea of the passing the UDamage around between players, but we’re going to change the *how*.

First off, the player who is killed first will drop the doubler for others to pickup.

So here’s the plan

The player who picks up the doubler is the only player who will be able to score points.

Anytime a player is killed, the doubler is recharged and informs all players in the game.

So here’s the plan

There will only ever be one of these *special* doublers in play at one time…we’re going to leave the regular pickup in game.

Anytime a player who has, the doubler is killed, all players in the game are informed that it has been dropped .

So here’s the plan

And, we’re going to modify the bot pathing information to give a high priority to acquiring this new damage doubler.

Getting started

As with any project, we need to figure out what we need to build.

Since we know the functionality we’re after, what kind things do we need to build or modify?

Anyone?….anyone?

Getting started

We can roll this all up into a set of 4 files to cover our game rules, our menu interface, a new item ( the doubler ) the messages.

The new Gametype

This one is going to be similar to the one we’ve already created, but we’re going to modify it to influence the scoring. In short have the double to score points.

This is also where we will cause the Doubler to be “dropped”.

The New UDamage

We’ll have to create a new UDamage class so that is plays nicely in our world.

It can’t respawn It sends messages to the players

The New Message class

Sending messages in UT203 is done a bit differently to allow for network optimization.

In effect, we don’t send a sting across the wire, but an index value…more on this shortly

The New int file

Nothing really fancy here, just creating a new int file so that our game shows up in the menu as an option.

Building the Game

Just for good OOP I’m building from scratch. Create a new package for our code and in goes our gametype.

To keep it crazy, we’re going to derive from xDeathMatch.

Building the Game

This will create the basics game definition, no rules yet…

So lets get the int done so that we can select it from the menu and play test.

[Public]Object=(Class=Class,MetaClass=Engine.GameInfo,Name=Eze.EzeGame,Description="DM|King of the hill MK2|xinterface.Tab_IADeathMatch |xinterface.MapListDeathMatch|false")

Building the Game

And at this point, it will play like a regular deathmatch.

So, lets go about changing that.

Building the Game

As always, start simple.

We’re going to override the Killed function again so that whenever a player is a doubler is created into the game.

Building the Game

So what’s this Spawn thingy and how’s it work?

function Killed( Controller Killer, Controller Killed, Pawn KilledPawn, class<DamageType> damageType )

{ spawn(class'UDamagePack',,,KilledPawn.location); super.Killed( Killer, Killed, KilledPawn, damageType );}

Building the Game

Well, Spawn is defined in the actor class as:

native(278) final function actor Spawn(

class<actor> SpawnClass,optional actor SpawnOwner,optional name SpawnTag,optional vector SpawnLocation,optional rotator SpawnRotation

);

Building the Game

Well, isn’t that just dandy, fortunate the header tells us a bit more:

Spawn an actor. Returns an actor of the specified class, not of class Actor (this is hardcode in the compiler). Returns None if the actor could not be spawned

Building the Game

Ah, ok…so a spawn a UDamage pickup….cool.

But what’s this optional keyword stuff and how do I get it to be created where I want it?

Building the Game

Well, time to consult the good old UDN:

With the "optional" keyword, you can make certain function parameters optional, as a convenience to the caller. For Unreal Script functions, optional parameters which the caller doesn't specify are set to zero. For native functions, the default values of optional parameters depends on the function. For example, the Spawn function takes an optional location and rotation, which default to the spawning actor's location and rotation.

Building the Game

In other words, you don’t have to pass in optional parameters…cool.

So in our spawn call, we’re telling to create the new object at the pawns location.

spawn(class'UDamagePack',,,KilledPawn.location);

Building the Game

The result is, whenever you kill a bot, a doubler is spawned at that location.

Cool….nes-pas?

Building the Game

But this causes a small problem, very quickly we get a sea of damage doublers to play with.

Not exactly what we want, time to add some game logic to fix that.

Building the Game

Actually, the logic that we’re adding is surprisingly simple.

We’re going to use a similar method to the last king of the hill gametype.

Building the Game

In our global space for the gametype, we need a handle to the pawn who has the doubler:

var pawn HasUDam;

Building the Game

And then, in the killed function, we add some logic…

if(killedpawn == HasUDam) { HasUDam=none; }

if(HasUDam == none) { spawn(class'UDamagePack',,,KilledPawn.location); } else { HasUdam.EnableUDamage(30); }

If the killed pawn had it, it’s now free for the taking

No one has it so drop one with this kill

Someone else has made a kill

Building the Game

And this should work…But…like always, there’s a problem

Any ideas?

Anyone?….anyone?

Building the Game

The damage doublers respawn…and this gonzo’s our “one doubler” rule.

NUTZ!

Building the Game

Don’t panic, we’re not dead yet. All this means is that we need a customized damage doubler class…not a problem.

All we need to do is derive a new class from the current damage doubler.

Building the Game

Digging this fella out of the class tree, we find it here, under pickups and derive a class off of it.

Building the Game

And there are 2 areas of interest the defaults and the touch function.

Don’t’ worry about the “auto state Pickup” line for now. We’ll be discussing states in great detail later on.

For the moment, it’s sufficient to know that the touch function gets called like any other function.

Building the Game

Copy and paste the entire state group from UDamagePack into your new class.

auto state Pickup{ function Touch( actor Other ) { local Pawn P;

if ( ValidTouch(Other) ) {

P = Pawn(Other);P.EnableUDamage(30);AnnouncePickup(P);SetRespawn();

} }}

Building the Game

In order to prevent the respawn, there’s one line we need to kill and one we need to add.

Comment out or delete the SetRespawn line.

function Touch( actor Other ){ local Pawn P;

if ( ValidTouch(Other) ) { P = Pawn(Other); P.EnableUDamage(30); AnnouncePickup(P); SetRespawn(); }}

Building the Game

And add a call to the destroyed function.

In effect, when the object is pickup up, the pickup item is removed from the game.

function Touch( actor Other ){ local Pawn P;

if ( ValidTouch(Other) ) { P = Pawn(Other); P.EnableUDamage(30); AnnouncePickup(P);

//SetRespawn(); Destroy(); }}

Building the Game

And in the defaults, we just change the pickup message

PickupMessage="Eze DOUBLE DAMAGE!"

Building the Game

And now going back into the gametype, we change the line where we spawn he UDamagepickup to spawn our own.

spawn(class'EzeUDam',,,KilledPawn.location);

Building the Game

HAZA!!!! It works!

Pickup messageBroadcast message…Well talk about this in a minute

Building the Game

Now, we need to do one last thing to ensure that we can’t have multiple EzeUDam’s in play.

In our game type, we create a global handle to it.

var EzeUDam dam;

Building the Game

And in our killed function, we modify it to check if anyone has it…

Remember, Spawn returns us an object.

if(HasUDam == none && dam == none){ //toss the UDamage in to the air dam=spawn(class'EzeUDam',,,KilledPawn.location);

Building the Game

And don’t forget to add this line to the EzeUdam’s Touch function.

What were doing here is accessing the gametype and setting this parameter to which pawn made the pickup.

EzeGame(level.Game).HasUDam=P;

Building the Game

All that this line does is access the current game and type cast it into our gametype so we can access the HasUDam parameter.

And, yes. We really should do some sanity checking here…Well cover this when we look at he “isa” function.

EzeGame(level.Game).HasUDam=P;

Building the Game

Great! Now we on ever have on in play.

But this is kind of boring…just spawning the pickup like that…

Now that we have a hold of the object, what say we manipulate it some…

Building the Game

Just by adding a few lines to this function, we can fling it into the air when the player is killed.

if(HasUDam == none && dam == none){ dam=spawn(class'EzeUDam',,,KilledPawn.location); dam.Velocity= 500*VRand(); dam.Velocity.Z= RandRange(500,800); dam.SetPhysics(PHYS_Falling);}

Building the Game

So what’s going on? Well, we’re changing some of its physics properties to give it a velocity

dam.Velocity= 500*VRand();dam.Velocity.Z= RandRange(500,800);

Building the Game

The VRand function returns to us a random direction. In fact a unit vector. Multiply this by a scalar to determine just where to toss it.

RandRange(500,800); gives us a number between 500 and 800 for the height. We over ride this since we don’t want to throw it downwards

Building the Game

And since we want to have this thing behave as thought there is gravity affecting it, we need to change it’s physics properties.

dam.SetPhysics(PHYS_Falling);

Building the Game

Now, all the physics types are defined in the actor class…as you can see there are just a few to choose from.

var(Movement) const enum EPhysics{

PHYS_None,PHYS_Walking,PHYS_Falling,PHYS_Swimming,PHYS_Flying,PHYS_Rotating,PHYS_Projectile,PHYS_Interpolating,PHYS_MovingBrush,PHYS_Spider,PHYS_Trailer,PHYS_Ladder,PHYS_RootMotion,

PHYS_Karma, PHYS_KarmaRagDoll,

PHYS_Hovering, PHYS_CinMotion,} Physics;

Building the Game

Also, although we can see the physics property in the editor, it’s defined as const so the assignment operator will not work.

You have to use the SetPhysics function.

Building the Game

And now to make the object land properly, we need to reset it’s physics value to PHYS_Rotating (it default) to make it spin properly.

Fortunately, there’s a Landed function defined for us…

Building the Game

This function gets called when an object stops moving…in this case when it lands.

Hence, we need to override this function too.

Building the Game

All we need to worry about here is to call the parent class where most of the functionality lives, and set the physics…

function Landed(vector HitNormal){ super.Landed(HitNormal); SetPhysics(PHYS_Rotating);}

Building the Game

Now ain’t that just a bit more interesting…

Body goes one way, the pickup another

Isn’t math grand

Building the Game

Lastly, we need to tell all the players that the doubler has been dropped/picked up/ recharged…whatever.

We send them a message to do so.

Building the Game

Now as I mentioned, the way this works is a *bit* different.

You don’t send the message itself across the wire but an index value. E.g. The message is ALREADY present on the other machine.

Building the Game

This has to do with network optimisations since it’s much smaller to send a single in value that it is an entire string of several characters.

Building the Game

From the LocalMessage header: LocalMessages are abstract classes which contain

an array of localized text. The PlayerController function

ReceiveLocalizedMessage() is used to send messages to a specific player by specifying the LocalMessage class and index. This allows the message to be localized on the client side, and saves network bandwidth since the text is not sent.

Building the Game

Actors (such as the GameInfo) use one or more LocalMessage classes to send messages.

The BroadcastHandler function BroadcastLocalizedMessage() is used to broadcast localized messages to all the players.

Building the Game

So what does this mean to us?

Well, we need to create a local message class, which we derive from LocalMessage.

Building the Game

In this class, we essentially have a couple of strings for our text and assign them in the defaults.

var localized string GottheDam;var localized string DroppedtheDam;var localized string recharged;

DefaultProperties{ GottheDam="The Doubler has been picked up!" DroppedtheDam="The Doubler has been picked Dropped!" recharged="The Doubler has been Recharged!" bFadeMessage=True PosY=0.7}

Building the Game

Now, we need a way of accessing this index value.

Localized messages come with a get sting function just for this…it’s really not much more than a switch statement.

Building the Game

Well play with the optional PlayerReplicationInfo parameters at a later date…for now, we just need to know that they are there.

static function string GetString(optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2, optional Object OptionalObject)

Building the Game

And here’s my function body…

switch(Switch){case 0:

return Default.GottheDam;break;

case 1:return Default.DroppedtheDam;break;

case 2:return Default.recharged;break;

}

Building the Game

In effect, when this function get called, it receives an index and returns a specific string…AKA our message.

Building the Game

That being said, we just add a line to each important function

In the EzeUDam touch:

BroadcastLocalizedMessage( class'EzeGameMSG', 0 );

Building the Game

What this is doing is saying

“send a message to everyone…make it an EzeGameMSG and get the string from index 0”

BroadcastLocalizedMessage( class'EzeGameMSG', 0 );

Building the Game

Sends message index 0 to the screen

Building the Game

And then put one in the landed function for index 1 and in the gametype, one for index 2…when the doubler gets recharged.

Building the Game

Sweetness! We’re almost there…But there’s one last thing…The dumb bots run right past the pickup!

Needless to say, this is kinda lame.

Building the Game

So somehow, we need to add it to the navigation network.

And believe it or not, its an easy fix…a one liner as a matter of fact.

Building the Game

If you root through the EzeUDam’s parent classes, you’ll eventually find a native function in the pickup base class called AddToNavigation.

And it does just that, it adds this object to the navigation network.

Building the Game

So, I added this to my Landed function, which now looks like:

function Landed(vector HitNormal){ super.Landed(HitNormal); SetPhysics(PHYS_Rotating); AddToNavigation(); BroadcastLocalizedMessage( class'EzeGameMSG', 1 );}

Building the Game

And lastly, you may want to turn up the likelihood of the bots desire to pickup the EzeUDam.

To do so, in the EzeUDam Class, just turn up this parameter in the defaults

MaxDesireability=4.0 //default is 2.0

Building the Game

And lastly, we need too change the scoring.

This was a real simple override of the gametype.

Building the Game

In the DeathMatch class, there’s a function called score kill. All we need to do is add an if condition it.

function ScoreKill(Controller Killer, Controller Other){ if(Killer.Pawn== HasUDam) super.ScoreKill(Killer, Other);}

Building the Game

And now, only the player with the doubler can score points on a kill.

Voila!

One complete gametype with affect on the game rules and Bot AI.

How you found this interesting…

Next time

We’re going to start manipulating the HUD some more to build our radar.