Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

15. December 2016

 

Welcome to the next part in the ongoing Closer Look At game engine series.  The Closer Look series combines a review, overview and getting started tutorial in one and aims to give a developer a good idea ofs2closerlook a given game engine is a good fit for them or not.  Today we are looking at the S2Engine, which is a commercial game engine available for purchase on Steam.  Right off the hop the S2Engine is going to have two major strikes against it for many game developers.  First, it’s Windows only.  Figured I’d get the out of the way right up front as if you are looking to create cross platform games, this is not the engine for you.  Second, it’s commercial and closed source, although the price tag is quite low at around $20USD.

 

As always there is an HD video version of this review available here.

 

Without further ado, let’s jump in to the S2Engine.

 

The Tools

The entire development experience in the S2Engine takes place in the S2EngineHD Editor.  Here is the editor in action running the included sample scene:

s1

 

The S2Engine Editor is absolutely loaded with tools, lets take a guided tour.  First off we have the scene window where you can see a live rendering of your game in action:

sceneoptimized

 

Please keep in mind the fuzziness in the picture above comes from the animated gif process, the real-time viewport looks vastly superior to the image above.  The Scene view is where you place entities in your scene and preview the results.

 

You can switch between the various available tools using the following toolbar:

image

Or via menu:

image

 

The Project Window

image

 

This is where the various assets of your game are organized.  It’s an analog to the underlying file system of your project.  The buttons across the left enable you to filter the various different objects ( Animations, Models, FSMs, etc ).

 

The Class Window

image

 

This is where you can instance various game objects.  Select an object, click Create and then click within the scene to place the entity.  The newly created entity (or a selected entity) can then be configured using the lower Params window, which is context sensitive to the selected object.

 

Terrain Tools

image

 

S2Engine has a full terrain editing system in.  The Terrain tool enables you to interactively edit the scenes landscape (height map).  There are tools for raising/lowering, flattening, stepping and painting terrain as well as drawing roads and vegetation.  The actual editing is done in the scene window:

terrain

 

Model Window

image

 

S2Engine supports 3D animated models of FBX format, simply click the Import button in the Project view:

image

 

There are multiple different panels for configuring models, setting and handling animations, managing joints/bones and even vertex level manipulations.

image

 

There is also an animation panel.

image

This enables you to blend animations together, set and remove animation keys and of course, preview animations.

 

Material Window

imageimage

 

A detailed material system allowing multiple layers.  You can control diffuse and normal maps, UV tilting, lighting properties and more.  You also have control over detailed material attributes like alpha blending, animations, scattering, light emission and more.

 

Special FX

image

 

Fine tuned control over multiple special FX of types weather, post processing and environmental.  Fine tune control over all aspects, including water fx, sky, weather, lens effects, etc.  You also have fine tune control over day/night cycles using a keyframe system:

image

 

Cutscene Tool

image

 

S2Engine has a complete system in place for authoring cut scenes.  Includes a curve editor:

image

 

As well as a detailed timeline:

image

 

Hierarchy

image

This is essentially your scene graph.

 

Font Tool

image

Enables you to create png textures for fonts of varying dimensions with font preview.  Fonts are imported in fnt format created using the BMFont tool.

 

GUI Editor

image

Currently in Beta, there is a UI editor available.  You can create a hierarchy of UI widgets and configure them using the Params class panel.

 

imageimage

Currently supported widgets include button, slider, frame, input box, combobox, label, checkbutton, colorbox, image, listbox, groupbox, tabbox and rangeinputbox.

 

Publishing

When you are complete publishing is a pretty straight forward process.  This is one of the advantages of only supporting a single platform publishing is as simple as choosing a file name, starting scene, main script (entry point) and a few other settings and clicking the Publish button.

image

 

Coding

There are two ways to code in S2Engine.  You can use their own scripting language or the visual FSM (Finite State Machine) visual programming language.  The scripting language has the sc2 extension and has a C like syntax.  You can read the language reference here while the API documentation is available here.  Scripts are simply connected to (and thus control) game objects.  Here is an example script that controls a jeep found in the demo game.

#message TakeControl
#message LeftControl
#message LockWaypoints
#message UnlockWaypoints

#use "engine.wav"

var float acc;
var float steer;
var bool brake;
var bool control;
var float steerAng;
var string steerNode;
var string handsNode;
var float oldsteerAng;
var bool _on;

