├── .gitignore
├── img
├── backgroundLevel1.png
├── backgroundLevel2.png
├── backgroundLevel3.png
├── box.png
├── doorOpen.png
└── king
│ ├── enterDoor.png
│ ├── idle.png
│ ├── idleLeft.png
│ ├── runLeft.png
│ └── runRight.png
├── index.html
├── index.js
└── js
├── classes
├── CollisionBlock.js
├── Player.js
└── Sprite.js
├── data
└── collisions.js
├── eventListeners.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/img/backgroundLevel1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/backgroundLevel1.png
--------------------------------------------------------------------------------
/img/backgroundLevel2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/backgroundLevel2.png
--------------------------------------------------------------------------------
/img/backgroundLevel3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/backgroundLevel3.png
--------------------------------------------------------------------------------
/img/box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/box.png
--------------------------------------------------------------------------------
/img/doorOpen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/doorOpen.png
--------------------------------------------------------------------------------
/img/king/enterDoor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/king/enterDoor.png
--------------------------------------------------------------------------------
/img/king/idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/king/idle.png
--------------------------------------------------------------------------------
/img/king/idleLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/king/idleLeft.png
--------------------------------------------------------------------------------
/img/king/runLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/king/runLeft.png
--------------------------------------------------------------------------------
/img/king/runRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/kings-and-pigs/bb77e92e7d89937500138ef690388fe067c2a4b1/img/king/runRight.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const canvas = document.querySelector('canvas')
2 | const c = canvas.getContext('2d')
3 |
4 | canvas.width = 64 * 16 // 1024
5 | canvas.height = 64 * 9 // 576
6 |
7 | let parsedCollisions
8 | let collisionBlocks
9 | let background
10 | let doors
11 | const player = new Player({
12 | imageSrc: './img/king/idle.png',
13 | frameRate: 11,
14 | animations: {
15 | idleRight: {
16 | frameRate: 11,
17 | frameBuffer: 2,
18 | loop: true,
19 | imageSrc: './img/king/idle.png',
20 | },
21 | idleLeft: {
22 | frameRate: 11,
23 | frameBuffer: 2,
24 | loop: true,
25 | imageSrc: './img/king/idleLeft.png',
26 | },
27 | runRight: {
28 | frameRate: 8,
29 | frameBuffer: 4,
30 | loop: true,
31 | imageSrc: './img/king/runRight.png',
32 | },
33 | runLeft: {
34 | frameRate: 8,
35 | frameBuffer: 4,
36 | loop: true,
37 | imageSrc: './img/king/runLeft.png',
38 | },
39 | enterDoor: {
40 | frameRate: 8,
41 | frameBuffer: 4,
42 | loop: false,
43 | imageSrc: './img/king/enterDoor.png',
44 | onComplete: () => {
45 | console.log('completed animation')
46 | gsap.to(overlay, {
47 | opacity: 1,
48 | onComplete: () => {
49 | level++
50 |
51 | if (level === 4) level = 1
52 | levels[level].init()
53 | player.switchSprite('idleRight')
54 | player.preventInput = false
55 | gsap.to(overlay, {
56 | opacity: 0,
57 | })
58 | },
59 | })
60 | },
61 | },
62 | },
63 | })
64 |
65 | let level = 3
66 | let levels = {
67 | 1: {
68 | init: () => {
69 | parsedCollisions = collisionsLevel1.parse2D()
70 | collisionBlocks = parsedCollisions.createObjectsFrom2D()
71 | player.collisionBlocks = collisionBlocks
72 | if (player.currentAnimation) player.currentAnimation.isActive = false
73 |
74 | background = new Sprite({
75 | position: {
76 | x: 0,
77 | y: 0,
78 | },
79 | imageSrc: './img/backgroundLevel1.png',
80 | })
81 |
82 | doors = [
83 | new Sprite({
84 | position: {
85 | x: 767,
86 | y: 270,
87 | },
88 | imageSrc: './img/doorOpen.png',
89 | frameRate: 5,
90 | frameBuffer: 5,
91 | loop: false,
92 | autoplay: false,
93 | }),
94 | ]
95 | },
96 | },
97 | 2: {
98 | init: () => {
99 | parsedCollisions = collisionsLevel2.parse2D()
100 | collisionBlocks = parsedCollisions.createObjectsFrom2D()
101 | player.collisionBlocks = collisionBlocks
102 | player.position.x = 96
103 | player.position.y = 140
104 |
105 | if (player.currentAnimation) player.currentAnimation.isActive = false
106 |
107 | background = new Sprite({
108 | position: {
109 | x: 0,
110 | y: 0,
111 | },
112 | imageSrc: './img/backgroundLevel2.png',
113 | })
114 |
115 | doors = [
116 | new Sprite({
117 | position: {
118 | x: 772.0,
119 | y: 336,
120 | },
121 | imageSrc: './img/doorOpen.png',
122 | frameRate: 5,
123 | frameBuffer: 5,
124 | loop: false,
125 | autoplay: false,
126 | }),
127 | ]
128 | },
129 | },
130 | 3: {
131 | init: () => {
132 | parsedCollisions = collisionsLevel3.parse2D()
133 | collisionBlocks = parsedCollisions.createObjectsFrom2D()
134 | player.collisionBlocks = collisionBlocks
135 | player.position.x = 750
136 | player.position.y = 230
137 | if (player.currentAnimation) player.currentAnimation.isActive = false
138 |
139 | background = new Sprite({
140 | position: {
141 | x: 0,
142 | y: 0,
143 | },
144 | imageSrc: './img/backgroundLevel3.png',
145 | })
146 |
147 | doors = [
148 | new Sprite({
149 | position: {
150 | x: 176.0,
151 | y: 335,
152 | },
153 | imageSrc: './img/doorOpen.png',
154 | frameRate: 5,
155 | frameBuffer: 5,
156 | loop: false,
157 | autoplay: false,
158 | }),
159 | ]
160 | },
161 | },
162 | }
163 |
164 | const keys = {
165 | w: {
166 | pressed: false,
167 | },
168 | a: {
169 | pressed: false,
170 | },
171 | d: {
172 | pressed: false,
173 | },
174 | }
175 |
176 | const overlay = {
177 | opacity: 0,
178 | }
179 |
180 | function animate() {
181 | window.requestAnimationFrame(animate)
182 |
183 | background.draw()
184 | // collisionBlocks.forEach((collisionBlock) => {
185 | // collisionBlock.draw()
186 | // })
187 |
188 | doors.forEach((door) => {
189 | door.draw()
190 | })
191 |
192 | player.handleInput(keys)
193 | player.draw()
194 | player.update()
195 |
196 | c.save()
197 | c.globalAlpha = overlay.opacity
198 | c.fillStyle = 'black'
199 | c.fillRect(0, 0, canvas.width, canvas.height)
200 | c.restore()
201 | }
202 |
203 | levels[level].init()
204 | animate()
205 |
--------------------------------------------------------------------------------
/js/classes/CollisionBlock.js:
--------------------------------------------------------------------------------
1 | class CollisionBlock {
2 | constructor({ position }) {
3 | this.position = position
4 | this.width = 64
5 | this.height = 64
6 | }
7 |
8 | draw() {
9 | c.fillStyle = 'rgba(255, 0, 0, 0.5)'
10 | c.fillRect(this.position.x, this.position.y, this.width, this.height)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/js/classes/Player.js:
--------------------------------------------------------------------------------
1 | class Player extends Sprite {
2 | constructor({ collisionBlocks = [], imageSrc, frameRate, animations, loop }) {
3 | super({ imageSrc, frameRate, animations, loop })
4 | this.position = {
5 | x: 200,
6 | y: 200,
7 | }
8 |
9 | this.velocity = {
10 | x: 0,
11 | y: 0,
12 | }
13 |
14 | this.sides = {
15 | bottom: this.position.y + this.height,
16 | }
17 | this.gravity = 1
18 |
19 | this.collisionBlocks = collisionBlocks
20 | }
21 |
22 | update() {
23 | // this is the blue box
24 | // c.fillStyle = 'rgba(0, 0, 255, 0.5)'
25 | // c.fillRect(this.position.x, this.position.y, this.width, this.height)
26 |
27 | this.position.x += this.velocity.x
28 |
29 | this.updateHitbox()
30 |
31 | this.checkForHorizontalCollisions()
32 | this.applyGravity()
33 |
34 | this.updateHitbox()
35 |
36 | // c.fillRect(
37 | // this.hitbox.position.x,
38 | // this.hitbox.position.y,
39 | // this.hitbox.width,
40 | // this.hitbox.height
41 | // )
42 | this.checkForVerticalCollisions()
43 | }
44 |
45 | handleInput(keys) {
46 | if (this.preventInput) return
47 | this.velocity.x = 0
48 | if (keys.d.pressed) {
49 | this.switchSprite('runRight')
50 | this.velocity.x = 5
51 | this.lastDirection = 'right'
52 | } else if (keys.a.pressed) {
53 | this.switchSprite('runLeft')
54 | this.velocity.x = -5
55 | this.lastDirection = 'left'
56 | } else {
57 | if (this.lastDirection === 'left') this.switchSprite('idleLeft')
58 | else this.switchSprite('idleRight')
59 | }
60 | }
61 |
62 | switchSprite(name) {
63 | if (this.image === this.animations[name].image) return
64 | this.currentFrame = 0
65 | this.image = this.animations[name].image
66 | this.frameRate = this.animations[name].frameRate
67 | this.frameBuffer = this.animations[name].frameBuffer
68 | this.loop = this.animations[name].loop
69 | this.currentAnimation = this.animations[name]
70 | }
71 |
72 | updateHitbox() {
73 | this.hitbox = {
74 | position: {
75 | x: this.position.x + 58,
76 | y: this.position.y + 34,
77 | },
78 | width: 50,
79 | height: 53,
80 | }
81 | }
82 |
83 | checkForHorizontalCollisions() {
84 | for (let i = 0; i < this.collisionBlocks.length; i++) {
85 | const collisionBlock = this.collisionBlocks[i]
86 |
87 | // if a collision exists
88 | if (
89 | this.hitbox.position.x <=
90 | collisionBlock.position.x + collisionBlock.width &&
91 | this.hitbox.position.x + this.hitbox.width >=
92 | collisionBlock.position.x &&
93 | this.hitbox.position.y + this.hitbox.height >=
94 | collisionBlock.position.y &&
95 | this.hitbox.position.y <=
96 | collisionBlock.position.y + collisionBlock.height
97 | ) {
98 | // collision on x axis going to the left
99 | if (this.velocity.x < -0) {
100 | const offset = this.hitbox.position.x - this.position.x
101 | this.position.x =
102 | collisionBlock.position.x + collisionBlock.width - offset + 0.01
103 | break
104 | }
105 |
106 | if (this.velocity.x > 0) {
107 | const offset =
108 | this.hitbox.position.x - this.position.x + this.hitbox.width
109 | this.position.x = collisionBlock.position.x - offset - 0.01
110 | break
111 | }
112 | }
113 | }
114 | }
115 |
116 | applyGravity() {
117 | this.velocity.y += this.gravity
118 | this.position.y += this.velocity.y
119 | }
120 |
121 | checkForVerticalCollisions() {
122 | for (let i = 0; i < this.collisionBlocks.length; i++) {
123 | const collisionBlock = this.collisionBlocks[i]
124 |
125 | // if a collision exists
126 | if (
127 | this.hitbox.position.x <=
128 | collisionBlock.position.x + collisionBlock.width &&
129 | this.hitbox.position.x + this.hitbox.width >=
130 | collisionBlock.position.x &&
131 | this.hitbox.position.y + this.hitbox.height >=
132 | collisionBlock.position.y &&
133 | this.hitbox.position.y <=
134 | collisionBlock.position.y + collisionBlock.height
135 | ) {
136 | if (this.velocity.y < 0) {
137 | this.velocity.y = 0
138 | const offset = this.hitbox.position.y - this.position.y
139 | this.position.y =
140 | collisionBlock.position.y + collisionBlock.height - offset + 0.01
141 | break
142 | }
143 |
144 | if (this.velocity.y > 0) {
145 | this.velocity.y = 0
146 | const offset =
147 | this.hitbox.position.y - this.position.y + this.hitbox.height
148 | this.position.y = collisionBlock.position.y - offset - 0.01
149 | break
150 | }
151 | }
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/js/classes/Sprite.js:
--------------------------------------------------------------------------------
1 | class Sprite {
2 | constructor({
3 | position,
4 | imageSrc,
5 | frameRate = 1,
6 | animations,
7 | frameBuffer = 2,
8 | loop = true,
9 | autoplay = true,
10 | }) {
11 | this.position = position
12 | this.image = new Image()
13 | this.image.onload = () => {
14 | this.loaded = true
15 | this.width = this.image.width / this.frameRate
16 | this.height = this.image.height
17 | }
18 | this.image.src = imageSrc
19 | this.loaded = false
20 | this.frameRate = frameRate
21 | this.currentFrame = 0
22 | this.elapsedFrames = 0
23 | this.frameBuffer = frameBuffer
24 | this.animations = animations
25 | this.loop = loop
26 | this.autoplay = autoplay
27 | this.currentAnimation
28 |
29 | if (this.animations) {
30 | for (let key in this.animations) {
31 | const image = new Image()
32 | image.src = this.animations[key].imageSrc
33 | this.animations[key].image = image
34 | }
35 | }
36 | }
37 | draw() {
38 | if (!this.loaded) return
39 | const cropbox = {
40 | position: {
41 | x: this.width * this.currentFrame,
42 | y: 0,
43 | },
44 | width: this.width,
45 | height: this.height,
46 | }
47 |
48 | c.drawImage(
49 | this.image,
50 | cropbox.position.x,
51 | cropbox.position.y,
52 | cropbox.width,
53 | cropbox.height,
54 | this.position.x,
55 | this.position.y,
56 | this.width,
57 | this.height
58 | )
59 |
60 | this.updateFrames()
61 | }
62 |
63 | play() {
64 | this.autoplay = true
65 | }
66 |
67 | updateFrames() {
68 | if (!this.autoplay) return
69 |
70 | this.elapsedFrames++
71 |
72 | if (this.elapsedFrames % this.frameBuffer === 0) {
73 | if (this.currentFrame < this.frameRate - 1) this.currentFrame++
74 | else if (this.loop) this.currentFrame = 0
75 | }
76 |
77 | if (this.currentAnimation?.onComplete) {
78 | if (
79 | this.currentFrame === this.frameRate - 1 &&
80 | !this.currentAnimation.isActive
81 | ) {
82 | this.currentAnimation.onComplete()
83 | this.currentAnimation.isActive = true
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/js/data/collisions.js:
--------------------------------------------------------------------------------
1 | const collisionsLevel1 = [
2 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3 | 0, 0, 0, 0, 0, 0, 0, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292,
4 | 292, 292, 292, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 292,
5 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 292, 292, 0, 0, 0, 0, 0, 0, 0,
6 | 0, 0, 0, 0, 292, 0, 0, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292,
7 | 292, 292, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9 | ]
10 |
11 | const collisionsLevel2 = [
12 | 292, 292, 292, 292, 292, 292, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0,
13 | 0, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 0, 0,
14 | 0, 0, 0, 292, 292, 292, 292, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
15 | 292, 0, 0, 292, 0, 0, 292, 292, 292, 292, 292, 292, 0, 0, 292, 292, 292, 0, 0,
16 | 292, 292, 292, 292, 0, 0, 0, 0, 292, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17 | 0, 0, 292, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 0, 292, 292, 292, 292, 292, 0, 0,
18 | 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 0, 0, 0, 0,
19 | ]
20 |
21 | const collisionsLevel3 = [
22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 250, 250, 250, 250,
23 | 250, 250, 250, 250, 250, 250, 250, 250, 250, 0, 0, 250, 0, 0, 0, 0, 0, 0, 0,
24 | 0, 0, 0, 0, 0, 250, 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0,
25 | 250, 0, 0, 0, 0, 0, 0, 0, 0, 250, 250, 250, 250, 250, 0, 0, 250, 0, 0, 0, 0,
26 | 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 250, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250,
27 | 250, 0, 0, 0, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,
28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
29 | ]
30 |
--------------------------------------------------------------------------------
/js/eventListeners.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('keydown', (event) => {
2 | if (player.preventInput) return
3 | switch (event.key) {
4 | case 'w':
5 | for (let i = 0; i < doors.length; i++) {
6 | const door = doors[i]
7 |
8 | if (
9 | player.hitbox.position.x + player.hitbox.width <=
10 | door.position.x + door.width &&
11 | player.hitbox.position.x >= door.position.x &&
12 | player.hitbox.position.y + player.hitbox.height >= door.position.y &&
13 | player.hitbox.position.y <= door.position.y + door.height
14 | ) {
15 | player.velocity.x = 0
16 | player.velocity.y = 0
17 | player.preventInput = true
18 | player.switchSprite('enterDoor')
19 | door.play()
20 | return
21 | }
22 | }
23 | if (player.velocity.y === 0) player.velocity.y = -25
24 |
25 | break
26 | case 'a':
27 | // move player to the left
28 | keys.a.pressed = true
29 | break
30 | case 'd':
31 | // move player to the right
32 | keys.d.pressed = true
33 | break
34 | }
35 | })
36 |
37 | window.addEventListener('keyup', (event) => {
38 | switch (event.key) {
39 | case 'a':
40 | // move player to the left
41 | keys.a.pressed = false
42 |
43 | break
44 | case 'd':
45 | // move player to the right
46 | keys.d.pressed = false
47 |
48 | break
49 | }
50 | })
51 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | Array.prototype.parse2D = function () {
2 | const rows = []
3 | for (let i = 0; i < this.length; i += 16) {
4 | rows.push(this.slice(i, i + 16))
5 | }
6 |
7 | return rows
8 | }
9 |
10 | Array.prototype.createObjectsFrom2D = function () {
11 | const objects = []
12 | this.forEach((row, y) => {
13 | row.forEach((symbol, x) => {
14 | if (symbol === 292 || symbol === 250) {
15 | // push a new collision into collisionblocks array
16 | objects.push(
17 | new CollisionBlock({
18 | position: {
19 | x: x * 64,
20 | y: y * 64,
21 | },
22 | })
23 | )
24 | }
25 | })
26 | })
27 |
28 | return objects
29 | }
30 |
--------------------------------------------------------------------------------