Tons of new chars. I’ll not release them individually anymore because now I’m using just one XCF multilayer Gimp file that permits create of new char combinations, so I’ll be releasing this file instead.
Mathematically, let’s assume I have just one layer. This permits create just one character (the nude one), ok? Each new layer I create, earrings as example, permits me create all chars I have done before with and without those earrings. That’s 2 times what we had before. With N layers I can create 2n different chars (2n-1? No, a char made of no layers can be the invisible man:)). We have now about 50 layers so we can create more than one quadrillion different combinations of chars. :o
Here’s the XCF Gimp file, chars.xcf (430Kb). To open and edit it you need the Gimp editor (The GNU Image Manipulation Program, download it here).
It’s also easier for you create your owns characters (try create yourself) or add hats, accessories, cloths, etc. Our My next step is write it to be programmability done with the same idea.
Tiled is general purpose game map editor, with support of several map formats (XML, JSON), multi plataform and runs installed or from browser, supports plugins to read and write others map formats and all free (under GPL license).
Make sure you have at least Java 1.5 installed and configured.
Creating a empty map
Afterlauching it, open the menu File → New and create a new 10×10 orthogonal mapwith 32×32 tiles.
Like this one
Creating a tileset
Now we need to add a tileset to start drawing a map. Let’s use this one
Save the tileset image above. Open the menu Tilesets → New Tileset select Reference tileset image and browser to find the tileset image you saved. Keep tile width and height as 32 and tile spacing and margin as 0.
Notice a new tab on the Tile palette section.
Working with layers
Select the first grass tile from the tileset and select the fill tool ( icon) to create a grass field. Use the paint tool ( icon) to add some stones and trees at random locations on grass. On the Layers section double click at Layer Name and put a name like “field”.
Now let’s create another layer to put the buildings and streets. We can do that by opening the menu Layer → Add Layer or just clicking the icon on layer’s section. Let’s call it “city”.
Now build your city by selecting tiles on the palette and using the paint tool. There’s tiles for horizontal and vertical street and all kinds of intersection. For the building you can click and drag in the palette to select multiple tiles at once.
Saving
You can save the map as tmx (XML Tiled map file) , JSON, LUA, wlk, map (Mappy) or export it as a image. There’s some options accessible on the Edit → Preferences menu like use base-64 gziped encoding.
Thanks to Adam Turk and Bjørn Lindeijer for developing that great project.
In a next post I want to show how to integrate this with a Java/JavaFX game.
Tile sets are a very simple way to draw scenarios with repeated elements. From simple to complex ones using a very low footprint.
First step, load the png file that stores the tileset into a Image. The file tiles.png shoud be in the same directory of the source code. I adjusted some tiles from those tile set I’ve blogged here before into a grid of 10×10 tiles.
var tileset =Image{
url:"{__DIR__}tiles.png"}
var tileset = Image {
url: "{__DIR__}tiles.png"
}
Notice that each tile have 32 of height and 32 of width. We will assume this and use theses numbers when performing calculations to find a single tile in our tile set.
def w =32;
def h =32;
def w = 32;
def h = 32;
To display a Image in the screen we use a ImageView node. A ImageView can have a viewport property to create crop or zoom effect. A viewport is just a Rectangle2D, a object with position (minX and minY), height and width. If we want to display the first tile in the tileset we do
Notice that the minX determines the column and minY the row in the tileset. The first row is 0*32, the second row is 1*32 and so on. If we want to display the tile at the second line and third column of the tileset we do
Those properties in a Rectangle2D are for init and read only. So I created a list with all Rectangles I can need for use as a viewport.
def viewports =for(row in [0..9]){for(col in [0..9]){Rectangle2D{
minX: col * w, minY: row * h, height: w, width: h
}}}
def viewports = for (row in [0..9]) {
for (col in [0..9]) {
Rectangle2D{
minX: col * w, minY: row * h, height: w, width: h
}
}
}
The scenario map is stored in another list. The first element of the list is 7, that is, the first tile in the scenario is the 7th tile from the tile set.
Finally to create a scenario with 100 tiles, 10 per row and with 10 rows, in a list called tiles. Each iteration of this loop creates a ImageView. Each ImageView will store a single tile. We get the tile number in the map list and so use it to index the viewports list.
var tiles =for(row in [0..9]){for(col in [0..9]){
ImageView {
x: col * w, y: row * h,
viewport: bind viewports[map[row *10+ col]]
image: tileset
}}}
var tiles = for (row in [0..9]) {
for (col in [0..9]) {
ImageView {
x: col * w, y: row * h,
viewport: bind viewports[map[row * 10 + col]]
image: tileset
}
}
}
Additionally I added two things to transform this program also in a (extremely)Â simple map editor. At each ImageView I added a callback for onMouseClicked event. When you click on a tile, it changes its map position, ie, the tile. The next tile for the left button and the last tile for any other button.
onMouseClicked: function( e:MouseEvent):Void{
var amount =if(e.button== MouseButton.PRIMARY){1}else{-1};
map[row *10+ col]=(map[row *10+ col]+ amount) mod 100;}
We are using just a image that can handle 100 tiles, tiles.png with less than 30Kb. The map is also composed with 100 tiles. Each tile we can choose between 100 different tiles, so we can compose 10100 different maps (one googol10 ). Most of them are useless and without any sense, but some are cool. :)
JavaFX 1.0 is out and there are tons of new cool features, specially for game development.trans
I’ll show in this tutorial how to create a very simple demo that shows how to load imtrages, handle sprites, collisions and keyboard events that you can use to create a game with a old school rpg like vision.
For the background scenario I’m using the house that I drew and we’ll call as house.png.
To put animation on the character we load all sprites into four lists. Each list for each direction.
// sprites
def up =for(i in [0..2]){Image{url:"{__DIR__}up{i}.png"}}
def right =for(i in [0..2]){Image{url:"{__DIR__}right{i}.png"}}
def down =for(i in [0..2]){Image{url:"{__DIR__}down{i}.png"}}
def left =for(i in [0..2]){Image{url:"{__DIR__}left{i}.png"}}
// sprites
def up = for(i in [0..2]) { Image {url: "{__DIR__}up{i}.png" } }
def right = for(i in [0..2]) { Image {url: "{__DIR__}right{i}.png" } }
def down = for(i in [0..2]) { Image {url: "{__DIR__}down{i}.png" } }
def left = for(i in [0..2]) { Image {url: "{__DIR__}left{i}.png" } }
And create vars to store the character position and frame of animation.
var frame =0;
var posx =320;
var posy =80;
var frame = 0;
var posx = 320;
var posy = 80;
Also store the house background.
// house background
def house = ImageView{ image: Image {url: "{__DIR__}house.png"} };
I create booleans to store some key states and at each interval of time I see how they are and do something about. You can handle keyboard event with less code but I like this way because keep visual and game logics a little bit more separated.
// keyboard
var upkey =false;
var rightkey =false;
var downkey =false;
var leftkey =false;// player
var player = ImageView{
x: bind posx y: bind posy
image:Image{url:"{__DIR__}down1.png"}
onKeyPressed: function(e:KeyEvent){if(e.code== KeyCode.VK_DOWN){
downkey =true;}elseif(e.code== KeyCode.VK_UP){
upkey =true;}elseif(e.code== KeyCode.VK_LEFT){
leftkey =true;}elseif(e.code== KeyCode.VK_RIGHT){
rightkey =true;}}// onKeyPressed
onKeyReleased: function(e:KeyEvent){if(e.code== KeyCode.VK_DOWN){
downkey =false;}elseif(e.code== KeyCode.VK_UP){
upkey =false;}elseif(e.code== KeyCode.VK_LEFT){
leftkey =false;}elseif(e.code== KeyCode.VK_RIGHT){
rightkey =false;}}// onKeyReleased}
// keyboard
var upkey = false;
var rightkey = false;
var downkey = false;
var leftkey = false;
// player
var player = ImageView{
x: bind posx y: bind posy
image: Image {url: "{__DIR__}down1.png"}
onKeyPressed: function(e:KeyEvent){
if (e.code == KeyCode.VK_DOWN) {
downkey = true;
} else if (e.code == KeyCode.VK_UP) {
upkey = true;
}else if (e.code == KeyCode.VK_LEFT) {
leftkey = true;
}else if (e.code == KeyCode.VK_RIGHT) {
rightkey = true;
}
} // onKeyPressed
onKeyReleased: function(e: KeyEvent){
if (e.code == KeyCode.VK_DOWN) {
downkey = false;
} else if (e.code == KeyCode.VK_UP) {
upkey = false;
}else if (e.code == KeyCode.VK_LEFT) {
leftkey = false;
}else if (e.code == KeyCode.VK_RIGHT) {
rightkey = false;
}
} // onKeyReleased
}
See a video of the game working so far:
Now we will add collisions. In a previous post I showed some math behind bounding box game collisions. The good news are that you no longer need to worry about that. There are a lot of API improvements in JavaFX 1.0 that do all the hard work for you, specially the new classes on javafx.geometry package, Rectangle2D and Point2D.
We create rectangles that represent the obstacles in the house.
We just have to change a little bit the game logics in order to handle collisions.
We define a bounding box around the player, it’s a rectangle from (4, 25) at the player coordinates system and with width 19 and height 10. The idea is to prospect where the player will be in the next step, see if it’s bouding box don’t collide with any obstacle and so pass it to the real game position.
// game logics
var gamelogics = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time : 1s/8
action: function(){
var nextposx = posx;
var nextposy = posy;if(downkey){
nextposy +=5;
player.image= down[++frame mod 3];}if(upkey){
nextposy -=5;
player.image= up[++frame mod 3];}if(rightkey){
nextposx +=5;
player.image= right[++frame mod 3];}if(leftkey){
nextposx -=5;
player.image= left[++frame mod 3];}for(obst in obstacles){if(obst.boundsInLocal.intersects(nextposx +4, nextposy +25, 19, 10)){return;}}
posx = nextposx;
posy = nextposy;}}}
// game logics
var gamelogics = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time : 1s/8
action: function() {
var nextposx = posx;
var nextposy = posy;
if(downkey) {
nextposy += 5;
player.image = down[++frame mod 3];
}
if(upkey) {
nextposy -= 5;
player.image = up[++frame mod 3];
}
if(rightkey) {
nextposx += 5;
player.image = right[++frame mod 3];
}
if(leftkey) {
nextposx -= 5;
player.image = left[++frame mod 3];
}
for(obst in obstacles) {
if(obst.boundsInLocal.intersects(nextposx + 4, nextposy + 25, 19, 10)) {
return;
}
}
posx = nextposx;
posy = nextposy;
}
}
}
This is enough to do the trick but I also added a way to smoothly show the obstacles when pressing the space key.
Here is the complete source code.
packageGame;importjavafx.stage.Stage;importjavafx.scene.*;importjavafx.scene.image.*;importjavafx.scene.input.*;importjavafx.scene.paint.*;importjavafx.scene.shape.*;importjavafx.animation.*;
var frame =0;
var posx =320;
var posy =80;// sprites
def up =for(i in [0..2]){Image{url:"{__DIR__}up{i}.png"}}
def right =for(i in [0..2]){Image{url:"{__DIR__}right{i}.png"}}
def down =for(i in [0..2]){Image{url:"{__DIR__}down{i}.png"}}
def left =for(i in [0..2]){Image{url:"{__DIR__}left{i}.png"}}// house background
def house = ImageView{ image:Image{url:"{__DIR__}house.png"}};// keyboard
var upkey =false;
var rightkey =false;
var downkey =false;
var leftkey =false;// player
var player = ImageView{
x: bind posx y: bind posy image: down[1]
onKeyPressed: function(e:KeyEvent){if(e.code== KeyCode.VK_DOWN){
downkey =true;}elseif(e.code== KeyCode.VK_UP){
upkey =true;}elseif(e.code== KeyCode.VK_LEFT){
leftkey =true;}elseif(e.code== KeyCode.VK_RIGHT){
rightkey =true;}if(e.code== KeyCode.VK_SPACE){if(fade==0.0){
fadein.playFromStart();}if(fade==1.0){
fadeout.playFromStart();}}}// onKeyPressed
onKeyReleased: function(e:KeyEvent){if(e.code== KeyCode.VK_DOWN){
downkey =false;}elseif(e.code== KeyCode.VK_UP){
upkey =false;}elseif(e.code== KeyCode.VK_LEFT){
leftkey =false;}elseif(e.code== KeyCode.VK_RIGHT){
rightkey =false;}}// onKeyReleased}// collidable obstacles
def obstacles =[Rectangle{ x:0 y:0 width:32 height:382 stroke:Color.RED},
Rectangle{ x:0 y:0 width:414 height:64 stroke:Color.RED},
Rectangle{ x:384 y:0 width:32 height:382 stroke:Color.RED},
Rectangle{ x:0 y:192 width:128 height:64 stroke:Color.RED},
Rectangle{ x:192 y:192 width:64 height:64 stroke:Color.RED},
Rectangle{ x:224 y:0 width:32 height:288 stroke:Color.RED},
Rectangle{ x:288 y:128 width:96 height:64 stroke:Color.RED},
Rectangle{ x:0 y:352 width:128 height:32 stroke:Color.RED},
Rectangle{ x:192 y:352 width:192 height:32 stroke:Color.RED},
Rectangle{ x:224 y:320 width:32 height:32 stroke:Color.RED},
Rectangle{ x:32 y:64 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:64 y:64 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:96 y:64 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:128 y:64 width:64 height:32 stroke:Color.YELLOW},
Rectangle{ x:192 y:32 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:64 y:128 width:64 height:32 stroke:Color.YELLOW},
Rectangle{ x:32 y:250 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:64 y:250 width:64 height:32 stroke:Color.YELLOW},
Rectangle{ x:200 y:255 width:20 height:20 stroke:Color.YELLOW},
Rectangle{ x:200 y:170 width:20 height:20 stroke:Color.YELLOW},
Rectangle{ x:257 y:32 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:288 y:32 width:32 height:32 stroke:Color.YELLOW},
Rectangle{ x:320 y:192 width:64 height:64 stroke:Color.YELLOW},
Rectangle{ x:352 y:295 width:32 height:60 stroke:Color.YELLOW},
Rectangle{ x:32 y:327 width:64 height:23 stroke:Color.YELLOW},
];// game logics
var gamelogics = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time : 1s/8
action: function(){
var nextposx = posx;
var nextposy = posy;if(downkey){
nextposy +=5;
player.image= down[++frame mod 3];}if(upkey){
nextposy -=5;
player.image= up[++frame mod 3];}if(rightkey){
nextposx +=5;
player.image= right[++frame mod 3];}if(leftkey){
nextposx -=5;
player.image= left[++frame mod 3];}for(obst in obstacles){if(obst.boundsInLocal.intersects(nextposx +4, nextposy +25, 19, 10)){return;}}
posx = nextposx;
posy = nextposy;}}}
gamelogics.play();// obstacles view
var fade =0.0;
var obstacleslayer =Group{
opacity: bind fade
content:[Rectangle{ x:0 y:0 width:500 height:500 fill:Color.BLACK},
obstacles,
Rectangle{
x: bind posx +4 y: bind posy +25 width:19 height:10
fill:Color.LIME}]}
var fadein = Timeline {
keyFrames:[
at (0s){fade =>0.0}
at (1s){fade =>1.0}]}
var fadeout = Timeline {
keyFrames:[
at (0s){fade =>1.0}
at (1s){fade =>0.0}]}// game stage
Stage {
title:"RPG-like demo", width:424, height:412
visible:true
scene: Scene{
fill:Color.BLACK
content:[house, player, obstacleslayer]}}
update: The applet version and Java Web Start versions should be working now. The applet version on Linux seems to be having problems with the keyboard handling, use the Java Web Start version while I’m trying to fix it.