Blog

WiX XNA Installer 3.0

in

This past week I've been working on my WiX XNA installer template again because I really wanted to integrate installer generation into my continuous integration builds. That way, I can hand test versions to friends without explaining in detail how to get it to run and it's one less worry I have when I release the game.

After some FAQ reading and some questions on the XNA Forums, I had the certainty that XNA 3.0 can be deployed with .NET 2.0 only (if you change your project configurations to target .NET 2.0). This is good news because the .NET 3.5 installer is huge and, on a fresh system, I've had about a 1 in 10 success quote of the installer finishing without an error, so my trust in the .NET 3.5 installer is completely shattered.

Aaron Stebner also revealed to me how I can unpack (or, to be precise, how to create a administrative install of) the XNA 3.0 redistributable (see here). This allowed me to recompress the files, reducing the redistributable's size from 7.61 MB to 5.5 MB, including the DirectX runtime files, XNA Framework 3.0 and the Windows Installer CA DLL to integrate the installation into my .msi file.

I am thinking about leaving out the .NET Framework redistributable completely for several reasons:

  • First, with 22.4 MB, it's huge. And that's only the 32 bit installer, if you want to support Windows XP x64 (the only 64 bit OS without .NET 2.0 preinstalled), make that 45.2 MB.
  • Second, while I'm definitely targeting .NET 2.0 as long as I can, others interested in using the installer template for their games may want to use .NET 3.5. This would not only add another level of complexity to the installer, but also, at 231.5 MB, the .NET 3.5 SP1 redistributable is prohibitively large (since it includes the x86, x64 and IA64 runtimes).
  • Third, if you wish to include the .NET Framework 2.0 or .NET Framework 3.5 for your players, you can still use a traditional bootstrapper (that's how most other installers including InstallShield and InstallAware do it).
  • Finally, users may already have obtained .NET in one way or another. Vista and Windows 7 have it preinstalled and many applications install it.

When the installer detects that .NET is not installed, it will display a friendly page to the user, explaining (very short) what .NET is and that it doesn't slow down their computers, providing a hyperlink to where the user can download .NET. I think the explanation page is neccessary because there's still resistance in uninformed user circles, to which an indie game consumer can very well belong.

Facts

So, in short, this is what the new installer experience will be like:

  • The installer will have an overhead of about 400 KB (with XNA detection only) or 6.2 MB (with integrated XNA + DirectX redistributable).
  • For Windows Vista and Windows 7 customers, the installer will simply install the combined XNA + DirectX redistributable, then your game - and off they go.
  • Windows XP customers will be asked to install .NET 2.0 x86 (a 23.6 MB download), .NET 2.0 x64 (a 45.2 MB download) or .NET 3.5 (a 231.5 MB download), depending on their OS and which version of the .NET framework you require.
  • If inclusion of .NET is not an issue (maybe you're pressing a DVD or all your customers are on a broadband connection :p), you can bootstrap the installer using a tool such as dotNetInstaller.
  • Customers without a shader-capable graphics card (or without a proper driver for the graphics card) will be warned upon installation.

IslandWar, Day X + 4

in

Today, I've got something to show! Read on :-)

Over the weekend, I implemented proper camera and terrain selection controls. Getting keyboard, mouse and gamepad working at the same time proved a bit of a nightmare. PC RTS players will expect the arrow keys to move around their view and the mouse to be usable as a selection tool. XBox 360 RTS players need some way of telling the game where to place buildings, but the gamepad isn't suited for this task as well as the mouse is.

So I decided to split my terrain into a regular grid in which a terrain cursor can be moved. The grid is in units of 3x3 terrain quads, which allows me to add some detail to the terrain on the sub-grid-cell level. Otherwise, it would look fairly obvious to the player that the terrain is based on a heightmap with regular X/Y samples.

Screenshot of an air defense turret firing at an incoming missile

I originally wanted the camera work like a helicopter that was trying to keep the player's terrain cursor in its view, but the way a helicopter tilts to change its position is exactly the opposite of what I want: If the cursor moves to the north, the helicopter would have to tilt forwards (showing even less of the north) until the helicopter itself had caught up.

So, instead of following the helicopter approach, I ran with the exact opposite. If the terrain cursor moves north, the camera will tilt to still show the cursor and then gradually drift north itself. It can also zoom in and out or rotate around the terrain cursor to view the map from other directions.

The hard part was unifying mouse, keyboard and gamepad. The keyboard should move the camera without moving the terrain cursor. The mouse should move the terrain cursor to the grid cell under the mouse cursor. And the gamepad, finally, should move both the terrain cursor and the camera. And if the player uses unlikely combinations of input devices, such as mouse and gamepad or keyboard and gamepad, the input system shouldn't be totally worthless.

I did it by keeping a permanent camera target cell (that is independent of the terrain cursor). If the terrain cursor is moved by the gamepad, the target cell is updated as well. If the terrain cursor is moved by the mouse, it isn't. The keyboard, then, adjusts the camera target cell on the sub-grid-cell level without moving the terrain cursor.

A weird mix of controls, but as far as I can tell, it's very intuitive and works well. This is where you, dear reader, enter the scene: I've prepared a small demo that let's you play around with the camera. If you happen to be an XNA developer with XNA Game Studio on your system, I'd love to get some feedback on how the controls feel and whether you think it's adequate for a strategy game.

Controls

Arrow keys - move camera
Del/PageDown - rotate view
Home/End - zoom in or out

W/A/S/D - simulate gamepad input (if you don't have an XBox 360 gamepad)

Mouse - move terrain cursor

Cool Stuff

Cool things contained in this demo (programmer-cool, not crowd-pleaser-cool, that is):

  • Terrain multitexturing using a splat map and dynamically updating that splat map - using freely positionable damage brushes to scrape off texture layers. This can be seen when the missile hits the ground in the demo.

  • Water reflections using projective texturing. The water isn't finished yet and looks pretty boring. However, the reflections are already working and you can see them if you look for them. It's a mix of the code by Graphics Runner and Riemer's XNA Tutorials.

  • Swept ray / height map intersection. I wrote a bullet-proof height map intersection test that is both very efficient and secure (no binary search or stepping involved). You can see it in action when you move the mouse over the terrain to move the terrain cursor.

  • Unit testing. There are in excess of 800 unit tests in the demo assemblies which you can even run yourself if you have NUnit installed. I'm embedding my unit tests directly into the assemblies to avoid having to break encapsulation for testability.

  • Custom Content Manager. The built-in XNA content compression is good, but mine is better ;). I've used the LZMA SDK (LZMA is the compression algorithm used by 7-Zip) to get part my content to 2/3rd of the size that the built-in compression achieves. And yes, it works on the XBox 360 as well!

  • Vector Font Rendering. The intro looks kinda shabby, but what you see there are true vector fonts, produced by my vector font rendering library. It consists of a content pipeline importer written in C++/CIL using FreeType and an XNA game library that loads the processed fonts and renders them on the PC or XBox 360.

For a little entertainment, I've also placed two air defense turrets and an incoming missile on the map that show off the units I reported about the week before here and here.

Enjoy and make sure to report your opinion about the camera controls back =)

IslandWar, Day X + 3

in

Way to start a weekend!

Last week, I did the missiles, which work just wonderfully. I have uncovered some lesser issues, such as the texture coordinates of my terrain being mirrored on the Z axis, meaning that a missile hit on the northern end of an island would leave a scorch mark on the southern end of the island, but after some good laugh I fixed this quickly.

Of course, having missiles flying at my island and destroying things was such a motivating achievement that I couldn't let off and started working on the code for an air defense turret to protect my island.

Originally, I was thinking about anti-air missiles (like the US patriot missile system) to intercept incoming attacks, but then I changed my decision and went for a minigun-like defense turret. Miniguns are cool and easier to implement as well.

My new air defense turret consists of a tower that can rotate and a gun barrel that can change its elevation. I'm normally averse to using any angles in 3D math code because vectors are faster, less prone to problems and often also easier to use, but this time, I decided to use angles to contol the turret's orientation and elevation.

First, I needed to determine the angle of the closest enemy as seen from the turret's point of view. This was easier than I thought using the Math.Atan2() method, simply ignoring the Y (height) coordinate of both turret and missile, effectively turning this into a plain and simple 2D calculation:

// Determine the direction of the enemy relative to the turret in
// flat space (without taking altitude into account)
Vector2 flatDirection = Vector2.Normalize(
  new Vector2(enemyPosition.X, enemyPosition.Z) -
  new Vector2(myPosition.X, myPosition.Z)
);

// Calculate the 'yaw' angle of that point
float targetYaw = (float)Math.Atan2(
  (double)(-flatDirection.Y), (double)flatDirection.X
);

The elevation was a little more complicated because I couldn't simply ignore one coordinate and use the Math.Atan2() method. My first attempt was to rotate the missile into the turret's local coordinate frame, thereby turning this into another 2D calculation, but then I had an idea: I only needed the distance of the missile in 2D (using only the X/Z coordinates) and its altitude relative to the turret and I had the values for Math.Atan2() without needing to rotate anything:

// Relative position of the enemy as seen from the turret
Vector3 enemyPositionFromTower = enemyPosition - myPosition;

// Calculate the distance of the enemy to the tower in flat map space
// (completely ignoring altitudes).
float flatDistance = new Vector2(
  enemyPositionFromTower.X, enemyPositionFromTower.Z
).Length();

// Now calculate the direction of the enemy as it would be if the tower
// was directly facing the enemy and the enemy would move on a vertical
// plane that cuts through the tower.
Vector2 verticalDirectionFromTower = Vector2.Normalize(
  new Vector2(flatDistance, enemyPositionFromTower.Y)
);

// Calculate the 'pitch' the turret's gun barrel will adjust to
float targetPitch = (float)Math.Atan2(
  (double)(-verticalDirectionFromTower.Y), (double)verticalDirectionFromTower.X
);

return targetPitch;

These two code snippets will find out the required yaw and pitch angles for the air defense turret's tower and barrel respectively. All I needed to do now was to rotate the turret towards the enemy and, if the barrel had aligned with the enemy position, fire up the minigun.

/// <summary>Aims the turret at the enemy and then fires at it</summary>
/// <param name="enemy">Enemy the turret will aim and fire at</param>
private void aimAndFire(Units.Unit enemy) {
  Vector3 myPosition = Position;
  Vector3 enemyPosition = enemy.Transform.Translation;

  // First, get the angle the rotating tower of the turret needs to rotate
  // to in order to face the targetted enemy
  float targetYaw = getYawAngle(ref myPosition, ref enemyPosition);

  // Next, calculate the pitch angle that the gun barrel needs to assume
  // as if the rotating tower was already facing the targetted enemy
  float targetPitch = getPitchAngle(ref myPosition, ref enemyPosition);

  // Rotate the gun into the direction of the enemy
  rotateGunTowards(targetYaw, targetPitch);

  // Is the gun ready to fire again?
  if(this.remainingCoolDownTime == 0) {

    // The turret will only fire if the gun barrel is aligned to
    // the enemy's current position
    bool isAligned =
      (Math.Abs(targetYaw - this.currentYaw) < MaximumRotationSpeed) &&
      (Math.Abs(targetPitch - this.currentPitch) < MaximumElevationSpeed);

    if(isAligned) {
      fire(enemy);
      this.remainingCoolDownTime = CoolDownTime;
    }

  } else { // Gun was not ready to fire
    --this.remainingCoolDownTime;
  }
}

That's basically what's controlling my air defense turret now. To make sure the turret only targeted air targets, I created a dummy interface named IAirborneUnit. The turret will scan for any enemy units in the vicinity implementing this interface and then try to shoot them down.

Of course, if the unit is too fast for the air defense turret to track, the minigun will never reach target alignment and no shots will be fired at all. But I can counter this by increasing the turret's rotation speed, the allowed deviation of the turret (the player won't see if the barrel is 1° off or not) and hitting the incoming missile/dropship is a matter of the random number generator since I'm not keen on keeping a physically accurate simulation of minigun turrets hitting a missile flying at mach 2 :)

