Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

7. July 2014

Today Oculus announced they have acquired RakNet and immediately released it under a modified BSD open source license.  The code is already available on their Github repository.  If you’ve never heard of it, RakNet is a low level C++ networking engine providing features like object replication, voice chat and patching.  A number of engines such as Unity and Havok already license RakNet and it has been used in hundreds of games.

 

From the Oculus Press release:

Oculus Acquires & Open Sources RakNet

We’re pleased to announce that we’ve acquired RakNet, one of the leading networking middleware systems in the games industry. We open-sourced it starting today under a modified BSD license (the same license Facebook uses for its open source projects) from the Oculus GitHub repo:https://github.com/OculusVR/RakNet.

For those unfamiliar with RakNet, it is a comprehensive C++ game networking engine designed for ease of use and performance. The tech is tuned for cross-platform, high-performance applications that operate across a wide variety of network types. Key features include object replication, remote procedure calls, patching, secure connections, voice chat, and real-time SQL logging. The technology has been licensed by thousands of indie developers, as well as companies like Unity, Havok, Mojang, Maxis and Sony Online Entertainment.

We’ve known Kevin Jenkins, founder of Jenkins Software and lead engineer on RakNet, for years, and we’ve used RakNet internally at Oculus for various networked systems and tools. After working with Kevin for a few months, we were all excited by the idea of open-sourcing RakNet to the community.

If you’re interested in checking out (or forking) RakNet, head over to the Oculus GitHub repo athttps://github.com/OculusVR/RakNet. We’re looking forward to seeing where the community takes the project next!

 

 

Pretty awesome news.  Perhaps the sale to Facebook wasn’t so terrible after all?

3. July 2014

 

Back in Part 3  of this SpriteKit/Swift tutorial series we looked at loading game sprites using a texture atlas and implemented a simple animation system.  At the time I said this wasn’t the way you perform animation and that I would cover the proper way later.  Then in Part 4 we covered SpriteKit actions showing how to perform a number of different actions.  Now we look to combine the two, using actions to perform sprite sheet based animations using a TextureAtlas.

 

This example is basically a perfect mash-up of the prior two tutorials, so it will be code heavy and explanation light.  So, on to the code!

 

import SpriteKit

 

class GameScene: SKScene {

    let textureAtlas = SKTextureAtlas(named:"jet.atlas")

    var spriteArray = Array<SKTexture>();

    

    var monsterSprite = SKSpriteNode();

    

    override func didMoveToView(view: SKView) {

        view.scene!.anchorPoint = CGPoint(x: 0.5,y: 0.5)

        

        spriteArray.append(textureAtlas.textureNamed("sprite1"));

        spriteArray.append(textureAtlas.textureNamed("sprite2"));

        spriteArray.append(textureAtlas.textureNamed("sprite3"));

        spriteArray.append(textureAtlas.textureNamed("sprite4"));

        spriteArray.append(textureAtlas.textureNamed("sprite5"));

        spriteArray.append(textureAtlas.textureNamed("sprite6"));

 

        monsterSprite = SKSpriteNode(texture:spriteArray[0]);

        monsterSprite.position = CGPoint(x:-view.bounds.width/2, y:0)

        monsterSprite.xScale = 2;

        monsterSprite.yScale = 2;

        addChild(self.monsterSprite);

        

 

        let animateAction = SKAction.animateWithTextures(self.spriteArray, timePerFrame: 0.20);

        let moveAction = SKAction.moveBy(CGVector(view.bounds.width,0), duration: 1.4);

        let group = SKAction.group([ animateAction,moveAction]);

        let repeatAction = SKAction.repeatActionForever(group);

        self.monsterSprite.runAction(repeatAction);

    }

    

    override func update(currentTime: NSTimeInterval) {

        if(monsterSprite.position.x > self.view.bounds.width/2){

            monsterSprite.position.x = -self.view.bounds.width/2;

        }

    }

 

}

 

And when you run it:

 

JetAnimation

 

Basically we create our texture atlas like we did before, but instead of creating individual SKSpriteNodes using the atlas, we instead create an array of SKTextures.  We then pass that array of textures, representing each animation frame, to the action animateWithTexture.  The parameter timePerFrame represents how long each individual frame will be shown.  The rest is just a couple more SKActions to make things interesting.  One to move the sprite from the left to the right, one to group the texture animation and move action together, then another action to repeat it forever.

 

