This is the fourth part of a series (that began here). So far, we have a game where you can whizz around the screen avoiding trees. This effectively encompasses most aspects of a game of this type; that is, you can move, and there is a something preventing you.
The next step is to introduce something for the player to try to do, and give a score based on that. In our case, our player is going to try and collect cups – as per the original game. The asset I’ve created for the cup is here. For anyone following this series, you may have noticed that my artwork is a little… crap.
The source for this post is here.
Cups
The first thing that we’ve done here is added some code to place a few cups around the screen; the code for this was essentially the same as build obstacles:
placeCups() { let cups = []; const cupCount = 1; for (let i = 1; i <= cupCount; i++) { const centreX = Math.floor(Math.random() * this.state.windowWidth) + 1; const centreY = Math.floor(Math.random() * this.state.windowHeight) + 1; cups.push(<GameItem key={i} image={cupImg} centreX={centreX} centreY={centreY} width={this.spriteWidth} height={this.spriteHeight} itemType={2} />); } return cups; }
In a later post, I hope to do a full refactor, but for now, we have a separate function. This is rendered in the same way as the obstacles:
render() { return <div onKeyDown={this.onKeyDown} tabIndex="0"> <GameStatus Lives={this.state.playerLives} Message={this.state.message} Score={this.state.score} /> <Background backgroundImage={backgroundImg} windowWidth={this.state.windowWidth} windowHeight={this.state.windowHeight} /> <Car carImage={this.state.playerCrashed ? brokenCarImg : carImg} centreX={this.state.playerX} centreY={this.state.playerY} width={this.spriteWidth} height={this.spriteHeight} rotation={this.state.playerRotation} /> {this.obstacles} {this.cups} </div> }
Collecting cups
In order to collect something, the player must collide with it. We need to change the collision code slightly to make it a little more re-usable:
detectAnyCollision(rect1) { // Have we crashed or left the screen if (this.detectOutScreen(rect1)) { return true; } let collided = this.detectGameItemCollision(this.halfWidth, this.halfHeight, rect1, this.obstacles); if (collided !== undefined) { return true; } return false; } detectGameItemCollision(halfWidth, halfHeight, rect1, gameItemList) { const collided = gameItemList.find(a => { var rect2 = { x: a.props.centreX - halfWidth, y: a.props.centreY - halfHeight, width: this.spriteWidth, height: this.spriteHeight }; return (this.detectCollision(rect1, rect2)); }); return collided; }
As you can see, we now have a function that returns the item that we collided with, rather than a simple boolean. We then use this at the end of the game loop to determine whether we collided with a cup:
// Check for collected cup const item = this.detectGameItemCollision(this.halfWidth, this.halfHeight, rect1, this.cups); if (item !== undefined) { this.collectedCup(item.key); }
Score
There’s little point in zooming around collecting cups, if there’s no permanent record, so we need to add a score. Let’s start with a state variable in game.jsx:
this.state = { playerX: 100, playerY: 100, windowWidth: 1500, windowHeight: 1500, playerMomentum: 0, playerRotation: 0, playerVelocityX: 0, playerVelocityY: 0, playerLives: 3, playerCrashed: false, gameLoopActive: false, message: "", score: 0 };
And here’s the collectedCup function we mentioned a second ago:
collectedCup(key) { this.setState({ score: this.state.score + 1 }); this.cups = this.cups.filter(cup => cup.key != key); this.updateMessage("Collected cup"); }
All we’re doing here is simply updating the score and then removing that cup from the list.
The final part is to display the score on the screen; let’s have a look at our updated GameStatus.jsx:
function GameStatus(props) { const flexStyle = { display: 'flex', position: 'absolute', zIndex: 1, margin: 20, justifyContent: 'center', alignItems: 'center', width: '100%' }; const labelStyle = { zIndex: 1, margin: 50 }; return ( <div className="flex-container" style={flexStyle}> <label style={labelStyle}> Lives Remaining: {props.Lives} </label> <label style={labelStyle}> Score: {props.Score} </label> <label style={labelStyle}> {props.Message} </label> </div> ); }
As you can see, we’re just displaying the score as part of the status.
In the next post, we’ll have a look at the concept of levels, and introduce a time limit.