Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

18. September 2012

 

The release candidate for the upcoming Blender 2.64 release was um, released today ( that’s a lot of releasing… ).blenderSS

 

For game developers, there isn’t really a ton in this release targeting you, but then, we made out like bandits in the prior release so we can’t be greedy I suppose.

 

Of perhaps most interest to game developers, are the following features:

 

Mesh Tools

Bevel and inset now are modal operator with mouse control, a wire frame tool was added to turn edges into wireframe tubes, and vertex/edge/face sorting tools were improved.

 

Sculpting

Sculpting has received some major improvements such has masking to control which areas of the mesh are influenced by sculpting, new brush map modes to control how textures are projected onto the model, and an input stroke averaging option to make brush strokes smoother.

 

Skin modifier

The Skin modifier takes a skeleton made up of vertices and edges as input and generates a polygon skin as output, consisting mostly of quads aligned along the edges. The output is mostly quads, although some triangles will appear around intersections.

 

Game Engine

Lamps and shadows were improved with support for variance shadow maps, shadow color, sun lamp shadows and lamp textures. Non-power of two textures and compressed textures will now load faster and use less memory. A new Character physics type was added, designed for player controlled characters for which a full dynamics simulation would give unwanted behavior.

 

and perhaps most important of all:

Collada

The Collada exporter has been improved for better support of export to game engines, with more fine grained options to control which data is exported.

 

In addition to the above changes, there were a number of changes to the camera tracking tool, the Cycles renderer, green screening tools and a number of smaller fixes and tweaks.

 

You can download the release candidate here.

Art

17. September 2012

 

In this tutorial, we are going to look at how to play audio using Moai.  This is going to make heavy use of moaigui, so if you haven’t already, you may want to check the prior tutorial in this series.

 

As always, let’s jump right in to the code.  You may notice that the code order is starting to seem a little… odd. This is because I am grouping code mostly in order of newness, as opposed to how it would logically be laid out.  If you are thinking “I wouldn’t order things this way!”, well.. neither would I, normally.

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
if screenWidth == nil then screenWidth =1280 end
if screenHeight == nil then screenHeight = 800 end


MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"


local gui = moaigui.GUI(screenWidth,screenHeight)


gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
    if (MOAITouchSensor.TOUCH_DOWN == eventType) then
        onMouseLeftEvent(true)
    elseif (MOAITouchSensor.TOUCH_UP == eventType) then
        onMouseLeftEvent(false)
    end
end


if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end


function onButtonClick(event,data)
    if(string.find(buttonPlay:getText(),"Play") ==1) then
        song1:play()
        buttonPlay:setText("Stop, that's creeping me out!")

        function checkSongDone()
            local song = song1
            while song1:isPlaying() do coroutine:yield() end

            song1:stop()
            buttonPlay:setText("Play Audio File using Untz")
        end

        if not checkLoop:getChecked() then
            songPlayingThread = MOAICoroutine.new()
            songPlayingThread:run(checkSongDone)
        end
    else
        song1:stop()
        buttonPlay:setText("Play Audio File using Untz")
    end
end

