Thursday, 27 September 2012

Yellow Daemon

Another enemy for the player. Variation of a red daemon.




Wednesday, 5 September 2012

Tuesday, 4 September 2012

Vertex Shader Outlines

During the development process I needed to have a way to mark an item/enemy with a border. Initially I wanted every 3D object on the scene to have such a glowing border, then decided to give the border to specific objects only when a special ability was in effect (e.g. temporary invulnerability).

My way of achieving this effect was to render whole model scaled up in the background, and then render the normal model. Standard scaling via scale matrix wouldn't work well in this case, so I had to write a vertex shader that would move every vertex of a mesh slightly along its Normal vector. This makes the model look like it's been inflated which is perfect for border/stroke effect. The important thing is to inflate the model after all transformations have been applied (move, rotation, scale, and bone transforms) which means that it needs to be done in world/scene space.

Here's the code for the Stroke Effect shaders.

struct VertexShaderInput
{
 float4 Position : POSITION0;
 float3 Normal : NORMAL0;
};

struct VertexShaderOutput
{
 float4 Position : POSITION0;
};



VertexShaderOutput VSShadowFunction(VertexShaderInput input)
{
 VertexShaderOutput output;
 float4 worldPosition = mul(input.Position, World);
 worldPosition += (normalize(mul(input.Normal, World)) * 0.02 );
 worldPosition.y = 0.05;
 float4 viewPosition = mul(worldPosition, View);
 output.Position = mul(viewPosition, Projection);
 return output;
}

float4 StrokeFunction(float2 TextureCoordinate : TEXCOORD0) : COLOR0
{
 float4 color = StrokeColor;

 return color;
}


And here is the final result.

Monday, 3 September 2012

Coordinates in mixed 3D/2D space

WizardBlast seems to be a legit 3D space game when you look at it, but when you start thinking about the in-game logic it turns out to be 2D limited. 

In fact WizardBlast is a 3D rendered 2D game, because everything happens on 2D platform (maze). It is the representation of the maze that gets to full 3D.

In such mixed environment some of the variables regarding position of an in-game entity need to be 2D (x,y), some need to be 3D (x,y,z), and some need both. Also the 3D y component doesn't necessarily need to be equal to the 2D y component.

When I started coding WizardBlast I used Vector3 and Vector2 to store coordinates, and then worked on one of them when needed. It all made the code quite messy and difficult to read/modify/refactor.

That is why I created a new class to store complex position vectors that work in an unificated way. At any time I am able to get both 2D and 3D components and any modification made to one them affects the other.

I think it's a piece of code worth sharing. Not because it's difficult to write, but because it's not always obvious that you need such code :-)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace WizardBlast.Entities
{
 public class Position
 {
  public static float ZeroFieldXLocation = -2.5f;
  public static float ZeroFieldZLocation = -2.5f;
  public static float Rift = .55f;
  public static float InvRift = 1 / Rift;

  private float z;
  private float x;
  private int ix;
  private int iz;

  public float Z { get { return z; } set { z = value; iz = FieldZIndex(value); } }
  public float Y { get; set; }
  public float X { get { return x; } set { x = value; ix = FieldXIndex(value); } }

  public int IX { get { return ix; } set { ix = value; x = FieldXPosition(value); } }
  public int IZ { get { return iz; } set { iz = value; z = FieldZPosition(value); } }

  public Position(Vector3 position)
  {
   Z = position.Z;
   Y = position.Y;
   X = position.X;
  }

  public Position(Position position)
  {
   IZ = position.IZ;
   Y = position.Y;
   IX = position.IX;
  }

  public Position(int x, int z)
  {
   IZ = z;
   IX = x;
   Y = 0f;
  }

  public Position(float x, float z)
  {
   X = x;
   Z = z;
   Y = 0f;
  }

  public static Position operator +(Position p1, Position p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Y = p1.Y + p2.Y;
   result.Z = p1.Z + p2.Z;
   return result;
  }

  public static bool operator ==(Position p1, Position p2)
  {
   return p1.ix == p2.ix && p1.iz == p2.iz;
  }

  public static bool operator !=(Position p1, Position p2)
  {
   return p1.ix != p2.ix || p1.iz != p2.iz;
  }

  //Always override GetHashCode(),Equals when overloading ==
  public override bool Equals(object o)
  {
   return this == (Position)o;
  }
  public override int GetHashCode()
  {
   return ix ^ iz;
  }

  public static Position operator +(Position p1, Vector3 p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Y = p1.Y + p2.Y;
   result.Z = p1.Z + p2.Z;
   return result;
  }

  public static Position operator +(Position p1, Vector2 p2)
  {
   var result = new Position(p1);
   result.X = p1.X + p2.X;
   result.Z = p1.Z + p2.Y;
   return result;
  }

  public static Position operator -(Position p1, Position p2)
  {
   var result = new Position(p1);
   result.X = p1.X - p2.X;
   result.Y = p1.Y - p2.Y;
   result.Z = p1.Z - p2.Z;
   return result;
  }

  public static Position operator *(Position p1, float m)
  {
   var result = new Position(p1);
   result.X = p1.X * m;
   result.Y = p1.Y * m;
   result.Z = p1.Z * m;
   return result;
  }

  public static implicit operator Vector3(Position pos)
  {
   return new Vector3(pos.X, pos.Y, pos.Z);
  }

  public static implicit operator Vector2(Position pos)
  {
   return new Vector2(pos.X, pos.Z);
  }

  public static explicit operator Position(Vector3 vec)
  {
   return new Position(vec);
  }

  public static explicit operator Position(Vector2 vec)
  {
   return new Position((int)vec.X, (int)vec.Y);
  }

  private float FieldXPosition(int index)
  {
   return ZeroFieldXLocation + index * Rift;
  }

  private float FieldZPosition(int index)
  {
   return ZeroFieldZLocation + index * Rift;
  }

  private int FieldXIndex(float position)
  {
   return Convert.ToInt32((position - ZeroFieldXLocation) * InvRift);
  }

  private int FieldZIndex(float position)
  {
   return Convert.ToInt32((position - ZeroFieldZLocation) * InvRift);
  }
 }
}


Sunday, 2 September 2012

Flying pumpkin

A typical arcade game consists of three main components:

  • player
  • level
  • enemies

Let me present you with one of the enemies from WizardBlast, the Flying Pumkin.

Pumpkin is the easiest to defeat type of enemy you will meet in the game (and the most stupid one). All it does, is wander around in a slow fashion.

These are AI parameters for this peculiar monster:

    <Lazyness>0.9</Lazyness>
    <TurnSpeed>6.5</TurnSpeed>
    <Predictability>2</Predictability>
    <Speed>0.35</Speed>
    <Intelligent>0</Intelligent>