As you can see, between TextureAtlas and animateWithTexture, creating animations in SpriteKit is pretty easy.

2. July 2014

 

Now that we’ve tackled the basics of graphics, let’s take a look at making our game “do” something.  This is often the job of SKActions, so let’s jump right in with a simple example.  Just like in the last example I am going to use the basic project structure we established back in the very first post.  All code samples are going to be in the GameScene.

 

 

import SpriteKit

 

class GameScene: SKScene {

    let monkey = SKSpriteNode(imageNamed: "EvilMonkey.png")

    

    override func didMoveToView(view: SKView) {

        let background = SKSpriteNode(imageNamed: "beach.png")

        background.position = CGPoint(x:0,y:0)

        background.anchorPoint = CGPoint(x:0.0,y:0.0)

        background.size = view.bounds.size

 

        monkey.anchorPoint = CGPoint(x:0.5,y:0.5)

        monkey.position = CGPoint(x:view.bounds.midX,y:view.bounds.midY)

        

        self.addChild(background)

        self.addChild(monkey)

    }

    

    override func mouseDown(theEvent: NSEvent!) {

        let action = SKAction.moveTo(

            CGPoint(x:theEvent.locationInWindow.x,y:theEvent.locationInWindow.y),

            duration:2

            );

        monkey.runAction(action)

    }

}

 

Now when you run it, wherever you click the screen, that’s where the monkey will go.  It will move at whatever rate is required to get there in 2 seconds.

Monkey

 

Note, the terrible jerkiness and colour saturation were a side effect of me trying to keep the animated gif small, not because of SpriteKit.

The magic here is of course the SKAction class.  Here we create a moveTo action and pass in the mouse pointer location and the duration of how long we want the action to last.  Finally we assign the action to our sprite using runAction, passing in the action to run.  Spritekit automatically determines how much to update each frame to match the animation to the duration you provided.

 

Here each time you click the mouse you override the current action, causing the last action to stop.  What if we wanted each action to queue up instead?  Let’s find out.

import SpriteKit

 

class GameScene: SKScene {

    let monkey = SKSpriteNode(imageNamed: "EvilMonkey.png")

    var actions = Array<SKAction>();

    

    override func didMoveToView(view: SKView) {

        let background = SKSpriteNode(imageNamed: "beach.png")

        background.position = CGPoint(x:0,y:0)

        background.anchorPoint = CGPoint(x:0.0,y:0.0)

        background.size = view.bounds.size

        

        monkey.anchorPoint = CGPoint(x:0.5,y:0.5)

        monkey.position = CGPoint(x:view.bounds.midX,y:view.bounds.midY)

        

        self.addChild(background)

        self.addChild(monkey)

    }

    

    override func mouseDown(theEvent: NSEvent!) {

        let action = SKAction.moveTo(

            CGPoint(x:theEvent.locationInWindow.x,y:theEvent.locationInWindow.y),

            duration:2

        );

 

        actions.insert(action, atIndex:0);

        

        if(monkey.hasActions() == false){

            monkey.runAction(actions.removeLast(), completion: queueNextActionIfExists);

        }

    }

    

    func queueNextActionIfExists(){

        if(actions.count > 0){

            monkey.runAction(actions.removeLast(), completion: queueNextActionIfExists);

        }

    }

}

 

In this example, the monkey will now follow your clicks after it finishes handling its current action.  So for example if you click 20 times, the monkey is going to move 20 times, taking a total of 40 seconds.  The trick here is the completion callback in runAction.  Basically this is a function that will be called when the current action complete.  In this case, when an action finishes, it checks to see if there are any more actions awaiting, and if there are, runs them.

 

This is a special case example of a completion, as it is recursive.  In many cases you can write more compact code using closures, a feature of most modern languages, including Swift.  Let’s take a look at a closure example for handling completion:

    override func mouseDown(theEvent: NSEvent!) {

        let action = SKAction.moveTo(

            CGPoint(x:theEvent.locationInWindow.x,y:theEvent.locationInWindow.y),

            duration:2

        );

        

        monkey.runAction(action,completion: { () -> Void in

            self.monkey.runAction(SKAction.moveTo(CGPoint(x:0,y:0),duration:2));

            }

        );

    }

 