function onButtonRewind(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos - tenPercent
        if(pos > 0) then
            song1:setPosition(pos)
        end
    end
end

function onButtonFastForward(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos + tenPercent
        if(pos < length) then
            song1:setPosition(pos)
        end
    end
end

function onCheckboxChanged()
    song1:setLooping(checkLoop:getChecked())
end

function onVolumeChanged()
    MOAIUntzSystem.setVolume(sliderVolume:getCurrValue() / 100)
end



buttonPlay = gui:createButton()
buttonPlay:setPos(0,0)
buttonPlay:setDim(100,25)
buttonPlay:setText("Play Audio File using Untz")
buttonPlay:registerEventHandler(buttonPlay.EVENT_BUTTON_CLICK,nil,onButtonClick)

checkLoop = gui:createCheckBox()
checkLoop:setPos(0,26)
checkLoop:setDim(100,10)
checkLoop:setText("Loop?")
checkLoop:registerEventHandler(checkLoop.EVENT_CHECK_BOX_STATE_CHANGE,nil,onCheckboxChanged)

labelVolume = gui:createLabel()
labelVolume:setText("Volume")
labelVolume:setPos(0,36)
labelVolume:setDim(30,10)

sliderVolume = gui:createHorzSlider()
sliderVolume:setPos(30,36)
sliderVolume:setDim(70,10)
sliderVolume:setRange(0,100)
sliderVolume:setCurrValue(50)
sliderVolume:setValueDisplayLoc(sliderVolume.VALUE_DISPLAY_RIGHT)
sliderVolume:registerEventHandler(sliderVolume.EVENT_SLIDER_VALUE_CHANGED,nil,onVolumeChanged)

buttonRewind = gui:createButton()
buttonRewind:setPos(0,50)
buttonRewind:setDim(50,10)
buttonRewind:setText("Rewind")
buttonRewind:registerEventHandler(buttonRewind.EVENT_BUTTON_CLICK,nil,onButtonRewind)

buttonFastForward = gui:createButton()
buttonFastForward:setPos(51,50)
buttonFastForward:setDim(50,10)
buttonFastForward:setText("Fast Forward")
buttonFastForward:registerEventHandler(buttonFastForward.EVENT_BUTTON_CLICK,nil,onButtonFastForward)

MOAIUntzSystem.initialize ()
MOAIUntzSystem.setVolume(0.5)
song1 = MOAIUntzSound.new()
song1:load("demongirls.ogg")

 

If we run the application( in the Moai-untz host ), we see:

 

image

 

Click the “Play Audio File using Untz” button and the song will play, the slider increases and decreases the volume, the loop check box I bet you can guess what it does.  Rewind and Fast Forward change the playback position by 10% at a time.

 

This looks like a wall of code, but don’t worry, it’s not all that bad.  In reality, most of it is maoigui code that we covered earlier.  We use a couple of new controls ( the check box and slider ), but nothing in the UI code should be all together new.  Most of our new logic is in the click handler methods, so let’s start there.

 

function onButtonClick(event,data)
    if(string.find(buttonPlay:getText(),"Play") ==1) then
        song1:play()
        buttonPlay:setText("Stop, that's creeping me out!")

        function checkSongDone()
            local song = song1
            while song1:isPlaying() do coroutine:yield() end

            song1:stop()
            buttonPlay:setText("Play Audio File using Untz")
        end

        if not checkLoop:getChecked() then
            songPlayingThread = MOAICoroutine.new()
            songPlayingThread:run(checkSongDone)
        end
    else
        song1:stop()
        buttonPlay:setText("Play Audio File using Untz")
    end
end

This is the code that is executed if the brilliantly named buttonPlay button is clicked.  The first thing we do is check to see if the button’s text starts with “Play”, which if it does, that means we are currently not playing a song.  Therefore we start playing our song ( aptly named song1 ) with a call to the play() method.  We then update our button’s text to indicate it’s new status.  The next little bit of code might seem a bit confusing at first, but it is pretty powerful.

 

First we are declaring a nested function checkSongDone().  The next line is where the magic happens.  It’s a while loop that loops until the condition isPlaying() becomes false.  Each pass through the loop we call coroutine:yield().  So, what the heck was that all about? Well… this is going to be a bit tricky to explain, but I will do my best.

 

If you are an old fart like me, you may remember multitasking in the pre-Win95 pre-emptive multitasking days.  Essentially you ran multiple programs at the same time by giving each one a chunk of processing time and a well behaved program was supposed to yield control when it wasn’t busy.  Essentially the operating system switched between running programs and said “ok, your turn” and a well behaved program would quickly exit if it was done doing whatever it needed to do.  Well, in a nutshell that is what is happening here.

 

A coroutine is sort of the Moai version of a thread.  Using coroutines, you are able to run code in parallel, so while our game loop is still running, the function checkSongDone is blissfully churning away in the background checking to see if the song is done.  Thing is, until the song is actually done, the coroutine doesn’t really have anything else to do, so it calls yield().  It is not until that condition is true ( isSongPlaying returns false ) will the function continue execution.  Once this happens we (somewhat redundantly) call song1:stop() then update the button’s text to “Play Audio…”.  The end result of this whole chunk of code is, we essentially created an on song end handler, so when the song is done playing, the text of the button will update.

 

The next chunk of code checks the checkbox checkLoop, which if checked indicates that we want the song to loop continuously.  If it is not checked, that means we want to run our coroutine, which we create with a call to MOAICoroutine.new(), then run with the run() method, passing in the function to run.

 

If the button’s text however indicates that the song is playing, in which case the button acts like a STOP button, simply stopping playback ( even if loop is checked ) and resetting back to the “Play…” text.  I encourage you to look deeper in to Coroutines, they are a very powerful feature of Lua. MOAICoroutine itself is only a thin wrapper over Lua’s coroutine abilities.

 

Also, you may have noticed MOAIThread in the examples and wondered when to using MOAIThread instead of MOAICoroutine.  The short answer is, you dont!  The more accurate and useful short answer is, MOAIThread *IS* MOAICoroutine.  It was renamed and the documentation just hasn’t caught up yet.  Well at least, it hadn’t at the point I wrote this.

 

TL;DR, coroutines are cool, learn about them, use them, love them.

 

function onButtonRewind(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos - tenPercent
        if(pos > 0) then
            song1:setPosition(pos)
        end
    end
end

function onButtonFastForward(event,data)
    if song1:isPlaying() then
        local pos = song1:getPosition()
        local length = song1:getLength()
        local tenPercent = (length * 1.10) - length
        pos = pos + tenPercent
        if(pos < length) then
            song1:setPosition(pos)
        end
    end
end

This is the code that executes if you hit the Fast Forward or Rewind buttons.  Basically it gets the current position of the song ( playback-wise ) using getPosition() and the length of the song using getLength().  Next we calculate what 10% of the length is, and either add or subtract that amount from the current position depending if we are fast forwarding or rewinding.  Next we check sure to make sure the position is valid after being modified and if it is, we change the position of the song using setPosition().  End result, pressing Fast forward advances the song by 10% of it’s length, while hitting rewind moves it back 10%.

 

function onCheckboxChanged()
    song1:setLooping(checkLoop:getChecked())
end

This is the code that executes if the checkbox checkLoop is checked or unchecked.  It simply toggles the value of setLooping based off the if the checkbox is checked, using getChecked().

 

function onVolumeChanged()
    MOAIUntzSystem.setVolume(sliderVolume:getCurrValue() / 100)
end

This is the code that is executed if the slider sliderVolume is changed.  This uses the MOAIUntzSystem singleton to change the global volume level using setVolume().  setVolume takes a value from 0.0 to 1.0, while the sliders value returned by getCurrValue() is from 0 to 100, so we divide it by 100.

 

So then, what the heck is an Untz?  It’s the included cross platform audio system.  You also have the option to use FMOD, a commercial audio solution.  Untz does the trick though, works on Android and iOS.  There is a very important caveat… if you are going to be running using the UNTZ audio library you need to use the Moai-untz host!!! when running on the PC.  You will get exceptions if you try to call MOAIUntzSystem from the normal Moai host.  The Moai-untz host is in the same directory as the Moai host.

 

Most of the UI code is pretty much identical to what you’ve seen already, so I wont really go into it, that leaves:

 

MOAIUntzSystem.initialize ()
MOAIUntzSystem.setVolume(0.5)
song1 = MOAIUntzSound.new()
song1:load("demongirls.ogg")

This code first initializes the MOAIUntzSystem singleton.  Then it sets the global volume to 50%.  Next we create our song song1 with a call to MOAIUntzSound.new().  Finally load the song ( in the same directory as main.lua ) with a call to load().

 

You might be asking yourself “what sound formats does Untz support?”.  That’s a good question with a tricky answer.  Basically the safe answer is ogg and wav.  The not so safe answer is, it depends on the platform.  For example, iOS, Mac and Windows will work with mp3, but Android wont.  Beyond WAV and OGG, the file is just passed to the native system for handling, so if the platform supports it, Moai supports it.

 

But if you really want to be cross platform, just encode everything in to ogg.  In this day of increasingly wealthy lawyers, the last thing we want to do is deal with a patent encumbered format like mp3!

 

Oh yeah, and the music… it’s seriously creepy stuff and I downloaded it from here.  Obviously you can substitute any song you want from your Justin Beiber collection, just make sure it’s not an MP3 if you are running on Android.  Of course, you can convert from just about any audio format to just about any other audio format using the free and amazing Audacity.  I do get 10% of all proceeds for saying that.  I may need a new business model.

 

Programming , ,

17. September 2012

In recent years I have gotten used to having increasingly larger hard drives, so I never really paid attention to how large Windows 7 had gotten.  Until I recently installed Win 7 on a 60GB SSD partition…

and nearly half of if was consumed by the OS!

 

This is simply put, insane.

 

The first obvious use of space is the PageFile ( 4GB, or 1 to 1 with actual RAM ), but for now I am leaving that one be.  So I wondered how much low hanging fruit I could get, and the answer is, surprisingly a lot.

 

First to go, System Restore.  I never use this feature anyways.  I backup remotely, so If my OS corrupts, I reinstall.  I never trust the state of a machine that requires a System Restore anyways.  Net savings, 1.5GB.

 

Next up, and this one kinda sucks to remove, but Hibernate.  Removing Hibernate ( powercfg –hibernate off ).  Net savings, 4GB.

 

Then I went in to Windows Features and removed the bits I didn’t use ( Games, DVD Maker, a few others ) for a grand total savings of a few hundred MB.  Meh.

 

At this point I started to think I was out of options, then I looked at WinSxS in the Windows folder… holy crap.

 

image

 

10GB, and a fresh install!  What the hell is going on here!

 

So looking in to it a little deeper, apparently Windows 7 makes a backup copy of pretty much every file it ever patches, so after a service pack there is a gigantic amount of bloat, so you can uninstall the Service Pack.  This one falls under the same category as System Restore… I will not undo a Service Pack install… I will reinstall completely.  Problem is, I couldn’t figure out a way to remove bits from WinSxS safely, until I found this.

Basically you run ( as Admin )

dism /online /cleanup-image /spsuperseded

and:

image

 

And after it finished:

 

image

 

A net savings of nearly 4GB. 

 

So, grand total I managed to shrink the install size down by damn near 50%, with almost no downsides.  So, if you are running on a solid state drive and need more space free, consider the steps above.  Just a warning, if something goes wrong ( a bad driver install, a corrupted program install, etc… ) you will probably be doing a reinstall.

Totally Off Topic ,

16. September 2012

 

So this weekend, my ultra-portable laptop gave up the ghost.  That machine dual booted Windows 8 and Ubuntu and I found myself using it more and more often, mostly because I’ve been travelling a lot and my primary laptop ( an Asus G53sx ) weighs slightly more than a small moon.  Well yesterday morning, I got the Windows 8 sad face of death I didn’t even know exist:

 

image 

 

That wasn’t my actual error, it was something to do with rdyboost.sys, which is somewhat odd, as it was disabled.  Anyways, it rebooted once, gave the exact same error counted up to 100% of something ( I don’t recall what ), then hung with interlaced video.  After about 20 minutes, I tried the power and…  nothing.  Machine wont even post, but it will get really really hot.

 

After trying the standard series of fixes ( pulling the battery, holding power down for 30 seconds, unplugging CMOS battery ) I promptly declared it dead.  Granted… it’s been two years, the battery life was fading and I was ready for a change, so perhaps there was more I could do to fix things…….  As a result I spent Saturday laptop shopping, one of my favourite activities, much to my wife’s chagrin.  The end result of what I purchased was a bit shocking… at least to me.

 

This is my old machine, may it rest in piece.

 

1830t

 

It’s an Acer Timeline 1830T and even with it’s relatively early trip to the laptop graveyard, I heartily recommend this machine.  It had 7 or 8 hour battery life, an i3 processor, 4 gigs of RAM and an HD3000 integrated GPU.  Nothing to write home about, but at the same time, a solid portable machine.

 

More to the point… I spent less than 500$ on it! 

 

So when it gave up the ghost, I decided “hell, Ill just buy the new version of the same thing”.  So basically I figured I’d toss out another 500$ for an updated ( maybe an HD4000 instead ) version of the same thing.  How wrong I was.  They don’t exist anymore.  I found machines with basically identical specs to this laptop I bought over 2 years ago… for 300$ more!  The equivalent Acer TimelineX now, with modern hardware is pushing 1000$.  This is insane!

 

And the kicker is, nobody makes good spec’d affordable ultraportables anymore.  There were lots of entry level crap machines, or if I was willing to spend 2 grand, there were some nice HP Envy’s.  Even Dell was a pretty heavy failure, the Alienware 13” seems to no longer exist, nor does that sexy 13” magnesium model they used to sell.  On top, their website is complete and utter crap ( they all were, far too much clutter, but Dell’s was particularly terrible… someone should get sacked for allowing this to happen, it reminded me of a Geocities made page for a mom and pop computer dealer ).

 

In the end I purchased…  A MacBook Air.

 

Unlike the MacBook Pro’s, the MacBook Air is actually reasonably priced ( just over a grand ), has the best screen for the price in a 13” ( 1440x900 ), plus gives me the added flexibility to do iOS development on the go, instead of being tethered to my iMac as I am now. 

 

This is an odd choice for one particular reason, I don’t particularly like Apple.  I find most of their products over-priced, I am not a huge fan of the OS ( the first thing I did was install Bootcamp and Windows 7 ) and I really disagree with their recent actions ( suing over rounded rectangles… walling every garden they see… ), but at the end of the day, on just straight hardware requirements, the MacBook Air 13” ticked all my boxes, often better than much more expensive Windows machines (HP Envy, I’m looking at you!).

 

So… if you are in the market for a medium end ultra-portable Windows machine… consider buying a Mac, bizarrely enough.

 

That is my only issue…  this isn’t by primary machine and I have a NAS at home for storage, as well as a Dropbox account I use on the go, so space isn’t a gigantic deal.  However, once you take a 125GB solid state drive, split it in half and install two OSes, XCode, Visual Studio and a dozen other dev tools… well let’s just say space get’s really tight fast.

 

If you were wondering at the lack of posts the last couple days, well, that’s why.  If there are a few more Mac/iOS related posts in the future, or Mac OS screenshots, that is also why. Smile  It has only been two days so far, and almost all of that time spent installing software and operating systems, but I have to say, I am very impressed thus far.  It sucks I can’t find an blogging program on OSX that comes even close to Windows Live Writer ( amazingly enough considering it’s freeness ) and the function key being where Control should be is aggravating, but those are small sacrifices.  The keyboard is awesome ( I use a Mac keyboard with my PC so… ), the screen is probably the best available at this resolution ( that was the biggest drawback to the Timeline, screen was horrid for glare, almost unusable outside ) and it’s snappy.  Not earth shatteringly fast, but I was pleasantly surprised when I ran the Windows Experience Index:

 

image

 

Now I just need to discover if turning off the PageFile is a bad idea in the age of Windows 7.  I have a feeling it might not actually be all that important anymore, and a 4GB pagefile is like 8% of my partition!  In retrospect, I kind of wish I made the Windows partition bigger, I don’t really need all that much space on the Mac OSX partition.  I wish I had a bit more space, but in classic Apple style, they really do screw you on upgrades.

Totally Off Topic

13. September 2012

 

This post is a simple summary of all the code used in the complete Vita game tutorial series, all on a single page.  Additionally, you can download the complete project here.

 

Here is the end result of all of this code in action:

Vita pong!

 

 

And here is the code:

 

AppMain.cs

using System;
using Sce.PlayStation.HighLevel.UI; 
using Sce.PlayStation.HighLevel.GameEngine2D;

namespace Pong
{
    public class AppMain
    {
        public static void Main (string[] args)
        {
            Director.Initialize();
            UISystem.Initialize(Director.Instance.GL.Context);
            Director.Instance.RunWithScene(new TitleScene());                
        }
    }
}

 

TitleScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Audio;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.Core.Input;
 
namespace Pong
{
    public class TitleScene : Scene
    {
        private TextureInfo _ti;
        private Texture2D _texture;
        
        private Bgm _titleSong;
        private BgmPlayer _songPlayer;
        
        public TitleScene ()
        {
            this.Camera.SetViewFromViewport();
            _texture = new Texture2D("Application/images/title.png",false);
            _ti = new TextureInfo(_texture);
            SpriteUV titleScreen = new SpriteUV(_ti);
            titleScreen.Scale = _ti.TextureSizef;
            titleScreen.Pivot = new Vector2(0.5f,0.5f);
            titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                              Director.Instance.GL.Context.GetViewport().Height/2);
            this.AddChild(titleScreen);
            
            Vector4 origColor = titleScreen.Color;
            titleScreen.Color = new Vector4(0,0,0,0);
            var tintAction = new TintTo(origColor,10.0f);
            ActionManager.Instance.AddAction(tintAction,titleScreen);
            tintAction.Run();
            
            _titleSong = new Bgm("/Application/audio/titlesong.mp3");
            
            if(_songPlayer != null)
            _songPlayer.Dispose();
            _songPlayer = _titleSong.CreatePlayer();
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);

            // Clear any queued clicks so we dont immediately exit if coming in from the menu
            Touch.GetData(0).Clear();
        }
        
        public override void OnEnter ()
        {
            _songPlayer.Loop = true;
            _songPlayer.Play();
        }
        public override void OnExit ()
        {
            base.OnExit ();
            _songPlayer.Stop();
            _songPlayer.Dispose();
            _songPlayer = null;
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            var touches = Touch.GetData(0).ToArray();
            if((touches.Length >0 && touches[0].Status == TouchStatus.Down) || Input2.GamePad0.Cross.Press)
            {
                Director.Instance.ReplaceScene(new MenuScene());
            }
        }
    
        ~TitleScene()
        {
            _texture.Dispose();
            _ti.Dispose ();
        }
    }
}
 

MenuScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Input;

using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;

using Sce.PlayStation.HighLevel.UI;

namespace Pong
{
    public class MenuScene : Sce.PlayStation.HighLevel.GameEngine2D.Scene
    {
        private Sce.PlayStation.HighLevel.UI.Scene _uiScene;
        
        public MenuScene ()
        {
            this.Camera.SetViewFromViewport();
            Sce.PlayStation.HighLevel.UI.Panel dialog = new Panel();
            dialog.Width = Director.Instance.GL.Context.GetViewport().Width;
            dialog.Height = Director.Instance.GL.Context.GetViewport().Height;
            
            ImageBox ib = new ImageBox();
            ib.Width = dialog.Width;
            ib.Image = new ImageAsset("/Application/images/title.png",false);
            ib.Height = dialog.Height;
            ib.SetPosition(0.0f,0.0f);
            
            Button buttonUI1 = new Button();
            buttonUI1.Name = "buttonPlay";
            buttonUI1.Text = "Play Game";
            buttonUI1.Width = 300;
            buttonUI1.Height = 50;
            buttonUI1.Alpha = 0.8f;
            buttonUI1.SetPosition(dialog.Width/2 - 150,200.0f);
            buttonUI1.TouchEventReceived += (sender, e) => {
                Director.Instance.ReplaceScene(new GameScene());
            };
            
            Button buttonUI2 = new Button();
            buttonUI2.Name = "buttonMenu";
            buttonUI2.Text = "Main Menu";
            buttonUI2.Width = 300;
            buttonUI2.Height = 50;
            buttonUI2.Alpha = 0.8f;
            buttonUI2.SetPosition(dialog.Width/2 - 150,250.0f);
            buttonUI2.TouchEventReceived += (sender, e) => {
            Director.Instance.ReplaceScene(new TitleScene());
            };        
                
            dialog.AddChildLast(ib);
            dialog.AddChildLast(buttonUI1);
            dialog.AddChildLast(buttonUI2);
            _uiScene = new Sce.PlayStation.HighLevel.UI.Scene();
            _uiScene.RootWidget.AddChildLast(dialog);
            UISystem.SetScene(_uiScene);
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        public override void Update (float dt)
        {
            base.Update (dt);
            UISystem.Update(Touch.GetData(0));
            
        }
        
        public override void Draw ()
        {
            base.Draw();
            UISystem.Render ();
        }
        
        ~MenuScene()
        {
            
        }
    }
}

GameScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;
using Sce.PlayStation.Core.Audio;

namespace Pong
{
    public class GameScene : Scene
    {
    private Paddle _player,_ai;
    public static Ball ball;
    private PongPhysics _physics;
    private Scoreboard _scoreboard;
    private SoundPlayer _pongBlipSoundPlayer;
    private Sound _pongSound;
        
        // Change the following value to true if you want bounding boxes to be rendered
        private static Boolean DEBUG_BOUNDINGBOXS = false;
        
        public GameScene ()
        {
            this.Camera.SetViewFromViewport();
            _physics = new PongPhysics();

            
            ball = new Ball(_physics.SceneBodies[(int)PongPhysics.BODIES.Ball]);
            _player = new Paddle(Paddle.PaddleType.PLAYER, 
                                 _physics.SceneBodies[(int)PongPhysics.BODIES.Player]);
            _ai = new Paddle(Paddle.PaddleType.AI, 
                             _physics.SceneBodies[(int)PongPhysics.BODIES.Ai]);
            _scoreboard = new Scoreboard();
            
            this.AddChild(_scoreboard);
            this.AddChild(ball);
            this.AddChild(_player);
            this.AddChild(_ai);
            
            
            // This is debug routine that will draw the physics bounding box around the players paddle
            if(DEBUG_BOUNDINGBOXS)
            {
                this.AdHocDraw += () => {
                    var bottomLeftPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMin;
                    var topRightPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftPlayer*PongPhysics.PtoM,topRightPlayer*PongPhysics.PtoM));

                    var bottomLeftAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMin;
                    var topRightAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftAi*PongPhysics.PtoM,topRightAi*PongPhysics.PtoM));

                    var bottomLeftBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMin;
                    var topRightBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftBall*PongPhysics.PtoM,topRightBall*PongPhysics.PtoM));
                };
            }
            
            //Now load the sound fx and create a player
            _pongSound = new Sound("/Application/audio/pongblip.wav");
            _pongBlipSoundPlayer = _pongSound.CreatePlayer();
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        private void ResetBall()
        {
            //Move ball to screen center and release in a random directory
            _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Position = 
                new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                            Director.Instance.GL.Context.GetViewport().Height/2) / PongPhysics.PtoM;
            
            System.Random rand = new System.Random();
            float angle = (float)rand.Next(0,360);
        
            if((angle%90) <=15) angle +=15.0f;
        
            _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Velocity = 
                new Vector2(0.0f,5.0f).Rotate(PhysicsUtility.GetRadian(angle));
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            
            if(Input2.GamePad0.Select.Press)
                Director.Instance.ReplaceScene(new MenuScene());
            
            //We don't need these, but sadly, the Simulate call does.
            Vector2 dummy1 = new Vector2();
            Vector2 dummy2 = new Vector2();
            
            //Update the physics simulation
            _physics.Simulate(-1,ref dummy1,ref dummy2);
            
            //Now check if the ball it either paddle, and if so, play the sound
            if(_physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Player) ||
                _physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Ai))
            {
                if(_pongBlipSoundPlayer.Status == SoundStatus.Stopped)
                    _pongBlipSoundPlayer.Play();
            }
            
            //Check if the ball went off the top or bottom of the screen and update score accordingly
            Results result = Results.StillPlaying;
            bool scored = false;
            
            if(ball.Position.Y > Director.Instance.GL.Context.GetViewport().Height + ball.Scale.Y/2)
            {
                result = _scoreboard.AddScore(true);
                scored = true;
            }
            if(ball.Position.Y < 0 - ball.Scale.Y/2)
            {
                result =_scoreboard.AddScore(false);
                scored = true;
            }
            
            // Did someone win?  If so, show the GameOver scene
            if(result == Results.AiWin) 
                Director.Instance.ReplaceScene(new GameOverScene(false));
            if(result == Results.PlayerWin) 
                Director.Instance.ReplaceScene(new GameOverScene(true));
            
            //If someone did score, but game isn't over, reset the ball position to the middle of the screen
            if(scored == true)
            {
                ResetBall ();
            }
            
            //Finally a sanity check to make sure the ball didn't leave the field.
            var ballPB = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball];
            
            if(ballPB.Position.X < -(ball.Scale.X/2f)/PongPhysics.PtoM ||
               ballPB.Position.X > (Director.Instance.GL.Context.GetViewport().Width)/PongPhysics.PtoM)
            {
                ResetBall();
            }
        }
        
        ~GameScene(){
            _pongBlipSoundPlayer.Dispose();
        }
    }
}

