关于我仿做了个steam很火的《Helltaker》游戏

《Helltaker》游戏简单介绍

《Helltaker》这款steam游戏相信有很多热爱白毛御姐风格的玩家应该多少都玩过或者看过这个游戏视频的吧,还记得2020年的时候这款游戏是真相当火爆的(主要是音乐是真的魔性),每个关卡都有不同的恶魔,过关之后还需要和恶魔对话来攻略恶魔妹纸,当然游戏里除了恶魔其实还是有一个小天使的

Z0)MA}MRM7Q1YLSMG2YYVN7.png image.png image.png image.png

游戏玩法

其实游戏的核心玩法是非常简单的推箱子游戏的玩法。不过有区别的是它有玩家步数限制,需要你计算可移动步数来达到目标;在过关之后,你还需要去通过和地狱妹纸的对话来“攻略他” ps:一句话讲错攻略失败会导致主角直接GG。

image.png

游戏实现

好了叭叭了那么多,现在就开始讲如何实现游戏的吧,接下来拿出来一些主要游戏制作思路分析(最后会把所有代码放出来)

文件的基本目录

image.png

图片预先加载函数封装

先封装好一些待会要用到的函数,方便接下来的游戏制作~[看]

// 图片路径
let oImgs = {
    "img0": "images/block.png",
    "img1": "images/wall.png",
    "img2": "images/demonSprites/ball1_45x45.png",
    "img3": "images/box.png",
    "img4": "images/player/hero0022_45x45.png",
}
//                srcs: 传入图片的路径   callback: 传入带图片的函数
function imgPreload(srcs, callback) {
    let count = 0,
        imgNum = 0,
        images = {};
    for(src in srcs){
        imgNum++;
    }
    for(src in srcs){
        images[src] = new Image();
        images[src].onload = function() {
            // 等待所有路径完成
            if(++count >= imgNum) {
                callback(images);
            }
        }
        images[src].src = srcs[src];
    }
}
//   地板   墙体   玩家   箱子   恶魔(或许我该起名叫demon)
let block, wall, player, box, ball;

imgPreload(oImgs, function(images) {
    block = images.img0;
    wall = images.img1;
    box = images.img3;
    ball = images.img2;
    player = images.img4; 
});

地图渲染

图片加载好了现在就是想怎么绘制地图了,我的思路就是用数字来代表这个地图图片以及这个区域的类型然后写一个二维数组,y轴就是: levels[第?关卡][?], x轴就是: levels[第?关卡][?][?] 0:空地

1:墙体

2:恶魔

3:箱子

4:玩家

let levels=[];
// 关卡1
levels[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,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,0,0,0,0,0,1,1,1,1,0,0,0,0],
[0,0,0,0,0,1,1,1,1,0,4,1,0,0,0,0],
[0,0,0,0,0,1,5,0,3,0,0,1,0,0,0,0],
[0,0,0,0,1,1,0,0,0,3,1,1,0,0,0,0],
[0,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0],
[0,0,0,0,1,0,3,0,0,3,0,1,1,0,0,0],
[0,0,0,0,1,0,3,0,3,0,0,2,1,0,0,0],
[0,0,0,0,1,1,1,1,1,1,1,1,1,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,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,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,0,0,0,0,0]];

// 关卡2
levels[1]=[
[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,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,1,1,1,1,1,0,0,0,0,0,0,0],
[0,0,0,0,1,4,0,0,1,0,1,1,1,0,0,0],
[0,0,0,0,1,0,3,3,1,0,1,2,1,0,0,0],
[0,0,0,0,1,0,3,0,1,0,1,0,1,1,0,0],
[0,0,0,0,1,1,1,0,1,1,1,0,0,1,0,0],
[0,0,0,0,0,1,1,0,0,3,0,0,3,1,0,0],
[0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0],
[0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0],
[0,0,0,0,0,1,1,1,1,1,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,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,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]];

// 渲染地图函数
function drawMap(level){
    for(let i = 0; level.length > i;i++){
        for(let j = 0; level[i].length > j;j++){
            let thing = block;
            switch(level[i][j]){
                case 0:
                    thing=block
                break;
                case 1:
                    thing=wall
                break;
                case 2:
                    thing=ball
                break;
                case 3:
                    thing=box
                break;
                case 4:
                    thing=player
                break;
            }
            ctx.drawImage(
                thing,
                45 * j - (thing.width - 45) / 2, 45 * i - (thing.height - 45), 
                thing.width, thing.height
            )
        }
    }
}

