Sunday, July 28, 2013

Grid Based Movement in Unity

If you have played old school games like Dungeon Master, Eye of the Beholder, the early Lands of Lore and Wizardry series or even more recent games like Legend of Grimrock, then you know what Grid Based Movement is.    This is my implementation of grid based movement using Grid Framework by HiPhish.  Feel free to use your own grid system.

See script in action

Setup a scene and if you are using Grid Framework create a new grid.
Add an empty gameobject to represent the player.  I also make the camera a child of this object and set the camera position to 0,0,0 so it is always looking forward from the centre of the player.

Add this script to the player object.  If you are not using Grid Framework, you will need to change/remove the GFGrid variable and the first line in the awake method.  All this line currently does is move the player to 0,0,0.  In a later post the grid will be used to determine if a move is valid or not.  You will also need to either create the inputs I have specified in the script or update the script to match your own.  I use w,s,a,d for forwards, backward, stepleft, stepright and q and e for turning.

public GFGrid Grid;//Grid the Player is attached to.
public float StepSpeed = 0.5f; //how fast in seconds each step or turn takes to complete
public Vector3 CurrentGridPosition = new Vector3(0,0,0);  //3d position in Grid
public string CurrentDirection; //Direction Player is facing (n,s,e,w)

float timer = 0; //used to disable turn left and turn right inputs when player is already turning
bool enableRotation = true; //used to disable turn left and turn right inputs when player is already turning
Vector3 intendedPosition;//Before player is moved, the position the player will end up at is stored here
//Is used to check that the move is valid and also used to disable move inputs
//when player is already moving

void Awake()
{
this.transform.position = Grid.GridToWorld(CurrentGridPosition); //move player to default position/grid origin
intendedPosition = CurrentGridPosition; //no movement has occured yet, so intended position is set to current position
CurrentDirection = "n"; //TODO: default direction is calculated from CurrentGridPosition.
}

void Start()
{
//sets timer to match time it takes to make one step or turn
timer = StepSpeed; 
}

public void MoveTo(Vector3 position)
{
//Move Player to position over time
LeanTween.move(gameObject, position, StepSpeed);
}

public void TurnPlayer(Vector3 position)
{
//Turn Player to new rotation over time
LeanTween.rotate(gameObject, position, StepSpeed);

}

#region Movement

public void MoveForward()
{
// calculate intended position. Get current direction and add one step forward
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
}

//TODO: Check move is valid, if not, break
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void MoveBackward()
{
// calculate intended position. Get current direction and add one step backward
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
}
//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void StepLeft()
{
// calculate intended position. Get current direction and add one step left
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
}
//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition));//Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void StepRight()
{
// calculate intended position. Get current direction and add one step right
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
}

//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void TurnLeft()
{
//check if player is already turning
if(!enableRotation)
return;

enableRotation = false; //disable turn inputs

Vector3 intendedPosition = transform.localEulerAngles + new Vector3(0,-90,0);//calculate intended position after turning 

TurnPlayer(intendedPosition); //actually turn player

//Because player has turned, player direction has changed.  This updates to correct value
switch (CurrentDirection.ToLower())
{
default:
case "n":
CurrentDirection = "w";
break;
case "e":
CurrentDirection = "n";
break;
case "s":
CurrentDirection = "e";
break;
case "w":
CurrentDirection = "s";
break;
}
}

public void TurnRight()
{
//check if player is already turning
if(!enableRotation)
return;

enableRotation = false; //disable turn inputs

Vector3 intendedPosition = transform.localEulerAngles + new Vector3(0,90,0);//calculate intended position after turning 

TurnPlayer(intendedPosition); //actually turn player

//Because player has turned, player direction has changed.  This updates to correct value
switch (CurrentDirection.ToLower())
{
default:
case "n":
CurrentDirection = "e";
break;
case "e":
CurrentDirection = "s";
break;
case "s":
CurrentDirection = "w";
break;
case "w":
CurrentDirection = "n";
break;
}
}

#endregion

void Update()
{
//checks if player is moving, if not enables movement inputs
if(CurrentGridPosition == intendedPosition)
{
if(Input.GetButtonDown("Forward"))
{
MoveForward();
}
if(Input.GetButtonDown("Backward"))
{
MoveBackward();
}
if(Input.GetButtonDown("StepLeft"))
{
StepLeft();
}
if(Input.GetButtonDown("StepRight"))
{
StepRight();
}
}

//checks if player is turning, if not enables turning inputs
if(enableRotation)
{
if(Input.GetButtonDown("TurnLeft"))
{
TurnLeft();
}
if(Input.GetButtonDown("TurnRight"))
{
TurnRight();
}
}

//if player is turning, checks if turn has finished.
if(!enableRotation)
{
timer -= Time.deltaTime;
if(timer < 0)
{
enableRotation = true;
timer = StepSpeed;

}
}
}





