Three drawing questions
Shri

Gio,

I have a couple of questions to ask regarding the draw functions

1. How do I draw a line for a chain of physics vertices. Like the floor in the stickman demos. In the past, I used the code below, but it doesn't seem to work anymore. It only draws the line once and then disappears. Also, I don't think this will work in a webGL environment ?

floorSprite.setDrawFunction(function(context){ self.drawLines(context,this.getSceneObject()); });

this.drawLines = function(context,obj) { 
    var points = obj.vertices;
    var pos = obj.getPosition();
    var strokeStyle = context.strokeStyle;

    context.strokeStyle = 'red';
    context.beginPath();
    context.moveTo(points[0].x+pos.x, points[0].y+pos.y);
    for (var i=1; i < points.length; i++)
    {
       context.lineTo(points[i].x+pos.x, points[i].y+pos.y);
    }
    if (obj.chainIsLoop) {
        context.lineTo(points[0].x+pos.x, points[0].y+pos.y);
    }
    context.stroke();
    context.strokeStyle = strokeStyle;
};

2. I would like to gradient fill a closed area given a list of vertices. For example, filling in the ground from the floor chain to the bottom of the game. I guess, this would be the same as (1) above, with two extra vertices for the bottom right and left corners and then calling context.fillStyle().

3. Sometimes animations only come in one direction (ex. runRight is a 4x4 animation). Right now, I use PS and flip the png manually to create the runLeft.


var runRight = new Animation('./images/runRight.png',4,4,24,true);

var runFlip = new Sprite('./images/runRight.png',wade.app.GAME_LAYER);
runFlip.setDrawFunction(wade.drawFunctions.mirror_(runFlip.draw));
var runLeft = new Animation(runFlip,4,4,24,true);

I tried doing the above, but the runLeft doesn't show anything ? Could you post a snippet of the correct way to do this ?

thanks -shri

All 7 Comments
Gio

Hey Shri

Good questions, let's go over them in order

1. Your code looks OK (though I guess in reality you will define this.drawLines first, and then set it as a draw function). The thing that is not visible in your code and that might prevent it from working properly, is the fact that whenever you use a custom draw function like that, you must be sure that you are always drawing inside the sprite's bounding box. So first of all, work out the size of the sprite considering all the vertices, then call Sprite.setSize(), and finally you can use your custom draw function and it should just work.

You are right, it won't work in a webgl context as it is, because your draw function is using some canvas-specific code. You have a couple of option though: you could force a canvas layer, with wade.setLayerRenderMode(layerId, '2d'). Or you could draw your sprite to an off-screen image in 2d canvas mode with Sprite.drawToImage(), and then use this image as a texture for a Sprite that lives in a webgl context. I hope that makes sense.

2. I think that's spot on - is it not working?

3. You are quite close there. If you set a draw function to mirror the sprite, it will draw the way you want. But you don't need to create a new animation, you keep using runRight, you just change the draw function of the sprite when you want to flip it. Even easier if you do it by setting a draw modifier rather than changing the draw function. So something like this:

mySprite.runRight = function()
{
    this.setDrawModifiers([]);
    this.playAnimation('runRight');
};

mySprite.runLeft = function()
{
    this.setDrawModifiers([{type: 'mirror'}]);
    this.playAnimation('runRight', 'reverse');
};

There are several different ways to do this. If you  wanted to have separate animations, you would pre-draw your animation with a mirror draw function to an off-screen image, then use this image to create a runLeft animation. I think the method I posted above is somewhat easier, but that's personal preference I guess.

Shri

Gio,

Thanks for the info. I got it all to work, but am having a couple of problems. I am changing the background of the levels demo to be a randomly generated texture based on your perlin noise blog. It all works, except I get a seam in the background when setting the size to the default 256. If I set the size of the background to a size larger than the screen, then I can get rid of the seam, but it takes a couple of seconds to generate the background, (and so delays the game start) Can you tell me how to get rid of the seam ? You alluded to it in the blog post, but I (obviously) didn't follow what to do.