IslandWar, Day X + 2

in

During the past week, I got missiles working to a point where they will launch, gain altitude, head towards their target and dive for the attack. They will damage the island (which means scraping off texture layers and possibly alter the terrain, not sure whether I want this, however). Buildings in the vicinity are then destroyed.

For the missile trajectory, I went to the simplest thing that could possibly work:

  • Until the missile has reached its cruise altitude, it will ascend at a 45 degree angle. If during this phase, missile closes in to the target so much that it needs to start diving, the diving phase will be entered.

  • Once at its cruise altitude, the missile will simply fly in a horizontal line towards its target. If the missile gets closes enough to its target, it will enter the diving phase.

  • In the diving phase, the missile accelerates to maximum speed and descends towards its target at a 45 degree angle. Once it hits the ground, it explodes.

That's the basic algorithm. Instead of using a state machine or something, I fixed it up into this little piece of code:

// Two-dimensional position of the missile and its target
Vector2 flatPosition = new Vector2(this.transform.M41, this.transform.M43);
Vector2 flatTarget = new Vector2(this.target.X, this.target.Z);

// Find out how far the missile is from its target location in flat map space
float flatDistanceToTarget = Vector2.Distance(flatPosition, flatTarget);
if(flatDistanceToTarget < TravelingSpeed) {

  explode();
  return;

}

