├── RNGRPG.gif ├── dungeon.avif ├── item.js ├── bullet.js ├── index.html ├── style.css ├── mob.js ├── bulletcontroller.js ├── hero.js ├── README.md └── script.js /RNGRPG.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MemeEngineer/RNGRPG/HEAD/RNGRPG.gif -------------------------------------------------------------------------------- /dungeon.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MemeEngineer/RNGRPG/HEAD/dungeon.avif -------------------------------------------------------------------------------- /item.js: -------------------------------------------------------------------------------- 1 | class Item{ 2 | constructor(itemid,item=[]){ 3 | this.itemid = itemid 4 | this.item = item 5 | } 6 | 7 | } 8 | 9 | 10 | export class Diamond extends Item{ 11 | constructor(itemid = 10, item= ['💎']){ 12 | super(itemid, item) 13 | this.itemid= itemid 14 | this.item = item 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /bullet.js: -------------------------------------------------------------------------------- 1 | export default class Bullet{ 2 | constructor(x,y,speed, damage){ 3 | this.x = x; 4 | this.y = y; 5 | this.speed = speed; 6 | this.damage = damage; 7 | this.width = 5; 8 | this.height = 15; 9 | this.color = "red"; 10 | } 11 | 12 | draw(ctx){ 13 | //makes color of the bullet 14 | ctx.fillStyle = this.color; 15 | // placement of the bullet 16 | this.y -= this.speed; 17 | //dimensional properties of the bullet 18 | ctx.fillRect(this.x,this.y, this.width, this.height) 19 | } 20 | //collision mechanic with bullet based on position on canvas 21 | collideWith(mob){ 22 | if( 23 | this.x < mob.x + mob.width && 24 | this.x + this.width > mob.x && 25 | this.y < mob.y + mob.height && 26 | this.y + this.height > mob.y 27 | ){ 28 | mob.takeDamage(this.damage); 29 | return true 30 | } 31 | return false 32 | } 33 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | RNGRPG 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 49 | 50 | 51 |
Items:
23 | Health 24 |
31 | Damage: 32 |
1
Kill Count:
42 | 43 |
52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: darkslateblue; 3 | display:flex; 4 | flex-direction: column; 5 | align-items: center; 6 | padding:0; 7 | margin:0; 8 | height:100vh; 9 | background-image: url(dungeon.avif); 10 | background-size: cover; 11 | 12 | 13 | } 14 | 15 | #canvas-container{ 16 | display:flex; 17 | 18 | } 19 | img{ 20 | height:25vh; 21 | width: 100%; 22 | } 23 | /* 24 | table#inventory{ 25 | display:flex; 26 | flex-direction:column; 27 | align-items: center; 28 | background-color: beige; 29 | } */ 30 | 31 | table, th, td { 32 | border: 10px gray solid; 33 | background-color: beige; 34 | display:flex; 35 | justify-content: center; 36 | 37 | } 38 | td#health{ 39 | color: red 40 | } 41 | /* canvas{ 42 | background-color: gray; 43 | width: 75%; 44 | height: 750px; 45 | position:relative; 46 | z-index: 0; 47 | 48 | } */ 49 | /* #item-container{ 50 | width:25%; 51 | } */ 52 | /* div#hero{ 53 | width: 10px; 54 | height:10px; 55 | background-color: red; 56 | position:absolute; 57 | z-index: 1; 58 | } */ 59 | 60 | /* div#mob{ 61 | width: 10px; 62 | height:10px; 63 | background-color: blue; 64 | position: absolute; 65 | z-index: 1; 66 | top:100px; 67 | left: 100px; 68 | right:100px; 69 | } */ -------------------------------------------------------------------------------- /mob.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default class Mob{ 4 | 5 | constructor(x,y,color,health=['♥','♥','♥','♥']){ 6 | this.x = x; 7 | this.y = y; 8 | this.color = color; 9 | this.health = health 10 | this.width = 50; 11 | this.height = 50; 12 | } 13 | 14 | 15 | 16 | draw(ctx){ 17 | ctx.fillStyle = this.color; 18 | ctx.fillRect(this.x,this.y,this.width,this.height) 19 | ctx.strokeRect(this.x,this.y,this.width,this.height) 20 | //draw text 21 | ctx.fillStyle = 'red'; 22 | ctx.font = "15px arial"; 23 | ctx.fillText( 24 | this.health, 25 | this.x + this.width / 100, 26 | this.y + this.height / 10 27 | ) 28 | } 29 | 30 | takeDamage(){ 31 | this.health.pop() 32 | } 33 | //collision mechanic with player/hero 34 | collideWithHero(knight){ 35 | if( 36 | this.x < knight.x + knight.width && 37 | this.x + this.width > knight.x && 38 | this.y < knight.y + knight.height && 39 | this.y + this.height > knight.y 40 | ){ 41 | knight.takeDamage(); 42 | return true 43 | } 44 | return false 45 | } 46 | // HealthBar(){ 47 | 48 | // for (i= 0; i < 4; i++){ 49 | // this.health.push('♥') 50 | // } 51 | // } 52 | // arrHealth = [] 53 | // fillHealth(){ 54 | // health = arrHealth.fill('♥')* Math.floor(Math.random()* 5) 55 | // console.log(arrHealth) 56 | // } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /bulletcontroller.js: -------------------------------------------------------------------------------- 1 | import Bullet from "./bullet.js" 2 | export default class BulletController{ 3 | //puts the bullets in an array 4 | bullets = [] 5 | //keeps a timer for each bullet 6 | timerTillNextBullet = 0; 7 | constructor(gameArea){ 8 | this.gameArea = gameArea; 9 | } 10 | shoot(x,y,speed,damage,delay){ 11 | //spaces out bullet attack in an array 12 | // when timer is less than or = to 0 fires off bullet 13 | if(this.timerTillNextBullet <= 0){ 14 | this.bullets.push(new Bullet(x,y,speed,damage)) 15 | this.timerTillNextBullet = delay; 16 | } 17 | //timer decrement 18 | this.timerTillNextBullet--; 19 | } 20 | draw(ctx){ 21 | //removes bullet in array if bullet goes past bullet height 22 | this.bullets.forEach((bullet) => { 23 | if(this.isBulletOffScreen(bullet)){ 24 | const index = this.bullets.indexOf(bullet); 25 | this.bullets.splice(index,1) 26 | } 27 | //draws bullet onto the canvas / game-area 28 | bullet.draw(ctx) 29 | }) 30 | } 31 | //if one bullet hits a mob remove the bullet from array 32 | collideWith(mob){ 33 | return this.bullets.some(bullet => { 34 | if( bullet.collideWith(mob)){ 35 | this.bullets.splice(this.bullets.indexOf(bullet), 1); 36 | return true 37 | } 38 | return false 39 | }) 40 | } 41 | 42 | 43 | //returns true/false if bullet Y position is less than the bullet height (- is because of the grid origin) 44 | isBulletOffScreen(bullet){ 45 | return bullet.y <= -bullet.height 46 | } 47 | } -------------------------------------------------------------------------------- /hero.js: -------------------------------------------------------------------------------- 1 | export default class Hero{ 2 | constructor(x,y, BulletController,health= ['❤','❤','❤'],damage,item =[]){ 3 | this.x = x; 4 | this.y = y; 5 | this.BulletController = BulletController 6 | this.width = 25; 7 | this.height = 25; 8 | this.speed = 3; 9 | this.health = health; 10 | this.damage = damage; 11 | this.item = item; 12 | //event listener when pressing W,S,A,D (up, down, left, right) 13 | document.addEventListener("keydown", this.keydown); 14 | document.addEventListener("keyup",this.keyup); 15 | } 16 | 17 | draw(ctx){ 18 | //calling the movement method 19 | this.move() 20 | //makes border color 21 | ctx.strokeStyle = "yellow"; 22 | //makes border around shape 23 | ctx.strokeRect(this.x,this.y,this.width,this.height); 24 | //fills in shape color 25 | ctx.fillStyle = 'black'; 26 | //makes shape a rectange with x, y location & width and height 27 | ctx.fillRect(this.x,this.y,this.width,this.height); 28 | //health properties 29 | ctx.fillStyle = 'red'; 30 | ctx.font = "15px arial"; 31 | ctx.fillText( 32 | this.health, 33 | this.x - (this.width / 2), 34 | this.y + this.height / .6 35 | ) 36 | 37 | //calling on the attack method 38 | this.attack(); 39 | } 40 | 41 | attack(){ 42 | if(this.attackPressed){ 43 | const speed = 4; 44 | const delay = 7; 45 | this.damage = 1; 46 | const bulletX = this.x + this.width /2; 47 | const bulletY = this.y; 48 | this.BulletController.shoot(bulletX, bulletY, speed, this.damage, delay) 49 | } 50 | } 51 | 52 | takeDamage(){ 53 | this.health.pop() 54 | } 55 | 56 | move(){ 57 | if(this.upPressed){ 58 | this.y -= this.speed; 59 | } 60 | if(this.downPressed){ 61 | this.y += this.speed; 62 | } 63 | if(this.leftPressed){ 64 | this.x -= this.speed; 65 | } 66 | if(this.rightPressed){ 67 | this.x += this.speed; 68 | } 69 | } 70 | 71 | keydown = (e) => { 72 | if(e.key === "w"){ 73 | this.upPressed = true 74 | } 75 | if(e.key === "s"){ 76 | this.downPressed = true 77 | } 78 | if(e.key === "a"){ 79 | this.leftPressed = true 80 | } 81 | if(e.key === "d"){ 82 | this.rightPressed = true 83 | } 84 | if(e.key === "h"){ 85 | this.attackPressed = true; 86 | } 87 | } 88 | 89 | keyup = (e) => { 90 | if(e.key === "w"){ 91 | this.upPressed = false 92 | } 93 | if(e.key === "s"){ 94 | this.downPressed = false 95 | } 96 | if(e.key === "a"){ 97 | this.leftPressed = false 98 | } 99 | if(e.key === "d"){ 100 | this.rightPressed = false 101 | } 102 | if(e.key === "h"){ 103 | this.attackPressed = false; 104 | } 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RNGRPG (Random Number Generator Role-Playing Game) 2 | _____________ 3 | # Live DEMO: https://memeengineer.github.io/RNGRPG/ 4 | _____________ 5 | 6 | ![RNGRPG](https://github.com/MemeEngineer/RNGRPG/assets/90629466/f0b6236d-5fb9-49f9-827c-4e49ba188078) 7 | 8 | # About 9 | ______________ 10 | A simple analog WASD and H key game. The game is simple: Defeat the enemies and find the Gem of Swag 💎 (1/10 Drop Rate). The lower the kill count the better. Game design was based off of Space invaders and Pac-Man game development. 11 | 12 | ![image](https://github.com/MemeEngineer/RNGRPG/assets/90629466/d976107e-ff36-46f3-8b5f-63d2c08d9150) 13 | 14 | ![image](https://github.com/MemeEngineer/RNGRPG/assets/90629466/e2afc6d8-5d2b-4d61-8175-53a9bc94cc31) 15 | 16 | # Motivation 17 | ______________ 18 | This game was created for my love of MMORPGs. Inspired by games with drop rates and rare items. RNG based games have always intrigued me because people love a good grind to find items that would boosts the character stats. This is a short homage, to games that give us that dopamine when trying to find rare items for your character. 19 | 20 | # Game Logic / Game Mechanic 21 | ______________ 22 | Created with Object Oriented programming in mind. Each class/object has X & Y coordinates. The bullets/attack event happens at the players X & Y coordinate and is put into an array. If the Mob(Moveable Objects) / enemies are in the same X & Y coordinate as the bullets then the collision will trigger a class method to reduce the Mobs health. The game takes place in a canvas element in HTML allowing graphical and color elements to be drawn. The top left corner of the canvas element is the origin and the game is based off of those coordinate positions. 23 | 24 | # Wireframe 25 | _____________ 26 | ![image](https://github.com/MemeEngineer/RNGRPG/assets/90629466/16fb3c07-2351-431e-a07a-50514917c46a) 27 | 28 | # Live ScreenShot 29 | _________________ 30 | ![image](https://github.com/MemeEngineer/RNGRPG/assets/90629466/564835ce-f183-4bb5-b539-82682badef4a) 31 | 32 | 33 | 34 | # Technology 35 | ______________ 36 | 37 | ![HTMLCSSJS](https://github.com/MemeEngineer/RNGRPG/assets/90629466/7ce08ecb-c62b-4772-a7e6-647473cf1176) 38 | 39 | 40 | # Improvements / Future Implementations 41 | ______________________________________ 42 | - Randomize mob movement (create mobs that move) 43 | - Fix lose condition to end when collision and health reduction 44 | - Add more items to boost damage or add health 45 | - Add Pond to make a game within a game to obtain more items 46 | - Need to map out border to prevent player from going off screen 47 | 48 | # Credit 49 | ______________ 50 | Special Thanks to Colton Wright for the idea of using Pac-Man / Space Invaders game mechanics. 51 | 52 | # Links Used 53 | _________________ 54 | ## (Game Foundation) 55 | - https://www.youtube.com/watch?v=i7FzA4NavDs 56 | 57 | ## (Future Use to build map collision) 58 | - https://www.youtube.com/watch?v=Tk48dQCdQ3E 59 | - https://www.youtube.com/watch?v=KsLChm2MIQY 60 | 61 | ## (2D collision Detection) 62 | - https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection#axis-aligned_bounding_box 63 | 64 | ## (Random Number Generation) 65 | - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random 66 | 67 | ## (Game movement) 68 | - https://www.w3schools.com/graphics/game_movement.asp 69 | 70 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | import Hero from "./hero.js" 2 | import BulletController from "./bulletcontroller.js"; 3 | import Mob from './mob.js' 4 | import { Diamond } from "./item.js"; 5 | 6 | // Selecting the Canvas element by id 7 | const gameArea = document.querySelector('#game-area') 8 | //initializing the canvas element with a 2d property 9 | const ctx = gameArea.getContext('2d'); 10 | //inventory slot 11 | const gameItem = document.querySelector('#item') 12 | 13 | //Health bar 14 | const healthbar = document.querySelector('#health') 15 | 16 | //kill count 17 | const killCount = document.querySelector('#kill-count') 18 | let kill = 0; 19 | 20 | // width and height properties 21 | gameArea.width = 550; 22 | gameArea.height = 600; 23 | 24 | //diamond is an item 25 | const diamond = new Diamond(10, ['💎']) 26 | 27 | //instance of the bulletcontroller 28 | const bulletController = new BulletController(gameArea); 29 | //instance of the hero 30 | const knight = new Hero(gameArea.width/2.2, gameArea.height / 1.3,bulletController, ['❤','❤','❤'] ) 31 | 32 | const arrMob = [ 33 | new Mob( 250, 20, "grey"), 34 | new Mob(350, 20, "purple"), 35 | new Mob(150, 20, "Red"), 36 | new Mob(50, 20, "blue"), 37 | new Mob(450, 20, "green") 38 | ] 39 | function spawnMob(){ 40 | while( arrMob.length <= 0){ 41 | const mobs1 = new Mob( 250, 20, "grey") 42 | const mobs2 = new Mob(350, 20, "purple") 43 | const mobs3 = new Mob(150, 20, "Red") 44 | const mobs4 = new Mob(50, 20, "blue") 45 | const mobs5 = new Mob(450, 20, "green") 46 | arrMob.push(mobs1, mobs2, mobs3, mobs4, mobs5) 47 | 48 | } 49 | } 50 | 51 | 52 | function gameLoop(){ 53 | 54 | //invoking the style properties of the hero with a function 55 | setCommonStyle() 56 | //setting canvas color to gray 57 | ctx.fillStyle = "gray"; 58 | //making the canvas start at 0,0 with the canvas width and height properties 59 | ctx.fillRect(0,0,gameArea.width, gameArea.height) 60 | //draw bullets on canvas/gameArea 61 | bulletController.draw(ctx) 62 | //drawing the knight instance with the draw method 63 | knight.draw(ctx) 64 | health.innerText = knight.health 65 | //mob spawn 66 | arrMob.forEach((mob)=> { 67 | if(bulletController.collideWith(mob)){ 68 | if(mob.health.length <= 0){ 69 | const index = arrMob.indexOf(mob); 70 | arrMob.splice(index, 1) 71 | spawnMob() 72 | kill++ 73 | killCount.innerText = kill 74 | let num = Math.floor(Math.random() * (11 - 0) + 0 ) 75 | if(num === diamond.itemid){ 76 | knight.item.push(diamond.item) 77 | gameItem.innerText = knight.item 78 | gameCondition.checkWin() 79 | } 80 | } 81 | }else{ 82 | mob.draw(ctx) 83 | } 84 | }) 85 | arrMob.forEach((mob)=> { 86 | if(mob.collideWithHero(knight)){ 87 | knight.takeDamage(); 88 | gameCondition.checkWin() 89 | window.location.reload() 90 | 91 | }else{ 92 | knight.draw(ctx) 93 | } 94 | }) 95 | 96 | 97 | 98 | 99 | } 100 | 101 | // using a windows interal method to call gameloop 60 times per second to fresh game 102 | setInterval(gameLoop, 1000 /60) 103 | 104 | //function with properties of the hero shape 105 | function setCommonStyle(){ 106 | // ctx.shadowColor = '#d53'; 107 | // ctx.shadowBlur = 10; 108 | ctx.lineJoin = `bevel`; 109 | ctx.lineWidth= 10; 110 | } 111 | 112 | //game win/lose conditions 113 | const gameCondition = { 114 | checkWin(){ 115 | if(knight.item[0] == '💎'){ 116 | alert('You found the Gem of Swag and have won the game!') 117 | } 118 | 119 | if(knight.health.length <= 0){ 120 | alert('You have lost, Please hit the Restart Button or Refresh Page') 121 | } 122 | } 123 | } 124 | //restart button 125 | const restart = document.querySelector('#restart') 126 | 127 | restart.addEventListener('click', function(){ 128 | window.location.reload() 129 | }) 130 | 131 | // const Johnny = new Hero(['💎', '💎','💎','💎','💎'], 5, [],1,1,"red", 1,1) 132 | // console.log(Johnny.attack()) 133 | // console.log(Johnny) 134 | 135 | // function startGame(){ 136 | // start: function start(){ 137 | // const Johnny = new Hero(100, 5, [],1,1,"red", 1,1) 138 | // console.log("start") 139 | // } 140 | // } 141 | // let horizontal = 0; 142 | // let vertical = 0; 143 | // document.addEventListener('keydown', function(e){ 144 | // if(e.key === 'a'){ 145 | // console.log(e) 146 | // horizontal-=2 147 | // hero.style.left= horizontal + "px" 148 | // console.log(horizontal) 149 | // } 150 | // }) 151 | 152 | // document.addEventListener('keydown',function(e){ 153 | // if(e.key === 'd'){ 154 | // console.log(e) 155 | // horizontal+=2 156 | // hero.style.left = horizontal + 'px' 157 | // console.log(horizontal) 158 | // } 159 | // }) 160 | // document.addEventListener('keydown',function(e){ 161 | // if(e.key === 'w'){ 162 | // console.log(e) 163 | // vertical-=2 164 | // hero.style.top = vertical+ 'px' 165 | // console.log(vertical) 166 | // } 167 | // }) 168 | 169 | // document.addEventListener('keydown',function(e){ 170 | // if(e.key === 's'){ 171 | // console.log(e) 172 | // vertical+=2 173 | // hero.style.top = vertical+ 'px' 174 | // console.log(vertical) 175 | // } 176 | // }) 177 | --------------------------------------------------------------------------------