In this example the completion function is passed in to runAction as an unnamed closure.  In other languages this is often known as an anonymous function.  When run, this will cause the monkey to move to where ever you click, then immediately to the origin once it’s done moving.  Which syntax you prefer is up to you.  Closures are a big part of functional programming, but they are also completely optional in Swift.

 

So far we have looked at performing a series of actions on demand ( as the user clicks, we queue them up ).  However, if you want to run a series of pre-determined actions in sequence, there is a much easier way to accomplish this.  Let’s take a look.

 

import SpriteKit

 

class GameScene: SKScene {

    let monkey = SKSpriteNode(imageNamed: "EvilMonkey.png")

    

    override func didMoveToView(view: SKView) {

        let background = SKSpriteNode(imageNamed: "beach.png")

        background.position = CGPoint(x:0,y:0)

        background.anchorPoint = CGPoint(x:0.0,y:0.0)

        background.size = view.bounds.size

        

        monkey.anchorPoint = CGPoint(x:0.5,y:0.5)

        monkey.position = CGPoint(x:view.bounds.midX,y:view.bounds.midY)

        

        self.addChild(background)

        self.addChild(monkey)

        

        doStuff();

    }

 

    func doStuff(){

        var actions = Array<SKAction>();

        actions.append(SKAction.moveTo(CGPoint(x:300,y:300), duration: 1));

        actions.append(SKAction.rotateByAngle(6.28, duration: 1));

        actions.append(SKAction.moveBy(CGVector(150,0), duration: 1));

        actions.append(SKAction.colorizeWithColor(NSColor.redColor(), colorBlendFactor: 0.5, duration: 1));

        let sequence = SKAction.sequence(actions);

        monkey.runAction(sequence);

    }

}

 

When you run this:

Monkey2

 

It runs each action, predictably enough, in sequence.  A sequence action is simply an array of SKActions that are run back to back.  As you can see there a number of different actions available and many more I haven’t covered here.  What you see in this example is a movement to a certain point, a rotation by an angle ( in radians… 360 degrees ), a moveBy, which is a movement relative to the current position.  Finally a colorize, which is basically tinting the image 50% red.

 

So that’s performing a number of actions in sequence, what if you want to perform then at the same time?  Well, there’s an app… er, Action for that too!

    func doStuff(){

        var actions = Array<SKAction>();

        actions.append(SKAction.moveTo(CGPoint(x:300,y:300), duration: 1));

        actions.append(SKAction.rotateByAngle(6.28, duration: 1));

        actions.append(SKAction.moveBy(CGVector(150,0), duration: 1));

        actions.append(SKAction.colorizeWithColor(NSColor.redColor(), colorBlendFactor: 0.5, duration: 1));

        let group = SKAction.group(actions);

        monkey.runAction(group);

    }

 

The only real difference is we create a group instead of a sequence.  You still create an array of SKActions and add them to the group.  Now when you run it:

Monkey3

 

Finally, this is something a reader wrote in asking me to cover.  Sometimes you want to simply make a sound when a sprite is selected.  This as well is easily accomplished using actions:

 

import SpriteKit

 

class SpriteWithClickSound: SKSpriteNode{

    override func mouseDown(theEvent: NSEvent!) {

        let clickAction = SKAction.playSoundFileNamed("ding.wav", waitForCompletion: true);

        self.runAction(clickAction);

    }

}

 

class GameScene: SKScene {

    let monkey = SpriteWithClickSound(imageNamed: "EvilMonkey.png")

    

    override func didMoveToView(view: SKView) {

        let background = SKSpriteNode(imageNamed: "beach.png")

        background.position = CGPoint(x:0,y:0)

        background.anchorPoint = CGPoint(x:0.0,y:0.0)

        background.size = view.bounds.size

        

        monkey.anchorPoint = CGPoint(x:0.5,y:0.5)

        monkey.position = CGPoint(x:view.bounds.midX,y:view.bounds.midY)

        monkey.userInteractionEnabled = true;

        

        self.addChild(background)

        self.addChild(monkey)

    }

 

}