Ball.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;

namespace Pong
{
    public class Ball : SpriteUV
    {
        private PhysicsBody _physicsBody;
        // Change this value to make the game faster or slower
        public const float BALL_VELOCITY = 5.0f;
        
        public Ball (PhysicsBody physicsBody)
        {
            _physicsBody = physicsBody;
            
            this.TextureInfo = new TextureInfo(new Texture2D("Application/images/ball.png",false));
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
            this.Position = new Sce.PlayStation.Core.Vector2(
                Director.Instance.GL.Context.GetViewport().Width/2 -Scale.X/2,
                Director.Instance.GL.Context.GetViewport().Height/2 -Scale.Y/2);
            
            
            //Right angles are exceedingly boring, so make sure we dont start on one
            //So if our Random angle is between 90 +- 25 degrees or 270 +- 25 degrees
            //we add 25 degree to value, ie, making 90 into 115 instead
            System.Random rand = new System.Random();
            float angle = (float)rand.Next(0,360);
        
            if((angle%90) <=25) angle +=25.0f;
            this._physicsBody.Velocity = new Vector2(0.0f,BALL_VELOCITY).Rotate(PhysicsUtility.GetRadian(angle));;
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        public override void Update (float dt)
        {

            this.Position = _physicsBody.Position * PongPhysics.PtoM;

            
            // We want to prevent situations where the balls is bouncing side to side
            // so if there isnt a certain amount of movement on the Y axis, set it to + or - 0.2 randomly
            // Note, this can result in the ball bouncing "back", as in it comes from the top of the screen
            // But riccochets back up at the user.  Frankly, this keeps things interesting imho
            var normalizedVel = _physicsBody.Velocity.Normalize();
            if(System.Math.Abs (normalizedVel.Y) < 0.2f) 
            {
                System.Random rand = new System.Random();
                if(rand.Next (0,1) == 0)
                    normalizedVel.Y+= 0.2f;
                
                else
                    normalizedVel.Y-= 0.2f;
            }
            
            // Pong is a mess with physics, so just fix the ball velocity
            // Otherwise the ball could get faster and faster ( or slower ) on each collision
            _physicsBody.Velocity = normalizedVel * BALL_VELOCITY;

        }
        
        ~Ball()
        {
            this.TextureInfo.Texture.Dispose();
            this.TextureInfo.Dispose();
        }
    }
}

Paddle.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;

namespace Pong
{
    public class Paddle : SpriteUV
    {
        public enum PaddleType { PLAYER, AI };
        
