实验要求:
写一篇短文,描述以下游戏需求的实现。请写步骤,贴代码并解释:
· 实现点击效果。
o 用 Plane 或其他物体做地面, tag 为“Finish”
o 点击地面后,出现一个圆形攻击标记,两秒后自动消失。注意:该攻击标记不能挡住点击。(Primitive Objects / Cylinder)
o 请使用一个简单工厂创建、管理这些的标记,并自动收回这些标记(注意,这些对象创建后,放在列表内,不必释放)。
用户仅需申请使用即可 GameObjectmyFactory.placeAttackMark(Vector3 postion)
这次要实现一个点击游戏,在一个平面上鼠标点击平面生成一个圆形攻击标志,所写代码结构如下图。
(1) Sign标志类
因为要进行计时操作,所以我想如果每一个标志里有一个属性是记录开始时间,再与自定的世界时间进行比较,判断,即可实现计时功能。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sign {
public float StartTime;
public GameObject sign;
}
所以在Sign类里除了有一个GameObject,还有一个开始时间StartTime。
(2) 实现对象管理
在这里,我声明了一个Factory类进行原形标志对象的管理,管理行为主要有三个,分别为初始化标志Start(),放置标志placeAttackMark()以及回收标志FreeSign()。首先是初始化标志,在Free队列里放进MaxSize个Sign对象,用于后面的放置。
void Start () {
Debug.Log ("Factory Start!");
Used = new Queue<Sign>();
Free = new Queue<Sign> ();
for (int i = 0; i < MaxSize; i++) {
Sign temp = new Sign ();
GameObject sign = Instantiate (Resources.Load ("Prefabs/sign")) as GameObject;
sign.SetActive (false);
sign.layer = 1;
temp.sign = sign;
temp.StartTime = 0f;
Debug.Log (temp.sign.name);
Free.Enqueue (temp);
}
}
在这里我用了一个for循环,将我预制好的sign载入放进Free中,并设置一下各种变量数据,设置是否激化为否,即不显示。然后是放置标志,放置标志的操作也很简单,首先判断Free队列里是否有剩余,有则将队列首取出,设置开始的时间(用于计时操作),位置以及激活,最后将它放进Used队列中。最后是回收操作。
public void placeAttackMark(Vector3 pos) {
if (Free.Count != 0) {
Sign temp = Free.Dequeue ();
temp.StartTime = NowTime;
temp.sign.transform.position = pos;
temp.sign.SetActive (true);
Used.Enqueue(temp);
}
}
因为队列有先进先出的特点,队列头元素一定是先进来的,所以对其进行时间判断,看它的StartTime与开始时间差是否大于等于2,是则出列,灭活并压进Free队列。
Free()需要在每一帧调用判断。NowTime是用于记录开始时间,在脚本一启动时就进行计算。
public void FreeSign() {
if (Used.Count == 0)
return;
if (NowTime - Used.Peek ().StartTime >= TimeLimit) {
Sign temp = Used.Dequeue();
temp.sign.SetActive (false);
Free.Enqueue (temp);
}
}
void Update () {
NowTime += Time.deltaTime;
FreeSign ();
}
(3) 场景调用
现在我们已经有对象与对象管理器了,最后就是场景调用对象管理器的操作了。
首先是鼠标点击操作,这个很简单,直接调用函数Input.GetMouseButtonDown(0)即可。然后如何判断我们选取了一个物体呢,现在就需要Ray的操作了,利用Camera.ScreenPointToRy(Input.MousePosition)返回从相机通过屏幕点的光线。有了射线,我们还得取出射线经过的对象,这里我用Raycast和RaycastHit(用于从raycast获取信息的结构)来实现。Raycast有多种变形,我用的是以下一种
void Update () {
if (Input.GetKeyDown (KeyCode.UpArrow)) {
print ("UpArrow Down!");
}
if (Input.GetMouseButtonDown (0)) {
Vector3 mp = Input.mousePosition; //get Screen Position
//create ray, origin is camera, and direction to mousepoint
Ray ray = ca.ScreenPointToRay (Input.mousePosition);
//Return the ray's hit
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1)) {
print (hit.transform.gameObject.name);
if (hit.collider.gameObject.tag.Contains("Finish")) { //plane tag
Fac.placeAttackMark(hit.point); //move to here
}
}
}
}
将我声明的RaycastHit放进去,通过out操作,就能取出对象放进我的RaycastHit中。还有一项工作就是忽略之前已经放置的标志,在这里我利用Raycast的LayerMasker参数实现。假如传进LayerMasker参数如1,函数就能忽略参数为1的物体,在初始化的时候,我就把所有标志的Layer设置为1,就能实现忽略操作。
(4) 计时器
我的计时原理非常简单,通过在一个Factory脚本里设置一个公共计时变量NowTime,
在每一个Sign里有一个会在放置时记录放置时间的StartTime,
通过对NowTime与StartTime的比较,大于等于限制时间的Sign就执行Free操作。
if (NowTime - Used.Peek ().StartTime >= TimeLimit) {
Sign temp = Used.Dequeue();
temp.sign.SetActive (false);
Free.Enqueue (temp);
}
(5) 完整代码
1.SceneController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneController : MonoBehaviour {
Factory Fac;
GameObject x;
GameObject cam;
Camera ca;
// Use this for initialization
void Start () {
Debug.Log ("Controller Start!");
cam = GameObject.Find ("Camera");
x = GameObject.Find ("sign");
ca = cam.GetComponent<Camera> ();
Fac = (Factory)FindObjectOfType(typeof(Factory)) as Factory;
Debug.Log (Fac);
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown (KeyCode.UpArrow)) {
print ("UpArrow Down!");
}
if (Input.GetMouseButtonDown (0)) {
Vector3 mp = Input.mousePosition; //get Screen Position
//create ray, origin is camera, and direction to mousepoint
Ray ray = ca.ScreenPointToRay (Input.mousePosition);
//Return the ray's hit
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1)) {
print (hit.transform.gameObject.name);
if (hit.collider.gameObject.tag.Contains("Finish")) { //plane tag
Fac.placeAttackMark(hit.point); //move to here
}
}
}
}
}
这里,必须将背景的plane的tag设为Finish,因为要识别背景物体是通过判断它tag是否为Finish来进行的。
2.Factory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Factory : MonoBehaviour {
private static int MaxSize = 3;
Queue<Sign> Used;
Queue<Sign> Free;
float NowTime = 0f;
static float TimeLimit = 2f;
// Use this for initialization
void Start () {
Debug.Log ("Factory Start!");
Used = new Queue<Sign>();
Free = new Queue<Sign> ();
for (int i = 0; i < MaxSize; i++) {
Sign temp = new Sign ();
GameObject sign = Instantiate (Resources.Load ("Prefabs/sign")) as GameObject;
sign.SetActive (false);
sign.layer = 1;
temp.sign = sign;
temp.StartTime = 0f;
Debug.Log (temp.sign.name);
Free.Enqueue (temp);
}
}
// Update is called once per frame
void Update () {
NowTime += Time.deltaTime;
FreeSign ();
}
public void placeAttackMark(Vector3 pos) {
if (Free.Count != 0) {
Sign temp = Free.Dequeue ();
temp.StartTime = NowTime;
temp.sign.transform.position = pos;
temp.sign.SetActive (true);
Used.Enqueue(temp);
}
}
public void FreeSign() {
if (Used.Count == 0)
return;
if (NowTime - Used.Peek ().StartTime >= TimeLimit) {
Sign temp = Used.Dequeue();
temp.sign.SetActive (false);
Free.Enqueue (temp);
}
}
}
3Sign.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sign {
public float StartTime;
public GameObject sign;
}