In this example, when you click on the monkey, the sound ding.wav will be played.  Of course, you will need to add a WAV to your project for this to work properly.  One thing you should notice here is I called userInteractionEnabled on my SKSpriteNode derived object.  If you don’t call this, your node won’t receive events ( like mouseDown for example! ).  The actual action of playing a sound via action is:

let clickAction = SKAction.playSoundFileNamed("ding.wav", waitForCompletion: true);

self.runAction(clickAction);

Most of the code above is about subclassing SKSpriteNode to implement mouseDown. 

One last thing to be aware of before I move on here… Swift does NOT inherit init functions from their parent and frankly this sucks.  I was going to put the call to userInteractionEnabled in SpriteWithClickSound’s init() method.  When I do this, like so:

class SpriteWithClickSound: SKSpriteNode{

    init(imageNamed:String){

        super.init(imageNamed:imageNamed);

        // handle user input on

        self.userInteractionEnabled = true;

    }

}

 

At runtime you will get the very cryptic error:

/Users/Mike/Documents/Projects/SpriteKit/Actions3/Actions3/GameScene.swift: 3: 3: fatal error: use of unimplemented initializer 'init(texture:)' for class 'Actions3.SpriteWithClickSound'


 

This is as I said earlier, because Swift doesn’t inherit init methods ( think constructor if you are coming from C++, C# or Java ) from it’s parent class.  This frankly really sucks, as you then have to either a) provide no init method, in which case the proper init method will be called, or b) implement every bloody init method the class you inherited from.  Take the SKSpriteNode for example, it has 4 init functions, so if you want to implement userInteractionEnabled in the init method, you would have to do it like this:

 

class SpriteWithClickSound: SKSpriteNode{

    init(imageNamed:String){

        super.init(imageNamed:imageNamed);

        // handle user input on

        self.userInteractionEnabled = true;

    }

    init(texture:SKTexture){

        super.init(texture:texture);

        self.userInteractionEnabled = true;

    }

    

    init(color:SKColor,size:CGSize){

        super.init(color:color,size:size);

        self.userInteractionEnabled = true;

    }

    init(texture:SKTexture,color:SKColor,size:CGSize)

    {

        super.init(texture:texture,color:color,size:size);

        self.userInteractionEnabled = true;

    }

    

 

That, is some seriously ugly code!  It was also a complete pain in the ass to write.  The fact it only throws up on runtime is even worse.  Hopefully this is something that get’s fixed in Swift soon.  Until then, basically keep any and all logic out of init methods if you can help it.

26. June 2014

 

Since LibGDX 1.1, there has been a massively improved way of creating a LibGDX project.  This document looks at how you get up and started using the setup application.

 

First thing you have to do is download it.  Head on over to the LibGDX download page and download Setup App.

 

Libgdx1

 

Now that you’ve downloaded the file gdx-setup.jar, double click it and say OK to any security questions.  If it doesn’t load when you double click it, you either don’t have Java installed ( you need a JDK to work with LibGDX ) or your file extensions handling for .java files are broken.  Assuming everything worked correctly the setup app should have loaded.

 

Here is an example I’ve configured already:

Libgdx2

 

One important thing is you need to have the Android SDK installed as well.  Google have made the process rather confusing, as their default download is bundled with Eclipse.  If you are intending to use a different IDE, the download you want is under “Download for other Platforms”, which is an extremely stupid name…  anyways, you need to have the Android SDK ( Android Build Tools version 19.1 as of time of writing, keep in mind you have to configure the Android SDK after downloading it ) installed as you have to provide the path to where you installed it.  

 

Otherwise it’s the usual suspects here, your project name, package and class.  Keep in mind they follow Java variable naming conventions, so for example, no spaces and mostly just alphanumeric characters.  LibGDX version allows you to choose which version of LibGDX to use, although oddly enough in every release there has only ever been the option for the most recent release.

 

In the Sub Projects section, you are deciding what targets you want to support.  If you aren’t on a Mac, iOS will not be an option.  

 

Extensions are where you configure which of the LibGDX extensions you want installed/configured.  Bullet is a physics engine, free type is a font engine, tools give you the various command line tools that are bundled with LibGDX ( I believe ), Controllers is gamepad support, Box2d is a 2D physics engine while Box2dlights is… well I have no idea what that is.

 

Now the part that is of critical importance is “Advanced”.  In all honest I think the “Advanced” options are far more important than the extensions.  I would actually instead suggest making the Setup app a 2 step process and make Advanced a mandatory process.  Anyways enough about my opinions, click advanced!

LibGdx3

 

This is where you pick what kind of IDE you use.  For each you intend to use, tick the checkbox and it will generate the appropriate project files.  In this case I am going to be using IntelliJ IDEA, so I will tick next to IDEA.  The Maven mirror box is probably something you will never use so let’s ignore it.  Offline mode is a bit more difficult to decide.  Basically the project you create will automatically go online and figure out what dependencies your project has and make sure your project is always up to date.  If you aren’t going to have an internet connection reliably or yours is ungodly slow, you may consider ticking Offline Mode.  Otherwise you are probably best served leaving it unticked.  Now click Save.

 

Now back in the Setup app, click Generate:

LibGdx4

 

Now the setup app will churn away for a while, downloading required files and configuring your project. If there is something wrong with your configuration, you will be warned.  On my PC using my cell connection the process took about a minute.  If successful it should look like this:

LibGDX5

 

There is still some configuration to be done for running each project type in your IDE.  I have already covered that process in IntelliJ right here.  You only need to read the last half of that link.

 

Programming ,

24. June 2014

 

 

In the previous part we looked at handling graphics in Phaser, now we are going to look at handling input.  This part is going to be code heavy and fairly light on description.  Look to the code comments for more details.

As is pretty common with game frameworks, there are a number of different ways to handle input and a number of different devices, so lets get started!

 

Using the cursor keys and polling for input

 

/// <reference path="phaser.d.ts"/>

// Demonstrate the use of arrow keys in a Phaser app
// This application demonstrates creation of a Cursor and polling for input
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;
    cursors: Phaser.CursorKeys;

    constructor() {
        this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {
            create: this.create, preload: this.preload,
        update: this.update});
    }

    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }

    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        // create the cursor key object
        this.cursors = this.game.input.keyboard.createCursorKeys();
    }

    update() {
        // Update input state
        this.game.input.update();

        // Check each of the arrow keys and move accordingly
        // If the Ctrl Key + Left or Right arrow are pressed, move at a greater rate
        if (this.cursors.down.isDown)
            this.jetSprite.position.y++;
        if (this.cursors.up.isDown)
            this.jetSprite.position.y--;
        if (this.cursors.left.isDown) {
            if (this.cursors.left.ctrlKey)
                this.jetSprite.position.x -= 5;
            else
                this.jetSprite.position.x--;
        }
        if (this.cursors.right.isDown) {
            if (this.cursors.right.ctrlKey)
                this.jetSprite.position.x += 5;
            else
                this.jetSprite.position.x++;
        }
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

When you run this code the familiar jet sprite is rendered centered to the canvas. You can then use the arrow keys to move the fighter around.  As you can see, in the state for each key is information on modifier keys like Control and Alt.  Polling for input ( that is, checking status each call to update ) is a valid way of controlling a game, but sometimes you instead want to respond to input as it arrives.  Let’s look now at an example of event driven keyboard handling:

 

/// <reference path="phaser.d.ts"/>

// Demonstrate keyboard input handling via callback
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;
    W: Phaser.Key;
    A: Phaser.Key;
    S: Phaser.Key;
    D: Phaser.Key;

    constructor() {
        this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', {
            create: this.create, preload: this.preload
        });
    }

    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }

    moveLeft() {
        this.jetSprite.position.add(-1, 0);
    }
    moveRight() {
        this.jetSprite.position.add(1, 0);
    }
    moveUp(e: KeyboardEvent) {
        // As you can see the event handler is passed an optional event KeyboardEvent
        // This contains additional information about the key, including the Control
        // key status.
        // Basically if the control key is held, we move up or down by 5 instead of 1
        if (e.ctrlKey) 
            this.jetSprite.position.add(0, -5);
        else
            this.jetSprite.position.add(0, -1);
    }
    moveDown(e: KeyboardEvent) {
        if (e.ctrlKey)
            this.jetSprite.position.add(0, 1);
        else
            this.jetSprite.position.add(0, 1);
    }

    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        // Create a key for each WASD key
        this.W = this.game.input.keyboard.addKey(Phaser.Keyboard.W);
        this.A = this.game.input.keyboard.addKey(Phaser.Keyboard.A);
        this.S = this.game.input.keyboard.addKey(Phaser.Keyboard.S);
        this.D = this.game.input.keyboard.addKey(Phaser.Keyboard.D);

        // Since we are allowing the combination of CTRL+W, which is a shortcut for close window
        // we need to trap all handling of the W key and make sure it doesnt get handled by 
        // the browser.  
        // Unfortunately you can no longer capture the CTRL+W key combination in Google Chrome
        // except in "Application Mode" because apparently Google thought an unstoppable un prompted
        // key combo of death was a good idea...
        this.game.input.keyboard.addKeyCapture(Phaser.Keyboard.W);

        // Wire up an event handler for each K.  The handler is a Phaser.Signal attached to the Key Object
        this.W.onDown.add(SimpleGame.prototype.moveUp, this);
        this.A.onDown.add(SimpleGame.prototype.moveLeft, this);
        this.S.onDown.add(SimpleGame.prototype.moveDown, this);
        this.D.onDown.add(SimpleGame.prototype.moveRight, this);
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