// Current height of the missile above the target
float heightAboveTarget = Math.Max(0, this.transform.M42 - this.target.Y);

// When the missile needs to start diving
float diveStartTime = flatDistanceToTarget - heightAboveTarget;

// If the missile has not reached its nosedive point yet, it will gain height
// until it reaches its traveling height. Otherwise, it will dive towards its target
if(diveStartTime > 0.0f) {
  this.velocity.Y = Math.Min(TravelingHeight - this.transform.M42, TravelingSpeed);
} else {
  this.velocity.Y = -TravelingSpeed;
}

// Flat map direction the missile has to fly to
Vector2 targetHeading = Vector2.Normalize(flatTarget - flatPosition);
this.velocity.X = targetHeading.X * TravelingSpeed;
this.velocity.Z = targetHeading.Y * TravelingSpeed;

// Update the missile's position
incrementPositionByVelocity();

Sweet, isn't it?

Nukes will follow a similar scheme, but raise vertically into the air, travel at a much greater height and then dive down vertically as well. This allows nukes to be used for hitting targets behind the enemies air defense lines.

Why Beginners should Start with XNA

in

Today, I watched an interesting discussion on gamedev.net where someone asked why everyone is recommending beginners to start off with XNA and C# whereas the entire gaming industry is based on C/C++.

