文章目录

创建脚本:Login.cs
在脚本中添加:using UnityEngine.SceneManagement;
脚本内容:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Login : MonoBehaviour
{
public void Enter()
{
SceneManager.LoadScene(1);
}
public void ReturnUi()
{
SceneManager.LoadScene(0);
}
public void ExitGame()
{
Application.Quit();
}
}
脚本中编号为1的场景在之后的实验中会逐渐完善。这次受到蔡老师上课的启发,决心做一款属于自己的fps射击游戏。
将脚本中的方法挂载到Button上

然后将enter()方法添加进去,如图所示

同理,将exit()方法添加到第二个Button中
接下来运行程序,点击"进入我的世界",可得到:

在脚本管理器中添加Audio
Source,将下载好的音乐文件放入AudioClip中(其中Unity支持AIFF WAV MP3
OGG等)
如下图所示:

如图在Toggle中添加脚本管理器

并找到mute

在Slider中添加volume

添加脚本:MusicControl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
MusicPlayControl();
}
void MusicPlayControl()
{
if (Input.GetKeyDown(KeyCode.O))
{
GetComponent<AudioSource>().Pause();
}
else if (Input.GetKeyDown(KeyCode.P))
{
GetComponent<AudioSource>().UnPause();
}
}
}
该脚本的意思是当按下P键时音乐暂停播放,按下空格键时音乐继续播放
注意:将这两个选项选中

并将下面选项去掉(Toggle中)

最终的UI设计界面:

实验的结论
通过这次实验,我学会了用一些基本元素来进行UI设计,因为我喜欢玩三蹦子和原神的缘故吧,这次的作业也是按照动漫像素风来做的,ui美化方面还有待提高。

导出的文件后缀为FBX,按照关键帧进行分割,在0-30帧进行开门操作,30-60帧进行关门操作。
也借鉴了一些github大佬做的简单场景,正好是像素风,也符合自己的个人审美。场景做的比较简单,只有最简单的wasd移动,没有跳跃。
因为这个门做的不是很符合场景的画风,所以导入动画的任务,分发给后续的子弹射击,奔跑等场景。
如下图所示:

实验的结论
在这次实验中,学着在3dMax和unity中建模,有些操作在一开始的时候还是很不熟练,一些模型的制作也不太成熟,总体做的比较简陋,动画的实现也比较少,画面的画风美化还也需要多下功夫。

https://www.bilibili.com/video/BV1vL4y1x7Qx?p=1
如图所示

参考官方文档来写。

这里参考网络上的脚本,使用GetAxis()方法,他有两种:Vertical:获得垂直方向Horizontal:获得水平方向。
float h = Input.GetAxis(“Horizontal”);//这个代码的意思是
对应键盘上面的左右箭头,当按下左或右箭头时触发
float v=Input.GetAxis(“Vertical”);
//对应键盘上面的上下箭头,当按下上或下箭头时触发
运用这些代码就可以实现简单的前后左右移动啦。
Input代表输入设备(键盘)输入

目录层次,把他放在player下,实现绑定。



这里要注意的一点是Time.deltaTime;这个特殊的存在。我的理解是这是个和游戏的帧率有关的时间单位。
Time.deltaTime的官方解释为"上一帧与当前帧间隔的秒数"
可以理解为"一帧的时间"
Update()代码是这样的:Time.deltaTime *
10,表示一帧移动的距离,即一帧的时间*速度,这里的时间是上一帧到当前帧的时间,10表示每秒钟移动的距离,因此二者相乘就是一帧移动的距离,由于Update()函数在一秒内执行的次数就是帧数,所以当时间经过一秒,对象的移动距离即为帧数*每一帧的时间*速度,也就是一秒移动的距离,与10m/s相对应。
这里展示一下我微调过后的代码吧
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 摄像机的旋转
/// 玩家左右旋转控制视线左右移动
/// 摄像机上下旋转控制视线上下移动
/// </summary>
public class MouseLook : MonoBehaviour
{
public float mouseSensitivity = 100f;//鼠标灵敏度
public Transform playerBody;//玩家的位置
private float xRotation = 0f;
// Start is called before the first frame update
void Start()
{
//将光标锁定在该游戏窗口的中心,并且隐藏光标
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
float mouseX = Input.GetAxis(“Mouse X”) * mouseSensitivity *
Time.deltaTime;
float mouseY = Input.GetAxis(“Mouse Y”) * mouseSensitivity *
Time.deltaTime;
xRotation -= mouseY;//将上下旋转的轴值进行累计
//向上视角限制为60度,向下限制为45度
xRotation = Mathf.Clamp(xRotation, -60f,
45f);//限制轴值的累计(这里就能发现上90度和下90度角正好相对于了90的轴值)
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
playerBody.Rotate(Vector3.up * mouseX);
}
}
轴数限制良好的限制了上下角度的移动方向。
//将光标锁定在该游戏窗口的中心,并且隐藏光标
Cursor.lockState = CursorLockMode.Locked;
锁定鼠标,