Monday, July 22, 2013

Using Grid Framework to build a platformer

The last months have been insanely busy.  I may have overdone it on my still healing foot, and so today I find myself at home and forced to rest.  It sounds like a perfect opportunity to make a game AND a tutorial!!

I enlisted the help of my 4 year old daughter in deciding what kind of game to make.  She wanted a game where Happy Face has to collect a crown.  Happy Face will encounter a witch, a Mother Spider and her Baby Spiders.  So, I am going to be building a single screen platformer.  By single screen, I mean that what is displayed is the entire game, no scrolling, no extra rooms.  At least not yet :)

I have also just purchased Grid Framework and this is a great time to see what it can do.

First thing is to create the actual grid.  GameObject > Create Grid > Rectangular Grid will do the trick.  Hexagonal Grids and Polar Grids are also available.  Hexagonal grids will be perfect for deep strategy games.  A polar grid is a circular grid, and the only use I can think of for it right now is as some kind of radar or motion sensor display.

Grid with Default Settings

Now to shape the grid to fit our game world.  For this game, I need a grid twice as wide as it is tall.  I dont care about depth, so I set the Z to 0.  X is 12 and Y is 5.  Your dimensions may vary.

2D Grid
I should now be able to use this grid to create a basic level layout.  Open up the Grid Align Panel (Window > Grid Align Panel) and turn on AutoSnapping.  Make sure you have assigned your grid to the panel too.
Create a basic cube (GameObject > Create Other > Cube) and drag it onto the grid.  You will see that it is perfectly aligned, and moving it around will snap it to the grid.  Place a bunch of these and you have a level.
I will create a boundary first, to stop an eager 4 year old from jumping off the world into oblivion.

Once the boundary is created, lets get Happy Face into the game and moving.   All artwork has been provided by my daughter :)

For the sake of simplicity, I am faking a 2D sprite by creating a cube with a transparent diffuse and her painting attached.  Also add a character controller.  This will replace the default box collider.

Fake2D Sprite settings
Lets add some simple scripting to get this happy fella moving.  Attach this script to your 'sprite'

float moveSpeed = 3.0f;
float jumpSpeed = 8.0f;
float gravity = 10.0f;
Vector3 moveDirection = Vector3.zero;
// Update is called once per frame
void Update ()
{
//handle input and move character controller;
CharacterController cc = GetComponent<CharacterController>();
if(cc.isGrounded)
{ moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, 0); // Gets Horizontal movement. Edit > Project Settings > Input to find out more about Axis.
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= moveSpeed;
if(Input.GetButton("Jump") && cc.isGrounded)
{
moveDirection.y = jumpSpeed; }
}
moveDirection.y -= gravity * Time.deltaTime;
cc.Move(moveDirection * Time.deltaTime);


Test it out and you should be able to walk your sprite back and forth without it falling out of the world.  Lets add a few platforms and the crown.  The grid will allow me to easily line up platforms, and with the current gravity settings on the player I know that the sprite can jump just over 3 grid squares high, so I need a maximum distance of 3 squares floor to floor for the character to be able to make the jump.

Basic level


Lets add a quick script to the crown to allow it be 'collected'.
When the player touches the crown, the following needs to happen:
Record that the crown has been collected
Play a 'pick up' sound
'Destroy' the game object.

To make a trigger on the crown object, I select it and set 'Is Trigger' to true on the box collider.  I also have an empty object in my scene called 'Game Manager', with a game manager script attached.  I'll use this to track any information that the entire game needs to know about.  Right now it just consists of a single public bool to track if the crown has been collected or not.  Set it's default value to false (uncollected).

My crown script.  I used the free tool SFXR to generate my sound fx.

using UnityEngine;
using System.Collections;

public class Crown : MonoBehaviour {

void OnTriggerEnter(Collider other) //fired when an object ENTERS the trigger zone
{
//Record that crown has been collected
GameManager gmScript = GameObject.Find("Game Manager").GetComponent<GameManager>();
gmScript.CrownCollected = true;

//Play Pick up sound
audio.Play();

//Destroy Object
Destroy(GameObject.Find("Crown"), 0.3f);
}
}

Test and you should have a playable level with a pick upable crown.  Whats missing?  Friends, of course!!

Next part coming soon....


Sunday, July 21, 2013

The battle with OAuth.

I've been battling OAuth, all day, today.  In Xamarin.Auth.  The fundamental problem is that it didn't support JSON responses.. so I pulled down the GitHub repo.. started dinking with it.

I eventually got it to work.  Took quite a bit of fighting.  I'm looking forward to using it here soon, for the Xamarin.IOS app and for the Xamarin.Android app.