As you can see, you can also create Phaser.Key objects and attach onDown event handlers ( technically Signals ) to each.  Of course you can reuse the same handler for multiple keys.  A couple key things to notice here… unlike the previous example, holding down a key will not cause continuous movement.  You must press and release the key over and over.  If you want constant movement, either use a polling method, use and action instead of updating each frame, or add some logic to move until the key is released.

 

The other thing to be aware of here is the use of the CTRL+W combination and addKeyCapture().  addKeyCapture() allows you to prevent the event from bubbling up, so once you’ve handled the key combination, it’s done.  Otherwise it would keep being passed up, either to other objects in the scene, or to the browser itself.  You can also use addKeyCapture to prevent default web behavior, such as scrolling when SPACE is pressed.

 

Programming , , , ,

Month List

Popular Comments

Creating a game sprite: Blender Lighting
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Home > Art >

14. September 2013

 

In Blender, when modelling, a world without light can be a great thing.  In a lightless scene, your model looks remarkably consistent from all angles:

image

 

That said, if you render a scene without light:

image

The results aren’t exactly brilliant!

 

The default Blender scene generally contains a single point light:

image

 

In object mode, these lights can be manipulated just like any other object in the 3D view.  Moving will effect where the light source is cast from, while the effect of scaling and rotate greatly depends on the type of light. 

 

