1. Trang chủ
  2. » Công Nghệ Thông Tin

Building XNA 2.0 Games- P14 doc

30 281 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Building XNA 2.0 Games
Trường học University of Sample Studies
Chuyên ngành Computer Science
Thể loại Thesis
Năm xuất bản 2023
Thành phố Sample City
Định dạng
Số trang 30
Dung lượng 769,55 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

The client spawns the particle and flags it for a network send.. At the next network write, the client sends the particle and unchecks its flag, signifying that it no longer needs to be

Trang 1

Network Game Interaction

The following is our NetGame class, which concerns itself with message composing, sending, receiving, and parsing:

public class NetGame

{

NetPlay netPlay;

public const byte MSG_SERVER_DATA = 0;

public const byte MSG_CLIENT_DATA = 1;

public const byte MSG_CHARACTER = 2;

public const byte MSG_PARTICLE = 3;

public const byte MSG_END = 4;

PacketWriter writer;

PacketReader reader;

float frame;

public float frameTime;

Our constructor, besides taking a reference to our overarching NetPlay class, initializes our PacketReader and PacketWriter We’ll be using the writer and reader to send and receive messages, respectively

public NetGame(NetPlay _netPlay)

{

netPlay = _netPlay;

writer = new PacketWriter();

reader = new PacketReader();

at too great a speed, it will just pile up somewhere The goal is to get the perfect amount across with the perfect timing, so that the players don’t notice anything whatsoever That’s a little

Trang 2

easier said than done Since we’re just testing a basic game, we don’t need to concern ourselves

with the problem If you plan on making this game available over the Live platform, this is a

problem you will need to tackle

For the time being, we’ll set it up to send data every 0.05 second, or at 20 frames per

second This is too fast for most, if not all, Live matches, but will work fine for System Link

As the host, we’ll send data about our own character as well as every non-null character

other than index 1 The character at index 1 is controlled by the client This is a fairly simple

client/server setup, in that the clients all report to a single server, and then the server relays

data back to all the clients This works in most cases; however, you may find that a more

peer-to-peer setup works better

c[0].WriteToNet(writer);

for (int i = 2; i < c.Length; i++)

if (c[i] != null)

c[i].WriteToNet(writer);

After our characters have been written, we’ll write particles, finish off with an end-message

byte, and send our data off with SendDataOptions.None, meaning we don’t care if it reaches its

destination or it arrives at its destination out of order

Likewise, our client writes the character only at index 1 (himself), as well as any particles

he may have spawned (more on the particles in the “Particle Net Data” section later in this

chapter)

Trang 3

c[1].WriteToNet(writer);

pMan.NetWriteParticles(writer);

writer.Write(MSG_END);

gamer.SendData(writer, SendDataOptions.None);

}

}

}