        private PaddleType _type;
        private PhysicsBody _physicsBody;
        private float _fixedY;
        
        public Paddle (PaddleType type, PhysicsBody physicsBody)
        {
            _physicsBody = physicsBody;
            _type = type;

            this.TextureInfo = new TextureInfo(new Texture2D("Application/images/Paddle.png",false));
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
            
            if(_type== PaddleType.AI)
            {
                this.Position = new Sce.PlayStation.Core.Vector2(
                    Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                    10 + this.Scale.Y/2);                    
            }
            else
            {
                this.Position = new Sce.PlayStation.Core.Vector2(
                    Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                    Director.Instance.GL.Context.GetViewport().Height - this.Scale.Y/2 - 10);
            }
            
            // Cache the starting Y position, so we can reset and prevent any vertical movement from the Physics Engien
            _fixedY = _physicsBody.Position.Y;
            
            // Start with a minor amount of movement
            _physicsBody.Force = new Vector2(-10.0f,0);
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        // This method will fix the physics bounding box to the sprites current position
        // Not currently used, was used for debug, left in for interest sake only
        private void ClampBoundingBox()
        {
            var bbBL = new Vector2(Position.X- Scale.X/2, Position.Y- Scale.Y/2) / PongPhysics.PtoM;
            var bbTR = new Vector2(Position.X+ Scale.X/2, Position.Y+ Scale.Y/2) / PongPhysics.PtoM;
            _physicsBody.AabbMin = bbBL;
            _physicsBody.AabbMax = bbTR;
            
        }
        public override void Update (float dt)
        {
            // Reset rotation to prevent "spinning" on collision
            _physicsBody.Rotation = 0.0f;
            
            
            if(_type == PaddleType.PLAYER)
            {
                if(Input2.GamePad0.Left.Down)
                {
                    _physicsBody.Force = new Vector2(-30.0f,0.0f);
                }
                if(Input2.GamePad0.Right.Down)
                {
                    _physicsBody.Force = new Vector2(30.0f,0.0f);
                }
            }
            else if(_type == PaddleType.AI)
            {
                if(System.Math.Abs (GameScene.ball.Position.X - this.Position.X) <= this.Scale.Y/2)
                    _physicsBody.Force = new Vector2(0.0f,0.0f);
                else if(GameScene.ball.Position.X < this.Position.X)
                    _physicsBody.Force = new Vector2(-20.0f,0.0f);
                else if(GameScene.ball.Position.X > this.Position.X)
                    _physicsBody.Force = new Vector2(20.0f,0.0f);
            }
            
            //Prevent vertical movement on collision.  Could also implement by making paddle Kinematic
            //However, lose ability to use Force in that case and have to use AngularVelocity instead
            //which results in more logic in keeping the AI less "twitchy", a common Pong problem
            if(_physicsBody.Position.Y != _fixedY)
                _physicsBody.Position = new Vector2(_physicsBody.Position.X,_fixedY);
            
            this.Position = _physicsBody.Position * PongPhysics.PtoM;
        }
        
        ~Paddle()
        {
            this.TextureInfo.Texture.Dispose ();
            this.TextureInfo.Dispose();
        }
    }
}

Scoreboard.cs

using System;

using Sce.PlayStation.Core.Imaging;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;

namespace Pong
{
    public enum Results { PlayerWin, AiWin, StillPlaying };
    