设置变量,方便ui界面调试

声明变量

重力变量乘时间用来模拟重力,图中*2是为了游戏手感调整加入的。
我们导入的这个像素风模型,他的所有建筑物都不是刚体。我们试验的圆柱体可以轻松穿过任何物体。我们要利用unity特性,设置刚体碰撞。


选择模型里的地面,山头,建筑物分类,选择子对象。批量设置刚体


现在已经不会掉下去了。
其实重力和起跳我都没有按照b站的视频来走。视频里的碰撞体感觉大材小用了。我选择了其他的方法,很简单的模拟重力和起跳。


加入update()方法里


将计算出的跳跃和重力变量,加入move方法。

设计ui,添加按钮

预览,然后再美化一下

接下来设计脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class menu : MonoBehaviour
{
public GameObject MenuXin;
public bool isStop = true;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (isStop)
{
if (Input.GetKeyDown(KeyCode.Escape))
{
MenuXin.SetActive(true);
// 鼠标显示
Cursor.lockState = CursorLockMode.None;
isStop = false;
Time.timeScale = (0);//设置为0,游戏暂停
}
}
else
{
if (Input.GetKeyDown(KeyCode.Escape))
{
MenuXin.SetActive(false);
// 鼠标锁定并消失
Cursor.lockState = CursorLockMode.Locked;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
}
}
}
public void Resume()
{
MenuXin.SetActive(false);
// 鼠标锁定并消失
Cursor.lockState = CursorLockMode.Locked;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
}
public void Restart()
{
MenuXin.SetActive(false);
// 鼠标显示
Cursor.lockState = CursorLockMode.None;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
SceneManager.LoadScene(0);
}
public void Quit()
{
Application.Quit();
}
}
这里要注意一下这两句话
MenuXin.SetActive(true);//显示/隐藏菜单
Cursor.lockState = CursorLockMode.None;//接触鼠标锁定
剩下的也都是基础的设计逻辑

绑定按钮


在player/GunCamera下拖入枪的模型



然后添加准心

在刚刚的画布下新建图片ui。

调整颜色大小,置于中心,改为绿色充当我们的准心。

同时设置空对象—射线口,调整位置到枪口位置,用来发射射线

写代码,创建对象

实现射击的核心代码
这里比较难的是引入射线检测这个函数。

origin 射线在世界坐标系中的起点。
direction 射线的方向。
maxDistance 射线应检查碰撞的最大距离。
layerMask [层遮罩]{.underline},用于在投射射线时有选择地忽略碰撞体。
所以这里我们写Physics.Raycast(shooterPoint.position, shootDirection, out
hit, range)来判断射击。

将刚刚创建的空对象—射线口拖入到这里,供脚本调用。

查看debug.log的输出内容,我们可以看到,射线已经生效了。
不加射速控制的射线,在游戏里是不能正常对战的。因为一秒不到就能射完一个弹夹的子弹,太离谱了。所以我们要设置一个量去影响它的射击间隔时间。


它存在的目的就是为了控制发射的。
当射击计时器小于咱们预设的射速值的话,直接返回return,不进行射击。这样就可以良好的控制射击间隔啦。


开始前将一个弹夹的子弹数,赋值给现有子弹数。
设置参数,每次射后使用
来减少一颗子弹。

开火之前进行判断,如果现有子弹数小于等于0,不进行开火。





效果展示

然后定义数据



放入start()和开火方法里。

把对象拖入脚本,每次开火后ui显示的子弹数会发生变化。


设置键位

书写判断 调用方法

简单的逻辑代码

给player添加audio source

设置公共变量

设置对应音效




