Category: Software

Why Make a Game for PC & Mac?

As Game Developers, when we talk about Platform we are talking about which devices and systems on which we will release our games. XBox? That’s a Platform? PS4? Platform. iPad and iPhone? That’s the iOS Platform. PC, Mac, Linux? All separate Platforms.

As players, it’s often confusing as to why a developer would choose to release for one platform and not another. We’ve seen passion around game console platforms approach a religious level of fervor, and there will always be a small number of vocal players who want a game on their relatively obscure but favorite platform (“Bring the game to Linux!”). Why does a developer create a game only for iOS, or only for PC/Mac, but not for XBox, PS4, or Android tablets?

From a developer standpoint, we ask ourselves several questions. First, does the platform meet the technical requirements for our game? Is the platform powerful enough to run the game? Is there a lot of variation on the platform (different GPUs on PC) or is it relatively uniform (iOS)? Does the platform primarily use the expected interface methods (game controller vs. multitouch vs. mouse and keyboard)? Is there typically enough memory on this platform for this type of game? Is a fast internet connection required, or even available?

Second, what are the costs for developing for that platform? Does the platform require a lot of low level code optimization (PS3, for example)? Will the developer need to purchase a $10k development kit, or a $100 developer license? What software is required to build for this platform? Is there an existing content pipeline, or must one be built? Is it easy to build cross-platform code, or is a from-scratch port (rebuilding the game for a different platform) necessary?  Game Engines like Unity can make the latter part easy, but incur their own costs.

Finally, what are the player expectations for this platform? Is there an already established predominant form of gameplay on this platform (Racing or Shooters for Consoles vs. Multitouch Games for Tablets & Phones vs. Adventure or Sim Games for PC)? How does this game align with that? Does the platform typically offer long gameplay experiences, or a few minutes of entertainment? What do players typically pay for a game on this platform? (iOS users, for example, are relatively happy to pay for DLC or powerups, while Android users are notoriously cheap.) How do players get games for their platform? How discoverable are new games?

When we started building V.Next, we knew we wanted to build for PC and Mac because it offered us the most straightforward way to build an interesting and differentiated game experience, while delivering what users on that platform expect. PC and Mac are the home to the most innovative, and even experimental, games that are being created today. Only on a PC, could a hacking game like Zachtronics TIS-100, or the upcoming Quadralateral Cowboy, work. We knew each episode of V.Next would incorporate up to an hour of gameplay, and we knew players on these systems want and enjoy the longer term engagement. We also knew that Keyboard and Mouse would be the right interface for what we wanted to deliver.

Will we port our games to iOS and Kindle Fire Tablets? It’s in our plans to do so, but we’ll have to alter our interaction model a bit. However, we’ll deliver on our original vision for the game by first releasing on PC and Mac, and we know players will love the nostalgic, retro experience of this game on those launch platforms.

 

Animation Subsystem

In this part 2 of 2 posts, I’m going to describe an animation system that I’ve used for quite some time. There are a few tricks that are slightly clever, and the system allows for a lot of complexity with a fairly simple framework. If you haven’t read my previous post about the Display/Logic split, you may want to get through it for a bit of context for this section.

Previously, I discussed how we would handle game logic in one class, and the display of the game state with a separate class. In typical casual games (poker, match-three, and jumping games for example) what I would do is have the logic update the game state all at once, and then have the display “catch up” by playing pretty animations. Of course, we could just have the display instantly match the logic, but that’s not quite as magical for the player.

While there are plenty of tweening and motion animation libraries that are available on the internet, I realized that I needed to solve a different problem: Sequencing. For example, let’s say that we have some Checkers-type game, where the player jumps pieces on a board over other pieces, making the jumped-over pieces vanish for points. There are score multipliers for many jumps in a row, and we want to communicate the score progression and the multipliers to the player.

Given our previous discussion, we may have our Display handle mouse clicks that select a board square with a piece to jump, or with a destination for that piece to jump to. The method in IGameLogic may be something like playerClickedSpot(x,y), where x,y determines the board square that was clicked.  There are many different things that can happen to that board spot. Maybe there’s nothing there and the click causes no action. Maybe a player clicks a game piece they can’t move. Maybe a player clicks a piece they can move, then clicks an invalid jump destination. Maybe they’re rapidly clicking a series of valid locations. We need to handle all of these in a way that makes sense to the player.