    public class Scoreboard : SpriteUV
    {
        public int playerScore = 0;
        public int aiScore = 0;
        
        public Scoreboard ()
        {
            this.TextureInfo = new TextureInfo();
            UpdateImage();
            
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Vector2(0.5f,0.5f);
            this.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                        Director.Instance.GL.Context.GetViewport().Height/2);
            
        }
        
        private void UpdateImage()
        {
            Image image = new Image(ImageMode.Rgba,new ImageSize(110,100),new ImageColor(0,0,0,0));
            Font font = new Font(FontAlias.System,50,FontStyle.Regular);
            image.DrawText(playerScore + " - " + aiScore,new ImageColor(255,255,255,255),font,new ImagePosition(0,0));
            image.Decode();

            var texture  = new Texture2D(110,100,false,PixelFormat.Rgba);
            if(this.TextureInfo.Texture != null)
                this.TextureInfo.Texture.Dispose();
            this.TextureInfo.Texture = texture;
            texture.SetPixels(0,image.ToBuffer());
            font.Dispose();
            image.Dispose();
        }
        public void Clear()
        {
            playerScore = aiScore = 0;
            UpdateImage();
        }
        
        public Results AddScore(bool player)
        {
            if(player)
                playerScore++;
            else
                aiScore++;
            if(playerScore > 3) return Results.PlayerWin;
            if(aiScore > 3) return Results.AiWin;
            
            UpdateImage();

            return Results.StillPlaying;
        }
    }
}

GameOver.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Audio;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.Core.Input;

namespace Pong
{
    public class GameOverScene : Scene
    {
        private TextureInfo _ti;
        private Texture2D _texture;
        