I would have expected to read something along the lines of "we recommend XNA because it's very easy to learn and you will hit the ground running fast. It teaches beginners the kind of thinking required to lay out the logic of a program and doesn't discourage them by forcing them to write boring console applications for months until they know the semantics of the language well enough to use a graphics library such as SDL, DirectX or OpenGL. Some people may not even want to enter the industry, so C# and XNA is a fine choice for them. Those that do can switch over to C/C++ after they're fluent in C# and it will be a mostly easy going, incremental learning process."

Instead, an ugly mess resulted with people firmly stating that .NET/Mono is the way to go for games, that the gaming industry is using it for prototyping, scripting and development tools and that the only reasons for not adopting C# yet are legacy code bases and unwillingness by developers to learn new stuff or to change proven ways. People began dissecting other's posts one by one and, well, if you've been on the internet some time, you probably know all that.

So, why do I think beginners should start with C#/XNA?

Mainly for the reasons I already wrote in the wished-for response at the beginning of this article. C# allows beginners to concentrate on learning programming itself whereas C++ requires a good understanding of the toolchain first. You can misplace include files, link to the wrong libraries, then get error messages that don't indicate either of them (if you are a beginner) and run into the countless traps just waiting in the language for beginners.

Will the gaming industry adopt anytime soon?

I don't think so. .NET is a beautiful concept with strong support for agile development, lots of interesting possibilities for game developers (like productivity gains, C#/Boo/JavaScript scripting where scripts are actually compiled to machine code, single executables that work in x64 and x86, easy linux portation). However, there are PS3s and Wiis out there which don't do .NET. There is a wealth of libraries designed which are only good for C/C++. Using P/Invoke wrappers heavily limits portability, lots of wrappers I know of are far from being mature and only few full C# ports of such libraries exist which would allow the benefits of .NET to hit home.

Knowing C# is still a good idea. It's booming in the desktop application and server markets and I've heard quite often that tools such as level editors, game scripting, model converters and code generators are being written in .NET. It wouldn't surprise me if games written in .NET would slowly begin to pop up every now and then. But .NET isn't mainstream in the gaming industry and probably won't be for a long time to come.

What about open source and hobbyist projects?