As I stated above, we typically have the game logic update the game state instantaneously. This example isn’t for a physics based FPS where we would have the logic track the arc of the jump curve for the piece on a frame-tick counter. It’s fine for the logic to instantly move a piece from origin to destination and tabulate the new score, as well as track any multipliers.  This way, we know if a subsequent input is valid or not, which is important.

So our game logic can respond by respectively calling methods exposed in our IGameDisplay interface such as NoAction(), InvalidPiece(x,y),  PieceSelect(x,y), InvalidDest(x,y), PieceJump(startX, startY, endX, endY, jumpedX, jumpedY, totalScore, multiplier). These are fine methods that the Display can respond to, perhaps by playing a buzzing sound for InvalidPiece or InvalidDest while wiggling the selected piece on the board. For PieceJump, we can kick off the motion of the jumping game piece, and update the numeric score. If multiplier is nonzero, we can show another fancy number floating upwards while playing a great sound.

But how do we know exactly when to do these things on the Display side? And what about that last case, where the player rapidly clicks a series of valid jump destinations in order? If the logic updates instantly, and calls back to display with a new PieceJump call instantly, won’t those motions override each other, confusing the player?  Yes, so we can’t handle the updates instantly on the display side, and we need a way to sequence those animations.

This is where the AnimationSequencer comes in. The AnimationSequencer is a class that handles ordering and playback of animations in our game. Every animation that it handles implements an interface, IAnimation, which exposes the following methods:

  • start()
  • tick(deltaT)
  • finish()
  • isStarted()
  • isComplete()
  • isBlocking()

Let’s focus on that last one, isBlocking(), because that’s the key to a huge amount of power here. Every class that implements IAnimation must set whether or not it is blocking, either as an option in the constructor, or as a default. Blocking is just a flag, true or false, either this animation blocks or it doesn’t.

The rest of the methods are straightforward. Start does setup, and is called on the first frame (if isStarted returns false). Tick is called every frame thereafter, and when the animation realizes it is done, it internally calls finish on itself, which will set the isComplete flag to true.

Here’s an example concrete class for doing linear motion:

package com.syncbuildrun.Engine.Animations
{
	import flash.display.DisplayObject;

	public class LinearMoveAnimation implements IAnimation
	{
		protected var m_started:Boolean;
		protected var m_complete:Boolean;
		protected var m_blocking:Boolean;
	
		private var m_element:DisplayObject;
		private var m_startX:Number;
		private var m_startY:Number;
		private var m_endX:Number;
		private var m_endY:Number;
		private var m_time:Number;
		private var m_totalTime:Number;
		private var m_preDelay:Number;
		
		private var m_deltaX:Number;
		private var m_deltaY:Number;
		
		public function LinearMoveAnimation(element:DisplayObject, startX:Number, startY:Number, endX:Number, endY:Number, time:Number, preDelay:Number,isBlocking:Boolean)
		{
			m_started = false;
			m_complete = false;
			
			m_blocking = isBlocking;
			m_element = element;
			m_startX = startX;
			m_startY = startY;
			m_endX = endX;
			m_endY = endY;
			m_time = time;
			m_totalTime = time;
			m_preDelay = preDelay;
			
			m_deltaX = m_endX - m_startX;
			m_deltaY = m_endY - m_startY;
		}
		
		public function start():void
		{
			m_started = true;
			m_complete = false;
		}
		
		
		public function tick(deltaTime:Number):void
		{
			// empty function
			if(m_preDelay > 0.0)
			{
				m_preDelay -= deltaTime;
				if(m_preDelay <= 0.0)
				{
					m_element.x = m_startX;
					m_element.y = m_startY;
				}
			}
			else
			{
				m_time -= deltaTime;
				
				if(m_time <= 0.0)
				{
					return finish();
				}
				
				var percentage:Number = 1.0 - (m_time/m_totalTime);
				
				m_element.x = m_startX + (m_deltaX * percentage);
				m_element.y = m_startY + (m_deltaY * percentage);
			}
		}
		
		public function finish():void
		{
			m_complete = true;
			
			m_element.x = m_endX;
			m_element.y = m_endY;
		}
		
		public function isComplete():Boolean
		{
			return m_complete;
		}
		
		public function isStarted():Boolean
		{
			return m_started;	
		}
		
		public function isBlocking():Boolean
		{
			return m_blocking;
		}
	}
}