找到枪口开火粒子特效

播放查看效果

添加粒子和点光照


手册介绍


调整粒子位置


设置变量

设计播放音效方法

官方手册对点光源介绍


设置颜色 范围 强度等等参数

设置光源位置,逐渐逼真起来。

把对象拖入脚本对应的公共变量

设置变量


核心代码就是实例化和回收



(18)添加武器手臂摇摆


通过移动视角,模拟武器手臂摆动


添加动画控制器



拖入动画

创建过度



现在已经可以播放对应动画了


在playermove添加iswalk判断并赋值

在WeaponController调用这两个变量

动画管理器里设置参数



再加一条,允许步行时查看武器

调整跑步收枪动画时间,优化手感



同时要设置换弹时,不能开火

设置状态量


加入开火判断



效果展示


查看文档,这里我们使用这个方法控制开火动画



我们这里通过调整相机视野,达到瞄准的目的


来一组对比图


效果展示





设置空对象,调整弹壳射出的位置



这个素材自带弹壳弹出脚本。因为涉及到蛋壳的无规则运动,和各种非线性运动。这里我们之间调用模型里的。





画布下添加text

设置枚举类型变量

开始时,定义一些默认的值

按键定义


设计一下整理的判断逻辑
全自动与半自动主要的实现代码是通过对鼠标键位的获取实现的。
Input.GetMouseButton(0);
Input.GetMouseButtonDown(0);
GetMouseButton:每一帧鼠标处于按下状态都会返回true,
Input.GetMouseButton(0):表示鼠标左键按下,
Input.GetMouseButton(1):表示鼠标右键按下,
Input.GetMouseButton(2):表示鼠标中键按下
GetMouseButtonDown:鼠标按下的那一帧返回true,
Input.GetMouseButtonDown(0):表示鼠标左键按下,
Input.GetMouseButtonDown(1):表示鼠标右键按下,
Input.GetMouseButtonDown(2):表示鼠标中键按下

设置Ui更新


实验的结论
通过这次实验,学会了"触发器"的使用,主要注意三点:两物体都必须有"碰撞组件";被碰物体的碰撞器的isTrigger属性必须打勾;施碰物体必须有刚体组件。在实验过程中,由于一开始使用Character
Controller组件,导致了运行程序的时候报错,最后慢慢也是改好了,不管还是有很多要改进的地方需要再精雕细琢。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
MusicPlayControl();
}
void MusicPlayControl()
{
if (Input.GetKeyDown(KeyCode.O))
{
GetComponent<AudioSource>().Pause();
}
else if (Input.GetKeyDown(KeyCode.P))
{
GetComponent<AudioSource>().UnPause();
}
}
}

其实就是简单的添加按钮,不过还是模仿了印象中游戏的键位布局,感觉这样比较美观没有违和感。
需要注意的是,要把"按键提示层"放到第一位。Unity渲染的时候有个逻辑就是,放在前面的先渲染,后面的后渲染。后渲染的会把前面的层数遮盖掉。为了不遮挡中心的按钮,把该画布放最前面,充当底子。

项目整体做完之后,还是有不少bug的,我们对其进行整理优化。








然后就是界面优化

这里选为屏幕大小缩放模式,参考的分辨率为我电脑的分辨率,因为我的电脑是2k屏,整体的ui设计我也是按照2k屏幕来的



界面一进行同样的调整





为优化前:

限制帧率为60后gpu占用量达到28%


(4)项目展示:






脚本
Menu:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class menu : MonoBehaviour
{
public GameObject MenuXin;
public bool isStop = true;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (isStop)
{
if (Input.GetKeyDown(KeyCode.Escape))
{
MenuXin.SetActive(true);
// 鼠标显示
Cursor.lockState = CursorLockMode.None;
isStop = false;
Time.timeScale = (0);//设置为0,游戏暂停
}
}
else
{
if (Input.GetKeyDown(KeyCode.Escape))
{
MenuXin.SetActive(false);
// 鼠标锁定并消失
Cursor.lockState = CursorLockMode.Locked;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
}
}
}
public void Resume()
{
MenuXin.SetActive(false);
// 鼠标锁定并消失
Cursor.lockState = CursorLockMode.Locked;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
}
public void Restart()
{
MenuXin.SetActive(false);
// 鼠标显示
Cursor.lockState = CursorLockMode.None;
isStop = true;
Time.timeScale = (1);//设置为1,游戏继续
SceneManager.LoadScene(0);
}
public void Quit()
{
Application.Quit();
}
}
MiniMapCameraFollow:
using UnityEngine;
/// <summary>
/// 小地图摄像机跟随
/// Auhor: linxinfa
/// </summary>
public class MiniMapCameraFollow : MonoBehaviour
{
public Transform targetPlayer;
/// <summary>
/// 高度
/// </summary>
public float distanceUp = 1.3f;
private Transform m_cameraTransform;
void Awake()
{
m_cameraTransform = transform;
}
void LateUpdate()
{
if (null == targetPlayer) return;
m_cameraTransform.position = targetPlayer.position + Vector3.up *
distanceUp;
}
}
MouseLook:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 摄像机的旋转
/// 玩家左右旋转控制视线左右移动
/// 摄像机上下旋转控制视线上下移动
/// </summary>
public class MouseLook : MonoBehaviour
{
public float mouseSensitivity = 100f;//鼠标灵敏度
public Transform playerBody;//玩家的位置
private float xRotation = 0f;
// Start is called before the first frame update
void Start()
{
//将光标锁定在该游戏窗口的中心,并且隐藏光标
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
float mouseX = Input.GetAxis(“Mouse X”) * mouseSensitivity *
Time.deltaTime;
float mouseY = Input.GetAxis(“Mouse Y”) * mouseSensitivity *
Time.deltaTime;
xRotation -= mouseY;//将上下旋转的轴值进行累计
//向上视角限制为60度,向下限制为45度
xRotation = Mathf.Clamp(xRotation, -60f,
45f);//限制轴值的累计(这里就能发现上90度和下90度角正好相对于了90的轴值)
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
playerBody.Rotate(Vector3.up * mouseX);
}
}
MusicControl:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
MusicPlayControl();
}
void MusicPlayControl()
{
if (Input.GetKeyDown(KeyCode.O))
{
GetComponent<AudioSource>().Pause();
}
else if (Input.GetKeyDown(KeyCode.P))
{
GetComponent<AudioSource>().UnPause();
}
}
}
PlayerCameraFollow:
using UnityEngine;
/// <summary>
/// 第三人称视角摄像机跟随
/// Author: linxinfa
/// </summary>
public class PlayerCameraFollow : MonoBehaviour
{
public Transform targetPlayer;
/// <summary>
/// 距离
/// </summary>
public float distanceAway = 1.7f;
/// <summary>
/// 高度
/// </summary>
public float distanceUp = 1.3f;
/// <summary>
/// 平滑度
/// </summary>
public float smooth = 2f;
private Vector3 m_targetPos;
private Transform m_cameraTransform;
void Awake()
{
m_cameraTransform = transform;
}
void LateUpdate()
{
if (null == targetPlayer) return;
// 计算摄像机的目标坐标
m_targetPos = targetPlayer.position + Vector3.up * distanceUp -
targetPlayer.forward * distanceAway;
// 插值设置坐标,有个平滑跟随效果
m_cameraTransform.position = Vector3.Lerp(m_cameraTransform.position,
m_targetPos, Time.deltaTime * smooth);
// 设置摄像机角度,对准跟随目标
m_cameraTransform.LookAt(targetPlayer);
}
}
Playermove:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Playermove : MonoBehaviour
{
private CharacterController characterController;
public float walkSpeed = 10f;// 移动速度
public float runSpeed = 15f;//奔跑速度
public float speed;//移动速度
public bool isWalk;//判断是否在行走
public bool isRun;//判断是否在奔跑
public float jumpForce = 5f;//跳跃力度
private Vector3 velocity;//设置玩家Y轴的一个冲量变化
public float gravity = -9.8f; //设置重力
public float VerticalVelocity;//垂直方向速度
private bool isJump;//判断是否在跳跃
public Vector3 movedirction;//设置移动方向
//键位设置
[Header(“声音设置”)]
[SerializeField]private AudioSource audioSource;
public AudioClip walkingSound;
public AudioClip runingSound;
[Header(“键位设置”)]
[SerializeField] [Tooltip(“奔跑按键”)] private KeyCode
runInputName;
// Start is called before the first frame update
void Start()
{
//获取player身上的组件
characterController = GetComponent<CharacterController>();
audioSource = GetComponent<AudioSource>();
runInputName = KeyCode.LeftShift;
}
// Update is called once per frame
void Update()
{
CaculateGravity();
Jump();
Move();
PlayFootStepSound();
}
//移动相关方法,主要控制characterController.Move
public void Move()
{
float h = Input.GetAxis(“Horizontal”);
float v = Input.GetAxis(“Vertical”);
isRun = Input.GetKey(runInputName);
isWalk = (Mathf.Abs(h) > 0 || Mathf.Abs(v) > 0) ? true : false;
if (isRun)
{
speed = runSpeed;
}
else
{
speed = walkSpeed;
}
movedirction = (transform.right * h +transform.forward *
v).normalized; //设置玩家移动方向
characterController.Move(movedirction * speed * Time.deltaTime);
//跳跃相关move
velocity.y = VerticalVelocity * Time.deltaTime;
characterController.Move(velocity);
}
//播放移动音效
public void PlayFootStepSound()
{
//判断是否在移动,这里不使用characterController.isGrounded,因为这个参数会抖动,不稳定,会使声音卡顿
if (movedirction.sqrMagnitude > 0.9f)
{
audioSource.clip = isRun ? runingSound :
walkingSound;//设置行走或奔跑音效
if (!audioSource.isPlaying)
{
audioSource.Play();
}
}
else
{
if (audioSource.isPlaying)
{
audioSource.Pause();
}
}
}
public void Jump()
{
//检测空格按下,因为GetKeyDown只在按键按下的一瞬间返回true,而GetKey是按键按下的过程中都为true,只要有几帧的时间都能在IsGrounded为true的那么一帧跳起来。
isJump = Input.GetKey(KeyCode.Space);
if (characterController.isGrounded && isJump)
{
VerticalVelocity = jumpForce * 3;
}
}
public void CaculateGravity()
{
//是否碰撞在一起
if (characterController.isGrounded)
{
VerticalVelocity = gravity * Time.deltaTime;
return;
}
else
{
VerticalVelocity += gravity * 2 * Time.deltaTime;
}
}
}
WeaponController:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
//武器射击
public class WeaponController : MonoBehaviour
{
//引入自己写的Playmove类,在脚本面板设置调用该对象
public Playermove PM;
public Transform shooterPoint;//射击的位置(枪口)
public int range = 100;//武器射程
public int bulletsMag = 31;//一个弹匣数量
public int bulletLeft = 300;//备弹
public int currentBullets;//当前子弹数
public float fireRate = 0.1f;//射速
private float fireTimer;//计时器
public ParticleSystem muzzleFlash;//枪口火焰特效
public Light muzzleFlashLight;//枪口火焰灯光
public GameObject hitParticles;//子弹击中粒子特效
public GameObject bulletHole;//弹孔
[Header(“声音设置”)]
private AudioSource audioSource;
public AudioClip AK47ShoundClip;/*枪声音效片段*/
[Tooltip(“没有子弹提示音效”)] public AudioClip
noreload;//换子弹1音效片段
[Tooltip(“普通换弹音效”)] public AudioClip
reloadAmmoLeftClip;//换子弹1音效片段
[Tooltip(“子弹全部打完音效”)] public AudioClip
reloadOutOFAmmoClip;//换子弹2音效片段(拉枪栓)
private bool isReloading;//判断是否在装弹
private bool isAiming;//判断是否在瞄准
public Transform casingSpawnPoint;//子弹壳抛出的位置
public Transform casingPrefab; //子弹壳预制体
[Header(“键位设置”)]
[SerializeField] [Tooltip(“填装子弹按键”)] public KeyCode
reloadInputName;
[SerializeField] [Tooltip(“查看武器按键”)] private KeyCode
inspectInputName;
[SerializeField] [Tooltip(“自动半自动切换按键”)] private KeyCode
GunShootModelInputName;
[Header(“ui设置”)]
[SerializeField] [Tooltip(“选择准心组件”)] public Image
crossHairUI;
[SerializeField] [Tooltip(“ui显示子弹”)] public Text ammoTextUI;
[SerializeField] [Tooltip(“ui显示射击模式”)] public Text
ShootModelTextUI;
[SerializeField] [Tooltip(“ui弹夹提示”)] public GameObject
NoreloadTextUI;
/*使用枚举区分全自动和半自动模式*/
public enum ShootMode { AutoRifle, SemiGun };
public ShootMode shootingMode;
private bool GunShootInput; //根据全自动和半自动 射击的键位输入发生改变
private int modeNum = 1;
//模式切换的一个中间参数(1:全自动模式,2:半自动模式)
private string shootModelName;
private Animator anim;
private Camera mainCamera;
//private bool GunShootInput;//是否在开枪
// Start is called before the first frame update
void Start()
{
mainCamera = Camera.main;
//获取audio组件
audioSource = GetComponent<AudioSource>();
anim = GetComponent<Animator>();
currentBullets = bulletsMag;
inspectInputName = KeyCode.F;
shootingMode = ShootMode.AutoRifle; //AK47步枪默认是全自动模式
shootModelName = “全自动”;
UpdateAmmoUI();
}
// Update is called once per frame
void Update()
{
//切换模式(全自动和半自动)
if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 1)
{
modeNum = 1;
shootModelName = “全自动”;
shootingMode = ShootMode.AutoRifle;
ShootModelTextUI.text = shootModelName;
}
else if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 0)
{
modeNum = 0;
shootModelName = “半自动”;
shootingMode = ShootMode.SemiGun;
ShootModelTextUI.text = shootModelName;
}
/*控制射击模式的转换 后面就要用代码去动态控制了*/
switch (shootingMode)
{
case ShootMode.AutoRifle:
GunShootInput = Input.GetMouseButton(0);
fireRate = 0.1f;
break;
case ShootMode.SemiGun:
GunShootInput = Input.GetMouseButtonDown(0);
fireRate = 0.2f;
break;
}
//获取鼠标左键
if (GunShootInput)
{
if (currentBullets > 0)
{
GunFire();
}
else
{
Playernoreload();//添加没有子弹音效
NoreloadTextUI.SetActive(true);//打开没有子弹提醒
muzzleFlashLight.enabled = false;//关闭灯光
}
}
else
{
muzzleFlashLight.enabled = false;//关闭灯光
}
//换子弹判断,按下按键后,当前子弹要少于弹夹,并且备弹要大于0
if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletsMag &&
bulletLeft > 0)
{
Reload();
}
//查看武器按键触发
if (Input.GetKeyDown(inspectInputName))
{
//查看武器动画
anim.SetTrigger(“Inspect”);
}
//自行设计奔跑开枪动作,把奔跑动作切换成走路(判断是否在开枪(按下左键),是否在奔跑(按下shift)和是否有子弹)
if (GunShootInput && Input.GetKey(KeyCode.LeftShift) && currentBullets
> 0)
{
anim.SetBool(“Run”, false);
anim.SetBool(“Walk”, true);
}
else
{
anim.SetBool(“Run”, PM.isRun);
anim.SetBool(“Walk”, PM.isWalk);
}
//瞄准方法
DoingAim();
AnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(0);
//两种换弹动画
if(info.IsName(“普通换弹”) || info.IsName(“子弹全部打完换弹”))
{
isReloading = true;
}
else
{
isReloading = false;
}
//计时器加时间
if (fireTimer < fireRate)
{
fireTimer += Time.deltaTime;
}
}
//射击
public void GunFire()
{
//控制射速,若计时器值比射速还小,那么跳出方法不执行,isReloading换弹动画播放状态,pm.isrun是是否在奔跑,
Time.timeScale == 0 解决暂停菜单走火问题
if (fireTimer < fireRate || currentBullets <= 0 || isReloading
|| Time.timeScale == 0)
{
muzzleFlashLight.enabled = false;//关闭灯光
return;
}
RaycastHit hit;
Vector3 shootDirection = shooterPoint.forward;//向前的射击方向
if (Physics.Raycast(shooterPoint.position, shootDirection, out hit,
range))//判断射击
{
Debug.Log(hit.transform.name + “打到了”);
GameObject hitParticleEffect = Instantiate(hitParticles, hit.point,
Quaternion.FromToRotation(Vector3.up,
hit.normal));//实例出子弹击中的火光特效
GameObject bulletHoleEffect = Instantiate(bulletHole, hit.point,
Quaternion.FromToRotation(Vector3.up, hit.normal));//实例出弹孔特效
//回收特效
Destroy(hitParticleEffect, 1f);
Destroy(bulletHoleEffect, 3f);
}
if (!isAiming)
{
anim.CrossFadeInFixedTime(“fire”, 0.1f);
//播放普通开火动画(使用动画的淡出淡入效果)
}
else
{//瞄准状态下,播放瞄准的开火动画
anim.Play(“aim_fire”, 0, 0f);
}
//实例抛弹壳
Instantiate(casingPrefab, casingSpawnPoint.transform.position,
casingSpawnPoint.transform.rotation);
currentBullets–;
//更新ui子弹
UpdateAmmoUI();
muzzleFlash.Play();//火光粒子特效
PlayerShootSound();//播放射击音效
muzzleFlashLight.enabled = true;//启用灯光
fireTimer = 0f;//重置计时器
//自定义奔跑开枪
}
//更新UI
public void UpdateAmmoUI()
{
ammoTextUI.text = currentBullets + " / " + bulletLeft;
ShootModelTextUI.text = shootModelName;
}
public void Reload()
{
if (bulletLeft <= 0) return;
DoReloadAnimation();
//计算需要填充的子弹
int bulletToLoad = bulletsMag - currentBullets;
//计算备弹扣除的子弹
int bulletToReduce = (bulletLeft >= bulletToLoad) ? bulletToLoad :
bulletLeft;
bulletLeft -= bulletToReduce; //备弹减少
currentBullets += bulletToReduce;//当前子弹增加
NoreloadTextUI.SetActive(false);//关闭没有子弹提醒
UpdateAmmoUI();
}
//播放装弹动画
public void DoReloadAnimation()
{
if (currentBullets > 0)
{
anim.Play(“普通换弹”, 0, 0);
audioSource.clip = reloadAmmoLeftClip;
audioSource.Play();
}
if (currentBullets == 0)
{
anim.Play(“子弹全部打完换弹”, 0, 0);
audioSource.clip = reloadOutOFAmmoClip;
audioSource.Play();
}
}
//播放射击音效
public void PlayerShootSound()
{
audioSource.clip = AK47ShoundClip;
audioSource.Play();
}
//播放没有子弹音效
public void Playernoreload()
{
audioSource.clip = noreload;
audioSource.Play();
}
//瞄准的逻辑
public void DoingAim()
{
//GetMouseButton(1)鼠标右键
if (Input.GetMouseButton(1) && !isReloading && !PM.isRun)
{
isAiming = true;
anim.SetBool(“Aim”, isAiming);
crossHairUI.gameObject.SetActive(false);
mainCamera.fieldOfView = 25;//瞄准的时候摄像机视野变小
}
else
{
isAiming = false;
anim.SetBool(“Aim”, isAiming);
crossHairUI.gameObject.SetActive(true);
mainCamera.fieldOfView = 60;//瞄准的时候摄像机视野恢复
}
}
}
WeaponSway:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 武器摇摆
/// </summary>
public class WeaponSway : MonoBehaviour
{
/*摇摆的参数*/
public float amout; //摇摆幅度
public float smoothAmout;//一个平滑值
public float maxAmout;//最大摇摆幅度
private Vector3 originPostion; //初始位置
// Start is called before the first frame update
void Start()
{
//自身位置(相对于父级物体变换得位置)
originPostion = transform.localPosition;
}
// Update is called once per frame
void Update()
{
//设置武器手臂模型位置得值,
float movementX = -Input.GetAxis(“Mouse X”) * amout;
float movementY = -Input.GetAxis(“Mouse Y”) * amout;
//限制
movementX = Mathf.Clamp(movementX, -maxAmout, maxAmout);
movementY = Mathf.Clamp(movementY, -maxAmout, maxAmout);
Vector3 finallyPostion = new Vector3(movementX, movementY, 0);
//手柄位置变换
transform.localPosition = Vector3.Lerp(transform.localPosition,
finallyPostion + originPostion, Time.deltaTime * smoothAmout);
}
}

