├── README.md ├── html5-rpg.png ├── images ├── forest.png ├── grass.png ├── hill.png ├── king.png ├── minister.png ├── mountain.png ├── player.png ├── soldier.png └── water.png ├── index.html ├── js ├── character.js ├── main.js ├── map.js ├── player.js └── utility.js ├── map ├── test.evt └── test.map └── music ├── castle.mp3 ├── castle.wav ├── field.mp3 └── field.wav /README.md: -------------------------------------------------------------------------------- 1 | html5-rpg 2 | ========= 3 | 4 | old-fashioned role playing game using html5 canvas and javascript 5 | 6 | License 7 | ------- 8 | MIT License 9 | 10 | 11 | -------------------------------------------------------------------------------- /html5-rpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/html5-rpg.png -------------------------------------------------------------------------------- /images/forest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/forest.png -------------------------------------------------------------------------------- /images/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/grass.png -------------------------------------------------------------------------------- /images/hill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/hill.png -------------------------------------------------------------------------------- /images/king.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/king.png -------------------------------------------------------------------------------- /images/minister.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/minister.png -------------------------------------------------------------------------------- /images/mountain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/mountain.png -------------------------------------------------------------------------------- /images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/player.png -------------------------------------------------------------------------------- /images/soldier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/soldier.png -------------------------------------------------------------------------------- /images/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/images/water.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | HTML5 RPG 16 | 17 | 18 |

HTML5 RPG

19 |

Project Home

20 |

Under construction ...

21 |

Test on newest Firefox, Safari and Chrome.

22 | 23 | Please use the web browser compatible with HTML5. 24 | 25 |

usage: move (arrow-key)

26 |

Debug Information

