Unity游戏实战项目-英雄无敌

news/2024/11/24 3:26:27/

在这里插入图片描述

敌人模块

在这里插入图片描述
在这里插入图片描述

策划

在这里插入图片描述

需求分析

在这里插入图片描述

EnemyMotor.cs

在这里插入图片描述

EnemyMotor.cs
注意:
1.transform.LookAt(targetPos) 表示当该物体设置了Lookat并指定了目标物体时,该物体的z轴将始终指向目标物体,所以MovementForward() 函数只用改变Z坐标即可,而且此处使用Time.deltaTime,是由于MovementForward()会在Pathfinding()中调用,而Pathfinding()将会在EnemyAI中的Update中调用,这使得敌人运动可以保证是恒速。
2.由于前期没有写WayLIne类,所以需要测试代码,测试子功能是否正确,注释掉部分是测试该功能的代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 敌人马达 提供移动、旋转、寻路的功能
/// </summary>
public class EnemyMotor : MonoBehaviour 
{//临时创建路点 用于测试//public Transform[] points;//创建线路public WayLine line;//速度public float Speed = 5;//移动public void MovementForward(){transform.Translate(0, 0, Speed * Time.deltaTime);}//朝着路点旋转public void LookRotation(Vector3 targetPos){transform.LookAt(targetPos);}private int currentIndex;public bool Pathfinding(){//寻路结束if (currentIndex >= line.Points.Length || line.Points == null)return false;LookRotation(line.Points[currentIndex]);MovementForward();//到达路点后,索引值++if (Vector3.Distance(this.transform.position, line.Points[currentIndex]) <= 0.1f)currentIndex++;//临时创建路点 用于测试//if (currentIndex >= points.Length || points == null)//    return false;//LookRotation(points[currentIndex].position);//MovementForward();//if(Vector3.Distance(this.transform.position, points[currentIndex].position) <= 0.1f)//currentIndex++;return true;}}

EnemyStatusInfo.cs

在这里插入图片描述

EnemyStatusInfo
注意:
public EnemySpawn spawn; 是为了EnemySpwn类中的CreatEnemy()函数中产生一个敌人后,将生成该敌人的生成器赋给该spawn变量,便于在EnemyStatusInfo类中Death()函数中完成一个敌人死后,可以用该生成器生成下一个敌人,避免使用到其他生成器,造成同一路线生成多个敌人。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
// 敌人的状态信息
/// </summary>
public class EnemyStatusInfo : MonoBehaviour
{public EnemySpawn spawn;//当前血量public float currentHP = 80;private void Demage(float amount){currentHP -= amount;if (currentHP <= 0)//死亡Death();}private EnemyAnimation anim;//死亡延迟时间public float deathDelay = 5;private void Death(){//销毁当前游戏对象Destroy(gameObject, deathDelay);//播放死亡动画anim = GetComponent<EnemyAnimation>();anim.Play(anim.deatName);//生成下一个敌人spawn.GenerateEnemy();}
}

EnemyAnimation.cs

在这里插入图片描述

EnemyAnimation
注意:
1.定义各种动画名称的变量的原因
EnemyAnmation脚本挂在Solier物体上,而其子物体Model上有Animation脚本,由于不同Model的Animation中动作的名称不同,所以我们要定义各种动画名称的变量,且是Public的,这样就可以对照着不同Model的动画名称在界面中填入相对应的名称,这样的话就算Soldier的子物体Model修改了,我们也只用在EnemyAnmation脚本的界面中做出相应的修改即可,不用改变EnemyAnmation代码内容。
2. anim = GetComponentInChildren< Animation>(); 因为EnemyAnmation脚本挂在父物体Solier物体上,而我们需要寻找子物体Model上的Animation组件,所以此处使用GetComponentInChildren。
3. Play和CrossFade的区别:
Play:直接切换动画,如果人物之前处于倾斜跑步状态,则会立即变成站立状态,表现上比较不真实,特别是当两个动画姿势差别较大时。
CrossFade:通过动画融合来切换动画,第二个参数可以指定融合的时间,如果人物之前处于倾斜跑步状态,则会在指定的融合时间内逐渐变成站立状态,表现上接近真实的人物动作切换效果。
在这里插入图片描述
EnemyAnimation

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 敌人动画
/// </summary>
public class EnemyAnimation : MonoBehaviour 
{public string runName = "run";//攻击动画public string atkName = "shooting";//死亡动画public string deatName = "death";//闲置动画public string idleName = "idleWgun";private Animation anim;private void Awake(){anim = GetComponentInChildren<Animation>();}//播放动画public void Play(string name){anim.CrossFade(name);    }//判断动画是否播放public bool IsPlaying(string name){return anim.IsPlaying(name);}}

EnemyAI.cs

在这里插入图片描述

EnemyAI
1.快捷键:选中内容,按control+R+M键,创建新方法。
2.Update中执行游戏逻辑,根据敌人状态,执行寻路或攻击。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 敌人 人工智能
/// </summary>
public class EnemyAI : MonoBehaviour 
{//获取[敌人马达、敌人动画]脚本对象引用private EnemyMotor motor;private EnemyAnimation anim;//枚举敌人状态public enum State{//攻击Attack,//寻路Pathfinding}private void Start(){motor = GetComponent<EnemyMotor>();anim = GetComponent<EnemyAnimation>();}private State state = State.Pathfinding;private float atkTimer;//攻击间隔public float atkInterval = 0.5f;//Update中执行的是游戏逻辑private void Update(){switch (state){case State.Attack:Attack();break;case State.Pathfinding:Pathfinding();break;default:break;}}private void Attack(){//当攻击动画未播放时,播放闲置动画if (!anim.IsPlaying(anim.atkName)){anim.Play(anim.idleName);}if (atkTimer <= Time.time){anim.Play(anim.atkName);atkTimer = Time.time + atkInterval;}}private void Pathfinding(){//执行寻路//如果寻路结束,修改状态为攻击anim.Play(anim.runName);if (!motor.Pathfinding())state = State.Attack;}}

敌人生成器

在这里插入图片描述

策划

在这里插入图片描述

需求分析

在这里插入图片描述

创建根路线

创建根路线:
1.调试阶段让路点坐标的标签显示出来,方便观察。
2.敌人生成器EnemySpawn脚本应该挂在WayLineRoot这个父物体身上,这样就可以选择下面不同的子路线来创建不同的敌人。
在这里插入图片描述

WayLine.cs

WayLine
1.Points这个数组存放的是三维向量的引用,所以默认是null,该Point变量的属性是可读可写。
2.当创建一条线路时,需要初始化线路,所以创建了构造函数。
3.当创建的数组内部存放的是引用类型的时候,我们往往需要New 引用类型来初始化。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 路线类
/// </summary>
public class WayLine
{//坐标数组 装的是三维向量的引用 默认是null public Vector3[] Points { get; set; }//线路是否可用 默认是 falsepublic bool IsUseAble { get; set; }//构造函数,初始化属性public WayLine(int countPoints){this.Points = new Vector3[countPoints];this.IsUseAble = true;}}

EnemySpawn

代码实现

在这里插入图片描述
在这里插入图片描述

注意事项

EnemySpawn
重点:1.lines这数组用于储存所有的路线,也是引用类型,所以需要New 引用类型来初始化,New完以后lines成为一个有数组大小的数组,且lines,IsUseable=true,但是每个数组内位置仍然为null,接下来就是遍历子物体,即界面中WayLIneRoot下面的子路线Wayline01、Wayline02、Wayline03,由于每条子路线为null,所以将每条子路线都New 引用类型,这样就初始化了每条子路线,但现在每条子路线中的Points是(0,0,0),所以遍历每条子路线的路点,将每个路点都赋值。
在这里插入图片描述2.由于有的线路如果当前产生了敌人,有敌人占据,则该线路不可用 IsUseable = false,所以需要SelectWayLines()方法将所有的线路进行筛选,选出所有的有用路线,且将该路线放在List<>集合里面,由于该方法的返回值是WayLine[]数组,所以需要ToArray操作,将集合转为数组。
3.在所选择路线的起始点创建一个敌人以后,需要注意需要配置敌人的信息,这个敌人创建好以后,上面应该自动挂着EnemyAI、EnemyAnimation、EnemyStatusInfo、EnemyMotor脚本(由于创建预制体时候已经设置好了哦),由于寻路需要知道线路,所以找到所创建的敌人的EnemyMotorjiao组件的引用,将路线line传给它,此外该线路被这个敌人占据,所以当前不可用。
4.由于后面敌人可能会死亡,所以每次创建敌人时,将产生该敌人的生成器保留,为了下次可能会使用该生成器来创建这条线路的敌人,避免产生一条线路产生多个敌人。

EnemySpawn.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 敌人生成器
/// </summary>
public class EnemySpawn : MonoBehaviour 
{//存储所有路线private WayLine[] lines;//记录敌人预制体public GameObject[] enemyTypes;//开始需要创建的敌人数目public int startCount = 2;//记录已经产生的敌人数量private int spawnedCount;//记录产生敌人数量的上限public int maxCount;//最大延迟时间public float maxDelay = 10;//计算所有路线以及坐标private void CalculateWayLines(){lines = new WayLine[transform.childCount];for (int i = 0; i < transform.childCount; i++){Transform WaylinesTF = transform.GetChild(i);lines[i] = new WayLine(WaylinesTF.childCount);for (int pointsIndex = 0; pointsIndex < WaylinesTF.childCount; pointsIndex++){lines[i].Points[pointsIndex] = WaylinesTF.GetChild(pointsIndex).position;}}}//产生敌人private void CreatEnemy(){//获取所有有用路线WayLine[] useableLines = SelectWayLines();//随机选择其中一条路线WayLine wayLine = useableLines[Random.Range(0, useableLines.Length)];//创建一个敌人int enemyTypeIndex = Random.Range(0, enemyTypes.Length);GameObject enemyGO = Instantiate(enemyTypes[enemyTypeIndex], wayLine.Points[0], Quaternion.identity) as GameObject;//给敌人配置信息EnemyMotor motor = enemyGO.GetComponent<EnemyMotor>();motor.line = wayLine;//wayLine.IsUseAble = false;//产生了敌人,该路线已经被占用motor.line.IsUseAble = false;enemyGO.GetComponent<EnemyStatusInfo>().spawn = this;}//计算出所有的路线,并且产生初敌人 private void Start(){CalculateWayLines();for (int i = 0; i < startCount; i++){ GenerateEnemy();}}public void GenerateEnemy(){spawnedCount++;if (spawnedCount >= maxCount) return;float delay = Random.Range(0, maxDelay);//延迟产生敌人Invoke("CreatEnemy", delay);}//获取所有有用路线private WayLine[] SelectWayLines(){List<WayLine> WayLines = new List<WayLine>(lines.Length);foreach (var item in lines){if (item.IsUseAble) WayLines.Add(item);}return WayLines.ToArray();}
}

敌人模块代码的逻辑顺序:
EnemySpawn(挂在WayLineRoot产生敌人) --> WayLine --> EnemyAI --> EnemyMotor --> EnemyAnimation --> EnemStausInfo(如果敌人受伤或死亡)

注意代码的执行顺序:由于Sodiler物体上挂有四个脚本,需要注意在同一物体上脚本的执行,关注下Awake、Start、Update的执行顺序。

武器模块

枪口火光特效

在这里插入图片描述
在这里插入图片描述
注意:
1.Gun应该放在FistPersonCharacter下面,而不是FPSController下面,因为Camera组件在FistPersonCharacter里面,这样Gun才会随着视角移动旋转,
2.因为可能有多把枪,所以创建空物体Gun,再做一个空物体HandGun作为手枪分类,该处手枪预制体放在HangGun下面。


http://www.ppmy.cn/news/819482.html

相关文章

由英雄无敌Online开源 想到的

至此,很多游戏开发都是属于内部开发的,我想这惟一的目的可能就只有在开发成功后然后宣传,并给予市场一个重磅炸弹,能起到非常好的广告效应,符合人们的好奇心,所以不免谁都会去玩上一把,这样更是冲高了一时间的人气,如果这个游戏平平,但也无非常厌人之处,或许能留住一些习惯随域…

保姆级系列教程-玩转Fiddler抓包教程(1)-HTTP和HTTPS基础知识

1.简介 有的小伙伴或者童鞋们可能会好奇地问&#xff0c;不是讲解和分享抓包工具了怎么这里开始讲解HTTP和HTTPS协议了。这是因为你对HTTP协议越了解&#xff0c;你就能越掌握Fiddler的使用方法&#xff0c;反过来你越使用Fiddler&#xff0c;就越能帮助你了解HTTP协议。 Fid…

c++实现贝塞尔曲线,生成缓动和回弹动画

贝塞尔曲线于1962年由法国工程师皮埃尔贝塞尔(Pierre Bzier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。 一般参数公式 贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即: 几何学的方向上理解贝塞尔曲线: 一阶贝塞尔曲线 二阶贝塞尔曲线 三阶贝塞尔曲…

tomcat集群部署内存分配

环境&#xff1a; 应用&#xff1a;tomcat9、默认JDK8 机器&#xff1a;单机物理内存16G&#xff0c;安装LINUX后可用内存15G 集群&#xff1a;集群部署2台机器&#xff0c;每机器各1个节点 业务&#xff1a;OLTP业务&#xff0c;用户量1万&#xff0c;交易并发数预估小于每秒5…

计算机专业内存8g和16g差距大吗,电脑内存8g内存跟16g内存不同多大

电脑内存升级方法1 关机 之前与您的计算机内的任何硬件打交道&#xff0c;你应该总是关闭你的系统正确&#xff0c;并拔掉电源线从墙上&#xff0c;从塔背面的显示器电缆。 2 打开 要访问你的系统内存里面&#xff0c;删除计算机的情况下举行的地方侧面板背面的螺丝。一旦被删除…

mysql服务性能优化—my.cnf配置说明详解(16G内存)

MYSQL服务器my.cnf配置文档详解 硬件&#xff1a;内存16G [client] port 3306 socket /data/3306/mysql.sock [mysql] no-auto-rehash [mysqld] user mysql port 3306 socket /data/3306/mysql.sock basedir /usr/local/mysql datadir /data/3306/data open_files_lim…

如何使用计算机备用内存,怎样解决emwin7/em备用存储空间占用高-win7 内存占用高,电脑物理内存占用90...

右键属性我的电脑&#xff0c;然后点击【高级系统设置】。 系统属百性窗口选择【高级】菜单&#xff0c;并点击【性能】选项中的【设置】。 然后在度弹出的性能选项窗口中点击【更改】。 选择驱动器(一般专为C盘)&#xff0c;选择【自定义属大小】即可手动修改虚拟内存的初始、…

linux free 信息,linux free 命令显示的内存信息

LinuxFree 命令内存分配信息 total used free shared buffers cached Mem: 32099 31909 189 1 100 28679 -/ buffers/cache: 3129 28969 Swap: 15999 0 15999 第二行的输出是操作系统的内存使用情况。 第三行的输出…