If any data has been sent to us and is ready for processing, gamer.IsDataAvailable will be true if (gamer.IsDataAvailable) {

NetworkGamer sender; gamer.ReceiveData(reader, out sender); if (!sender.IsLocal) {

byte type = reader.ReadByte(); Here’s a tricky bit: it’s the host’s responsibility to send out data on all currently active (non-null) characters So, in order to handle character death, we’ll set a flag in all characters to false and check it again after processing the update Any character not updated by the message will be presumed dead and made null if (netPlay.joined) {

for (int i = 0; i < c.Length; i++) if (i != 1) if (c[i] != null) c[i].receivedNetUpdate = false; }

We enter a while loop in which we process each portion of the incoming message until we read a MSG_END All bit-by-bit processing is done within the classes that are updated bool end = false; while (!end) {

byte msg = reader.ReadByte(); switch (msg) {

case MSG_END:

end = true;

break;

case MSG_CHARACTER:

Trang 4

When we read a character, we’ll read off the first three fields from this method before

passing the reader to the character to finish processing the update These three fields— defID,

team, and ID—are used to create the character if this is the first time the reader has seen the

This is the first time we use NetPacker, which we’ll define in the next section As we’ve said,

essentially, NetPacker’s function is to pack and unpack big data types into small data types Here,

we see an 8-bit signed byte (Sbyte) being turned into a 32-bit integer This will be fine as long

as we never have any defID, team, or ID fields greater than 127 It’s easy to just use 32-bit

inte-gers for everything in our game, but when bandwidth is at a premium, we take what we can get!

For parsing particles, we first read the type and a bit to specify whether it’s a background

particle (remember that we use this field for our AddParticle() method)

Trang 5

We’re being a bit sneaky here: particles are sent only when they are created All particles

that aren’t owned by the client are created and sent by the host, while all particles that are

owned by the client (for example, bullets that the client spawns) are sent from the client to the server At the same time, it’s important for the server to abort any client-owned particles that the game might try to spawn outside a network read Likewise, the client must abort all particle spawns that it does not own unless they come through the network

The client will iterate through its characters again to see if any have not been updated in the last update, killing off those that have not been updated

Finally, here’s our GetGamer() method It uses a bit of trickery to figure out which

LocalNetworkGamer is at player index 1

private LocalNetworkGamer GetGamer()

{

foreach (LocalNetworkGamer gamer in

netPlay.netSession.LocalGamers)

Trang 6

Now we get to NetPacker, whose function is to turn big data types into small data types and vice

versa It works fine as long as the data we’re looking at does not go beyond the bounds of the

smaller data types

Take a look at the first function, TinyFloatToByte() and its counterpart, ByteToTinyFloat():

Trang 7

We’re also handling small floats, medium (mid) floats, and big floats Because the range of

a short is –32767 and 32767, our value conversion ranges are as shown in Table 12-1

If we keep using the best conversions (we’ll have to play it by ear), we’ll maximize width efficiency and minimize precision loss

public static short BigFloatToShort(float f)

Table 12-1. NetPacker Conversion Ranges

Big float –32767 32767

Mid float –6553 6553

Small float –1638 1638

Tiny float 0 1

Trang 8

public static float ShortToMidFloat(short s)

Character Net Data

Let’s move on to the write and read functions for Character We’ll be sending references to a

packet reader and writer for ReadFromNet() and WriteToNet(), respectively Here’s WriteToNet():

public void WriteToNet(PacketWriter writer)

Trang 9

writer.Write(keyRight);

writer.Write(keyLeft);

writer.Write(NetPacker.IntToShort(HP));

}

Take a look at how ReadFromNet() differs from WriteToNet():

public void ReadFromNet(PacketReader reader)

There’s a bit of noticeable waste here Fields like defID, team, and ID don’t change every frame, if ever If we wanted to optimize more, we would include these as a separate message This could get a bit hairy though We would need to flag new characters to make sure we send out this data, we would need to account for special cases where packets arrived out of order and the recipient received the character location data before the character ID data, and so on and so forth

Particle Net Data

Getting our particles in shape is a much uglier task We broke down our strategy for dealing with particles in a multiplayer setting a few pages earlier, but let’s lay it down again in a series

of scenarios:

Trang 10

Client adds particle that client owns: This happens when the client fires bullets, swings his

wrench, or creates any other particle where owner = 1 The client spawns the particle and

flags it for a network send At the next network write, the client sends the particle and unchecks

its flag, signifying that it no longer needs to be sent The server receives and spawns the

particle

Client adds particle that client does not own: This happens when the client’s game tries

to spawn explosions, blood, and so on its own For instance, if a bullet hits a zombie, the

game will try to spawn blood However, if the server doesn’t think the bullet hit the zombie,

we don’t want blood being spawned on the client and not on the server The server is final

arbiter for particles that the client does not own The client does not spawn the particle

Hopefully, at the next network update, the client will receive the particle data that it tried

to spawn This time, because the data is from a network source, the client will create the

particle

Server adds particle that client owns: This happens when a client tries to create a particle,

like firing a bullet, on the server machine Because we’re constantly updating all characters

on both machines, and because the FireTrig() call in the character is called from the

update, a client updated on the host will attempt to fire bullets if in the right animation

However, if there’s a bit of a network hiccup, the server could end up seeing the client skip

over the fire frame or hit it twice, so we want to make sure we spawn bullet particles only

when the client sends them In this case, the server does not spawn the particle Again,

hopefully at the next network update, the server will receive the particle data from the

client and create it

Server adds a particle that client does not own: This happens when the server spawns

anything that is not owned by the client The server spawns the particle and flags it for

a network send At the next network write, the server sends the particle and unchecks its

flag, signifying that it no longer needs to be sent The client receives and spawns the

particle

The big omission in this is that particle data is sent only at creation and is not updated We

figured we could get away with this for now—we don’t have any particles change trajectory

mid-flight If we included homing rockets, collectable items, or anything else that lingered for

longer than a second, we would definitely need to implement some sort of particle-updating

messaging functionality

To allow particles to be sent and received, we’ll need particle-specific code in every particle

class We’ll put a virtual NetWrite() method in the base Particle class, which will be

over-loaded from each class that extends Particle, and as you may have noticed from the NetGame

code, we’ll be making a new constructor for every type of particle that will accept a PacketReader

We’ll also define some constant values for our particle types We use these from NetGame as

well Let’s start in Particle

public const byte PARTICLE_NONE = 0;

public const byte PARTICLE_BLOOD = 1;

public const byte PARTICLE_BLOOD_DUST = 2;

public const byte PARTICLE_BULLET = 3;

public const byte PARTICLE_FIRE = 4;

Trang 11

public const byte PARTICLE_FOG = 5;

public const byte PARTICLE_HEAT = 6;

public const byte PARTICLE_HIT = 7;

public const byte PARTICLE_MUZZLEFLASH = 8;

public const byte PARTICLE_ROCKET = 9;

public const byte PARTICLE_SHOCKWAVE = 10;

public const byte PARTICLE_SMOKE = 11;

a PacketReader which, when read, will reveal all of these values

public Blood(Vector2 loc,

Trang 12

As with our characters, we do a bit of extra writing here We need to specify the message

type, particle type, and background bit When we did our reading in the constructor, we just

started at the location because the previous three items are read in NetGame

public override void NetWrite(PacketWriter writer)

Let’s take a look at another one:

public Fog(PacketReader reader)

Trang 13

this.traj = new Vector2(80f, -30f);

All Fog really needed was location data

Because all of the particles have different constructors and need to be constructed with

different data, we (groan) must add this overloaded constructor/overloaded NetWrite() combo

for every last particle What’s more, one misstep along the way will mess up everything If

we try to read the wrong amount of bits, every subsequent read will have an incorrect offset, leading to weird performance (most likely in the form of crashes) When we implemented this,

we started with just Fire, then tried to implement another one, caused a crash, fixed the crash, and moved on One suggestion to change this would be to keep track of how many bits we have read in and at what offset the new particle needs to be This way, we could fix reading errors as they happen However, because of time issues, we will just get down and dirty while hoping we haven’t made a mistake

We need to update ParticleManager First off, we add an overload to AddParticle() to allow us to add a particle specified as sent through the network

public void AddParticle(Particle newParticle, bool background)

Trang 14

Here’s where we handle the scenarios laid out a few pages ago It looks much shorter in code!

We’ll send off any particles flagged for a send in NetWriteParticles(), and then unflag them

public void NetWriteParticles(PacketWriter writer)

We’ll need to round up a few more odds and ends before everything is ready for prime

time We need to add player 2’s health to the HUD We need to give player 2 a different skin so

that we don’t end up with two clones running around together We should turn off bucket

monster spawning from the client side Lastly, we need to plug everything into Game1

Trang 15

Adding the Second Player to the HUD

In HUD.Draw(), we modify our heart-drawing algorithm a little to turn it into a loop that allows for two players Remember our floating HP value that we used for a smoothly adjusting health bar? We had only one We need two All we need to do is declare it as a float array of size 2 and change all affected code (Update() would be a good place to start)

Our heart-drawing algorithm in HUD.Draw() is modified like this:

for (int p = 0; p < Game1.players; p++)

{

float fProg = fHP[p] / (float)character[p].MHP;

float prog = (float)character[p].HP / (float)character[p].MHP;

Here’s a tremendously ugly draw call:

sprite.Draw(spritesTex, new Vector2(t, 66f),

Trang 16

Rectangle(i * 32 + (int)(32f * (1f - ta)),

The two big conditionals involve the source rectangle and the center vector The first

conditional chooses between the rectangle we were using originally (for player 1), in which the

width scales as the heart changes sizes, and a rectangle for player 2, in which the x coordinate

shifts and the width scales.

The second conditional is required for player 2’s heart This causes the center to shift as

well While changing the width of the hearts on player 1’s health bar involved changing only the

source rectangle width, doing this for player 2 involves changing the source rectangle width

and x coordinate, as well as the x coordinate of the center vector

Giving the Second Player a Skin

We need to get a new skin for player 2 We’ll call him Esteban Esteban is a well-seasoned

zombie smasher He wears a hoodie and looks slightly emo We made some new images: head,

torso, and legs He can use a wrench and revolver as well for now The new images are shown

in Figure 12-6

Figure 12-6. Player 2 images

Ngày đăng: 01/07/2014, 22:20

TỪ KHÓA LIÊN QUAN

w