网上定做衣服的网站,怎样申请网站,网页设计的理解,更换动易网站模板的方法文章目录 前言素材开始一、绘制地图二、玩家设置三、玩家移动四、玩家四方向动画运动切换 五、放置炸弹六、生成爆炸效果七、墙壁和可破坏障碍物的判断八、道具生成和效果九、玩家死亡十、简单的敌人AI十一、简单敌人AI十二、随机绘制地图十三、虚拟摇杆 最终效果待续源码完结 … 文章目录 前言素材开始一、绘制地图二、玩家设置三、玩家移动四、玩家四方向动画运动切换 五、放置炸弹六、生成爆炸效果七、墙壁和可破坏障碍物的判断八、道具生成和效果九、玩家死亡十、简单的敌人AI十一、简单敌人AI十二、随机绘制地图十三、虚拟摇杆 最终效果待续源码完结 前言
我们将在这个视频中学习如何在Unity中制作《炸弹人心》《炸弹人》是—个游戏系列最初于1983年7月在日本发行《炸弹人》的游戏玩法包括策略性地放炸弹在一定时间后以多个方向爆炸以摧毁障碍物和杀死敌人。
本文重点介绍了实现瓦片地图精灵动画的方法你可以用许多不同的方式自定义游戏 想出独特的游戏模式并建立自己的关卡。
先来看看实现的最终效果
素材 开始
一、绘制地图
使用tilemap绘制地图其实挺简单的这里我就不再介绍如何使用了节省大家时间tilemap还不会用的可以看我之前的tilemap文章【Unity小技巧】Unity2D TileMap的探究最简单最全面的TileMap使用介绍
绘制的最终效果你也可以按自己的喜欢绘制不同的地图
二、玩家设置
给玩家添加刚体和碰撞器重力设置为0注意添加圆形的碰撞器这样可以有效防止角色转弯时卡在墙角
新建2d物理材质设置角摩擦力和弹力为0防止角色卡墙 刚体和碰撞器都挂载刚才的2d物理材质
三、玩家移动
Player代码
public class Player : MonoBehaviour
{Rigidbody2D rb;Vector2 movement;private float horizontalInput;private float verticalInput;public float speed;//移动速度private void Start(){rb GetComponentRigidbody2D();}void Update(){ horizontalInput Input.GetAxisRaw(Horizontal);verticalInput Input.GetAxisRaw(Vertical);movement new Vector2(horizontalInput, verticalInput).normalized;}private void FixedUpdate(){//移动代码rb.MovePosition(rb.position movement * speed * Time.fixedDeltaTime);}
}运行效果
四、玩家四方向动画运动切换
这里我们使用2D混合动画实现2D Simple Directional混合动画的基础使用我之前有说过不懂得可以回去先看 零基础带你从小白到超神27——混合状态混合动画动画分类 Run混合动画配置down动画片段是默认人物站立面向观众 修改人物移动代码
//控制动画
animator.SetFloat(Horizontal, horizontalInput);
animator.SetFloat(Vertical, verticalInput);运行效果 问题 如果你的游戏对细节要求不高其实到这里就已经算完成了。 但是我本着严谨的态度会发现人物移动停止时都会面向屏幕也就是前面的down站立动画这显然不符合逻辑我们希望人物最终停止面向对应的位置
那么要如何做呢方法其实有很多最简单的方法呢就是在Run混合动画前面再加一个Idle混合动画 我们通过isRun参数来控制动画的切换 Run混合动画配置所有动画片段都是人物不同方向的站立动画
修改代码
//控制动画
if (horizontalInput 0 verticalInput 0){animator.SetBool(isRun, false);
} else {animator.SetBool(isRun, true);animator.SetFloat(Horizontal, horizontalInput);animator.SetFloat(Vertical, verticalInput);
}效果
五、放置炸弹
炸弹控制脚本
using System.Collections;
using UnityEngine;public class Bomb : MonoBehaviour
{[Header(Bomb)]private KeyCode inputKey KeyCode.Space; // 输入的按键public GameObject bombPrefab; // 炸弹预制体public float bombFuseTime 3f; // 炸弹引线时间public int bombAmount 1; // 炸弹数量private int bombsRemaining; // 剩余炸弹数量private void OnEnable(){bombsRemaining bombAmount; // 初始化剩余炸弹数量}private void Update(){if (bombsRemaining 0 Input.GetKeyDown(inputKey)) // 如果还有剩余炸弹且按下了指定按键{StartCoroutine(PlaceBomb()); // 放置炸弹}}private IEnumerator PlaceBomb(){Vector2 position transform.position; // 获取当前位置position.x Mathf.Round(position.x)-0.5f; // 四舍五入x坐标-0.5偏移量position.y Mathf.Round(position.y)-0.5f; // 四舍五入y坐标-0.5偏移量GameObject bomb Instantiate(bombPrefab, position, Quaternion.identity); // 实例化炸弹bombsRemaining--; // 剩余炸弹数量减一yield return new WaitForSeconds(bombFuseTime); // 等待炸弹引线时间Destroy(bomb.gameObject); // 销毁炸弹游戏对象bombsRemaining; // 剩余炸弹数量加一}//炸弹默认是触发器 角色离开时开启碰撞效果private void OnTriggerExit2D(Collider2D other){if (other.gameObject.layer LayerMask.NameToLayer(Bomb)) // 如果离开触发器的物体属于Bomb层{other.isTrigger false; // 取消触发器属性}}
}角色绑定脚本配置参数记得Bomb预制体开启触发器并指定图层为Bomb 运行效果
六、生成爆炸效果
新增爆炸效果代码
[Header(爆炸)]
public GameObject explosionEnd; // 爆炸结束
public GameObject explosionMiddle; // 爆炸中间
public GameObject explosionStart; // 爆炸结束
public int explosionRange;//爆炸范围//生成爆炸效果
public void createExplosion(Vector2 position)
{//爆炸中心GameObject explosionStartData Instantiate(explosionStart, position, Quaternion.identity);Destroy(explosionStartData, 0.5f);for (int i 1; i explosionRange; i){ClearDestructible(new Vector2(position.x i, position.y), i, 0);ClearDestructible(new Vector2(position.x - i, position.y), i, 180);ClearDestructible(new Vector2(position.x, position.y i), i, 90);ClearDestructible(new Vector2(position.x, position.y -i), i, -90);}
}private bool ClearDestructible(Vector2 position, int i, int rotate)
{//是不是最后爆炸区if (i explosionRange){GameObject explosionEndData Instantiate(explosionEnd, position, Quaternion.identity);//设置爆炸效果的方向explosionEndData.transform.eulerAngles new Vector3(0, 0, rotate);Destroy(explosionEndData, 0.5f);}else{GameObject explosionMiddleData Instantiate(explosionMiddle, position, Quaternion.identity);//设置爆炸效果的方向explosionMiddleData.transform.eulerAngles new Vector3(0, 0, rotate);Destroy(explosionMiddleData, 0.5f);}return true;
}效果
七、墙壁和可破坏障碍物的判断
修改代码
[Header(爆炸)]
public Tilemap wallTileMap; // 可破坏物墙壁的Tilemap组件
public GameObject explosionEnd; // 爆炸结束预制体
public GameObject explosionMiddle; // 爆炸中间预制体
public GameObject explosionStart; // 爆炸结束预制体
public GameObject brickWall;//破坏的墙
public int explosionRange;//爆炸范围
public LayerMask explosionLayerMask; // 墙壁层级//生成爆炸效果
public void createExplosion(Vector2 position)
{//爆炸中心GameObject explosionStartData Instantiate(explosionStart, position, Quaternion.identity);Destroy(explosionStartData, 0.5f);for (int i 1; i explosionRange; i){bool res ClearDestructible(new Vector2(position.x i, position.y), i, 0);if (!res) break;}for (int i 1; i explosionRange; i){bool res ClearDestructible(new Vector2(position.x - i, position.y), i, 180);if (!res) break;}for (int i 1; i explosionRange; i){bool res ClearDestructible(new Vector2(position.x, position.y i), i, 90);if (!res) break;}for (int i 1; i explosionRange; i){bool res ClearDestructible(new Vector2(position.x, position.y - i), i, -90);if (!res) break;}
}private bool ClearDestructible(Vector2 position, int i, int rotate)
{if (Physics2D.OverlapBox(position, new Vector2(0.5f, 0.5f), 0f, explosionLayerMask)) // 如果爆炸位置有墙壁{return false;}Vector3Int cell wallTileMap.WorldToCell(position); // 将世界坐标转换为Tilemap的单元格坐标TileBase tile wallTileMap.GetTile(cell); // 获取指定单元格的Tileif (tile ! null) // 如果爆炸位置有可破坏障碍物{wallTileMap.SetTile(cell, null); // 清除TileGameObject brickWallData Instantiate(brickWall, position, Quaternion.identity); // 实例化可破坏物体Destroy(brickWallData, 0.5f);return false;} else {//是不是最后爆炸区if (i explosionRange){GameObject explosionEndData Instantiate(explosionEnd, position, Quaternion.identity);//设置爆炸效果的方向explosionEndData.transform.eulerAngles new Vector3(0, 0, rotate);Destroy(explosionEndData, 0.5f);} else {GameObject explosionMiddleData Instantiate(explosionMiddle, position, Quaternion.identity);//设置爆炸效果的方向explosionMiddleData.transform.eulerAngles new Vector3(0, 0, rotate);Destroy(explosionMiddleData, 0.5f);}return true;}
}效果记得先设置和配置好墙壁的层级
八、道具生成和效果
新建破坏的墙脚本
public class BrickWall : MonoBehaviour
{public float destructionTime 1f;[Range(0f, 1f)]public float itemSpawnChance 0.2f;//生成道具的概率public GameObject[] spawnableItems;private void Start(){Destroy(gameObject, destructionTime);}//销毁时按比例生成道具private void OnDestroy(){if (spawnableItems.Length 0 Random.value itemSpawnChance){int randomIndex Random.Range(0, spawnableItems.Length);Instantiate(spawnableItems[randomIndex], transform.position, Quaternion.identity);}}
}新增道具代码
using UnityEngine;//道具代码
public class Prop : MonoBehaviour
{public enum ItemType{ExtraBomb,BlastRadius,SpeedIncrease,}public ItemType type;private void OnItemPickup(GameObject player){switch (type){case ItemType.ExtraBomb:player.GetComponentBomb().bombAmount; // 炸弹数量加一player.GetComponentBomb().bombsRemaining; // 剩余炸弹数量加一break;case ItemType.BlastRadius:player.GetComponentBomb().explosionRange;//爆炸范围增加break;case ItemType.SpeedIncrease:player.GetComponentPlayer().speed;//移动速度增加break;}Destroy(gameObject);}private void OnTriggerEnter2D(Collider2D other){if (other.CompareTag(Player)) {OnItemPickup(other.gameObject);}}
}挂载脚本和配置好道具参数这里为了测试方便我就把生成道具的概率先设置为1 效果
九、玩家死亡
角色添加代码
//检测碰撞 玩家死亡
private void OnTriggerEnter2D(Collider2D other)
{if (other.gameObject.layer LayerMask.NameToLayer(Explosion)) {animator.SetTrigger(isDeath);//播放死亡动画//TODO:结束游戏}
}效果
十、简单的敌人AI
实现敌人碰壁随机往其他可移动的方向移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyAI : MonoBehaviour
{private float speed 0.05f;private Rigidbody2D rig;private SpriteRenderer spriteRenderer;private Color color;/// summary/// 方向0上 1下 2左 3右/// /summaryprivate int dirId 0;private Vector2 dirVector;private float rayDistance 0.7f;private float x;private float y;private void Awake(){Physics2D.queriesStartInColliders false;spriteRenderer GetComponentSpriteRenderer();color spriteRenderer.color;rig GetComponentRigidbody2D();InitDir(Random.Range(0, 4));}private void Update(){//移动rig.MovePosition(rig.position dirVector * speed);}private void InitDir(int dir){Debug.Log(dir);dirId dir;switch (dirId){case 0:dirVector Vector2.up;//控制偏移量x Mathf.Round(rig.position.x) rig.position.x ? Mathf.Round(rig.position.x) 0.5f : Mathf.Round(rig.position.x) - 0.5f;transform.position new Vector2(x, transform.position.y);break;case 1:dirVector Vector2.down;x Mathf.Round(rig.position.x) rig.position.x ? Mathf.Round(rig.position.x) 0.5f : Mathf.Round(rig.position.x) - 0.5f;transform.position new Vector2(x, transform.position.y);break;case 2:dirVector Vector2.left;y Mathf.Round(rig.position.y) rig.position.y ? Mathf.Round(rig.position.y) 0.5f : Mathf.Round(rig.position.y) - 0.5f;transform.position new Vector2(transform.position.x, y);break;case 3:dirVector Vector2.right;y Mathf.Round(rig.position.y) rig.position.y ? Mathf.Round(rig.position.y) 0.5f : Mathf.Round(rig.position.y) - 0.5f;transform.position new Vector2(transform.position.x, y);break;default:break;}}private void ChangeDir(){Listint dirList new Listint();if (Physics2D.Raycast(transform.position, Vector2.up, rayDistance).collider null){dirList.Add(0);}if (Physics2D.Raycast(transform.position, Vector2.down, rayDistance).collider null){dirList.Add(1);}if (Physics2D.Raycast(transform.position, Vector2.left, rayDistance).collider null){dirList.Add(2);}if (Physics2D.Raycast(transform.position, Vector2.right, rayDistance).collider null){dirList.Add(3);}if (dirList.Count 0){int index Random.Range(0, dirList.Count);InitDir(dirList[index]);}}//画辅助线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));}//碰撞检测private void OnCollisionEnter2D(Collision2D collision){//碰到层级if (collision.gameObject.layer LayerMask.NameToLayer(Wall) || collision.gameObject.layer LayerMask.NameToLayer(BrickWall) || collision.gameObject.layer LayerMask.NameToLayer(Bomb)){ChangeDir();}}//检测敌人死亡private void OnTriggerEnter2D(Collider2D other){if (other.gameObject.layer LayerMask.NameToLayer(Explosion)){Destroy(gameObject);}}
}效果
十一、简单敌人AI
实现敌人随机移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyAI : MonoBehaviour
{private float speed 0.05f;private Rigidbody2D rig;private SpriteRenderer spriteRenderer;private Color color;/// summary/// 方向0上 1下 2左 3右/// /summaryprivate int dirId 0;private Vector2 dirVector;private float rayDistance 0.7f;private float x;private float y;private void Awake(){Physics2D.queriesStartInColliders false;spriteRenderer GetComponentSpriteRenderer();color spriteRenderer.color;rig GetComponentRigidbody2D();InitDir(Random.Range(0, 4));}private void Update(){if (GameApp.instance.enemyMove){//移动rig.MovePosition(rig.position dirVector * speed);}}private void InitDir(int dir){dirId dir;switch (dirId){case 0:dirVector Vector2.up;//控制偏移量x Mathf.Round(rig.position.x) rig.position.x ? Mathf.Round(rig.position.x) 0.5f : Mathf.Round(rig.position.x) - 0.5f;transform.position new Vector2(x, transform.position.y);break;case 1:dirVector Vector2.down;x Mathf.Round(rig.position.x) rig.position.x ? Mathf.Round(rig.position.x) 0.5f : Mathf.Round(rig.position.x) - 0.5f;transform.position new Vector2(x, transform.position.y);break;case 2:dirVector Vector2.left;y Mathf.Round(rig.position.y) rig.position.y ? Mathf.Round(rig.position.y) 0.5f : Mathf.Round(rig.position.y) - 0.5f;transform.position new Vector2(transform.position.x, y);break;case 3:dirVector Vector2.right;y Mathf.Round(rig.position.y) rig.position.y ? Mathf.Round(rig.position.y) 0.5f : Mathf.Round(rig.position.y) - 0.5f;transform.position new Vector2(transform.position.x, y);break;default:break;}}private void ChangeDir(){Listint dirList new Listint();if (Physics2D.Raycast(transform.position, Vector2.up, rayDistance).collider null){dirList.Add(0);}if (Physics2D.Raycast(transform.position, Vector2.down, rayDistance).collider null){dirList.Add(1);}if (Physics2D.Raycast(transform.position, Vector2.left, rayDistance).collider null){dirList.Add(2);}if (Physics2D.Raycast(transform.position, Vector2.right, rayDistance).collider null){dirList.Add(3);}if (dirList.Count 0){int index Random.Range(0, dirList.Count);InitDir(dirList[index]);}}//画辅助线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));}//碰撞检测private void OnCollisionEnter2D(Collision2D collision){//碰到层级if (collision.gameObject.layer LayerMask.NameToLayer(Wall) || collision.gameObject.layer LayerMask.NameToLayer(BrickWall) || collision.gameObject.layer LayerMask.NameToLayer(Bomb)){ChangeDir();}}//检测敌人死亡private void OnTriggerEnter2D(Collider2D other){//碰到炸弹层级换方向if (other.gameObject.layer LayerMask.NameToLayer(Bomb)){ChangeDir();}if (other.gameObject.layer LayerMask.NameToLayer(Explosion)){GameApp.instance.deathEnemyCount;GameApp.instance.score;Destroy(gameObject);}}
}效果
十二、随机绘制地图
using System.Globalization;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UIElements;
using System.Collections.Generic;
using System.Collections;
//随机生成可破坏瓦片
public class MapController : MonoBehaviour
{public static MapController instance;[Range(0f, 1f)]public float itemSpawnChance 0.2f;//生成瓦片的概率public Tilemap map;// 瓦片资源基类通过它可以得到瓦片资源public TileBase tileBase;private ListVector3 emptyPosition;//保存所有的空位private ListVector3 wallPosition;//保存所有的可破坏墙的位置public GameObject enemy;//怪物预制体public GameObject door;//门预制体public GameObject playerData;//主角预制体private int mapWidth;private int mapHeight;private void Awake(){if (instance null){instance this;}else{if (instance ! this){Destroy(gameObject);}}DontDestroyOnLoad(gameObject);emptyPosition new ListVector3();wallPosition new ListVector3();mapWidth 16;mapHeight 8;}public void Init(){// 1.清空瓦片地图map.ClearAllTiles();for (int i 0; i mapWidth; i){for (int j 0; j mapHeight; j){//跳过砖块if (i % 2 ! 0 j % 2 ! 0){continue;}Vector3 position new Vector3(-7.5f i, 3.5f - j, 0);Vector3Int cell map.WorldToCell(position); // 将世界坐标转换为Tilemap的单元格坐标if (Random.value itemSpawnChance){map.SetTile(cell, tileBase); // 设置wallPosition.Add(position);}else{emptyPosition.Add(position);}}}addPlayer();addEnemy();addDoor();//清空地图的炸弹GameObject[] res GameObject.FindGameObjectsWithTag(Bomb);foreach (GameObject item in res){Destroy(item);}//清空地图的道具res GameObject.FindGameObjectsWithTag(Props);foreach (GameObject item in res){Destroy(item);}}//生成主角private void addPlayer(){GameObject res GameObject.Find(Player(Clone));if (res ! null){Destroy(res);}GameObject player Instantiate(playerData);//生成在左下角player.transform.position new Vector2(-7.5f, -4.5f);//去除相邻的mapmap.SetTile(map.WorldToCell(new Vector2(-7.5f, -4.5f)), null);map.SetTile(map.WorldToCell(new Vector2(-6.5f, -4.5f)), null);map.SetTile(map.WorldToCell(new Vector2(-7.5f, -3.5f)), null);emptyPosition.Remove(new Vector3(-7.5f, -4.5f, 0));emptyPosition.Remove(new Vector3(-6.5f, -4.5f, 0));emptyPosition.Remove(new Vector3(-7.5f, -3.5f, 0));}//生成怪物private void addEnemy(){//清空地图上的所有敌人GameObject[] res GameObject.FindGameObjectsWithTag(Enemy);foreach (GameObject item in res){Destroy(item);}if (emptyPosition.Count 0){for (int i 0; i GameApp.instance.enemyCount; i){int index Random.Range(0, emptyPosition.Count);Instantiate(enemy, emptyPosition[index], Quaternion.identity);}}}
}
效果每次进来地图都不一样
十三、虚拟摇杆
引入虚拟摇杆实现玩家移动不懂的可以看我这篇文章3种实现虚拟移动摇杆控制人物移动的方法
效果
最终效果 待续
后面还准备了一些内容有空再补充包括音频管理器、随机生成地图和敌人、通关门效果、主角生命值游戏开始和结束UI界面关卡选择界面、保存主角属性到下一关、更多的敌人可能加入boss、优化代码架构、完善虚拟摇杆、发布游戏到微信小游戏
源码
后面整理好我会放上来
完结
赠人玫瑰手有余香如果文章内容对你有所帮助请不要吝啬你的点赞评论和关注以便我第一时间收到反馈你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法也欢迎评论私信告诉我哦
好了我是向宇https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者出于兴趣爱好于是最近才开始自习unity。如果你遇到任何问题也欢迎你评论私信找我 虽然有些问题我可能也不一定会但是我会查阅各方资料争取给出最好的建议希望可以帮助更多想学编程的人共勉~