function void Init()
{
	_on=false;
	control=false;
	steerAng=0.0;
	steerNode="steer";
	handsNode="hands";
	AICreateObject(false,false,205.0,200.0);
	resetNodeFlags("camera","visible");
	resetNodeFlags("hands","visible");
	SetSource(10.0,15000.0);
}

function void PostInit()
{
	SetPerObjectMaterialData(3,0.0);
	resetNodeFlags("light01","visible");
	resetNodeFlags("light02","visible");
	_on=false;
}

function void update()
{
	if(control==true)
	{
		acc=0.0;
		steer=0.0;
		brake=false;
		RotateNode(steerNode,"rgt",-steerAng);
		RotateNode(handsNode,"rgt",-steerAng);
		
		SetSourcePitch((PhysicsGetVehicleRPM()*0.0025)+1.0);
		/*LOG( string(PhysicsGetVehicleRPM()) );*/
		
		if(IsKeyPressed("w"))
		{
			acc=1.0;
		}
		if(IsKeyPressed("s"))
		{
			acc=-0.5;
		}
		oldsteerAng=steerAng;
		if(IsKeyPressed("d"))
		{
			steerAng=steerAng+(frametime);
			steer=-1.0;
		}
		else
		{
			if(IsKeyPressed("a"))
			{
				steerAng=steerAng-(frametime);
				steer=1.0;
			}
			else
			{
				steerAng=0.0;
			}
		}
		if(IsKeyPressed(" "))
		{
			brake=true;
		}
		if(steerAng>=35.0)
		{
			steerAng=35.0;
		}
		if( steerAng<=(-35.0) )
		{
			steerAng=-35.0;
		}
		steerAng=ScalarInterpolate(oldsteerAng,steerAng,(frametime/200.0));	
		RotateNode(steerNode,"rgt",steerAng);
		RotateNode(handsNode,"rgt",steerAng);
		PhysicsVehicleControl(steer,acc,brake);
		
		if(acc!=0.0)
		{
			var vec3 fwdaxis;
			fwdaxis=GetWorldAxis("fwd");
			SendMessageMulti("pushOut",string(fwdaxis),400.0,"null");
		}
	}
	else
	{
		PhysicsVehicleControl(0.0,0.0,true);
		if(ObjectIsInRange("player01",300.0))
		{
			SendMessageSingle("player01","DriveVehicle","");
		}
	}
}

function void message()
{
	if( ReceivedMessage("TakeControl") )
	{
		/* lights control */
		var float tod;
		tod=float(GetLevelParam("TimeOfDay"));
		LOG("tod"+string(tod));
		if( (tod>=18.0) && (tod<=24.0) )
		{
			SetPerObjectMaterialData(3,1.0);
			if(!_on)
			{
				SetNodeFlags("light01","visible");
				SetNodeFlags("light02","visible");
				_on=true;
			}
		}
		if( (tod>0.0) && (tod<6.0) )
		{
			SetPerObjectMaterialData(3,1.0);
			if(!_on)
			{
				SetNodeFlags("light01","visible");
				SetNodeFlags("light02","visible");
				_on=true;
			}
		}
		if( (tod>6.0) && (tod<18.0) )
		{
			SetPerObjectMaterialData(3,0.0);
			ResetNodeFlags("light01","visible");
			ResetNodeFlags("light02","visible");
			_on=false;
		}
	
		/*=======================================0*/
		control=true;
		SetNodeFlags("hands","visible");
		PlaySound("engine.wav",true);
	}
	
	if( ReceivedMessage("LeftControl") )
	{
		control=false;
		ResetNodeFlags("hands","visible");
		StopSound();
		SetPerObjectMaterialData(3,0.0);
		ResetNodeFlags("light01","visible");
		ResetNodeFlags("light02","visible");
		_on=false;
	}
}

 

It’s a straight forward enough language, but I generally prefer that engines use an off the shelf scripting engine instead of rolling their own.  This gives the community access to a much larger source of expertise, sample code and generally is much more time tested and stable.  As you can see from the script above, much of the logic and communication is implemented via message passing.

The majority of in game programming however is done using FSM (Finite State Machines ) via the FSM graph.

s2

