大家小时候肯定玩过这款游戏,炸弹人也算是经典中的经典啦~
希望看到这篇小游戏,可以让你重拾童年跟小伙伴一起对着大屁股电视机玩游戏的美好时光!
时间在慢慢的流逝,那些陪你一起度过童年的小伙伴有多久没联系了呢~
看完这篇炸弹人,有时间的话就找自己童年的小伙伴们聊会天吧,一起找回童年的回忆和梦想!
回归主题,炸弹人小游戏制作开始!
来看一下炸弹人小游戏的效果吧!
老规矩,做之前我们先来整一下做这个小游戏的思路 让我们动一下脑袋瓜想一下一个炸弹人小游戏里面都有什么东西呢
首先要有一个游戏场景,这个场景就是我们在游戏运行的时候,我们可以看到的地方这个场景中会有许多墙体,其中四周会有一个游戏边缘墙体,这些墙体是无法被我们的炸弹毁掉的,称他为超级墙体!场景里面也会有一些墙体,可以被摧毁,我们成为普通墙体~有些是固定的,有些是可被摧毁的,这就是一个经典的炸弹人玩法了!其次,我们要有一个主角,就是我们的炸弹人!我们的主角可以上下左右移动,然后还可以”下蛋”,就是放炸弹,炸敌人然后还要有血量等等当然少不了敌人了,我们给场景中加入一个可以随机左右移动的敌人,碰到我们之后就会让我们掉血这也是一个最经典而且基础的玩法啦~乍一想好像也就这么点东西,也不是很难的样子
那我们现在就开始动手操作吧!
其中有一些精灵图片素材,为我们做主角、敌人和墙体时候使用
还有几个简单的声音特效和动画特效,为我们的游戏制作提供后勤支援!
上代码:
public enum objecttype{ superwall, wall, prop, bomb, enemy, bombeffect}[system.rializable]public class type_prefab{ public objecttype type; public gameobject prefab;}public class objectpool : monobehaviour{ public static objectpool instance; public list<type_prefab> type_prefabs = new list<type_prefab>(); /// <summary> /// 通过物体类型获取该预制体 /// </summary> /// <param name="type"></param> /// <returns></returns> private gameobject getprebytype(objecttype type) { foreach (var item in type_prefabs) { if (item.type == type) return item.prefab; } return null; } /// <summary> /// 物体类型和对应的对象池关系字典 /// </summary> private dictionary<objecttype, list<gameobject>> dic = new dictionary<objecttype, list<gameobject>>(); private void awake() { instance = this; } /// <summary> /// 通过物体类型从相对应的对象池中取东西 /// </summary> /// <param name="type"></param> /// <returns></returns> public gameobject get(objecttype type, vector2 pos) { gameobject temp = null; //判断字典中有没有与该类型匹配的对象池,没有则创建 if (dic.containskey(type) == fal) dic.add(type, new list<gameobject>()); //判断该类型对象池中有没有物体 if (dic[type].count > 0) { int index = dic[type].count - 1; temp = dic[t唐诗三百首视频ype][index]; dic[type].removeat(index); } el { gameobject pre = getprebytype(type); if (pre != null) { temp = instantiate(pre, transform); } } temp.tactive(true); temp.transform.position = pos; temp.transform.rotation = quaternion.identity; return temp; } /// <summary> /// 回收 /// </summary> /// <param name="type"></param> public void add(objecttype type, gameobject go) { //判断该类型是否有对应的对象池以及对象池中不存在该物体 if (dic.containskey(type) && dic[type].contains(go) == fal) { //放入对象池 dic[type].add(go); } go.tactive(fal); }}有了这个简单的对象池之后,我们再写一个脚本mapcontroller来生成场景中的一些墙体通过两个二维向量列表来生成普通墙体和超级墙体
我们需要给预制体标记不同的tag用于区分它们各自的属性
将以下预制体都添加上,只有墙体需要添加layer层,后面在怪物随机移动时会用到,其他的只需要添加tag即可
上代码:
public class mapcontroller : monobehaviour{ public gameobject doorpre; public int x, y; private list<vector2> nu中秋节搞笑图片llpointslist = new list<vector2>(); private list<vector2> superwallpointlist = new list<vector2>(); private gameobject door; //表示从对象池中取出来的所有物体集合 private dictionary<objecttype, list<gameobject>> poolobjectdic = new dictionary<objecttype, list<gameobject>>(); /// <summary> /// 判断当前位置是否是实体墙 /// </summary> /// <param name="pos"></param> /// <returns></returns> public bool issuperwall(vector2 pos) { if (superwallpointlist.contains(pos)) return true; return fal; } public vector2 getplayerpos() { return new vector2(-(x + 1), (y - 1)); } private void recovery() { nullpointslist.clear(); superwallpointlist.clear(); foreach (var item in poolobjectdic) { foreach (var obj in item.value) { objectpool.instance.add(item.key, obj); } } poolobjectdic.clear(); } public void initmap(int x, int y, int wallcount, int enemycount) { recovery(); x = x; y = y; createsuperwall(); findnullpoints(); createwall(wallcount); createdoor(); createprops(); createenemy(enemycount); } /// <summary> /// 生成实体墙 /// </summary> private void createsuperwall() { for (int x = -x; x < x; x+=2) { for (int y = -y; y < y; y+=2) { spawnsuperwall(new vector2(x, y)); } } for (int x = -(x + 2); x <= x; x++) { spawnsuperwall(new vector2(x, y)); spawnsuperwall(new vector2(x, -(y + 2))); } for (int y = -(y + 1); y <= y-1; y++) { spawnsuperwall(new vector2(-(x + 2), y)); spawnsuperwall(new vector2(x, y)); } } private void spawnsuperwall(vector2 pos) { superwallpointlist.add(pos); gameobject superwall = objectpool.instance.get(objecttype.superwall, pos); if (poolobjectdic.containskey(objecttype.superwall) == fal) poolobjectdic.add(objecttype.superwall, new list<gameobject>()); poolobjectdic[objecttype.superwall].add(superwall); } /// <summary> /// 查找地图中所有的空点 /// </summary> private void findnullpoints() { for (int x = -(x + 1); x <= (x -1); x++) { if (-(x + 1) % 2 == x % 2) for (int y = -(y + 1); y <= (y - 1); y++) { nullpointslist.add(new vector2(x, y)); } el for (int y = -(y + 1); y <= (y - 1); y += 2) { nullpointslist.add(new vector2(x, y)); } } nullpointslist.remove(new vector2(-(x + 1), (y - 1))); //将左上角第一个位置空出来,用来生成炸弹人(出生点) nullpointslist.remove(new vector2(-(x + 1), (y - 2))); //左上角第一个位置下面的位置,保证炸弹人能出来,不被自己炸死 nullpointslist.remove(new vector2(-x, (y - 1))); //左上角第一个位置右边的位置,保证炸弹人能出来,不被自己炸死 } /// <summary> /// 创建可以销毁的墙 /// </summary> pri青岛最好的大学vate void createwall(int wallcount) { if (wallcount >= nullpointslist.count) wallcount = (int)(nullpointslist.count * 0.7f); for (int i = 0; i < wallcount; i++) { int index = random.range(0, nullpointslist.count); gameobject wall = objectpool.instance.get(objecttype.wall, nullpointslist[index]); nullpointslist.removeat(index); if (poolobjectdic.containskey(objecttype.wall) == fal) poolobjectdic.add(objecttype.wall, new list<gameobject>()); poolobjectdic[objecttype.wall].add(wall); } } private void createprops() { int count = random.range(0, 2 + (int)(nullpointslist.count * 0.05f)); for (int i = 0; i < count; i++) { int index = random.range(0, nullpointslist.count); gameobject prop = objectpool.instance.get(objecttype.prop, nullpointslist[index]); nullpointslist.removeat(index); if (poolobjectdic.containskey(objecttype.prop) == fal) poolobjectdic.add(objecttype.prop, new list<gameobject>()); poolobjectdic[objecttype.prop].add(prop); } }}该脚本中,通过使用二维向量列表来生成墙体,并且生成之前判断当前位置是否已经有物体存在在一初始化地图的时候,先将列表清空,再执行其他操作然后我们新建一个gamecontroller物体并挂载上gamecontroller脚本该脚本就是后面需要的游戏控制器,但是我们现在只让他生成游戏地图
上代码:
/// <summary> /// 关卡控制器 /// </summary> private void levelctrl() { time = levelcount * 50 + 130; int x = 6 + 2 * (levelcount / 3); int y = 3 + 2 * (levelcount / 3); //每3关增加2个 if (x > 18) x = 18; if (y > 15) y = 15; enemycount = (int)(levelcount * 1.5f) + 1; if (enemycount > 40) enemycount = 40; mapcontroller.initmap(x, y, x * y, enemycount); if (player == null) { player = instantiate(playerpre); playerctrl = player.getcomponent<playerctrl>(); playerctrl.init(1, 3, 2); } playerctrl.retplayer(); player.transform.position = mapcontroller.getplayerpos(); //回收场景中残留的爆炸特效 gameobject[] effects = gameobject.findgameobjectswithtag(tags.bombeffect); foreach (var item in effects) { objectpool.instance.add(objecttype.bombeffect, item); } camera.main.getcomponent<camerafollow>().init(player.transform, x, y); levelcount++; uicontroller.instance.playlevelfade(levelcount); } public bool issuperwall(vector2 pos) { return mapcontroller.issuperwall(pos); }
一个简单地图随机生成后是这样的~
比如普通墙体身上的脚本wall代码:
public class wall : monobehaviour{ private void ontriggerenter2d(collider2d collision) { if(collision.comparetag(tags.bombeffect)) { objectpool.instance.add(objecttype.wall, gameobject); } }}门door身上的脚本,这个还有些特殊,因为他一开始是墙体,被我们用炸弹炸掉之后会变成通往下一关的门~这也是炸弹人的经典玩法啦!
看一下door脚本代码!
public sprite doorsprite,defaultsp; private spriterenderer spriterenderer; private void awake() { spriterenderer = getcomponent<spriterenderer>(); defaultsp = spriterenderer.sprite; } public void retdoor() { tag = "wall"; gameobject.layer = 8; spriterenderer.sprite = defaultsp; getcomponent<collider2d>().istrigger = fal; } private void ontriggerenter2d(collider2d collision) { if (collision.comparetag(tags.bombeffect)) { tag = "untagged"; gameobject.layer = 0; spriterenderer.sprite = doorsprite; getcomponent<collider2d>().istrigger = true; } if (collision.comparetag(tags.player)) { //判断当前场景中的敌人是否都消灭了 gamecontroller.instance.loadnextlevel(); } }
上脚本playerctrl代码
/// <summary> /// 移动方法 /// </summary> private void move() { float h = input.get完形填空解题技巧axis("horizontal"); float v = input.getaxis("vertical"); anim.tfloat("horizontal", h); anim.tfloat("vertical", v); rig.moveposition(transform.position + new vector3(h, v) * speed); } private void createbomb() { if (input.getkeydown(keycode.space) && bombcount > 0) { audiocontroller.instance.playfire(); bombcount--; gameobject bomb = objectpool.instance.get(objecttype.bomb, new vector3(mathf.roundtoint(transform.position.x), mathf.roundtoint(transform.position.y))); bomb.getcomponent<bomb>().init(range, bombboomtime, () => { bombcount++; bomblist.remove(bomb); }); bomblist.add(bomb); } }
然后炸弹人身上还有一个动画控制器,用于炸弹人上下左右移动时分别播放不同的动画
资源包中动画片段都有,我们来设置上就好了,很简单的动画片段执行
动画片段切换时的效果:
一个场景中简单的移动效果:
还有角色死亡时播放动画的方法代码
/// <summary> /// 播放结束动画 /// </summary> public void playdieanim() { time.timescale = 0; anim.ttrigger("die"); } /// <summary> /// 结束动画播放完毕 /// </summary> private void dieanimfinish() { gamecontroller.instance.gameover(); }
死亡动画效果:
炸弹身上有一个脚本bomb,初始化方法init在playerctrl中炸弹人丢炸弹的时候被调用! 脚本中的dealyboom方法用于处理被我们的炸弹人丢出来以后检阅四周可爆炸的范围~
然后炸弹爆炸后也有一个预制体,我们也需要在上面挂载一个脚本,让他在炸弹爆炸后执行一个爆炸效果!
上脚本bomb和bombeffect:
public class bomb : monobehaviour{ private int range; private action anifinaction; public void init(int range, float dealytime, action action) { this.range = range; startcoroutine("dealyboom", dealytime); anifinaction = action; } ienumerator dealyboom(float time) { yield return new waitforconds(time); if(anifinaction != null) anifinaction(); audiocontroller.instance.playboom(); objectpool.instance.get(objecttype.bombeffect, transform.position); boom(vector2.left); boom(vector2.right); boom(vector2.down); boom(vector2.up); objectpool.instance.add(objecttype.bomb, gameobject); } private void boom(vector2 dir) { for (int i = 1; i <= range; i++) { vector2 pos = (vector2)transform.position + dir * i; if (gamecontroller.instance.issuperwall(pos)) break; objectpool.instance.get(objecttype.bombeffect, pos); } }}
public class bombeffect : monobehaviour{ private animator anim; private void awake() { anim = getcomponent<animator>(); } private void update() { animatorstateinfo info = anim.getcurrentanimatorstateinfo(0); if (info.normalizedtime >= 1 && info.isname("bombeffect")) { objectpool.instance.add(objecttype.bombeffect, gameobject); } }}
生成敌人代码
private void createenemy(int count) { for (int i = 0; i < count; i++) { int index = random.range(0, nullpointslist.count); gameobject enemy = objectpool.instance.get(objecttype.enemy, nullpointslist[index]); enemy.getcomponent<enemyai>().init(); nullpointslist.removeat(index); if (poolobjectdic.containskey(objecttype.enemy) == fal) poolobjectdic.add(objecttype.enemy, new list<gameobject>()); poolobjectdic[objecttype.enemy].add(enemy); } }然后敌人生成以后还要可以自由移动,然后寻找我们的炸弹人,只要碰到我们的炸弹人,炸弹人就会受到伤害这里需要注意的细节还是挺多的,首先我们需要让他上下左右随机移动移动是通过射线检测来判断的,这里我们给场景中的墙体的layer设置成8层然后怪物在检测的时候,只检测第八层的物体来判断自身是否可以向该方向移动还要处理敌人在碰到炸弹人和他们的同类时,会改变自身的颜色,这样会有一个简单的视觉交互效果
上脚本enemyai脚本代码
public class enemyai : monobehaviour{ private float speed = 0.04f; private rigidbody2d rig; private spriterenderer spriterenderer; private color color; /// <summary> /// 方向:0上 1下 2左 3右 /// </summary> private int dirid = 0; private vector2 dirvector; private float raydistance = 0.7f; private bool canmove = true; //是否可以移动 private void awake() { spriterenderer = getcomponent<spriterenderer>(); color = spriterenderer.color; rig = getcomponent<rigidbody2d>(); } /// <summary> /// 初始化方法 /// </summary> public void init() { color.a = 1; //当敌人穿过后离开时,恢复之前颜色 spriterenderer.color = color; canmove = true; initdir(random.range(0, 4)); } /// <summary> /// 初始化敌人方向 /// </summary> /// <param name="dir"></param> private void initdir(int dir) { dirid = dir; switch (dirid) { ca 0: dirvector = vector2.up; break; ca 1: dirvector = vector2.down; break; ca 2: dirvector = vector2.left; break; ca 3: dirvector = vector2.right; break; default: break; } } private void update() { if (canmove) rig.moveposition((vector2)transform.position + dirvector * speed); el changedir(); } private void ontriggerenter2d(collider2d collision) { //消灭敌人 if(collision.comparetag(tags.bombeffect) && gameobject.activelf) { gamecontroller.instance.enemycount--; objectpool.instance.add(objecttype.enemy, gameobject); } if (collision.comparetag(tags.enemy)) { color.a = 0.3f; //当敌人相互穿过时,改变其颜色为半透明 spriterenderer.color = color; } if (collision.comparetag(tags.superwall) || collision.comparetag(tags.wall)) { //复位 transform.position = new vector2(mathf.roundtoint(transform.position.x), mathf.roundtoint(transform.position.y)); //roundtoint取整 changedir(); } } private void ontriggerstay2d(collider2d collision) { if (collision.comparetag(tags.enemy)) { color.a = 0.3f; //当敌人在一块时,改变其颜色为半透明 spriterenderer.color = color; } } private void ontriggerexit2d(collider2d collision) { if (collision.comparetag(tags.enemy)) { color.a = 1; //当敌人穿过后离开时,恢复之前颜色 spriterenderer.color = color; } } private void changedir() { list<int> dirlist = new list<int>(); if (physics2d.raycast(transform.position, vector2.up, raydistance, 1 << 8) == fal) //1左移8,表示只检测第8层(添加 layer)。 若是 0 << 8 则表示忽略第8层 { dirlist.add(0); //如果上方没有检测到物体就向上方移动 } if (physics2d.raycast(transform.position, vector2.down, raydistance, 1 << 8) == fal) { dirlist.add(1); } if (physics2d.raycast(transform.position, vector2.left, raydistance, 1 << 8) == fal) { dirlist.add(2); } if (physics2d.raycast(transform.position, vector2.right, raydistance, 1 << 8) == fal) { dirlist.add(3); } if (dirlist.count > 0) { canmove = true; int index = random.range(0, dirlist.count); initdir(dirlist[index]); } el canmove = fal; } private void ondrawgizmos() { gizmos.color = color.red; gizmos.drawline(transform.position, transform.position + new vector3(0, raydistance, 0)); gizmos.color = color.blue; gizmos.drawline(transform.position, transform.position + new vector3(0, -raydistance, 0)); gizmos.drawline(transform.position, transform.position + new vector3(-raydistance, 0, 0)); gizmos.drawline(transform.position, transform.position + new vector3(raydistance, 0, 0)); }
怪物自动移动效果:
终于到了游戏控制器这一步啦~
细心地小伙伴可能发现了,从开头到现在大部分都是代码
因为这个小游戏在引擎操作的步骤真的很少,大多数都在脚本上进行的逻辑编写,所以本篇文章可能有些枯燥~
如果说上面的步骤已经将游戏大概玩法写完了,那这一步则是最为重要的游戏控制器的编写这个游戏中的游戏控制器,用于控制一个游戏的进行如果说没有游戏控制器,那就相当于一个没有游戏规则的游戏demo有了游戏控制器才算是一个制定游戏规则的人,才能让游戏有条不紊的进行下去!那就来搞一下我们这个游戏控制器吧!
我们通过游戏控制器给这个炸弹人小游戏设置关卡
还有一个关卡计数器,判断下一关的进行,和更新地图和怪物!
最后还要有一个游戏结束界面,在炸弹人三条命都用完的时候触发结束界面~
好了,大体思路 就是这样了
上gamecontroller脚本代码看一下:
/// <summary> /// 关卡计时器 /// </summary> private void leveltimer() { //时间用完了,游戏结束 if (time <= 0) { if (playerctrl.hp > 0) { playerctrl.hp--; //用生命换时间 time = 200; return; } playerctrl.playdieanim(); return; } timer += time.deltatime; if (timer >= 1.0f) { time--; timer = 0; } } /// <summary> /// 游戏结束 /// </summary> public void gameover() { uicontroller.instance.showgameoverpanel(); //显示游戏结束界面 } private void update() { leveltimer(); // uicontroller.instance.refresh(playerctrl.hp, levelcount, time, enemycount); } /// <summary> /// 加载下一关 /// </summary> public void loadnextlevel() { if (enemycount <= 0) levelctrl(); } /// <summary> /// 关卡控制器 /// </summary> private void levelctrl() { time = levelcount * 50 + 130; int x = 6 + 2 * (levelcount / 3); int y = 3 + 2 * (levelcount / 3); //每3关增加2个 if (x > 18) x = 18; if (y > 15) y = 15; enemycount = (int)(levelcount * 1.5f) + 1; if (enemycount > 40) enemycount = 40; mapcontroller.initmap(x, y, x * y, enemycount); if (player == null) { player = instantiate(playerpre); playerctrl = player.getcomponent<playerctrl>(); playerctrl.init(1, 3, 2); } playerctrl.retplayer(); player.transform.position = mapcontroller.getplayerpos(); //回收场景中残留的爆炸特效 gameobject[] effects = gameobject.findgameobjectswithtag(tags.bombeffect); foreach (var item in effects) { objectpool.instance.add(objecttype.bombeffect, item); } camera.main.getcomponent<camerafollow>().init(player.transform, x, y); levelcount++; uicontroller.instance.playlevelfade(levelcount); } public bool issuperwall(vector2 pos) { return mapcontroller.issuperwall(pos); }
例如第一关的话就是这样的
上代码看一下:
private void init() { gameoverpanel.transform.find("btn_again").getcomponent<button>().onclick.addlistener(() => { time.timescale = 1; //重新加载当前正在运行的场景 scenemanager.loadscene(scenemanager.getactivescene().buildindex); }); gameoverpanel.transform.find("btn_main").getcomponent<button>().onclick.addlistener(() => { time.timescale = 1; scenemanager.loadscene("start"); }); } public void refresh(int hp, int level, int time, int enemy) { txt_hp.text = "hp:" + hp.tostring(); txt_level.text = "level:" + level.tostring(); txt_time.text = "time:" + time.tostring(); txt_enemy.text = "enemy:" + enemy.tostring(); } public void showgameoverpanel() { gameoverpanel.tactive(tlol光辉女郎出装rue); } /// <summary> /// 播放关卡提示动画 /// </summary> /// <param name="levelindex"></param> public void playlevelfade(int levelindex) { time.timescale = 0; levelfadeanim.transform.find("txt_level").getcomponent<text>().text = "level " + levelindex.tostring(); levelfadeanim.play("levelfade", 0, 0); startdelay = true; } private bool startdelay = fal; private float timer = 0; private void update() { if (startdelay) { timer += time.unscaleddeltatime; if (timer > 0.7f) { startdelay = fal; time.timescale = 1; timer = 0; } } }
以上就是unity游戏开发之炸弹人游戏的实现的详细内容,更多关于unity炸弹人游戏的资料请关注www.887551.com其它相关文章!
本文发布于:2023-04-06 04:39:51,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/2007ae2c3fa27804a9ee927a48041e26.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:Unity游戏开发之炸弹人游戏的实现.doc
本文 PDF 下载地址:Unity游戏开发之炸弹人游戏的实现.pdf
留言与评论(共有 0 条评论) |