目录导航:
该游戏是基于MagicalVoxel(类似于我的世界的像素模型)的模型,利用免费www.mixamo.com给静态模型添加动画,在Unity3D中完成的小型像素可爱风的解谜游戏——逃离寝室游戏
本人与室友合作完成制作周期为7天,制作成本由于MagicalVoxel与mixamo的免费而达到0。
主页及游戏界面:
五种结局(完成会解锁):
一些建模(还原自己学校寝室,略丑勿怪):
代码部分主要分:相机控制代码,人物移动跳跃代码,事件触发及UI设计,实例结构四块来陈述,在最后将附上源码文件,望大家能参考并指正错误。
代码包括:第三人称相机跟随镜头旋转,缩放,实现遇到障碍:有物体透明化或拉近镜头的功能
这部分代码参考于另以为博主(由于时间久远具体不知)的第三人称相机设置,若相机与player间有物体将会自动拉近相机(部分被注释掉代码实现的功能),后因我的室友发现掉落时出现bug而修改为物体半透明化,在此我搜索了部分资料重新修改并做了更详细的注解。
public class CameraController : MonoBehaviour
{
public GameObject player;
//初始观察距离
public float Distance = 1F;
//旋转速度
public float SpeedX = 240;
public float SpeedY = 120;
//角度限制
public float MinLimitY = 5;
public float MaxLimitY = 180;
//旋转角度
private float mX = 0.0F;
private float mY = 0.0F;
//鼠标缩放距离最值
public float MaxDistance = 3;
public float MinDistance = 0.2F;
//鼠标缩放速率
private float ZoomSpeed = 2F;
//速度
public float Damping = 10F;
private Quaternion mRotation;
private List<GameObject> collideredObjects;//本次射线hit到的GameObject
private List<GameObject> bufferOfCollideredObjects;//上次射线hit到的GameObject
void Start()
{
mX = transform.eulerAngles.x;
mY = transform.eulerAngles.y;
collideredObjects = new List<GameObject>();
bufferOfCollideredObjects = new List<GameObject>();
}
void LateUpdate()
{
CameraSet();//相机设置
//定义一条射线
/*
RaycastHit hit;
if (Physics.Linecast(player.transform.position, this.transform.position, out hit))
{
GameObject item = hit.collider.gameObject;
string name = item.name;
if (name != "Main Camera")
{
//如果射线碰撞的不是相机,那么就取得射线碰撞点到玩家的距离
float currentDistance = Vector3.Distance(hit.point, player.transform.position);
//如果射线碰撞点小于玩家与相机本来的距离,就说明角色身后是有东西,为了避免穿墙,就把相机拉近
if (currentDistance < m_distanceAway)
{
this.transform.position = hit.point;
}
}
}*/
//清除上一次碰撞的对象
bufferOfCollideredObjects.Clear();
//循环将本次碰撞的对象添加到上次碰撞对象list中
for (int temp = 0; temp < collideredObjects.Count; temp++)
{
bufferOfCollideredObjects.Add(collideredObjects[temp]);//得到上次的
}
//清除本次
collideredObjects.Clear();
//发射射线
//dir是玩家到camera的方向
Vector3 dir = -(player.transform.position - transform.position).normalized;
RaycastHit[] hits;
//意思是将从玩家出发以dir方向射出Vector3.Distance(player.transform.position, transform.position)距离的射线,将碰撞的物体添加到hits中
hits = Physics.RaycastAll(player.transform.position, dir, Vector3.Distance(player.transform.position, transform.position));
//将碰撞对象添加到本次碰撞list中
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.gameObject.name != "Plane" && hits[i].collider.gameObject.name != "Cube")
{
collideredObjects.Add(hits[i].collider.gameObject);//得到现在的
}
}
//把上次的还原,这次的透明
for (int i = 0; i < bufferOfCollideredObjects.Count; i++)
{
SetMaterialsColor(bufferOfCollideredObjects[i].GetComponent<Renderer>(), false);
}
for (int i = 0; i < collideredObjects.Count; i++)
{
SetMaterialsColor(collideredObjects[i].GetComponent<Renderer>(), true);
}
}
//是否搞透明
void SetMaterialsColor(Renderer r, bool isClear)
{
if (isClear)
{
//获得对象r的所有材质列表的数量
int materialsNumber = r.sharedMaterials.Length;
for (int i = 0; i < materialsNumber; i++)
{
//将所有材质修改为Transparent / Diffuse,并修改颜色属性1为完全不透明与255等价,0为完全透明,这里的0.4为半透明
r.materials[i].shader = Shader.Find("Transparent/Diffuse");
Color tempColor = r.materials[i].color;
tempColor.a = 0.4f;
r.materials[i].color = tempColor;
}
}
else
{
//同理
int materialsNumber = r.sharedMaterials.Length;
for (int i = 0; i < materialsNumber; i++)
{
r.materials[i].shader = Shader.Find("Legacy Shaders/Diffuse");
Color tempColor = r.materials[i].color;
tempColor.a = 1f;
r.materials[i].color = tempColor;
}
}
}
void CameraSet()
{
mX += Input.GetAxis("Mouse X") * SpeedX * 0.02F;
mY -= Input.GetAxis("Mouse Y") * SpeedY * 0.02F;
//范围限制
mY = ClampAngle(mY, MinLimitY, MaxLimitY);
//计算旋转
//我们可以通过Quaternion.Euler()方法将一个Vector3类型的值转化为一个四元数, 进而通过修改Transform.Rotation来实现相同的目的
mRotation = Quaternion.Euler(mY, mX, 0);
transform.rotation = Quaternion.Lerp(transform.rotation, mRotation, Time.deltaTime * Damping);
//鼠标滚轮缩放
Distance -= Input.GetAxis("Mouse ScrollWheel") * ZoomSpeed;
Distance = Mathf.Clamp(Distance, MinDistance, MaxDistance);
//重新计算位置
Vector3 mPosition = mRotation * new Vector3(0.0F, 0.0F, -Distance) + player.transform.position;
transform.position = Vector3.Lerp(transform.position, mPosition, Time.deltaTime * Damping);
}
//角度范围限制
private float ClampAngle(float angle, float min, float max)
{
if (angle < -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp(angle, min, max);
}
}
代码包括:跳跃判定,玩家跟随镜头旋转,角色移动,已详细注解
// Update is called once per frame
void Update()
{
//时刻受重力作用
moveDirection.y -= gravity * Time.deltaTime;
controller.Move(moveDirection * Time.deltaTime);
moveH = Input.GetAxis("Horizontal");
moveV = Input.GetAxis("Vertical");
if (gameController.gamePaused)
{
moveH = 0;
moveV = 0;
}
//判断玩家是否在地上,系统自带isGrounded
if (controller.isGrounded)
{
moveDirection.y = 0;
if (Input.GetButtonDown("Jump"))
{
moveDirection.y = jumpSpeed;
GameObject tmp = Instantiate(jumpAudio);
Destroy(tmp, 1);
}
}
controller.Move(moveDirection * Time.deltaTime);
//角色模型方向跟随相机转动,sight是传入的相机对象,判断两者方向是否一致
if (Mathf.Abs(transform.rotation.y) != Mathf.Abs(sight.transform.rotation.y))
{
transform.eulerAngles = new Vector3(0, sight.transform.eulerAngles.y, 0);
}
//利用系统提供的人物模型类CharacterController移动,在start()中controller = this.GetComponent()
controller.Move(moveH * new Vector3(transform.right.x, 0, transform.right.z) * speed);
controller.Move(moveV * new Vector3(transform.forward.x, 0, transform.forward.z) * speed);
//UI设置
if (Input.GetMouseButtonDown(1) && UI.canRelease)
{
Item item = playerInformation.carryItem.GetComponent<Item>();
Release(item);
UI.getItem(item);
UI.showItem(item);
}
}
代码包括:捡起释放物体(固定物体与player一起移动),事件触发状态判定
private void PickUp(GameObject MeetItem)
{
//判断状态是否已经捡起物体
if (!playerInformation.isCarryItem)
{
UI.carryState = true;
//获得遇到物体中的组件item(每个可捡起的物体都有一个组件Script,包含是否被捡起及UI信息,下文图片实例)
Item item = MeetItem.GetComponent<Item>();
//调整方向
gameObject.transform.LookAt(MeetItem.transform.Find("MenuBox"));
//设置将要捡起的物体的父对象为player,并且用GetComponent().constraints冻结物体的位移,并更新状态
MeetItem.transform.SetParent(gameObject.transform);
playerInformation.isCarryItem = true;
playerInformation.carryItem = MeetItem;
item.isCarryed = true;
MeetItem.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
UI.hideAll();
}
}
private void Release(Item item)
{
//同理,捡起反之
item.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.None;
UI.carryState = false;
item.transform.parent = null;
UI.hideAll();
playerInformation.isCarryItem = false;
playerInformation.carryItem = null;
item.isCarryed = false;
}
public void MeetEvent(GameObject MeetItem)
{
//获得遇到物体中的组件item,同上
Item item = MeetItem.GetComponent<Item>();
UI.getItem(item);
UI.showAll();
//一系列的状态判断
if (playerInformation.isCarryItem)
{
Item carryItem = playerInformation.carryItem.GetComponent<Item>();
if (carryItem.itemName == "枕头")
{
if (item.itemName == "下方")
{
UI.changeBehaviour2("扔下去");
}
}
//此处省略很多并列判断
}
else
{
if (item.itemName == "寝室门")
{
if (powerful)
{
UI.changeBehaviour2("踢飞");
item.description = "我感觉我能把门踢飞!";
}
if (big)
{
UI.changeBehaviour2("打开");
item.description = "现在我可以直接开门了";
}
}
}
}
//右键点击触发事件同理
public void RightClickEvent(GameObject MeetItem)
{
Item item = MeetItem.GetComponent<Item>();
if (playerInformation.isCarryItem)
{
Item carryItem = playerInformation.carryItem.GetComponent<Item>();
if (carryItem.itemName == "枕头")
{
if (item.itemName == "下方")
{
Release(carryItem);
carryItem.isCarryed = true;
pillowNew.SetActive(true);
GameObject.Find("Pillow").SetActive(false);
}
}
//此处省略很多并列判断
}
}
}
Player结构
Model:Animator controller
stepOn: 用于判断高度及是否player站在其他物体上
举例:
Trash:一个可触发的物体,小的碰撞范围检查碰撞
MenuBox:大的碰撞范围,检测player靠近触发UI提示
源码及游戏,github链接:
https://github.com/923076046/Escape-from-the-dormitory-game.git