If you’ve ever worked in Blueprints in Unreal Engine or Flowgraph in CryEngine you should have a pretty good idea how FSM programming works.  You’re code responds to various events and creates program flows using a series of connecting cables to other states.  Each node can have multiple actions, configured like so:

s3

There are dozens of states available, and new ones can easily be created.

image

Variables are easily created as well.

image

In addition to local variables, parameters and globals can also be defined.

image

 

 

The Documentation, Community and Content

The S2Engine has a decent amount of documentation, reference materials, getting started videos and beginner projects.  There are however a few issues, the first of which is English.  The developers primary language is not English and it shows on occasion in the documentation.  The actual UI is very well translated but some of the documentation  is certainly a tad “Engrish”.  Worse, some of the linked starting videos aren’t in English at all.  I have no issue with non-English videos, but I would recommend not linking them directly from an English localized application.

In terms of actual available documentation, there is a Wiki available here, a very solid reference manual available here, and a series of video tutorials available here.  S2Engine also comes with the beginner scene you’ve seen throughout this review.

The community for S2Engine isn’t huge but there is an active forum.  There is also a Trello bug tracking board available on Trello as well as a few other community options.  One impressive thing about the engine is the engine developer is very responsive to user requests and feedback.

One interesting aspect of the S2Engine is the existence of Content DLC.  These are themed content packs you can download and use in your game.  Currently the only content pack is the Medieval content pack shown in the video below. There is another content DLC pack in the works.

 

 

Conclusion

I pointed out the two biggest negatives to this game engine in the very first paragraph.  It’s Windows only, both for tooling and target platforms.  It’s closed source and commercial.  For many those are going to be big enough deal breakers that nothing else really matters.  For the rest though, is this a worthwhile engine?  For a small team effort, there is a massive amount of functionality included in this engine, it’s capable of some staggeringly pretty results and the workflow, once understood, is pretty accessible to people with limited programming experience.

My biggest recommendation to the developer behind this engine is to make a proper demo available.  What will get people to use this engine over the other options is that they prefer the workflow, the tools, the built in assets, etc.  The lack of a current demo is going to vastly limit the potential audience.  Even with a low price tag, few people will spend money to evaluate an engine and having a previous weaker version of your engine available as the trial is certainly a mistake.  When you go to the download section of the website, you are greeted by this text:

NOTE: The version to be downloaded here (1.4.5) is a previous, very old, FREE BETA version. It is useful just for letting you to view How S2ENGINE HD is organized and How it works. It doesn’t represent the final quality of the 1.4.6 Steam version.

This is quite simply a mistake.  A demo is about selling people on your engine, so having them experience a “very old” version is a bad idea.  Always put your best foot forward when showing an engine.  I would recommend creating a version of the current engine that is full featured but either time locked, save limited or publish limited.  You will have a great many more developers willing to give your engine a try. 

I have found the performance to be a bit inconsistent.  I was running consistently at 70+ FPS, then struggled to hit 15FPS for a while with a ton of UI glitches.  Upgrading to the newest nVidia drivers didn’t help.  Then oddly, switching Optimus to use the integrated GPU, then back to dedicated seemed to fix the problems.  Hopefully these problems are localized to me and not widespread.  I wish the developers used a standard UI toolkit like Qt, as their custom implementation can be a bit buggy or not perform as you’d expect.  I also unfortunately experienced a half a dozen crashes while evaluating the engine, including one while making the video version of this review.

 

The Video

Programming , ,

6. December 2016

 

A couple years ago I did a detailed text tutorial on how to use a debugger which oddly is a massively important skill that simply isn’t taught.  Given that this article is still popular two years later I’ve decided to follow it up with a video version.  This video, Debugging 101, walks through the basic tasks involved in debugging.  It used Visual Studio 2017 and C++ but should be applicable in most languages and IDEs.  The video shows how breakpoints and conditional break points work, how to step into, over and out of your code, how to use the local and watch window, call stacks, how to do memory debugging and more.  Basically the video shows you how to get started using a debugger.

 

The following is the code used in this example.  There is nothing special to this code, it’s extremely contrived, but it enabled me to show the various features available in most debuggers.

#include <iostream>

// These two functions are used to illustrate how the call stack works
// As well as how step into and step out of behave.
int innerFunction(int input) {
	int meaninglessCounter = 0;
	for (int i = input; i > 0; i--) {
		// First show stepping through the loop
		// Set a conditional breakpoint that breaks when i is a certain value.
		meaninglessCounter++;
	}
	return input;
}