写完之后大概就是这种效果出来之后就是这种效果 image.png

主要功能:移动,判断,推箱子和计算步数

按键判断

// 用于保存移动的方向
let dir = '';

// 按键点击事件
window.onkeydown = function(event){
    // 保存按下的按键的值
    let key = event.key;
    // 根据按下的按键判断到底怎么走
    switch(key.toLowerCase()){
        case "arrowleft":
            dir = 'left';
            // 调用函数移动判断的函数(接下来的代码片段里会有)
            person.move();
        break;
        case "arrowdown":
            dir = 'bottom';
            person.move();
        break;
        case "arrowright":
            dir = 'right';
            person.move();
        break;
        case "arrowup":
            dir = 'top';
            person.move();
        break;
    }
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    init();
}

步数判断

封装一个玩家所走的步数判断的函数

function step(){
    stepT -= 1;
    if(stepT === 0){
        alert("GameOver");
    }
    $(".count-left-text>span").text(stepT);
}

移动及移动后的事件

最后,移动很简单,基本谁便找都能找到相关移动事件的代码,所以就和移动后进行的判断一起放出来吧,代码已经注释好了

// 目前关卡数
let rankNum = 0;
class Person{
    constructor(canvas, i, j) {
        this.canvas = canvas;
        // 玩家初始x位置
        this.i = i;
        // 玩家初始y位置
        this.j = j;
    }
    move(){
        // 预先计算的x位置
        this.depI = 0;
        // 预先计算的y位置
        this.depJ = 0;
        // 通过按键得到上下左右的预先位置的数字类型
        if(dir === 'left'){
            this.dirGrid = [this.j, this.i - 1];
            this.dirMag = [this.j, this.i - 2];
            this.depI = -1;
        }else if(dir === 'bottom'){
            this.dirGrid = [this.j + 1, this.i];
            this.dirMag = [this.j + 2, this.i]
            this.depJ = 1;
        }else if(dir === 'right'){
            this.dirGrid = [this.j, this.i + 1]
            this.dirMag = [this.j, this.i + 2]
            this.depI = 1;
        }else if(dir === 'top'){
            this.dirGrid = [this.j - 1, this.i]
            this.dirMag = [this.j - 2, this.i]
            this.depJ = -1;
        }
        // 判断是否是空地
        if(levels[rankNum][this.dirGrid[0]][this.dirGrid[1]] === 0){
            levels[rankNum][this.j][this.i] = 0;
            levels[rankNum][this.dirGrid[0]][this.dirGrid[1]] = 4;
            this.j += this.depJ;
            this.i += this.depI;
            step();
        // 判断是否是箱子
        }else if(levels[rankNum][this.dirGrid[0]][this.dirGrid[1]] === 3){
            if(levels[rankNum][this.dirMag[0]][this.dirMag[1]] !== 1 && levels[rankNum][this.dirMag[0]][this.dirMag[1]] !== 3){
                setTimeout(() => {
                    levels[rankNum][this.dirMag[0]][this.dirMag[1]] = 3;
                    levels[rankNum][this.dirGrid[0]][this.dirGrid[1]] = 0;
                }, 300);
                step();
            }
        // 判断是否是恶魔
        }else if(levels[rankNum][this.dirGrid[0]][this.dirGrid[1]] === 2){
            $(".win").css({
                "display":"block"
            })
            step();
        }
    }
}

let person = new Person(canvas, 10, 4);

效果

image.png

最后的最后

以上就是主要的功能代码啦![庆祝]当然还有一些很简单的功能,比如完成这关之后的对话功能等等。

游戏截图

image.png

image.png

image.png

虽然是2020年的时候自己作的2关,但是如果大家喜欢的话,我将继续更新把所有恶魔都给做完[飞吻]

想要源文件的可以点赞后私信我领取[看]

点个赞再走呗,下次见喽!

MB[(7V3YB$YIXRG)5B$E3[9.gif

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

猜你喜欢

转载自juejin.im/post/7121497221899485221