大作业要求:
制作一款特定技术应用小游戏,并提交技术报告。
历届师兄优秀作品参考:https://blog.csdn.net/pmlpml/article/details/72236930?spm=1001.2014.3001.5502
DDL:2023.01.14日前。
如有特殊情况如中招、生病,请以保重身体为第一要务,合理安排提交日期。
游戏作品的新颖性、技术博客面向大众易读易懂性,占 60%
博客逻辑清晰,图文效果好,文字通顺, 占40%
有较好的 UML 设计图,可以适当提高评分等级
有较严重抄袭行为,要适当降低评分等级
评分成绩采用 10 分制。不设0.5分
我参考的教程视频:这个教程比较短,需要细心看,另外他使用的Unity版本跟老师讲的不一样,请尽量使用跟教程同一个版本,或者参考老师给的教程
配置需要很多耐心,请做好准备!
多图预警!!!
可能前期配置有点长,但是请耐心细心的跟着做,不然很可能会踩坑噢…
最后一个前期工作啦!!!请耐心
step3: 将前面在Vuforia复制的license key黏贴到这个位置,我们的AR Camera就配置好啦!
(Tips:Max Simultaneous Trackes Images控制同时识别图的数量,我这里是3,根据需求设置)
step5: 创建好之后如下,选择刚刚从Vuforia下载的Database和你想要的识别图
step1: 按照我之前的博客 ch06-物理系统与碰撞——Arrowshooting射箭小游戏,配置各种预制件
step2:经过测试,在AR模式下,高速物体更容易穿模,所以Aroow的配置需做如下更改
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
protected SSAction() { }
public virtual void Start() { throw new System.NotImplementedException(); }
public virtual void Update() { throw new System.NotImplementedException(); }
public virtual void FixedUpdate() { throw new System.NotImplementedException(); }
}
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback { void SSActionEvent(SSAction source, GameObject arrow = null); }
public class SSActionManager : MonoBehaviour, ISSActionCallback
{
private Dictionary actions = new Dictionary();
private List waitingAdd = new List();
private List waitingDelete = new List();
protected void Update()
{
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy) waitingDelete.Add(ac.GetInstanceID());
else if (ac.enable) ac.Update();
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
protected void FixedUpdate()
{
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy) waitingDelete.Add(ac.GetInstanceID());
else if (ac.enable) ac.FixedUpdate();
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void SSActionEvent(SSAction source, GameObject arrow = null)
{
if (arrow != null)
{
ArrowTremble tremble = ArrowTremble.GetSSAction();
this.RunAction(arrow, tremble, this);
}
else
{
Controllor scene_controller = (Controllor)SSDirector.GetInstance().CurrentScenceController;
}
}
}
public class ArrowFlyActionManager : SSActionManager
{
private ArrowFlyAction fly;
public Controllor scene_controller;
protected void Start()
{
scene_controller = (Controllor)SSDirector.GetInstance().CurrentScenceController;
scene_controller.action_manager = this;
}
public void ArrowFly(GameObject arrow, Vector3 wind, float holdTime)
{
fly = ArrowFlyAction.GetSSAction(wind, holdTime);
this.RunAction(arrow, fly, this);
}
}
public class ArrowFlyAction : SSAction
{
private float holdTime;
public Vector3 wind;
private ArrowFlyAction() { }
public static ArrowFlyAction GetSSAction(Vector3 wind, float holdTime_)
{
ArrowFlyAction action = CreateInstance();
action.holdTime = holdTime_;
action.wind = wind;
return action;
}
public override void Update() { }
public override void FixedUpdate()
{
//加上风的力
this.gameobject.GetComponent().AddForce(wind*10, ForceMode.Force);
if (this.transform.position.z >=20 || this.gameobject.tag == "hit")
{
this.destroy = true;
this.callback.SSActionEvent(this, this.gameobject);
}
}
public override void Start()
{
Transform arrow = this.gameobject.transform;
arrow.GetComponent().isKinematic = false;
//这个是添加力的
arrow.GetComponent().AddForce((arrow.GetChild(0).position-arrow.position) * 40f * holdTime, ForceMode.Impulse);
arrow.SetParent(null);//解除父子关系
}
}
public class ArrowTremble : SSAction
{
float radian = 0;
float per_radian = 3f;
float radius = 0.01f;
Vector3 old_pos;
public float left_time = 0.8f;
private ArrowTremble() { }
public override void Start() { old_pos = transform.position; }
public static ArrowTremble GetSSAction()
{
ArrowTremble action = CreateInstance();
return action;
}
public override void Update()
{
left_time -= Time.deltaTime;
if (left_time <= 0)
{
transform.position = old_pos;
this.destroy = true;
this.callback.SSActionEvent(this);
}
radian += per_radian;
float dy = Mathf.Cos(radian) * radius;
transform.position = old_pos + new Vector3(0, dy, 0);
}
public override void FixedUpdate() { }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowCollider : MonoBehaviour
{
public Controllor scene_controller;
public scoreRecorder recorder;
void Start()
{
scene_controller = SSDirector.GetInstance().CurrentScenceController as Controllor;
recorder = singleton.Instance;
}
void OnTriggerEnter(Collider c)
{
if (c.gameObject.name == "T1" || c.gameObject.name == "T2" || c.gameObject.name == "T3" || c.gameObject.name == "T4" || c.gameObject.name == "T5")
{
gameObject.transform.parent.gameObject.GetComponent().isKinematic = true;
gameObject.SetActive(false);
//Debug.Log(this.gameObject.transform.position);
float dis = Mathf.Sqrt(this.gameObject.transform.position.x * this.gameObject.transform.position.x + this.gameObject.transform.position.y * this.gameObject.transform.position.y);
float point = 0;
if (dis >= 0 && dis < 1) point = 5f;
else if(dis >= 1 && dis < 2) point = 4f;
else if(dis >= 2 && dis < 3) point = 3f;
else if(dis >= 3 && dis < 4) point = 2f;
else if(dis >= 4 && dis < 5) point = 1f;
//Debug.Log("集中了靶子:" + c.gameObject.name + " "+ point);
recorder.Record((int)Mathf.Floor(point));
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowFactory : MonoBehaviour
{
private GameObject arrow = null;
private GameObject AB = null;
private GameObject ImageTargetAB = null;
private List usingArrowList = new List();
private Queue unusedArrowList = new Queue();
public Controllor sceneControler;
private int arrownum = 0;
private List ArrowList = new List();
public void initiate(GameObject AB_,GameObject ImageTargetAB_)
{
AB = AB_;
ImageTargetAB = ImageTargetAB_;
}
private void Start()
{
GameObject a;
for(int i = 0; i < 20; i++)
{
a = GameObject.Find("Arrow (" + i + ")");
ArrowList.Add(a);
a.SetActive(false);
}
}
public GameObject GetArrow()
{
//还原工厂
//AB.transform.SetPositionAndRotation(new Vector3(0, 0, 0), Quaternion.Euler(0, 90, 0));
//AB.transform.localScale = new Vector3(1, 1, 1);
//if (unusedArrowList.Count == 0)
//{
// //arrow = Instantiate(Resources.Load("Prefabs/Arrow"));
// arrow = GameObject.Find("Arrow");
// Debug.Log("Arrow的位置:" + arrow.transform.position);
// Debug.Log("Arrow的角度:" + arrow.transform.rotation);
//}
//else
//{
// arrow = unusedArrowList.Dequeue();
// arrow.gameObject.SetActive(true);
//}
//arrow.transform.parent = AB.transform;
//arrow.transform.SetPositionAndRotation(ImageTargetAB.transform.position, Quaternion.Euler(ImageTargetAB.transform.rotation.x, ImageTargetAB.transform.rotation.y+90, ImageTargetAB.transform.rotation.z ));
//Debug.Log("Image的位置: " + ImageTargetAB.transform.position);
//Debug.Log("Image的角度: " + ImageTargetAB.transform.rotation);
//arrow.transform.localScale = new Vector3(1.6f, 1.6f, 1.6f);
//usingArrowList.Add(arrow);
//return arrow;
//因为AR识别的图片角度不精确,没有办法使用工厂模式动态添加Arrow,使用这种方式能更好的保证体验感
if (arrownum < 20)
{
arrow = ArrowList[arrownum];
arrow.SetActive(true);
arrownum++;
}
usingArrowList.Add(arrow);
return arrow;
}
public void FreeArrow()
{
for (int i = 0; i < usingArrowList.Count; i++)
{
if (usingArrowList[i].transform.position.y <= -15 || usingArrowList[i].transform.position.y >= 15)
{
usingArrowList[i].GetComponent().isKinematic = true;
usingArrowList[i].SetActive(false);
usingArrowList[i].transform.position = new Vector3(0, 0, 0);
unusedArrowList.Enqueue(usingArrowList[i]);
usingArrowList.Remove(usingArrowList[i]);
i--;
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Controllor : MonoBehaviour, IUserAction, ISceneController
{
public scoreRecorder recorder;
public ArrowFactory arrow_factory;
public ArrowFlyActionManager action_manager;
public UserGUI user_gui;
SSDirector director;
private GameObject AB;
private GameObject arrow;
private GameObject target;
private GameObject ImageTargetAB;
private bool game_start = false;
private string wind = "";
private float wind_directX;
private float wind_directY;
public string GetScore() {
return recorder.getScore();
}
public string GetWind() {
return wind;
}
public void Restart() { SceneManager.LoadScene(0); }
public void BeginGame() { game_start = true; }
void Update() { if (game_start) arrow_factory.FreeArrow(); }
public void LoadResources() {
//找到场景中的弓和靶
target = GameObject.Find("target");
AB = GameObject.Find("AB");
ImageTargetAB = GameObject.Find("ImageTargetAB");//这个主要是控制弓箭的方向
}
public bool haveArrowOnPort() { return (arrow != null); }
void Start()
{
director = SSDirector.GetInstance();
director.CurrentScenceController = this;
LoadResources();
arrow_factory = singleton.Instance;
arrow_factory.initiate(AB, ImageTargetAB);
recorder = gameObject.AddComponent() as scoreRecorder;
user_gui = gameObject.AddComponent() as UserGUI;
action_manager = gameObject.AddComponent() as ArrowFlyActionManager;
}
public void create()
{
if (arrow == null)
{
wind_directX = Random.Range(-2f, 2f);
wind_directY = Random.Range(-2f, 2f);
CreateWind();
arrow = arrow_factory.GetArrow();
}
}
public void UpdateArrowPositioin()
{
arrow.transform.SetPositionAndRotation(ImageTargetAB.transform.position, Quaternion.Euler(ImageTargetAB.transform.rotation.x, ImageTargetAB.transform.rotation.y+90, ImageTargetAB.transform.rotation.z));
}
public void MoveBow(Vector3 fwd,float Xinput,float Yinput)
{
if (!game_start) { return; }
//这里就不直接用触屏移动弓箭了,使用AR的方式移动并旋转角度
//Vector3 vaxis = Vector3.Cross(fwd, Vector3.right);
//AB.transform.Rotate(vaxis, -Xinput * 5, Space.World);
//Vector3 haxis = Vector3.Cross(fwd, Vector3.up);
//AB.transform.Rotate(haxis, -Yinput * 5, Space.World);
}
public void Shoot(float holdTime)
{
if (game_start)
{
Vector3 wind = new Vector3(wind_directX, wind_directY, 0);
action_manager.ArrowFly(arrow, wind, holdTime);
//这里添加一个音效控件
arrow = null;
}
}
public void CreateWind()
{
string Horizontal = "", Vertical = "", level = "";
if (wind_directX > 0)
{
Horizontal = "西";
}
else if (wind_directX <= 0)
{
Horizontal = "东";
}
if (wind_directY > 0)
{
Vertical = "南";
}
else if (wind_directY <= 0)
{
Vertical = "北";
}
if ((wind_directX + wind_directY) / 2 > -0.5f && (wind_directX + wind_directY) / 2 < 0.5f)
{
level = "1 级";
}
else if ((wind_directX + wind_directY) / 2 > -1f && (wind_directX + wind_directY) / 2 < 1f)
{
level = "2 级";
}
else if ((wind_directX + wind_directY) / 2 > -1.5f && (wind_directX + wind_directY) / 2 < 1.5f)
{
level = "3 级";
}
else if ((wind_directX + wind_directY) / 2 > -2f && (wind_directX + wind_directY) / 2 < 2f)
{
level = "4 级";
}
wind = Horizontal + Vertical + "风" + " " + level;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController
{
void LoadResources();
}
public interface IUserAction
{
void MoveBow(Vector3 fwd, float Xinput, float Yinput);
void Shoot(float holdTime);
string GetScore();
void Restart();
string GetWind();
void BeginGame();
void create();
bool haveArrowOnPort();
void UpdateArrowPositioin();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class scoreRecorder : MonoBehaviour
{
public int score;
void Start() { score = 0; }
public void Record(int point) {
score = point + score;
Debug.Log("scoreRecorder:" + score);
}
public string getScore()
{
return score.ToString();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class singleton : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null)
{
Debug.LogError("An instance of " + typeof(T)
+ " is needed in the scene, but there is none.");
}
}
return instance;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class : object
{
private static SSDirector _instance;
public ISceneController CurrentScenceController { get; set; }
public static SSDirector GetInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
private IUserAction action;
GUIStyle style = new GUIStyle();
GUIStyle style2 = new GUIStyle();
GUIStyle style3 = new GUIStyle();
private bool game_start = false;
private float deltatime = 0;
public scoreRecorder recorder;
public AudioSource music;//音源AudioSource相当于播放器,而音效AudioClip相当于磁带
public AudioClip shoot;//这里我要给主角添加跳跃的音效
public Camera cam;
private float holdTime =0;
private int arrow_sum = 21;
void Start()
{
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
recorder = singleton.Instance;
//给对象添加一个AudioSource组件
music = gameObject.AddComponent();
//设置不一开始就播放音效
music.playOnAwake = false;
//加载音效文件,我把跳跃的音频文件命名为jump
shoot = Resources.Load("music/shoot");
//把音源music的音效设置为jump
music.clip = shoot;
style.normal.textColor = new Color(0, 0, 0, 1);
style.fontSize = 32;
style2.normal.textColor = new Color(1, 1, 1);
style2.fontSize = 64;
style3.normal.textColor = new Color(1, 1, 1);
style3.fontSize = 32;
holdTime = 0;
cam = Camera.main;
}
void Update()
{
if (game_start)
{
if (action.haveArrowOnPort())
{
//更新箭的位置
//action.UpdateArrowPositioin();
//这里改成每2秒射击一次
deltatime += Time.deltaTime;
Vector3 fwd = cam.transform.forward;
fwd.Normalize();
if (deltatime >= 1.3)
{
//action.MoveBow(fwd, Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
holdTime = deltatime;
action.Shoot(holdTime);
holdTime = 0;
deltatime = 0;
}
}
else if(arrow_sum>0)
{
deltatime += Time.deltaTime;
if (deltatime >= 0.5)
{
action.create();
deltatime = 0;
//播放音效
music.Play();
arrow_sum--;
}
}
}
}
private void OnGUI()
{
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
if (game_start)
{
GUI.Label(new Rect(10, 5, 200, 50), "风向: ", style);
GUI.Label(new Rect(10, 45, 200, 50), "分数:", style);
//GUI.Label(new Rect(10, 85, 200, 50), "力量:", style);
//Debug.Log("风&&&&&&&&&&&&&&&&&&" + action.GetScore());
GUI.Label(new Rect(90, 5, 200, 50), action.GetWind(), style);
//GUI.Label(new Rect(90, 45, 200, 50), action.GetScore(), style);
GUI.Label(new Rect(90, 45, 200, 50), recorder.score.ToString(), style);
//GUI.Label(new Rect(90, 85, 200, 50), holdTime.ToString(), style);
if (GUI.Button(new Rect(Screen.width - 200, 5, 200, 100), "重新开始") )
{
Time.timeScale = 1;
action.Restart();
return;
}
if (arrow_sum==0 && recorder.score >= 50)
{
GUI.Label(new Rect(Screen.width / 2 - 220, Screen.width / 2 - 300, 100, 100), "Congratulations!", style2);
Time.timeScale = 0;
}
else if (arrow_sum == 0 && recorder.score < 50)//射完了箭
{
GUI.Label(new Rect(Screen.width / 2 - 220, Screen.width / 2 - 300, 100, 100), "Please Try Again!", style2);
Time.timeScale = 0;
}
}
else
{
Time.timeScale = 1;
GUI.Label(new Rect(Screen.width / 2 - 220, Screen.width / 2 - 400, 100, 100), "Arrow Shooting", style2);
GUI.Label(new Rect(Screen.width / 2 - 180, Screen.width / 2 - 300, 100, 100), " 获得50分即游戏胜利!", style3);
if (GUI.Button(new Rect(Screen.width / 2 - 120, Screen.width / 2 - 150, 200, 100), "游戏开始"))
{
game_start = true;
action.BeginGame();
}
}
}
}
using UnityEngine;
using System.Collections;
using Vuforia;
public class clearCamera : MonoBehaviour
{
// Use this for initialization
void Start()
{
VuforiaARController.Instance.RegisterVuforiaStartedCallback(OnVuforiaStarted);
VuforiaARController.Instance.RegisterOnPauseCallback(OnPaused);
}
void Update()
{
}
private void OnVuforiaStarted()
{
CameraDevice.Instance.SetFocusMode(
CameraDevice.FocusMode.FOCUS_MODE_CONTINUOUSAUTO);
}
private void OnPaused(bool paused)
{
if (!paused)
{ // resumed
// Set again autofocus mode when app is resumed
CameraDevice.Instance.SetFocusMode(
CameraDevice.FocusMode.FOCUS_MODE_CONTINUOUSAUTO);
}
}
}
链接:
github仓库
bilibili视频