Thursday, July 18, 2013

Using Intents in Xamarin to catch "Send to" and "Open with" file prompts

In Xamarin for Android you don't directly edit the AndroidManifest.xml file which means you need to create code that will generate the necessary portions of the manifest which direct your application to make itself available to handle incoming file hand-offs.  This is done using an IntentFilter which you place at the top of the class for the activity which you want to handle the incoming data.  In my case, I chose a neutral splashscreen as the loading activity so that the user will see my app's loading screen while I determine and load the true activity that will be handling the file.

namespace MyApp

{

 [Activity (Label="MyAppName", MainLauncher = true, Icon="@drawable/icon", Theme = "@style/ThemeBase.Splash", NoHistory = true)]

 [IntentFilter (new[]{Intent.ActionView},

 Categories=new[]{Intent.ActionDefault, Intent.CategoryBrowsable, Intent.ActionSend, Intent.ActionSendMultiple},

 DataScheme="mimetype",

 DataPathPattern="*/*",

 DataHost="*.*")]

 public class SplashActivity : Activity

 {

  protected override void OnCreate (Bundle bundle)

  {

   base.OnCreate (bundle);



   Intent intent = Intent;

   String action = intent.Action;

   String type = intent.Type;

   if (Intent.ActionSend.Equals(action) && type != null) {

    if (type.StartsWith("image/")) {

     handleSendImage(intent); // Handle single image being sent

    }

   } else if (Intent.ActionSendMultiple.Equals(action) && type != null) {

    if (type.StartsWith("image/")) {

     handleSendMultipleImages(intent); // Handle multiple images being sent

    }

   } 

   else

   {
    // Start our real activity if no acceptable file received

    StartActivity (typeof (MainActivity));

   }

  }

    }

}

The above code allows me to handle any filetype at all (DataHost="*.*") and will cause my application to appear as an option in the "Send" link of any other app.  If you only need to handle a specific filetype you would change the second * in DataHost to the filetype you need, such as (DataHost="*.png") would only handle PNG (Portable Network Graphics) files.  

In my case, since I am accepting all filetypes, I need to determine what type of file I was given and handle it appropriately.  You can see this occurring in the operations with the data derived from intent (intent, action, type), which is the incoming file details and requested action by the other app.  The action type gives a hint on what needs to be done with the file being sent (Send, Open, View, etc.) and the type tells you the MIME type information for the file, whether it's a PDF, image file, APK, MP3, webpage, etc.  If you aren't sure what file type you need to receive, set your DataHost to *.* then set a breakpoint, debug your app and inspect the intent.type after opening the desired file.

Wednesday, July 10, 2013

Making good progress on the Android Version.


Things are looking pretty decent, actually.  Just gotta keep making progress. I really have gotten a lot better at this whole mobile thing.  Kind of impressed.   It's amazing how much time you save, when your projects have a lot of classes without native references.

I really can't wait for the release this weekend on ITunes.  

I'll have to add a back navigation button.. up in that window.. but I think I can live with how it's taking shape already.

Tuesday, July 9, 2013

Starting up the Android Version of Print Hub.



I've been dying to kick some tush (as I'd say to my boys) on this stuff that's been floating around.
Jared is kicking it with me.  Camera functions and Album functions work, moving on to the rest of the stuff here soon.  Had to take a bit of time and get some native Java code ported over.  I should have started with Android for my first store app, instead of spending so much time on IOS, but I think if I had.. I would have been really frustrated later, moving to IOS.  Java code ports for functionality, are so fast.  I could do it in my sleep. : )

That's one problem that will always exist in Xamarin, because there is no Graphics port for Mono, you always have to work with graphics / pdf's.. almost everything natively.. unless, you can find something that has been ported purely to Mono.

You have to give Apple this:   Their best apps always look good.  Or at least, a heckuva lot better than most Android Apps out there.  The game, is to understand enough.. to be able to style both sides. Android, and IOS.. powerfully.  I think I know have the ability to make.. purdy apps.  Now it's just porting API's.. luckily, I do that once.

What I really love about porting API's.. is that the code to use those API's.. is always so tiny..

Print Hub Progress.

I did a lot of work yesterday on Print Hub.  I'm not even sure how long I worked on it.
It seemed to be a lot more fun than it was before.  In fact, I got a whole pile of the Google Cloud Print API working, that didn't work before previously (Not entirely sure what was up there, but I rewrote a number of classes, and cleaned things up).   Here in the next couple days, I'll be pushing a couple other app ideas, out the door (I'm hoping), but frankly, there doesn't seem to be a lot in the way right now. : )


I added Processing Invites, and deleting Jobs.
Obviously, I did some styling work.
I wanted it to be a bit cleaner.. overall. Faster on that main dialog for printing.