int outerFunction() {
	int i = 42;
	return innerFunction(i);
}


class Demo {
	std::string stringValue;
	int intValue;
	bool booleanValue;

	public: 
		Demo(std::string a, int b, bool c) : stringValue(a), intValue(b), booleanValue(
		c) {};
};

int main(int argc, char ** argv) {
	// Callstack demo, jump into, jump over example
	int someVal = 0;
	someVal = outerFunction();

	// Data example -- simply create a char buffer, fill it with 'a' then null 
	terminate it so 
	// it can be treated like a string.
	char * data = new char[1000];
	for (int i = 0; i < 1000; i++)
		data[i] = 'a';
	data[999] = 0;
	std::cout << data << std::endl;

	//set a watch on d.  Demonstrates watches and drilling into complex object
	Demo d("Hello", 42, true);
	
	std::cout << "End of demo" << std::endl;
	delete[] data;
	// delete[] data;  Calling delete again will trigger an exception
}

Programming , , ,

29. November 2016

 

Welcome to the next part in the ongoing BabylonJS Tutorial Series.  In the previous tutorial we created our first simple scene which contained a simple camera.  In this tutorial we are going to explore the concept of camera’s in more depth.  As always there is an HD video version of this tutorial available here.

 

In the previous example we created a Free Camera.  A Free Camera is a camera that can be moved around using the mouse and keyboard.  Often of course you are going to want to be able to customize which keys are used to move the camera.  Here is an example that configured the camera to respond to the WASD keys.

 

var canvas = document.getElementById('canvas');

        var engine = new BABYLON.Engine(canvas, true);

        var createScene = function(){
            var scene = new BABYLON.Scene(engine);
            scene.clearColor = new BABYLON.Color3.White();
            var camera = new BABYLON.FreeCamera('camera1', new BABYLON.Vector3(0,
            0,-10), scene);
            camera.setTarget(BABYLON.Vector3.Zero());
            camera.attachControl(canvas,true);
            camera.keysUp.push(87);    //W
            camera.keysDown.push(83)   //D
            camera.keysLeft.push(65);  //A
            camera.keysRight.push(68); //S


            var box = BABYLON.Mesh.CreateBox("Box",4.0,scene);
            return scene;
        }

        var scene = createScene();
        engine.runRenderLoop(function(){
            scene.render();
        });

 

This code running:

1

When we create the camera, we pass it’s initial location within our scene.  In this case the position is (0,0,-10) or –10 down the Z axis.  Of course we also pass the scene the camera belongs to into the constructor as well.  Next we set the target of the camera, in this case the origin (or a Zero vector).  Finally we need the camera to actually receive input controls from the canvas.  This is simply done by calling attachControl.  This will result in input (such as mouse and keyboard) being passed to the camera for processing.  There is a member of the camera for keys representing up, down, left and right movement.  To each we pass the appropriate key scancode for the each key in the WASD combination.  When you run this code, you can now navigate around the scene using the WASD and free look using the mouse.

 

Another common camera type is a camera that orbits an object.  That is the camera revolves around the target in a circular orbit.  This is accomplished using an ArcRotateCamera.  Keep in mind however, you could also implement this camera in any other camera object available in Babylon, it would however be your responsibility to implement the functionality.  The follow is the code to create an ArcRotateCamera:

        var createScene = function(){
            var scene = new BABYLON.Scene(engine);
            scene.clearColor = new BABYLON.Color3.White();

            var box = BABYLON.Mesh.CreateBox("Box",4.0,scene);
            var camera = new BABYLON.ArcRotateCamera("arcCam",
                    BABYLON.Tools.ToRadians(45),
                    BABYLON.Tools.ToRadians(45),
                    10.0,box.position,scene);
            camera.attachControl(canvas,true);

            return scene;
        }

 

This code running:

2

The three major parameters to the ArcRotateCamera are the alpha, beta and radius.  Radius is straight forward, this is the distance to orbit the target.  Think of the target as the mid point of a circle.  The radius then defines the size of the circle the camera will follow.  Alpha is the rotation angle around the X axis, while beta is the rotation angle around the Y axis.  Note that both take their parameter in radians instead of degrees, so we have to convert using the ToRadians() helper method.

 

