├── 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 | | Items: |
17 |
18 |
19 | |
20 |
21 |
22 | |
23 | Health
24 | |
25 |
26 |
27 | |
28 |
29 |
30 | |
31 | Damage:
32 | |
33 |
34 |
35 | | 1 |
36 |
37 |
38 | | Kill Count: |
39 |
40 |
41 | |
42 |
43 | |
44 |
45 |
46 | |
49 |
50 |
51 |
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 | 
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 | 
13 |
14 | 
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 | 
27 |
28 | # Live ScreenShot
29 | _________________
30 | 
31 |
32 |
33 |
34 | # Technology
35 | ______________
36 |
37 | 
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 |
--------------------------------------------------------------------------------