Open Source people still seem to be wary of using Mono - mostly out of fear that Microsoft might have other intentions behind the C#/.NET ECMA standardization and that they may well be in a position to sue anyone using Mono to hell and back, which would cause major damage once the adoption of Mono for linux apps increased. That, and the fact that linux is an overwhelmingly huge collection of libraries whose only common ground is that their APIs talk C. You won't hear the developers of libpng saying "aw, let's do the next version of the library in .NET, guys." So the current state that most important software libraries being written in C/C++ for C/C++ won't change soon.

Assuming Microsoft indeed has honorable intentions (which I do), adoption will likely increase over time, but the change will be a slow process. Much slower than we might expect from the IT business.

That's my take on the situation. I'm doing C#/.NET/XNA because I like it better than C++, because the Agile/TDD methodology has taken hold much more firmly on this platform and because as an independent developer, I'm not going to develop for the PS2, the only major platform I can't reach with .NET.

Islandwar Game Art

in

Currently, I fixed up my game art needs by using stuff freely available on the web. Eventually, I'm going to need a real artist to produce good-looking and especially coherent art that accentuates my game's atmosphere.

My plan is to postpone this until the game is actually playable and I know exactly what models, textures and sounds I'm going to need. I don't know if this is a good idea, maybe an artist could give valuable input about the game's design, but for this game, that's what I'm going to do.

I'm not sure yet how to go about contacting an artist when the time comes. I could try a typical "help wanted" post in a game development forum, but I'm leaning towards contacting a professional artist who provides services to indie developers. I've seen some real capable artists post on the Indiegamer Forums that I think I might be able to afford.

Until then, I'll resort the picking free models, textures and sounds from online archives such as ImageAfter, TurboSquid, 3D Plants or the Sound Effects Archive. This might not yield a uniform look in my game and other games may be using the same models, but it should be more than sufficient to complete a playable prototype.

A lot of the models were in formats not understood by the XNA content pipeline importers. I first tried AutoDesk's FBX importer, but somehow, more often than not, it produces garbage. Most prominent is that whenever I try to convert a model with a texture on it, the FBX file will contain some long relative path to the texture ending somewhere in C:\Program files\...\ that I have to fix by hand -- which is only possible if I export to an Ascii FBX that then takes up excessive amounts of space in my subversion repository.

Screenshot of Milkshape 3D running on Windows 7

I finally found the solution to most of my problems in Milkshape 3D, a lightweight modeling tool for games that reads and writes an astounding number of model file formats. Not only that, as a complete modeling tool, it allows me to scale and rotate the model before saving it as an .x or .fbx file. While it's a bit inconvenient at times (eg. to export the model, I have to pick the format from the menu using the mouse), it is a pretty good modeler that even can be used to fit textures onto meshes.

Getting rid of all my format conversion and modeling needs for $35 seemed to be a real bagain, so now I own my first modeling tool. I'll post some screens as soon as I got the first model in my world.

IslandWar Oceans

in

During the past few evenings, I've worked on improved water quality in Island War, and I've made some solid progress.

