diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..737b4a70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +script2bk.js +*.pptx +onionFace.png +playerRed.png +cow.png +bandit1.png +fireball.psd +char.png +bandit1.png diff --git a/README.md b/README.md index 317a3e58..9a29a8e6 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,111 @@ -# Project Name (Start editing here) - +# Project 1: Crops and Robbers -# ![](https://ga-dash.s3.amazonaws.com/production/assets/logo-9f88ae6c9c3871690e33280fcf557f33.png) Project #1: The Game +### Introduction -### Overview +1. Play as a farmer, Johnny Wicklebottom. +2. Defend your farm against the OnionFace Bandit Gang. +3. Survive them all to win. -Let's start out with something fun - **a game!** +Play the game at: https://soemn.github.io/project-1/ -Everyone will get a chance to **be creative**, and work through some really **tough programming challenges** – since you've already gotten your feet wet with Tic Tac Toe, it's up to you to come up with a fun and interesting game to build. +![Introduction](/assets/images/introScreen2.png) -**You will be working individually for this project**, but we'll be guiding you along the process and helping as you go. Show us what you've got! +##### Gameplay +Shoot the enemies while avoiding hit or getting your house damaged. The enemies keep spawning so defeat them fast and avoid being overrun! You will lose if the enemies destroy your house or kills you. ---- +### Controls -### Technical Requirements +![Instructions](/assets/images/controls.png) -Your app must: -* **Render a game in the browser** -* **Any number of players** will be okay, switch turns will be great -* **Design logic for winning** & **visually display which player won** -* **Include separate HTML / CSS / JavaScript files** -* Stick with **KISS (Keep It Simple Stupid)** and **DRY (Don't Repeat Yourself)** principles -* Use **Javascript** for **DOM manipulation**, jQuery is not compulsory -* **Deploy your game online**, where the rest of the world can access it -* Use **semantic markup** for HTML and CSS (adhere to best practices) -* **No canvas** project will be accepted, only HTML5 + CSS3 + JS please +### In Game Screen Shot ---- +![screen shot](/assets/images/screenShot.png) -### Necessary Deliverables +--- +### Game Flow -* A **working game, built by you**, hosted somewhere on the internet -* A **link to your hosted working game** in the URL section of your GitHub repo -* A **git repository hosted on GitHub**, with a link to your hosted game, and frequent commits dating back to the very beginning of the project -* **A ``readme.md`` file** with explanations of the technologies used, the approach taken, installation instructions, unsolved problems, etc. +![Gameflow](/assets/images/fightPhase.png) --- -### Suggested Ways to Get Started +### Technical Assets -* **Break the project down into different components** (data, presentation, views, style, DOM manipulation) and brainstorm each component individually. Use whiteboards! -* **Use your Development Tools** (console.log, inspector, alert statements, etc) to debug and solve problems -* Work through the lessons in class & ask questions when you need to! Think about adding relevant code to your game each night, instead of, you know... _procrastinating_. -* **Commit early, commit often.** Don’t be afraid to break something because you can always go back in time to a previous version. -* **Consult documentation resources** (MDN, jQuery, etc.) at home to better understand what you’ll be getting into. -* **Don’t be afraid to write code that you know you will have to remove later.** Create temporary elements (buttons, links, etc) that trigger events if real data is not available. For example, if you’re trying to figure out how to change some text when the game is over but you haven’t solved the win/lose game logic, you can create a button to simulate that until then. +#### Content update +The update loop uses ```window.requestAnimationFrame()``` to update the game when not paused. ---- +The ```update``` function is used to move the characters using ```element.moveChar``` and check interaction using ```checkCollision()```. Each element behave differently upon collision. -### Potential Project Ideas -##### Blackjack -Make a one player game where people down on their luck can lose all their money by guessing which card the computer will deal next! +#### Element creation +The ```generateEnemy```, ```generateBullet```, ```generateUpgrade```, ```generateCat``` functions are used to create elements using the ```Character``` class. -##### Self-scoring Trivia -Test your wits & knowledge with whatever-the-heck you know about (so you can actually win). Guess answers, have the computer tell you how right you are! +The ```Character``` class is used to create all game elements on the gameBoard. +``` +class Character { + constructor( + type, //player, enemy, bullet, pet + id, //element identifier + sizeX, //element size + sizeY, + spdX, //movement speed + spdY, + hp, /character health + x, //location on gameBoard + y, + atkSpd, //attack speed + bulletOwner, //who shot the bullet + aniFrame, //animation counter + aimAngle //bullet direction + ) ---- + addChar() + //adds the character element to the gameBoard -### Useful Resources + removeChar() + //removes the character element from the gameBoard -* **[MDN Javascript Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript)** _(a great reference for all things Vanilla Javascript)_ -* **[jQuery Docs](http://api.jquery.com)** _(if you're using jQuery)_ -* **[GitHub Pages](https://pages.github.com)** _(for hosting your game)_ -* **[How to write readme - Markdown CheatSheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)** _(for editing this readme)_ -* **[How to write a good readme for github repo!](https://gist.github.com/PurpleBooth/109311bb0361f32d87a2)** _(to make it better)_ + moveChar() + //moves and animates the character ---- + shoot() + //Generates a bullet at the character's position + //Shoots direction base on aimAngle. + //All characters can shoot. Even pets and bullets. +``` -### Project Feedback + Evaluation -* __Project Workflow__: Did you complete the user stories, wireframes, task tracking, and/or ERDs, as specified above? Did you use source control as expected for the phase of the program you’re in (detailed above)? -* __Technical Requirements__: Did you deliver a project that met all the technical requirements? Given what the class has covered so far, did you build something that was reasonably complex? +##### Levels -* __Creativity__: Did you add a personal spin or creative element into your project submission? Did you deliver something of value to the end user (not just a login button and an index page)? +3 Levels with varying enemy spawn rates base on a timer. Past a certain point, enemies stop spawning and once all enemies are dead, the game is won. -* __Code Quality__: Did you follow code style guidance and best practices covered in class, such as spacing, modularity, and semantic naming? Did you comment your code as your instructors have in class? -* __Deployment__: Did you deploy your application to a public url using GitHub Pages? +##### Game Design Log -* __Total__: Your instructors will give you a total score on your project between: +###### First Idea: +Create a resource management game, where the player has to plant crops and then defend his crops from the enemies. Earning gold at the end of each level which can be used to buy seeds or defence for the next level. - Score | Expectations - ----- | ------------ - **0** | _Incomplete._ - **1** | _Does not meet expectations._ - **2** | _Meets expectations, good job!_ - **3** | _Exceeds expectations, you wonderful creature, you!_ +###### Idea Testing: +The fighting mechanic was tested first as it was more complicated than the resource management component. While testing the fighting mechanic, it was found to be quite fun and the game was then decided to be simplified to focus only on the fighting aspect. + +###### Detailed Design +Originally functions were used to create ```objects``` without the use of ```Class```. After refactoring the code to use a ```Class``` to define all characters, new entities and mechanics could be created quickly. + +###### Juicing +Pets and sounds were added. Improved sprites. + +###### To do: +1. Dog enemy (does not shoot, chases player and attacks) +2. Additional sounds for getting hit +3. Improvement to level design and difficulty adjustment +4. Reduce the use of `setInterval` and improve game performance + + +--- - This will serve as a helpful overall gauge of whether you met the project goals, but __the more important scores are the individual ones__ above, which can help you identify where to focus your efforts for the next project! +Sprites from: +- opengameart.org +- spriters-resource.com (Stardew Valley) +- charas-project.net diff --git a/assets/audio/catMeow.mp3 b/assets/audio/catMeow.mp3 new file mode 100644 index 00000000..7582771f Binary files /dev/null and b/assets/audio/catMeow.mp3 differ diff --git a/assets/audio/fightMusic.mp3 b/assets/audio/fightMusic.mp3 new file mode 100644 index 00000000..83e32571 Binary files /dev/null and b/assets/audio/fightMusic.mp3 differ diff --git a/assets/audio/shootSoundEnemy.wav b/assets/audio/shootSoundEnemy.wav new file mode 100755 index 00000000..d80bf3df Binary files /dev/null and b/assets/audio/shootSoundEnemy.wav differ diff --git a/assets/audio/shootSoundPlayer.wav b/assets/audio/shootSoundPlayer.wav new file mode 100755 index 00000000..0a0b69b2 Binary files /dev/null and b/assets/audio/shootSoundPlayer.wav differ diff --git a/assets/audio/spring.mp3 b/assets/audio/spring.mp3 new file mode 100644 index 00000000..f7295f72 Binary files /dev/null and b/assets/audio/spring.mp3 differ diff --git a/assets/css/stylesheet.css b/assets/css/stylesheet.css index e69de29b..4d62476a 100644 --- a/assets/css/stylesheet.css +++ b/assets/css/stylesheet.css @@ -0,0 +1,88 @@ +.container { + display: flex; + justify-content: left; +} + +body { + background: url("../images/background.png"); + font-family: "Press Start 2P"; +} + +.gameWrapper { + width: 800px; + height: 730px; +} + +.controls { + height: 400px; + width: 450px; + background: url("../images/controls.png"); + background-size: 100%; +} + +.startGame { + display: inline-block; + padding: 0; + height: 90px; + width: 250px; + background: green; + text-align: center; + color: white; + position: absolute; + top: 500px; + left: 500px; + font-size: 20px; +} + +#startGame { + margin: 7px auto; +} + +button { + margin-top: 20px; +} + +.gameBoard { + width: 800px; + height: 630px; + border: 3px solid black; + position: absolute; + display: inline-block; + background: url("../images/introScreen2.png"); + background-size: 103%; + background-repeat: no-repeat; +} + +.info { + width: 800px; + height: 60px; + display: flex; + justify-content: space-between; +} + +.aside { + display: inline-block; + padding: 0 50px; +} + +.hitpointsDiv { + display: inline-block; +} + +.scoreDiv { + display: inline-block; +} + +.cow1 { + background: url("../images/cow1.png"); + width: 80px; + height: 80px; + background-size: 100%; +} + +.cow2 { + background: url("../images/cow2.png"); + width: 80px; + height: 80px; + background-size: 100%; +} diff --git a/assets/images/annoyingDog.png b/assets/images/annoyingDog.png new file mode 100644 index 00000000..5a51a829 Binary files /dev/null and b/assets/images/annoyingDog.png differ diff --git a/assets/images/background.png b/assets/images/background.png new file mode 100644 index 00000000..7c5b61bb Binary files /dev/null and b/assets/images/background.png differ diff --git a/assets/images/bandit1.png b/assets/images/bandit1.png new file mode 100644 index 00000000..b8b51f28 Binary files /dev/null and b/assets/images/bandit1.png differ diff --git a/assets/images/char.png b/assets/images/char.png new file mode 100644 index 00000000..b21e494d Binary files /dev/null and b/assets/images/char.png differ diff --git a/assets/images/controls.png b/assets/images/controls.png new file mode 100644 index 00000000..e273c207 Binary files /dev/null and b/assets/images/controls.png differ diff --git a/assets/images/cow1.png b/assets/images/cow1.png new file mode 100644 index 00000000..cfe46149 Binary files /dev/null and b/assets/images/cow1.png differ diff --git a/assets/images/cow2.png b/assets/images/cow2.png new file mode 100644 index 00000000..8fe46e78 Binary files /dev/null and b/assets/images/cow2.png differ diff --git a/assets/images/doggo.png b/assets/images/doggo.png new file mode 100644 index 00000000..ef726b74 Binary files /dev/null and b/assets/images/doggo.png differ diff --git a/assets/images/fightPhase.png b/assets/images/fightPhase.png new file mode 100644 index 00000000..b668ad0d Binary files /dev/null and b/assets/images/fightPhase.png differ diff --git a/assets/images/fightPhase.svg b/assets/images/fightPhase.svg new file mode 100644 index 00000000..244a1073 --- /dev/null +++ b/assets/images/fightPhase.svg @@ -0,0 +1,360 @@ +
Hit. Reduce hitpoints
no.
Miss.
yes
no
yes
Defence Phase
Generate enemies
Shoot enemy. Create projectile
Dead?
remove projective on contact with border
All enemies dead?
Next level
\ No newline at end of file diff --git a/assets/images/fireball.png b/assets/images/fireball.png new file mode 100644 index 00000000..fd9b9595 Binary files /dev/null and b/assets/images/fireball.png differ diff --git a/assets/images/fireballBlue.png b/assets/images/fireballBlue.png new file mode 100644 index 00000000..844d5fe3 Binary files /dev/null and b/assets/images/fireballBlue.png differ diff --git a/assets/images/health.png b/assets/images/health.png new file mode 100644 index 00000000..afb29f15 Binary files /dev/null and b/assets/images/health.png differ diff --git a/assets/images/introScreen.png b/assets/images/introScreen.png new file mode 100644 index 00000000..e479c7ce Binary files /dev/null and b/assets/images/introScreen.png differ diff --git a/assets/images/introScreen2.png b/assets/images/introScreen2.png new file mode 100644 index 00000000..62affdbd Binary files /dev/null and b/assets/images/introScreen2.png differ diff --git a/assets/images/introScreen3.png b/assets/images/introScreen3.png new file mode 100644 index 00000000..4494d246 Binary files /dev/null and b/assets/images/introScreen3.png differ diff --git a/assets/images/onionFace2.png b/assets/images/onionFace2.png new file mode 100644 index 00000000..adbe1010 Binary files /dev/null and b/assets/images/onionFace2.png differ diff --git a/assets/images/overallFlow.png b/assets/images/overallFlow.png new file mode 100644 index 00000000..d82efd59 Binary files /dev/null and b/assets/images/overallFlow.png differ diff --git a/assets/images/overallFlow.svg b/assets/images/overallFlow.svg new file mode 100644 index 00000000..914e6b56 --- /dev/null +++ b/assets/images/overallFlow.svg @@ -0,0 +1,360 @@ +
clear previous data
Calculate crop type and value
Fight!
yes
no
no
no
yes
yes
No. Update assets
yes
Game Start
Phase 1 - Plant crop. Anywhere on map, no overlap, timed
Phase 2 - Defend. Generate enemies base on crop type and value
All enemies defeated?
Enough gold for next level?
Player Dead?
Game Over
Final Level?
Win!
\ No newline at end of file diff --git a/assets/images/plantingPhase.png b/assets/images/plantingPhase.png new file mode 100644 index 00000000..3166c585 Binary files /dev/null and b/assets/images/plantingPhase.png differ diff --git a/assets/images/plantingPhase.svg b/assets/images/plantingPhase.svg new file mode 100644 index 00000000..da12575a --- /dev/null +++ b/assets/images/plantingPhase.svg @@ -0,0 +1,360 @@ +
yes
no
yes
yes
No. Deduct gold cost for plant
no
Planting Phase
Time left?
Select plant.
enough gold?
Planting phase end. Start defence phase
Place plant on ground
Plant clash with another?
Planted. Update assets
\ No newline at end of file diff --git a/assets/images/playerRed1.png b/assets/images/playerRed1.png new file mode 100644 index 00000000..e2ef87f8 Binary files /dev/null and b/assets/images/playerRed1.png differ diff --git a/assets/images/screenShot.png b/assets/images/screenShot.png new file mode 100644 index 00000000..1967fbb5 Binary files /dev/null and b/assets/images/screenShot.png differ diff --git a/assets/images/spring.png b/assets/images/spring.png new file mode 100644 index 00000000..8131e0a4 Binary files /dev/null and b/assets/images/spring.png differ diff --git a/assets/images/spring2.png b/assets/images/spring2.png new file mode 100644 index 00000000..42fc25f0 Binary files /dev/null and b/assets/images/spring2.png differ diff --git a/assets/images/weilisCat1.png b/assets/images/weilisCat1.png new file mode 100644 index 00000000..88194ade Binary files /dev/null and b/assets/images/weilisCat1.png differ diff --git a/assets/images/weilisCat2.png b/assets/images/weilisCat2.png new file mode 100644 index 00000000..46bf4971 Binary files /dev/null and b/assets/images/weilisCat2.png differ diff --git a/assets/images/weilisCat3.png b/assets/images/weilisCat3.png new file mode 100644 index 00000000..9e5198f1 Binary files /dev/null and b/assets/images/weilisCat3.png differ diff --git a/assets/js/script.js b/assets/js/script.js index e69de29b..3af91059 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -0,0 +1,990 @@ +const $gameBoard = $(".gameBoard") +const $body = $("body") +const gameWidth = parseInt($gameBoard.css("width")) +const gameHeight = parseInt($gameBoard.css("height")) +const $score = $(".score") +const $hitpoint = $(".hitpoints") +const $pauseBtn = $("#pauseBtn") +const $startBtn = $("#startBtn") +const $restartBtn = $("#restartBtn") +const $loseDiv = $("
") +const $levelDiv = $(".levelDiv") +const $winDiv = $('
') +const $levelText = $(".levelText") +const $houseDiv = $("
") +const $house = $(".house") +const $houseHp = $('
') +const $houseHpBar = $('
') +const $cowDiv = $("
") +const catMeow = new Audio("./assets/audio/catMeow.mp3") +const fireball = new Audio("./assets/audio/catMeow.mp3") +const introMusic = new Audio("./assets/audio/spring.mp3") +const fightMusic = new Audio("./assets/audio/fightMusic.mp3") +const shootSoundEnemy = new Audio("./assets/audio/shootSoundEnemy.wav") +const shootSoundPlayer = new Audio("./assets/audio/shootSoundPlayer.wav") +const $startGame = $(".startGame") +let level = 1 +let enemyList = [] +let bulletList = [] +let upgradeList = [] +let petList = [] +let score = 0 +let pause = true +let gameCounter = 0 +let gameStatus = "playing" + +class Character { + constructor( + type, + id, + sizeX, + sizeY, + spdX, + spdY, + hp, + x, + y, + atkSpd, + bulletOwner, + aniFrame, + aimAngle + ) { + this.type = type + this.id = id + this.jTarget = $gameBoard.find("." + this.id) + this.sizeX = sizeX + this.sizeY = sizeY + this.spdX = spdX + this.spdY = spdY + this.hp = hp + this.x = x //spawn position + this.y = y + this.timer = 0 //how long has this asset been in the game? + this.atkSpd = atkSpd //per ms. attack speed + this.isShooting = false + this.pressingDown = false + this.pressingUp = false + this.pressingLeft = false + this.pressingRight = false + this.aimAngle = aimAngle + this.bulletOwner = bulletOwner + this.aniFrame = 0 + this.bulletMod = 0 + } + + //appends to game board + addChar() { + let $char = $("
") + $char.addClass(`${this.id}`) + $char.css({ + position: "absolute", + width: `${this.sizeX}px`, + height: `${this.sizeY}px` + }) + + $gameBoard.append($char) + this.jTarget = $gameBoard.find(`.${this.id}`) + + if (this.type === "player") { + this.jTarget.css({ + background: `url("./assets/images/playerRed1.png")`, + backgroundPosition: "0 -190px", + backgroundSize: `350%` + }) + } + + if (this.type === "enemy") { + this.jTarget.css({ + background: `url("./assets/images/onionFace2.png")`, + backgroundPosition: `0px -200px`, + backgroundSize: "350%" + }) + } + + if (this.type === "upgrade") { + this.jTarget.css({ + background: `url("./assets/images/health.png")`, + backgroundSize: "100%" + }) + } + + if (this.type === "cow") { + this.jTarget.css({ + background: 'url("./assets/images/cow.png")' + }) + } + + if (this.type === "cat") { + let randomCat = Math.random() + if (randomCat < 0.333) { + this.jTarget.css({ + background: `url("./assets/images/weilisCat1.png")`, + backgroundPosition: `0px -200px`, + backgroundSize: "450%" + }) + } else if (randomCat > 0.333 && randomCat < 0.666) { + this.jTarget.css({ + background: `url("./assets/images/weilisCat2.png")`, + backgroundPosition: `0px -200px`, + backgroundSize: "450%" + }) + } else + this.jTarget.css({ + background: `url("./assets/images/weilisCat3.png")`, + backgroundPosition: `0px -200px`, + backgroundSize: "450%" + }) + } + + if (this.type === "bullet") { + if (this.bulletOwner === "enemy") { + this.jTarget.css({ + background: `url("./assets/images/fireballBlue.png")`, + backgroundPosition: `50px -45px`, + backgroundSize: "200%" + }) + } else { + this.jTarget.css({ + background: `url("./assets/images/fireball.png")`, + backgroundPosition: `50px -45px`, + backgroundSize: "200%" + }) + } + } + } + + //removes from game board + removeChar() { + this.jTarget.remove() + } + + //moves select character on game board + moveChar() { + this.jTarget.css({ + left: `${this.x}px`, + top: `${this.y}px` + }) + + if (this.type === "enemy") { + this.x += this.spdX + this.y += this.spdY + if (this.spdX > 0) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -100px` + }) + } + if (this.spdX < 0) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -290px` + }) + } + if (this.spdY > 0 && this.spdY > this.spdX) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -200px` + }) + } + if (this.spdY < 0 && this.spdY > this.spdX) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -20px` + }) + } + this.aniFrame += 0.07 + //enemy collision with border + if (this.x <= 0 || this.x >= gameWidth - this.sizeX) this.spdX *= -1 //border collision x + if (this.y <= 0 || this.y >= gameHeight - this.sizeY) this.spdY *= -1 //border collision y + } + + if (this.type === "upgrade") { + this.x += this.spdX + this.y += this.spdY + if (this.x <= 0 || this.x >= gameWidth - this.sizeX) this.spdX *= -1 //border collision x + if (this.y <= 0 || this.y >= gameHeight - this.sizeY) this.spdY *= -1 //border collision + } + + if (this.type === "cat") { + this.x += this.spdX + this.y += this.spdY + if (this.spdX === 0) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 4) * + -67.5}px -270px` + }) + } + if (this.spdX > 0 && this.spdY > 0) { + //right + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 4) * + -67.5}px -200px` + }) + } + if (this.spdX < 0 && this.spdY > 0) { + //left + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 4) * -67.5}px 0px` + }) + } + if (this.spdX > 0 && this.spdY < 0) { + //down + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 4) * + -67.5}px -132px` + }) + } + if (this.spdX < 0 && this.spdY < 0) { + //up + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 4) * -67.5}px -65px` + }) + } + this.aniFrame += 0.1 + + //enemy collision with border + if (this.x <= 0 || this.x >= gameWidth - this.sizeX) this.spdX *= -1 //border collision x + if (this.y <= 0 || this.y >= gameHeight - this.sizeY) this.spdY *= -1 //border collision y + } + + if (this.type === "bullet") { + this.x += this.spdX + this.y += this.spdY + if (this.x <= 0 || this.x >= gameWidth - this.sizeX) this.removeChar() //border collision x + if (this.y <= 0 || this.y >= gameHeight - this.sizeY) this.removeChar() + if (Math.floor(this.aniFrame % 4) === 0) { + this.jTarget.css({ + backgroundPosition: `0 0`, + transform: `rotate(${this.aimAngle - 90}deg)` + }) + } else if (Math.floor(this.aniFrame % 4) === 1) { + this.jTarget.css({ backgroundPosition: `50px 0` }) + } else if (Math.floor(this.aniFrame % 4) === 2) { + this.jTarget.css({ backgroundPosition: `0 -48px` }) + } else { + this.jTarget.css({ backgroundPosition: `50px -48px` }) + } + this.aniFrame += 0.1 + } + + if (this.type === "player") { + $body.on("keydown", e => { + if (e.key === "w" || e.key === "ArrowUp") { + player.pressingUp = true + } else if (e.key === "a" || e.key === "ArrowLeft") { + player.pressingLeft = true + } else if (e.key === "s" || e.key === "ArrowDown") { + player.pressingDown = true + } else if (e.key === "d" || e.key === "ArrowRight") { + player.pressingRight = true + } + }) + $body.on("keyup", e => { + if (e.key === "w" || e.key === "ArrowUp") { + player.pressingUp = false + } else if (e.key === "a" || e.key === "ArrowLeft") { + player.pressingLeft = false + } else if (e.key === "s" || e.key === "ArrowDown") { + player.pressingDown = false + } else if (e.key === "d" || e.key === "ArrowRight") { + player.pressingRight = false + } + }) + + if (player.pressingRight) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -100px` + }) + player.x += this.spdX + } + if (player.pressingLeft) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -290px` + }) + player.x -= 4 + } + if (player.pressingDown) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -190px` + }) + player.y += 4 + } + if (player.pressingUp) { + this.jTarget.css({ + backgroundPosition: `${Math.floor(this.aniFrame % 3) * 70}px -10px` + }) + player.y -= 4 + } + + //border collision + if (player.x <= 0) player.x = 0 + if (player.y <= 0) player.y = 0 + if (player.x > gameWidth - player.sizeX - 4) + player.x = gameWidth - player.sizeX - 4 + if (player.y > gameHeight - player.sizeY - 4) + player.y = gameHeight - player.sizeY - 4 + + //update aniFrame + this.aniFrame += 0.06 + } + } + + //allows object to shoot + shoot() { + if (pause === true) return + if (this.type === "player") { + //mouse targeting + shootSoundPlayer.play() + $gameBoard.on("mousemove", e => { + let targetX = e.clientX - $gameBoard.offset().left //mouse X + let targetY = e.clientY - $gameBoard.offset().top //mouse Y + let distanceX = targetX - this.x - this.sizeX / 2 + let distanceY = targetY - this.y - this.sizeY / 2 + this.aimAngle = Math.atan2(distanceY, distanceX) / Math.PI * 180 + }) + } + + if (this.type === "enemy") { + shootSoundEnemy.play() + let targetX = player.x + let targetY = player.y + let distanceX = targetX - this.x - this.sizeX / 2 + let distanceY = targetY - this.y - this.sizeY / 2 + this.aimAngle = Math.atan2(distanceY, distanceX) / Math.PI * 180 + } + + if (this.bulletMod === 0) { + generateBullet(this) + } else { + generateBullet(this) + this.aimAngle += 5 + generateBullet(this) + this.aimAngle -= 5 + } + + //allows special bullets (example: shotgun style etc.) + // if (this.bulletMod === 0) generateBullet(this) + // else (this.bulletMod === > 0) { + //default 0 -> 1 bullet + // generateBullet(this) + // this.aimAngle += 5 + // generateBullet(this) + // } + // if (this.bulletMod === "a") { + // //special bullets to be added + // } + } +} + +//type, id, sizeX, sizeY, spdX, spdY, hp, x, y, atkSpd, bulletOwner, aniFrame +let player = new Character( + "player", + "player", + 60, + 90, + 4.5, + 4.5, + 100, + 50, + 50, + 1000, + "", + 0 +) + +const generateCow = () => { + $cowDiv.addClass("cow1") //static cow used + $gameBoard.append($cowDiv) + //moving cow to be implemented + // let id = Math.floor(Math.random() * 100000) + // let sizeX = 60 + // let sizeY = 90 + // let spdX = Math.random() * 2.5 + // let spdY = Math.random() * 2.5 + // let x = 150 + // let y = 150 + // + // petList[id] = new Character("cow", id, sizeX, sizeY, spdX, spdY, 0, x, y) + // + // petList[id].addChar() +} + +const generateCat = () => { + let id = Math.floor(Math.random() * 100000) + let sizeX = 60 + let sizeY = 80 + let spdModX = Math.random() + let spdMultX = 1 + if (spdModX < 0.5) spdMultX = -1 + let spdModY = Math.random() + let spdMultY = 1 + if (spdModY < 0.5) spdMultY = -1 + let spdX = Math.random() * 0.3 * spdMultX + let spdY = Math.random() * 0.3 * spdMultY + let x = Math.random() * 700 //to regenerate when spawned inside house + let y = 330 + Math.random() * 200 + + petList[id] = new Character("cat", id, sizeX, sizeY, spdX, spdY, 0, x, y) + + petList[id].addChar() +} + +//generate Enemies +const generateEnemy = () => { + let id = Math.floor(Math.random() * 100000) //To do: fix potential duplicate id + let sizeX = 60 + let sizeY = 90 + let spdX = Math.random() * 2.5 + let spdY = Math.random() * 2.5 + let hp = 100 + let x = 360 + Math.random() * 350 + let y = Math.random() * 500 + let atkSpd = Math.floor(1000 + Math.random() * 1500) + + enemyList[id] = new Character( + "enemy", + id, + sizeX, + sizeY, + spdX, + spdY, + hp, + x, + y, + atkSpd + ) + + enemyList[id].addChar() +} + +//generate Bullets +const generateBullet = entity => { + let id = Math.floor(Math.random() * 100000) //To do: fix potential duplicate id + let angle = entity.aimAngle + let sizeX = 50 + let sizeY = 50 + let spdX = Math.cos(angle / 180 * Math.PI) * 5 + let spdY = Math.sin(angle / 180 * Math.PI) * 5 + let hp = 0 + let x = entity.x + entity.sizeX / 2 + let y = entity.y + entity.sizeY / 2 + let owner = entity.type + + bulletList[id] = new Character( + "bullet", + id, + sizeX, + sizeY, + spdX, + spdY, + hp, + x, + y, + 0, + owner, + 0, + angle + ) + + bulletList[id].addChar() +} + +//creates health packs +const generateUpgrade = () => { + let id = Math.floor(Math.random() * 100000) //To do: fix potential duplicate id + let sizeX = 20 + let sizeY = 20 + let spdX = Math.random() + let spdY = Math.random() + let hp = 0 + let x = Math.random() * 700 + let y = Math.random() * 500 + let atkSpd = 0 + let owner = "" + + upgradeList[id] = new Character( + "upgrade", + id, + sizeX, + sizeY, + spdX, + spdY, + hp, + x, + y, + atkSpd, + owner + ) + upgradeList[id].addChar() +} + +//creates house borders and health bar +const makeHouse = () => { + $houseDiv.css({ + width: "100px", + height: "120px", + position: "absolute", + top: "200px", + left: "260px" + }) + $houseHp.css({ + width: "100px", + height: "20px", + position: "absolute", + top: "170px", + left: "260px" + }) + $houseHpBar.css({ + width: "100px", + height: "20px", + position: "absolute", + top: "170px", + left: "260px" + }) + $gameBoard.append($houseHpBar) + $gameBoard.append($houseHp) + $gameBoard.append($houseDiv) +} + +//house properties +const house = { + x: 260, + y: 200, + sizeX: 100, + sizeY: 120, + hp: 100 +} + +// spawn enemies +let enemyFrequency = 500 +const spawnEnemy = () => { + if (pause) return + if (gameCounter % (enemyFrequency * 2) === 0) { + generateUpgrade() + } + + if (gameCounter > 5000) { + //if player survives all levels + gameStatus = "won" + } else if (gameCounter > 3500) { + $levelText.text("Level 3") + enemyFrequency = 300 + if (gameCounter % enemyFrequency === 0) { + generateEnemy() + generateEnemy() + } + } else if (gameCounter > 2500) { + $levelText.text("level 2") + enemyFrequency = 300 + if (gameCounter % enemyFrequency === 0) { + generateEnemy() + } + } else if (gameCounter < 2500) { + if (gameCounter % enemyFrequency === 0) { + generateEnemy() + } + } +} + +//function start ------------------------------------------------------------ +$(function() { + let gameStart = false + $pauseBtn.on("click", togglePause) + + //spawns the house + makeHouse() + + //displays intro screen + if (!gameStart) { + let currentFrame = 1 + introMusic.play() + let animatedStart = setInterval(function() { + if (currentFrame === 1 && !gameStart) { + $gameBoard.css({ + background: "url('./assets/images/introScreen3.png')", + backgroundSize: "103%" + }) + currentFrame = 2 + } else if (!gameStart) { + $gameBoard.css({ + background: "url('./assets/images/introScreen2.png')", + backgroundSize: "103%" + }) + currentFrame = 1 + } else { + clearInterval(animatedStart) + introMusic.pause() + } + }, 400) + } else { + clearInterval(animatedStart) + } + + //toggle pause + function togglePause() { + if (player.hp <= 0) return + if (pause === false) pause = true + else { + pause = false + requestAnimationFrame(update) + } + } + + //start game function. mapped to start button + const startGame = () => { + if (player.hp <= 0) return + + gameStart = true + if ($startGame) { + $startGame.remove() + } + if (pause === true) { + drawMap(1) + player.addChar() + generateUpgrade() + generateCat() + generateEnemy() + generateCow() + $levelText.text("Level 1") + gameCounter = 0 + pause = false + requestAnimationFrame(update) + } + } + + //restart game button + const restartGame = () => { + if (pause === false) { + return + } + for (key in enemyList) { + enemyList[key].removeChar() + } + for (key in bulletList) { + bulletList[key].removeChar() + } + for (key in upgradeList) { + upgradeList[key].removeChar() + } + for (key in petList) { + petList[key].removeChar() + } + + player.hp = 100 + house.hp = 100 + $houseHpBar.css({ + background: "#00ff00", + width: `${house.hp}px`, + border: "2px solid #00ff00" + }) + $houseHp.css({ + border: "2px solid black" + }) + score = 0 + gameCounter = 0 + enemyList = [] + bulletList = [] + upgradeList = [] + petList = [] + startGame() + } + + $startGame.on("click", startGame) //starts the game + $restartBtn.on("click", restartGame) //restarts the game + + //lets cat(s) meow + for (cat in petList) { + let catMeow = new Audio("./assets/audio/catMeow.mp3") + if (petList[cat].type === "cat") { + this.jTarget.on("click", catMeow.play()) + } + } + + //click shooting + $body.on("click", () => { + player.isShooting = true + let totalBulletsOnScreen = 0 + for (key in bulletList) { + totalBulletsOnScreen++ //counts bullets on screen + } + if (totalBulletsOnScreen < 10) { + player.shoot() + } + }) + + //for mouse hold shooting + // $body.on("mousedown", () => { + // player.isShooting = true + // }) + // $body.on("mouseup", () => { + // player.isShooting = false + // }) + // let intervalShootID = setInterval(() => { + // if (player.isShooting) { + // player.shoot() + // } + // // console.log("test") //to do: kill interval + // }, player.atkSpd) + + //enemy shooting + setInterval(() => { + if (pause === false) { + for (key in enemyList) { + enemyList[key].shoot() + } + } + //to do: clear interval after enemy delete + }, 2000) + + //update loop -------------------------------------------------------------- + const update = () => { + if (pause === true) return + if (gameStatus === "won") { + winScreen() + togglePause() + gameStatus = "playing" + } + + gameCounter++ // keeps track of time spent in game 60 fps + $score.text(`Score: ${Math.floor(score)}`) + player.moveChar() + fightMusic.play() + + //Temp solution to animate cow + if (gameCounter % 30 === 0) { + if ($cowDiv.attr("class") === "cow1") { + $cowDiv.attr("class", "cow2") + } else $cowDiv.attr("class", "cow1") + } + + spawnEnemy() + for (key in enemyList) { + enemyList[key].moveChar() + if (checkCollision(enemyList[key], player)) { + player.hp-- //player takes damage on collision + checkDead() + } + if (checkCollision(enemyList[key], house)) { + // + if ( + enemyList[key].x + enemyList[key].sizeX >= house.x && + enemyList[key].x <= house.x + house.sizeX + ) + enemyList[key].spdX *= -1 + if ( + enemyList[key].y + enemyList[key].sizeY >= house.y && + enemyList[key].y <= house.y + house.sizeY + ) { + enemyList[key].spdY *= -1 + } + } + } + + //moves pets + for (key in petList) { + petList[key].moveChar() + if (checkCollision(petList[key], player) && petList[key].type === "cat") { + catMeow.play() + // console.log('Mr Scruffington says "Get off my lawn, pesky farmer!"') + } + if (checkCollision(petList[key], house)) { + // + if ( + petList[key].x + petList[key].sizeX >= house.x && + petList[key].x <= house.x + house.sizeX + ) + petList[key].spdX *= -1 + if ( + petList[key].y + petList[key].sizeY >= house.y && + petList[key].y <= house.y + house.sizeY + ) { + petList[key].spdY *= -1 + } + } + } + + //moves upgrades (health packs) + for (key in upgradeList) { + upgradeList[key].moveChar() + if (checkCollision(upgradeList[key], player)) { + player.hp += 10 + // player.bulletMod++ (future development. Allows for more bullets) + upgradeList[key].removeChar() + upgradeList.splice(key, 1) + } + } + + //move bullets + for (key in bulletList) { + bulletList[key].moveChar() + bulletList[key].timer++ + if (bulletList[key].timer > 150) { + bulletList[key].removeChar() + bulletList.splice(key, 1) + continue + } + + //bullet collision with player + if ( + checkCollision(player, bulletList[key]) && + bulletList[key].bulletOwner === "enemy" + ) { + player.hp -= 10 //if player is hit + player.jTarget.css({ + filter: "grayscale(1)" + }) + setTimeout(function() { + player.jTarget.css({ + filter: "none" + }) + }, 200) + checkDead() + + bulletList[key].removeChar() + bulletList.splice(key, 1) + continue + } + //bullet collision with enemy + for (enemy in enemyList) { + if ( + checkCollision(enemyList[enemy], bulletList[key]) && + bulletList[key].bulletOwner === "player" + ) { + enemyList[enemy].hp -= 50 + enemyList[enemy].jTarget.css({ + filter: "grayscale(1)" + }) + setTimeout(function() { + if (enemyList[enemy]) { + enemyList[enemy].jTarget.css({ + filter: "none" + }) + } + }, 200) + + //bullet pushes the enemy when hit + if (bulletList[key].x < enemyList[enemy].x) { + enemyList[enemy].x += 5 + } else enemyList[enemy].x -= 5 + if (bulletList[key].y < enemyList[enemy].y + enemyList[enemy].sizeY) { + enemyList[enemy].y -= 5 + } else enemyList[enemy].y -= 5 + if (enemyList[enemy].hp <= 0) { + score += 100 + enemyList[enemy].removeChar() + enemyList.splice(enemy, 1) + } + bulletList[key].removeChar() + bulletList.splice(key, 1) + break + } + } + + //bullet collision with house (damages the house) + for (key in bulletList) { + if (checkCollision(bulletList[key], house)) { + bulletList[key].removeChar() + bulletList.splice(key, 1) + house.hp -= 5 + $houseHpBar.css({ + background: "#00ff00", + width: `${house.hp}px`, + border: "2px solid #00ff00" + }) + $houseHp.css({ + border: "2px solid black" + }) + checkDead() + } + } + } + + $hitpoint.text(`HP: ${player.hp}`) + if (pause === false) requestAnimationFrame(update) + } + + //60 fps + requestAnimationFrame(update) +}) + +//displays lose screen (to be updated) +const loseScreen = () => { + let $deadText = $('

') + let $deadScore = $('

') + $deadText.text(`You died. Score: ${score}`) + + $deadText.css({ + position: "absolute" + }) + + $deadScore.css({ + position: "absolute" + }) + + $loseDiv.append($deadText) + $loseDiv.append($deadScore) + $loseDiv.css({ + color: "white", + display: "flex", + "justify-content": "center" + }) + + $gameBoard.append($loseDiv) + $gameBoard.css({ + filter: "grayscale(1)" + }) +} + +//displays win screen +const winScreen = () => { + let $winText = $('

') + let $winScore = $('

') + $winText.text(`You won!`) + + $winText.css({ + position: "absolute" + }) + + $winScore.css({ + position: "absolute" + }) + + $winDiv.append($winText) + $winDiv.append($winScore) + $winDiv.css({ + color: "white", + display: "flex", + "justify-content": "center" + }) + + $gameBoard.append($winDiv) +} + +//to be updated, shows different map background per level +const drawMap = level => { + if (level === 1) { + //to do: update map base on level (spring -> summer -> autumn -> winter) + $gameBoard.css({ + background: `url("./assets/images/spring.png")`, + display: "flex", + "justify-content": "center", + "align-items": "center", + filter: "none" + }) + } + $loseDiv.empty() + $loseDiv.remove() + $winDiv.empty() + $winDiv.remove() +} + +//collision check between two objects +const checkCollision = (obj1, obj2) => + obj1.x <= obj2.x + obj2.sizeX && + obj2.x <= obj1.x + obj1.sizeX && + obj1.y <= obj2.y + obj2.sizeY && + obj2.y <= obj1.y + obj1.sizeY + +//check if player or house is dead +const checkDead = () => { + if (player.hp <= 0 || house.hp <= 0) { + pause = true + loseScreen() + } +} diff --git a/index.html b/index.html index 5b002212..d9893e67 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,41 @@ - + + - +
+
+
+
+

HP: 100

+
+
+

+
+
+

Score: 0

+
+
+
+
+

Start Game

+
+
+
+ +
+

Crops and Robbers

+

Avoid enemies and shoot them to reduce their number. Can you kill them before they destroy your farm?

+

Instructions

+
+ + +
+ +
+ +