I also want to fill the ground area with a grass texture similar to the background. The top of the texture would be the chain vertices that comprise the floor of the demo. The bottom of the texture would be the bottom of the game. So, instead of using context.fillStyle(), I would want to fill this area with a texture. I am unclear how to load the texture given these parameters. I can do this in the canvas context, but that is obviously not a web gl texture. You said the following in the last post:

"You are right, it won't work in a webgl context as it is, because your draw function is using some canvas-specific code. You have a couple of option though: you could force a canvas layer, with wade.setLayerRenderMode(layerId, '2d'). Or you could draw your sprite to an off-screen image in 2d canvas mode with Sprite.drawToImage(), and then use this image as a texture for a Sprite that lives in a webgl context. I hope that makes sense."

But I'm unsure of the specific steps to take. Could you post something here or point me at somewhere in the source you do something similar ?

as usual any help is appreciated - cheers - shri

Gio

First let me clarify the process to use canvas-based draw functions in a webgl context.

Say that you have a sprite, let's call it spriteA , that is in the scene, in a webgl layer, and let's say that it is using myImage as a source image; spriteA does not have a custom draw function, just the default draw function that displays myImage on the screen.

Now you create a second sprite, let's call it spriteB. SpriteB is not added the scene - it can be created on any layer, it doesn't matter if it's a canvas or webgl layer, becasue it will never be added to the scene. SpriteB can have its custom draw function that does some canvas-specic drawing with vector graphics, gradients and all that. You can do this:

spriteB.drawToImage('myImage', true, null, null, null, '2d');

This will draw spriteB into myImage, effectively replacing myImage. Now spriteA should automatically update to display the new myImage. Note that the last parameter is set to '2d' to ensure that spriteB is drawn into myImage using a canvas context.

Now regarding the seam - are you generating a tileable texture, or simply a noise texture that is not tileable? If it's the latter, the seam is to be expected. If it is the former, it's possible you'll still see a seam depending on how your graphics card is filtering the texture. But if the texture was not a power of 2, the filtering would be disabled, so if that's your problem, making the texture 255x255 might solve it.

 

Shri

Gio,

What I want to do is replace the ground gradient with a texture. If you pop over to my website and run the stickman levels demo, you will see what I mean. The background is a noisy texture, but the ground is simply a gradient fill.

This is what I currently do to fill in the ground with a gradient via some canvas drawing routines In the game.js file

// I do this in wade.init() function
wade.setLayerRenderMode(wade.app.GAME_LAYER, '2d');


// fill in the ground with a gradient
this.drawLines = function(context,obj) { 
    var points = obj.vertices;
    var pos = obj.getPosition();
    var strokeStyle = context.strokeStyle;
    var fillStyle = context.fillStyle;
    context.strokeStyle = "#53b0b2";
    context.beginPath();
    context.moveTo(points[0].x+pos.x, points[0].y+pos.y);
    for (var i=1; i < points.length; i++)
    {
       context.lineTo(points[i].x+pos.x, points[i].y+pos.y);
    }
    context.lineTo(points[points.length-1].x+pos.x, 2000+pos.y);
    context.lineTo(points[0].x+pos.x, 2000+pos.y);
    var gradient = context.createLinearGradient(0,0,0,170);
    gradient.addColorStop(0,"#53b0b2");
    gradient.addColorStop(0.5,"#4283a3");
    gradient.addColorStop(1,"#174082");
    context.fillStyle = gradient;
    context.fill();
    context.stroke();
    context.fillStyle = fillStyle;
    context.strokeStyle = strokeStyle;
};

In the gameWorld.js file, when I create the floor chain

var floorSprite = new Sprite(0,wade.app.GAME_LAYER);
floorSprite.setSize(data.maxWidth*2,2000);
floorSprite.setDrawFunction(function(context)
{ wade.app.drawLines(context,this.getSceneObject()); 
});
		
floor.addSprite(floorSprite);
wade.addSceneObject(floor,true);

Can you tell me how / where I would use the drawToImage function ?

On the other question, I'm pretty sure I am just generating a noisy texture that is not tileable. I am just generating the texture using the code from your perlin noise blog post

wade.setImage('clouds', generateTexture(255, clouds));

So, how do I make this texture tileable ?

thanks for the assistance - cheers - shri