安装安卓相关组件


全部勾选



点击密钥库管理器

创建

Alias 是给你新建的 key 起一个别名,password 和 Confirm password
输入相同的你想要设置的密码,Organization
填上你组织的名称,可以先随便起一个,之后点击右下角的 添加密钥。

选择密钥

设置名称版本号

设置报名,格式为com.xxxxxxxx

然后设置横屏模式

生成apk,安装即可



在网上搜了很多摇杆,逐一了解过后,选择官方发布的解决方案,别问,问就是官方yyds!
参考教程:
Unity3D 官方资源包Standard
Assets导入:https://blog.csdn.net/Sakuya__/article/details/113815974

然后看了官方介绍教程:
Starter Assets 轻量的角色控制器:
https://www.bilibili.com/video/BV1oU4y137D6/

包管理器导入

这里我选择的是一键生成官方案例,然后在案例的基础上进行修改适配。

然后拖入对应的ui界面示例

这是官方自带的ui界面,我们对他进行魔改,加入开火键和换弹键



因为unity里的虚拟按键映射,实时监听我搞了半天没搞出来,不知道为什么,所以换个方式解决问题。我们监听按钮的按下和弹起,给我们的开火方法,传递参数。



接下来原理都一样了,然后替换之前的判断条件。
Pc端:

移动端:

我们来一波对比。


换弹按键也绑定对应方法


其他的就是一些bug的修复,动作的微调。逻辑变化等等了。比较零碎这里不做展示。

无意间看到了这个,哈哈。突然想到了之前自己玩过的ar游戏,那就做一个也算是圆梦了。
当然目前咱做的没有教程里的那么高级,虽然在python里使用过OpenCV,但是最近也有其他作业了。那就简单进行一下游戏适配吧。
参考教程:
Unity:从零开始搞AR|02】Unity中调用摄像头显示实时画面
https://zhuanlan.zhihu.com/p/468167770
【Unity:从零开始搞AR|06】创建3D物体并在Canvas之前显示
https://zhuanlan.zhihu.com/p/505141761
创建一个单独的画布


摄像机类型选择原始图像
创建脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CameraScript : MonoBehaviour
{
private int currentCamIndex = 0;
private WebCamTexture webCamTexture;
public RawImage rawImage;
public Text startStopText;
public void SwapCameraClicked()
{
if (WebCamTexture.devices.Length > 0)
{
currentCamIndex += 1;
currentCamIndex %= WebCamTexture.devices.Length;
if (webCamTexture != null)
{
StopCamera();
StartStopCameraClicked();
}
}
}
public void StartStopCameraClicked()
{
if (webCamTexture != null)
{
StopCamera();
}
else
{
WebCamDevice device = WebCamTexture.devices[currentCamIndex];
if (!device.Equals(null))
{
int width = 1920;
int height =1080;
webCamTexture = new WebCamTexture(device.name,width,height,24);
rawImage.texture = webCamTexture;
webCamTexture.Play();
startStopText.text = “Stop”;
}
}
}
private void StopCamera()
{
rawImage.texture = null;
webCamTexture.Stop();
webCamTexture = null;
startStopText.text = “Start”;
}
}