The AnimationSequencer operates on the frame tick of our game, so it’s called by the onEnterFrame method of our IDisplayLayer. The Sequencer has two data structures, a queue and a list. The queue is a pending queue; The rest of our code adds animations to be played into this queue. The list is the current playing list; The Sequencer ensures that the animations in this list are playing, or clears them out when they are complete.

Here’s the magic: Every frame, the Sequencer first checks if there are Blocking Animations in the list. If there aren’t, it starts pulling IAnimations from the queue until either the queue is empty, or there’s a blocking animation in the list. Then it stops, and services the IAnimations in the currently playing list by calling tick (or start). It checks every animation that is started to see if isComplete returns true, in which case it pulls that IAnimation from the list and discards it – it’s done. If a Blocking Animation was removed, the next frame we’ll pull more animations from the queue onto the list until the above conditions are met.

AnimationSequencer

This seems incredibly simplistic, but it affords amazing power. Now, by correctly setting queued animations as blocking, we can control the order and pacing of every update from the Game Logic. In our above example, where we get a first call into PieceJump(), we might queue up the following animations:

  • PlaySound(jumpSound)
  • ScoreRollup(newScoreval, duration=1sec)
  • PieceJump(selectedPieceSprite, startX, startY, endX, endY, duration=1sec, BLOCK)
  • PieceRemove(removeX, removeY, BLOCK)

Our Sequencer will start playing a sound, start rolling the score up to the new value from the presently displayed value, and will start the selectedPieceSprite jumping from the board square at StartX, StartY to the square at endX,endY over the duration of 1 second. Because this last animation is blocking, it will wait until that completes before it pulls PieceRemove into the list, which will vanish the just jumped piece at removeX, removeY.

But wait, half a second into this, the player clicked another square for a second valid jump. Now we get a call into PieceJump() while these animations are still playing. But that’s fine! Because now we add the following to the queue:

  • PlaySound(jumpSound)
  • ScoreRollup(newScoreval, duration=1sec)
  • FloatMultiplier(removeX,removeY, MultiplierValue, duration=0.5sec)
  • PieceJump(selectedPieceSprite, startX, startY, endX, endY, duration=1sec, BLOCK)
  • PieceRemove(removeX, removeY, BLOCK)

We added a multipler float up animation, but that won’t start playing until the first PieceRemove from above has finished. All these new animations will sit and wait until the first move completed, and then they will proceed in a normal fashion, which is what the player expects.

Here’s the tick function for our Sequencer:

public function tick(deltaTime:Number):void
{
	var blockerExists:Boolean = false;
	var removeArray:Array = new Array();  // of ints
			
	for(var i:int = 0;i<m_activeList.length;i++) 
	{ 				
		var anim:IAnimation = m_activeList[i];
		if(anim.isStarted() == true) 
		{ 
			anim.tick(deltaTime);
 		} 
		else 	
		{ 
			anim.start(); 
		} 				 
		if(anim.isComplete() == false) 
		{ 	
			blockerExists = blockerExists || anim.isBlocking(); 					
		}
 		else
		{
 			removeArray.push(i); // add index of finished anim 				
		}
 	}
 
	// now walk backwards to remove finished anims from the active array 							
	for(var j:int=(removeArray.length - 1);j >= 0;j--)
	{
		m_activeList.splice(removeArray[j],1);
	}
			
	while(blockerExists == false)
	{
		var newAnim:IAnimation = m_pendingQueue.shift();
		if(newAnim != null)
		{
			blockerExists = blockerExists || newAnim.isBlocking();
					
			m_activeList.push(newAnim);
		}
		else
		{
			blockerExists = true;
		}
	}
}

Two final notes: First, PlaySound is not really an animation, but we can queue anything into the AnimationSequencer that conforms to the IAnimation interface. In fact, I found one of the most useful animation types is the BlockingDelayAnimation. It doesn’t affect a single thing on screen, but it takes up time, and it blocks further animations down the queue. It’s very useful for pacing apart screen events into digestible chunks for the player.

Second, I stated above that an IAnimation will call finish on itself when it reaches the end of it’s runtime inside of the it’s tick method. However, exposing finish via the interface allows us to have a clean way to skip animations while ensuring that everything winds up in the correct final position. If you want to allow players to skip through sequences, or you want to shut down all animations before leaving a display mode, simply call finish() on every animation in the list and then every animation in the queue in order. If the Animation classes correctly put all of their final object positioning/state/whatever code in the finish method, then everything will look exactly as if all the animations had completed normally.