Gio

Right, I see what you mean

To be honest I don't think that webgl would be helpful in that case - a 2d canvas may be better for what you need.

Still, it may be a good idea to draw the sprite to an image. If you do that, your (pretty expensive) draw function doens't need to be called every time you draw the sprite, it needs to be executed only once. Then when the sprite needs to be drawn, it will just use a plain image. It would be faster. But considering the image size, I think you'd get much (much!) better performance by splitting it up into multiple adjacent sprites.

Anyway, there are a couple of ways to draw that floor sprite to an image. The easiest one is

floorSprite.cache();

This does exactly what I described above and is roughly equivalent to this (which is really the same thing, just more verbose):

 

floorSprite.drawToImage('floorImage', true, null, null, null, '2d');

var s = new Sprite('floorImage', wade.app.GAME_LAYER);
floor.addSprite(s);
wade.addSceneObject(floor, true);

However I think you want to use the latter, more explicit version of the code. Because it can easily be modified to draw a texture for the terrain.

You would need to remove the gradient bit from your floor draw function, and just fill it with a plain color (I mean you could keep the gradient, but it'd be pointless as it's getting replaced by the texture).

You would then draw the terrain texture on top of it using the 'source-in' composite operation. This means that the texture is drawn only on opaque areas of the image. So in short:

floorSprite.drawToImage('floorImage', true, null, null, null, '2d');

var groundTextureSprite = new Sprite('ground.png', wade.app.GAME_LAYER); 
groundTextureSprite.setSize(....);
groundTextureSprite.drawToImage('floorImage', false, null, null, 'source-in', '2d');

var s = new Sprite('floorImage', wade.app.GAME_LAYER);
floor.addSprite(s);
wade.addSceneObject(floor, true);

Regarding the tileable texture: if you are following my blog post, it does say towards the bottom how you'd use 3d noise to project the noise on the surface of a cylinder and all that. Basically it's a quick trick to make the texture tileable. However I wouldn't really recommend it as it doesn't look as good, a larger non-tileable texture would look better. Given the blurry nature of clouds, I don't think the texture needs to be very large either - it could be a reasonably-sized texture that gets stretched to fit the space that you need.

Shri

Gio,

Thanks for the help. I got it to work, I attached a little test zip file for anyone who wants to see. It creates a sprite from a set of vertices and then loads a texture over it using what you recommended. You have to be aware of the coordinate offsets between wade and drawing into a canvas.

On another note, I am trying to slice the large floor sprite into smaller segments. I wanted to fill the different slices with slightly different shades of color to give the floor some contrasting stripe effect. Anyway, I cut up the floor vertices like this

    var verts = wade.cloneArray(floor.vertices);
    var start = 0;
    for (var v=1; v<verts.length; v++) {
        var diff = verts[v].x - verts[start].x;
        if (diff >= 512) {
            var dist = v+1-start;
            var sliceVerts = verts.slice(start,dist);
            this.addSpriteSlice(sliceVerts);
            start = v+1;
        }
    }

and then use this to add the sprite slices to the floor

gameWorld.addSpriteSlice = function(v) {
    var floorSlice = new Sprite(0,wade.app.GAME_LAYER);
    floorSlice.setSize(512,1000);
    floorSlice.obj = floor;
    floorSlice.setDrawFunction(function(context)
        { wade.app.drawLines(context,floor); 
    });
    floor.addSprite(floorSlice);
}

It looks like it works, but once the stickman starts to run, the floor disappears. I can get the floor to appear the whole time by setting the size of the floorSlice to the width of the level, but that sort of defeats the purpose of slicing the sprite into smaller sizes.

Any tip on why the floor sprite is disappearing ?

thanks - shri

Gio

Hi Shri

Again, it sounds like a case of drawing outside the bounding box, but I can't be sure without seeing your draw function. What seems to be missing from above, is the sprite offsets. If each sprite is 512 wide, then I would expect sprite positions (or sprite offsets) to be 512 pixels apart. Then the sprite draw function should draw using its "local" coordinate space, so it will always draw from -256 to 256, not using world coordinates.

Post a reply
Add Attachment
Submit Reply
Login to Reply