直接拖入添加。

运行就可以看到已经可以显示画面了
但是明显看到画面大小不对,把咱们的物体和ui界面全部遮盖了
我们看第二个教程

这里我放我调好的参数。

最终成功运行

然后在入口界面,添加新的ar入口(新的场景)

排好顺序,编号打包。
效果展示






移动端的操作逻辑还有待优化,电脑端的是丝滑的。
以后要继续加油。
实验的结论
通过这次实验,我感触很深,简陋的完成了人生中第一款小游戏,收获还是很大的。同时通过做项目,也了解到了很多游戏的运行原理。比如unity中的update()方法,里面的代码在游戏的每一帧都要运行一次,所以代码的好坏也决定了这个游戏的整体性能。同时也还感触特别深的接触到动画这个东西,也突然理解到了之前打lol,或者王者荣耀中,什么什么皮肤手感好不好的问题,原来这个真的不是玄学,而是有游戏原理在里面的。
编译完apk端后,突然又联想到原神的一个特性,就是三端同步,原神就是unity制作的,他们的三端适配其实也仅仅只是适配了操作逻辑,身下的交给unity编译就好了。再一次感受到了unity的强大。
如果时间足够,我说不定可以加入自己的玩法,发布属于自己的人生第一款游戏,哈哈。顺便记录一下时间从零开始到PC端做出来花了3天时间,然后适配安卓,编译优化,制作ar花了1.5天
非常感谢学校提供这么一个机会,每个课件都有许多细节需要反复实践,也建立了属于自己的人生第一个游戏,受益很多。
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c