As a first step, I switched to rendering the water surface before I render the terrain. This may sound strange (after all, the water should be semi-transparent and the underwater parts of the terrain have to be visible through the water.

I fixed that by making the terrain itself become transparent depending on its underwater depth. This means I can completely fade out the terrain at a certain depth and I don't have to bother hiding the borders of the rectangular height maps used for the islands.

Another benefit is that I can now draw the water completely opaque, thereby fully hiding the bottom face of the skybox. It would be quite ugly to have the water surface in the skybox be partially visible through the real rendered water surface.

Screenshot of transparent water in IslandWar

Next on the list were proper reflections. I'm still working on this part, but two very good resources I found were the Water Game Component by Graphics Runner and Riemer's XNA Tutorial Series 4.

Projective texturing was new to me, but after getting the reflection matrix right (I preferred the method used by the graphics runner component) I could freely experiment with the projective texturing stuff until I got it right.

Screenshot of transparent water in IslandWar

What you can also see in that last screenshot is my first model. I grabbed a few free models from various sites and converted them into the .fbx format.

I also bought $45 worth of grass. That also includes various foliage like flowers and small bushes. The models look really nice but I'll have to think about whether I'm going to use them or not. My plan was to release the game with its source code, but if I don't have redistribution rights for the art, I'm forced to ship the source code on its own, in a state where it doesn't run out of the box.

IslandWar Terrain Rendering

in

The terrain renderer is finally working again. I also added skybox that I quickly created in TerraGen

I modified the terrain renderer to use triangle strips instead of triangle lists to reduce the number of indices required and to improve vertex caching. What you see below is a 256x256 vertex height map, in other words, 65.025 quads. This isn't an issue for modern graphics accelerators, but it's nice to see the entire system coping with the load easily.

Screenshot of IslandWar with a 256x256 vertex height map

Because both the graphics card of my PC and my XBox 360 seem to be idling away at this rate, I added some per-pixel real-time phong shading to the shaders.

The same terrain with lighting applied by a phong shader

It still looks a bit like plastic and I'll have to fine-tune the highlights to look more realistic, but take note of the smooth lighting gradients. These are thanks to my normal interpolator which will automatically update any normals when the height map gets deformed due to missile impacts or smoothed for building construction.

Next I'm going to work on a more realistic seam between the water and the terrain as well as make the water more realistic. Adding reflections and animated waves shouldn't be the slightest problem because even with the complicated shader, the graphics card is idling away.

IslandWar, Day X + 1

in

Last week, I wrote about how I set sail for the Island War project and how my motivation faltered slowly over time. And I promised to write a follow-up post that explains how I believe to get back on track and finish development of my Island War project. Well, here it is, folks!

I see the primary reason for my loss of motivation in my tendency to write and fully implement systems instead of just the piece of code I need. While I knew that I had to "write a game, not an engine", I still fell for the same old trap: I didn't technically write an engine, but I startet to design entire systems and fully implement them when all I needed was a small routine to get the job done.

Ultimately, I attempted to create my island war world as a closed library which automatically handled everything the game does and provided a nice API to the user. This is a reasonable goal, but I approached it with a holistic integration attitude - I tried to just write it an then test it from the outside. And with all the attention to detail the perfectionist in me needed to be satisfied.

Complexity explosion resulted. Take this: when a missile hits the ground, for example, the height map can be deformed. This would automatically trigger an event that notified the terrain renderer to recalculate normals for the affected region and to do a minimal update to its vertex buffer. The same event would notify the building manager to check the ground near buildings. It the ground became to uneven, the buildings on it would automatically be destroyed.

This is not a bad approach at all, but you can't keep all this in your head if you design it as a whole. Don't misunderstand me, my classes were modular and not interdependent on each other, but I tried to mentally map the interactions of the whole thing when glued together and get it right on the first try.

So the problem was that, each time I tried to continue work on my project, I first had to recall everything the system does to even know how it's supposed to behave. This became a huge wall I had to climb each time I sat down to write some code. And soon, XNA 2.0 had come, and gone again, and XNA 3.0 was here (yep, I actually began this when XNA 1.0 Refresh was just released).

The New Approach

The cure for my perfectionist system architect mentality seems to be Test-Driven Development. Instead of working out the whole complicated interaction of the system at once, I use unit testing to ensure the building blocks do exactly what they're supposed to.

And whenever I add something new, I begin by deciding what exactly I require (instead of how the new requirement could be turned into a system that does the job for me) and begin by creating a test for it. My code is still modular, reusable and doesn't form huge dependency chains, but it's down-to-the-purpose and not systems upon systems interacting with each other to form the game.

In other words, when I need power in my house, I now build myself a diesel generator instead of designing a nuclear power plant :)

I'll keep you updated on how my test-driven approach to game creation works out!

Weekend of Desaster

in

This was a nice Saturday morning to wake up to. Not only do I have a slight fever and headache from a cold I must have caught around Thursday, no, between 5 and 6 o'clock local time, something killed my home network.

My home server unexpectedly rebooted at that time, also taking with it a virtual machine that I'm using as a build agent for continuous integration. My gigabit switch that connected my workstation, TV box, PPPoE modem, home server and XBox 360 is fried. Its power supply feels cold to the touch, so probably that's the reason for the switch going down.

Another Gigabit switch I was using for my XBox 360 and workstation in my room is still working, so I recruited that as a replacement for for the time being. The only problem is that now I can't connect my XBox 360 to the network unless I run another 8 meters of ethernet cable from my room to the central switch.

So I just ordered a NetGear GS108GE.

A NetGear GS108GE GigaBit ethernet switch

While I do need more than 8 ports, I prefer having two 8 port switches. 16 port gigabit switches still cost more than twice the money and having two interconnected 8 port switches reduces the amount of cable I need and is a good fail-safe measure as I found out just now.