        public GameOverScene (bool win)
        {
            this.Camera.SetViewFromViewport();
            if(win)
                _texture = new Texture2D("Application/images/winner.png",false);
            else
                _texture = new Texture2D("Application/images/loser.png",false);
            _ti = new TextureInfo(_texture);
            SpriteUV titleScreen = new SpriteUV(_ti);
            titleScreen.Scale = _ti.TextureSizef;
            titleScreen.Pivot = new Vector2(0.5f,0.5f);
            titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                               Director.Instance.GL.Context.GetViewport().Height/2);
            this.AddChild(titleScreen);
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            
            Touch.GetData(0).Clear();
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            int touchCount = Touch.GetData(0).ToArray().Length;
            if(touchCount > 0 || Input2.GamePad0.Cross.Press)
            {
                Director.Instance.ReplaceScene( new TitleScene());
            }
        }
        
        ~GameOverScene()
        {
            _texture.Dispose();
            _ti.Dispose ();
        }
    }
}

Programming , , ,

Month List

Popular Comments

Blender 2.6 Release Candidate um… released.
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Home > Art >

5. October 2011

 

So if you want to stay on the bleeding edge, go get it.

 

Or of course you can just wait 10 days for the 2.60 official release.

 

The main changes are:

 

Not really a ton there for game developers yet.  Was kind of hoping 2.60 was when BMesh would get merged in.  Haven’t personally checked this release out and will probably wait for the non-RC release.

Art

blog comments powered by Disqus

Month List

Popular Comments