When positioning lights, it’s often very handy to have your view set to either Texture or Rendered view.  The other view modes simply do not show the effect of the light.  Rendered view most accurately captures the effect of moving a light, but you are hugely restricted at what you can do in rendered view.  I generally use Texture view when placing lights, then flip over to rendered view while fine tuning, using a separate 3D view in textured, wireframe or solid view to position the lights.

image

 

There are a number of different light options in Blender, you can see them all via the Add->Lamp menu (why lamp???):

image

 

Here is the effect of each type, using the same scene:

 

The Scene:

image

 

Point Light:

image

 

Sun:

image

 

Spot:

image

 

Hemi:

image

 

Area:

image

 

 

With the exception of Point lights, all other lights have a direction, while a spot light also has a falloff.

 

A spot light in action

image

 

If you need to aim a light, you do it using the standard rotational tools.

 

Like the camera, there is a context sensitive control panel for controlling lights:

image

 

You can change between the various light types here.  The preview area is a sample of how the light will affect a black surface.  Each different light has a number of different settings.  For example, the Sun has options for sky and atmospheric effects, while the spot light has settings for controlling light falloff.  If you want to have a coloured light, you can do it here. 

image

 

Here, for example is the spotlight changed to red.

image

 

You can add as many lights to your scene as you want.  The actual art of getting proper lighting, well… that’s almost an entire career in and of itself!  For now a Hemi light is about as simple as it gets, so start there for now.

 

Rendering time next!


Click here for the Next Part

 

Art ,

blog comments powered by Disqus

Month List

Popular Comments