27 |
28 |
keycode
29 |
player position
30 | 31 | 32 | -------------------------------------------------------------------------------- /js/character.js: -------------------------------------------------------------------------------- 1 | STOP = 0; 2 | MOVE = 1; 3 | PROB_MOVE = 0.1; 4 | 5 | function Character(name, x, y, dir, movetype, message) { 6 | this.name = name; 7 | this.x = x; 8 | this.y = y; 9 | this.px = this.x * GS; 10 | this.py = this.y * GS; 11 | this.vx = 0; 12 | this.vy = 0; 13 | this.speed = 4; 14 | this.moving = false; 15 | this.direction = dir; 16 | this.movetype = movetype; 17 | this.animcycle = 12; 18 | this.frame = 0; 19 | this.message = message; 20 | 21 | // images are class property 22 | Character.images = new Object(); 23 | var names = ["player", "king", "minister", "soldier"]; 24 | for (var i = 0; i < names.length; i++) { 25 | Character.images[names[i]] = new Image(); 26 | Character.images[names[i]].src = "images/" + names[i] + ".png"; 27 | } 28 | } 29 | 30 | Character.prototype.update = function(map) { 31 | this.frame += 1; 32 | 33 | // continue moving until player fits in the fixed cell 34 | if (this.moving == true) { 35 | this.px += this.vx; 36 | this.py += this.vy; 37 | if (this.px % GS == 0 && this.py % GS == 0) { 38 | this.moving = false; 39 | this.x = div(this.px, GS); 40 | this.y = div(this.py, GS); 41 | } else { 42 | return; 43 | } 44 | } else if (this.movetype == MOVE && Math.random() < PROB_MOVE) { 45 | this.direction = Math.floor(Math.random() * 4); // 0 - 3 46 | this.moveStart(this.direction, map); 47 | } 48 | } 49 | 50 | Character.prototype.draw = function(ctx, offset) { 51 | offsetx = offset[0]; 52 | offsety = offset[1]; 53 | var no = div(this.frame, this.animcycle) % 4 54 | ctx.drawImage(Character.images[this.name], no*GS, this.direction*GS, GS, GS, 55 | this.px-offsetx, this.py-offsety, GS, GS); 56 | } 57 | 58 | Character.prototype.moveStart = function(dir, map) { 59 | if (dir == LEFT) { 60 | this.direction = LEFT; 61 | if (map.isMovable(this.x-1, this.y)) { 62 | this.vx = - this.speed; 63 | this.vy = 0; 64 | this.moving = true; 65 | } 66 | } else if (dir == UP) { 67 | this.direction = UP; 68 | if (map.isMovable(this.x, this.y-1)) { 69 | this.vx = 0; 70 | this.vy = - this.speed; 71 | this.moving = true; 72 | } 73 | } else if (dir == RIGHT) { 74 | this.direction = RIGHT; 75 | if (map.isMovable(this.x+1, this.y)) { 76 | this.vx = this.speed; 77 | this.vy = 0; 78 | this.moving = true; 79 | } 80 | } else if (dir == DOWN) { 81 | this.direction = DOWN; 82 | if (map.isMovable(this.x, this.y+1)) { 83 | this.vx = 0; 84 | this.vy = this.speed; 85 | this.moving = true; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | var musicList = [ 2 | { name : "field", url: "music/field" }, 3 | { name : "castle", url: "music/castle" } 4 | ]; 5 | 6 | window.onload = function() { 7 | // initialize global objects 8 | activeKey = null; 9 | map = new Map("test"); 10 | player = new Player("player", 1, 1, DOWN); 11 | map.addChara(player); 12 | 13 | // start mainloop 14 | setInterval('mainLoop()', 16); 15 | 16 | // start music 17 | var bgmNo = 0; 18 | var audioObj = new Audio(); 19 | if (audioObj.canPlayType("audio/wav") == "maybe") { var ext = ".wav"; } 20 | if (audioObj.canPlayType("audio/mp3") == "maybe") { var ext = ".mp3"; } 21 | (new Audio(musicList[bgmNo].url + ext)).play(); 22 | } 23 | 24 | function mainLoop() { 25 | var ctx = document.getElementById('canvas').getContext('2d'); 26 | 27 | // initialize 28 | ctx.clearRect(0, 0, WIDTH, HEIGHT); 29 | 30 | // update 31 | map.update(); 32 | 33 | // draw 34 | offset = calcOffset(player); 35 | map.draw(ctx, offset); 36 | player.draw(ctx, offset); 37 | } 38 | 39 | // key management 40 | 41 | document.onkeydown = function(e) { 42 | activeKey = e.which; 43 | e.preventDefault(); 44 | } 45 | 46 | document.onkeyup = function(e) { 47 | activeKey = null; 48 | e.preventDefault(); 49 | } 50 | 51 | // calculate player-map offset 52 | function calcOffset(player) { 53 | var offsetx = player.px - WIDTH / 2; 54 | var offsety = player.py - HEIGHT / 2; 55 | return [offsetx, offsety]; 56 | } 57 | -------------------------------------------------------------------------------- /js/map.js: -------------------------------------------------------------------------------- 1 | function Map(name) { 2 | this.name = name; 3 | this.row = -1; 4 | this.col = -1; 5 | this.defaultTile = 0; 6 | this.data = null; 7 | this.charas = []; // characters on this map 8 | 9 | // images are class property 10 | Map.images = new Array(256); 11 | for (var i = 0; i < 256; i++) { 12 | Map.images[i] = new Image(); 13 | } 14 | Map.images[0].src = "images/grass.png"; 15 | Map.images[1].src = "images/water.png"; 16 | Map.images[2].src = "images/forest.png"; 17 | Map.images[3].src = "images/hill.png"; 18 | Map.images[4].src = "images/mountain.png"; 19 | 20 | // load map data 21 | this.load("map/" + name + ".map"); 22 | 23 | // load event data 24 | this.loadEvent("map/" + name + ".evt"); 25 | } 26 | 27 | Map.prototype.load = function(filename) { 28 | // load map file 29 | var httpObj = $.ajax({ 30 | url: filename, 31 | async: false // synchronous (wait until file is loaded) 32 | }); 33 | var lines = httpObj.responseText.split("\n"); 34 | // the number of row and column 35 | var temp = lines[0].split(" "); 36 | this.row = parseInt(temp[0]); 37 | this.col = parseInt(temp[1]); 38 | // default map tile value 39 | this.defaultTile = parseInt(lines[1]); 40 | // map data 41 | this.data = new Array(this.row); 42 | for (var i = 0; i < this.row; i++) { 43 | this.data[i] = new Array(this.col); 44 | for (var j = 0; j < this.col; j++) { 45 | // map data starts from lines[2] 46 | this.data[i][j] = parseInt(lines[i+2][j]); 47 | } 48 | } 49 | } 50 | 51 | Map.prototype.loadEvent = function(filename) { 52 | // load event file 53 | var httpObj = $.ajax({ 54 | url: filename, 55 | async: false 56 | }); 57 | var lines = httpObj.responseText.split("\n"); 58 | for (var i = 0; i < lines.length - 1; i++) { 59 | if (lines[i][0] == "#") continue; // comment line 60 | var data = lines[i].split("\t"); 61 | var eventType = data[0]; 62 | if (eventType == "CHARA") { 63 | this.createChara(data); 64 | } 65 | } 66 | } 67 | 68 | Map.prototype.update = function() { 69 | // update characters on this map 70 | for (var i = 0; i < this.charas.length; i++) { 71 | this.charas[i].update(this); 72 | } 73 | } 74 | 75 | Map.prototype.draw = function(ctx, offset) { 76 | offsetx = offset[0]; 77 | offsety = offset[1]; 78 | 79 | // calculate the range of map 80 | startx = div(offsetx, GS); 81 | endx = startx + div(WIDTH, GS) + 1; 82 | starty = div(offsety, GS); 83 | endy = starty + div(HEIGHT, GS) + 1; 84 | for (var y = starty; y < endy; y++) { 85 | for (var x = startx; x < endx; x++) { 86 | // draw default image at the outside of map 87 | if (x < 0 || y < 0 || x > this.col-1 || y > this.row-1) { 88 | ctx.drawImage(Map.images[this.defaultTile], x*GS-offsetx, y*GS-offsety); 89 | } else { 90 | ctx.drawImage(Map.images[this.data[y][x]], x*GS-offsetx, y*GS-offsety); 91 | } 92 | } 93 | } 94 | 95 | // draw characters on this map 96 | for (var i = 0; i < this.charas.length; i++) { 97 | this.charas[i].draw(ctx, offset); 98 | } 99 | } 100 | 101 | Map.prototype.isMovable = function(x, y) { 102 | if (x < 0 || x > this.col-1 || y < 0 || y > this.row-1) { 103 | return false; 104 | } 105 | // cannot move at sea(1) and mountan(4) tile 106 | if (this.data[y][x] == 1 || this.data[y][x] == 4) { 107 | return false; 108 | } 109 | 110 | // cannot move to character cell 111 | for (var i = 0; i < this.charas.length; i++) { 112 | if (this.charas[i].x == x && this.charas[i].y == y) { 113 | return false; 114 | } 115 | } 116 | 117 | return true; 118 | } 119 | 120 | Map.prototype.addChara = function(chara) { 121 | this.charas.push(chara); 122 | } 123 | 124 | Map.prototype.createChara = function(data) { 125 | var name = data[1]; 126 | var x = parseInt(data[2]); 127 | var y = parseInt(data[3]); 128 | var direction = parseInt(data[4]); 129 | var moveType = parseInt(data[5]); 130 | var message = data[6]; 131 | var chara = new Character(name, x, y, direction, moveType, message); 132 | this.addChara(chara); 133 | } 134 | -------------------------------------------------------------------------------- /js/player.js: -------------------------------------------------------------------------------- 1 | // Player is a subclass of Character 2 | function Player(name, x, y, dir) { 3 | Character.call(this, name, x, y, dir); 4 | } 5 | 6 | Player.prototype = new Character(); 7 | 8 | Player.prototype.update = function(map) { 9 | this.frame += 1; 10 | 11 | // continue moving until player fits in the fixed cell 12 | if (this.moving == true) { 13 | this.px += this.vx; 14 | this.py += this.vy; 15 | if (this.px % GS == 0 && this.py % GS == 0) { 16 | this.moving = false; 17 | this.x = div(this.px, GS); 18 | this.y = div(this.py, GS); 19 | } else { 20 | return; 21 | } 22 | } 23 | 24 | // activeKey defined at main.js 25 | if (activeKey == 37) { 26 | this.moveStart(LEFT, map); 27 | } else if (activeKey == 38) { 28 | this.moveStart(UP, map); 29 | } else if (activeKey == 39) { 30 | this.moveStart(RIGHT, map); 31 | } else if (activeKey == 40) { 32 | this.moveStart(DOWN, map); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /js/utility.js: -------------------------------------------------------------------------------- 1 | // const 2 | WIDTH = 640; 3 | HEIGHT = 480; 4 | GS = 32; 5 | 6 | DOWN = 0; 7 | LEFT = 1; 8 | RIGHT = 2; 9 | UP = 3; 10 | 11 | function div(a, b) { 12 | return Math.round(a / b - 0.5); 13 | } 14 | 15 | function DisplayPropertyNames(obj) { 16 | var names = ""; 17 | for (var name in obj) names += name + " / "; 18 | alert(names); 19 | } 20 | -------------------------------------------------------------------------------- /map/test.evt: -------------------------------------------------------------------------------- 1 | # test2.evt 2 | # event file 3 | CHARA minister 3 1 0 1 I am a minister. 4 | CHARA king 2 1 0 0 I am a king! 5 | CHARA soldier 4 1 0 1 I am a soldier. 6 | -------------------------------------------------------------------------------- /map/test.map: -------------------------------------------------------------------------------- 1 | 15 20 2 | 1 3 | 11111111111111111111 4 | 10000000000000000001 5 | 10003333300000000001 6 | 10004444400000000001 7 | 10000022222200000001 8 | 10000002222222000001 9 | 10000002211222200001 10 | 10000002211220000001 11 | 10000000222200000001 12 | 10000000000044400001 13 | 10000000004440000001 14 | 10000444444000000001 15 | 10000333300000000001 16 | 10000000000000000001 17 | 11111111111111111111 18 | -------------------------------------------------------------------------------- /music/castle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/music/castle.mp3 -------------------------------------------------------------------------------- /music/castle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/music/castle.wav -------------------------------------------------------------------------------- /music/field.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/music/field.mp3 -------------------------------------------------------------------------------- /music/field.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidiary/html5-rpg/5580534154d58c9a3558db17692c803aab7d337d/music/field.wav --------------------------------------------------------------------------------