The final kind of camera we are going to look at is the FollowCamera.  This camera does exactly what you’d expect, it follows a given target.  Let’s look at some code:

        var canvas = document.getElementById('canvas');

        var engine = new BABYLON.Engine(canvas, true);

        var createScene = function(){
            var scene = new BABYLON.Scene(engine);
            scene.clearColor = new BABYLON.Color3.White();

            var box = BABYLON.Mesh.CreateBox("Box",4.0,scene);

            // Create a second object so we can actually witness the movement
            // Make this one wireframe to distiguish the difference.
            var box2 = BABYLON.Mesh.CreateBox("Box2",4.0,scene);
            var material = new BABYLON.StandardMaterial("material1",scene);
            material.wireframe = true;
            box2.material = material;

            box2.position = new BABYLON.Vector3(0,5,0);

            var camera = new BABYLON.FollowCamera("followCam",BABYLON.Vector3.
            Zero(),scene);
            camera.target = box;
            camera.radius = 100;
            camera.heightOffset = 0;
            camera.attachControl(canvas,true);

            scene.activeCamera = camera;
            return scene;
        }

        var scene = createScene();
        engine.runRenderLoop(function(){
            scene.getMeshByName("Box").position.y += 0.1;
            scene.getMeshByName("Box").position.x += 0.1;
            scene.render();
        });

 

This code running:

3

This code contains two rendered cubes, the second a box with a wireframe material attached.  This was done so you could actually detect movement!  We will cover the specifics of materials shortly so don’t sweat the details.  Notice in the creation of the FollowCamera we pass in a target to follow (box), how far away we should follow from (radius) and there is also a control for how height the camera should be relative to the target.

 

It should be noted these are only a few of the possible cameras implemented in BabylonJS.  There are a good dozen other cameras available out of the box with features like touch or gamepad control, VR rendering and more.  The basic principals remain the same regardless to camera type.

The Video

Programming , ,

23. November 2016

 

A conversation just happened on /r/gamedev with the confusing title “What don't new game developers know that they don't know?”.  Essentially it was a question asking what important advice new developers don’t know (for lack of experience) but should.  My answer seems to have been extremely well received and the question is quite good, so I figure I would replicate the answer here for those of you that don’t frequent reddit.

So essentially what follows is my advice I would give my beginner self if I owned a time machine. 

  • most people fail and fail hard. Perseverance is probably the most underrated but required skill of a successful game developer.

  • learning tasks in parallel is rarely productive. While I know you want to learn everything now... don't. Learn one task/language/skill to a base level of competency before moving on to the next. It's hard because everything is shiny and new, but it's critical. Trying to learn too many things at once results in learning nothing at all.

  • when you are 90% done, you're actually 50% done. Maybe... if you are lucky.

  • there is no ego in programming, or at least there shouldn't be. A plumber or carpenter come into a job with a toolbox full of tools, and don't limit themselves to a number three hex drive because "it's cool". They use the best tool for the job and sometimes that tool is a horrific hack and that's ok too. Programmers... have often failed to learn this lesson. People invest themselves in "their language" and this frankly, is stupid. Working in C++, Java, Haskell, F#, Go, Rust or whatever other language doesn't make you cool, just as working in GameMaker, Lua, JavaScript doesn't make you uncool. Only exception, PHP. %#@k PHP. Moral of the story... use the right language or tool for the job. Sometimes that means performance, sometimes that means maintainability, sometimes that means quickness, sometimes that means designer friendly and sometimes it means using the tool the rest of your team is using. Be pragmatic, always be pragmatic. I personally would never hire a programmer who claimed there was a "best" language. If you have been programming for several years and don't have several languages in your arsenal, you are probably doing it wrong.

  • if it feels wrong, it probably is. If you encounter such a code smell, even if you can't fix it, comment on it and move on.

  • even if you work alone, comments are good. But comments for the sake of comments is worse than no comments at all. Take the time to write legible code, take the time to smartly comment what you've written. Six months or six years down the road, you will thank yourself.

  • version control or at least automatic backups. Do it. Now.

  • premature ejac.... er, optimization is the root of all evil. Yes, you need to be mindful of performance on a macro level, but don't sweat the micro level until you have to. If you are just starting out, but your primary concern is performance, you are doing it wrong.

  • learn how to use a debugger. Early. It will be an invaluable skill and will help you understand how your code works better than any other single task you can perform. Also learn how to use a profiler and code linter, these will give you great insights into your code as well, but you can do this later. If you have learned the basics of a programming language but haven't groked debugging yet, stop everything and dedicate yourself to that task. For a somewhat generic debugging tutorial start here. Seriously, learn it, now.

  • if you have access to a peer, TAKE IT. Peer reviewed coding, while at first annoying, is invaluable. Even if there is a large skill mismatch between the two people. Even just having someone go through your code and ask "why did you do this?" forces you to explain it... and often you realize... hey... why did I do that? Many programmers are solitary creatures, so the idea of peer code reviews or paired programming is anathema. Or people can be very shy about showing their code... it's worth it to get over it. Of course not everyone has access to another programmer to use as a sounding board and not being in person makes it a lot harder and a lot less useful.

  • books are great, as is a gigantic collection of links to great articles on the web. That said unfortunately, you can't just buy a book and learn via osmosis... you actually have to read the thing. More to the point, if you are following a tutorial or video, DO THE WORK. You will learn it a great deal more, develop muscle memory of sorts, if you actually do it. This means typing out the code and getting it to run, instead of just loading the project and pressing play. Trust me, you learn a lot more actually recreating the project.

 

There you go, 11 hard earned pieces of wisdom for new developers, hope you find them useful.  Anything you you would add or anything you disagree with?  Let me know in the comments below!

Programming

9. November 2016

BabylonBannerSplattered_thumb[3]

Welcome to the GameFromScratch.com tutorial series covering the BabylonJS HTML5 game engine.  The home page of the series is available here.  For each tutorial in the series there is both a text and video version available.  In this post we are simply going to introduce the BabylonJS engine, the scope of this tutorial series and discuss why you might want to use the Babylon engine and also some of the alternatives available should you decide not to.  If you’ve already decided on using the BabylonJS game engine, jump forward to the next tutorial.

 

BabylonJS Overview

I’m not going to go into a great deal of detail on the functionality included in BabylonJS as I have already featured this game engine in the Closer Look game engine series.  Instead we are going to take a quick top level look at the engine features.

 

Why Choose BabylonJS

So, why is the BabylonJS game engine a good choice for you?

  • Open source (Apache 2 license) and free to use
  • Full featured 3D game library (scene graph, physics, particles, models, sprites, etc)
  • Compatible with most modern WebGL browsers
  • Excellent tooling, including level editor, Unity exporter, model converters
  • Good documentation, samples and other learning materials

You can read the full feature set here.

 

Why Not Choose BabylonJS?

So we mentioned a number of Babylon’s strengths, but why would you *not* choose to use BabylonJS?  Well, beyond the fact you may not like how they implement things the biggest reason all comes down to your priorities.  WebGL performance isn’t on par with desktop OpenGL or even OpenGL ES, so there is a bit of a performance penalty at work here.  While HTML5 applications can be wrapped to run as applications on various mobile devices, again there is a price to be paid, in both performance and labor.

At the end of the day, personally, I think a lot of it comes down to your primary target.  If you are creating a browser game first and foremost, I recommend working in a browser native library such as BabylonJS.  This has the most direct workflow, is easiest to debug, etc.  If on the other hand the browser is just another target for you you are probably better off working in a game engine that also targets HTML5, such as Unreal or Unity.

 

Alternatives to BabylonJS

Shockingly there aren’t actually a ton of HTML5 3D game engines or frameworks like BabylonJS.  The most direct alternatives are:

 

By no means is that an exclusive list, but it does represent some of the most common 3D engines with WebGL as their primary target.

In addition to these engines several 3D engines offer HTML5 as a target including Unreal, Unity, Godot and many more.  The primary challenge with these options is the generated code is often illegible, acting almost identically to a compiled binary.  So if things don’t work right you are dependent on the engine developer to fix things.  Or if you wish to use a native browser feature, you are again dependent on the engine developers to support it in some form.

 

Enough overview, lets jump into the technical details.  I am aiming to keep each tutorial somewhat short and concise, both text and video versions.  Stay tuned for the first tutorial covering getting BabylonJS up and running.

 

The Video

Programming , , , ,

Month List

Popular Comments