一、游戏背景介绍
你是一名经验老道的猎手,而经验来源于平日刻苦的训练。今天你像往常一样来到林中靶场展开固定靶和移动靶射击训练,你相信,百发百中,就在今朝!
二、游戏实现功能
以下全部
三、游戏玩法介绍
1.玩家仅能在白色射箭区域进行射击游戏,在射箭区域外无法进行射击;
2.每次游戏限时30秒,10支箭,倒计时结束或箭矢射完本轮游戏结束;
3.射中标靶后得10分,同时若标靶为移动靶会停止;
4.弓弩移动时会与树木山峦等地形发生碰撞阻挡,请不要尝试强行穿过障碍,否则头晕目眩的感觉可不好受;
5.随着时间流逝游戏内的天色也会变化,享受在无尽日夜射击的快感吧。
(具体按键功能可查看游戏内提示)
四、游戏代码讲解
游戏大体上采用MVC架构制作,以下针对重点对象进行讲解。
1.Crossbow(Player)
游戏中的弓弩可以通过AWSD移动以及通过点击右键控制俯仰旋转,加载了Move和Rotate两个脚本实现对应功能。(弓弩移动存在y轴限制,达到边界时可能减速,需要注意调整俯仰角度)。
Move
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
public float speed = 1.5f;//控制移动速度
public Transform m_transform;
// Use this for initialization
void Start()
{
m_transform = this.transform;
}
// Update is called once per frame
void Update()
{
//向左
if (Input.GetKey(KeyCode.A))
{
if (m_transform.position.y >= 2 && m_transform.position.y <= 3)
m_transform.Translate(Vector3.left * Time.deltaTime * speed);
else if (m_transform.position.y < 2)
m_transform.position = new Vector3(m_transform.position.x, 2, m_transform.position.z);
else
m_transform.position = new Vector3(m_transform.position.x, 3, m_transform.position.z);
}
//向右
if (Input.GetKey(KeyCode.D))
{
if (m_transform.position.y >= 2 && m_transform.position.y <= 3)
m_transform.Translate(Vector3.right * Time.deltaTime * speed);
else if (m_transform.position.y < 2)
m_transform.position = new Vector3(m_transform.position.x, 2, m_transform.position.z);
else
m_transform.position = new Vector3(m_transform.position.x, 3, m_transform.position.z);
}
//向前
if (Input.GetKey(KeyCode.W))
{
if (m_transform.position.y >= 2 && m_transform.position.y <= 3)
m_transform.Translate(Vector3.forward * Time.deltaTime * speed);
else if (m_transform.position.y < 2)
m_transform.position = new Vector3(m_transform.position.x, 2, m_transform.position.z);
else
m_transform.position = new Vector3(m_transform.position.x, 3, m_transform.position.z);
}
//向后
if (Input.GetKey(KeyCode.S))
{
if (m_transform.position.y >= 2 && m_transform.position.y <= 3)
m_transform.Translate(Vector3.back * Time.deltaTime * speed);
else if (m_transform.position.y < 2)
m_transform.position = new Vector3(m_transform.position.x, 2, m_transform.position.z);
else
m_transform.position = new Vector3(m_transform.position.x, 3, m_transform.position.z);
}
}
}
Rotate
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
//上下旋转最大角度限制
public int yMinLimit = -20;
public int yMaxLimit = 60;
//旋转速度
public float xSpeed = 250.0f;//左右旋转速度
public float ySpeed = 120.0f;//上下旋转速度
//旋转角度
private float x = 0.0f;
private float y = 0.0f;
void Update()
{
if (Input.GetMouseButton(1))
{
//Input.GetAxis("MouseX")获取鼠标移动的X轴的距离
x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
y = ClampAngle(y, yMinLimit, yMaxLimit);
//欧拉角转化为四元数
Quaternion rotation = Quaternion.Euler(y, x, 0);
transform.rotation = rotation;
}
}
//角度范围值限定
static float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp(angle, min, max);
}
}
2.Target
游戏中的标靶分为固定靶和移动靶,移动靶由动画控制移动,本体为自制圆柱组合体。需要注意的是Target加载了自制模块Freeze来控制与箭矢碰撞时的双方冻结,移动靶的移动动画中止和得分累计。
Target
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Target
{
public GameObject target;
public int mtype;
public Target(Vector3 position, int id, int type)
{
mtype = type;
if (type == 0)
target = GameObject.Instantiate(Resources.Load("Prefabs/target", typeof(GameObject))) as GameObject;
else if (type == 1)
target = GameObject.Instantiate(Resources.Load("Prefabs/wandertarget", typeof(GameObject))) as GameObject;
else
target = GameObject.Instantiate(Resources.Load("Prefabs/wandertarget2", typeof(GameObject))) as GameObject;
target.name = "target" + id;
target.transform.position = position;
target.AddComponent<Freeze>();
}
}
TargetCtrl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetCtrl
{
Target targetModel;
public void CreateTarget(Vector3 position, int id, int type)
{
if (targetModel == null)
{
targetModel = new Target(position, id, type);
}
}
public void DeleteTarget()
{
Object.DestroyImmediate(targetModel.target);
}
}
Freeze
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Freeze : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Arr"))
{
gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
if (gameObject.GetComponent<Animator>() != null)
gameObject.GetComponent<Animator>().enabled = false;
collision.gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
GameObject.FindWithTag("Admin").GetComponent<UserGUI>().score += 10;
}
}
}
3.Arrow
游戏中的箭矢模型源自资源商店中的RyuGiKen,自带功能十分完善,这里只添加了赋予发射力的函数Fire。
Arrow
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Arrow
{
public GameObject arrow;
public bool isHit = false;
public Collider ac;
public TrailRenderer tr;
public Rigidbody rb;
public float time;
public float power;
public Arrow(Vector3 position, Quaternion rotation, int id)
{
arrow = GameObject.Instantiate(Resources.Load("Prefabs/arrow", typeof(GameObject))) as GameObject;
arrow.name = "arrow" + id;
arrow.transform.position = position;
arrow.transform.rotation = rotation;
rb = arrow.GetComponent<Rigidbody>();
ac = arrow.GetComponent<Collider>();
tr = arrow.GetComponent<TrailRenderer>();
}
}
ArrowCtrl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowCtrl
{
Arrow arrowModel;
public void CreateArrow(Vector3 position, Quaternion rotation, int id)
{
arrowModel = new Arrow(position, rotation, id);
}
public void DeleteArrow()
{
Object.DestroyImmediate(arrowModel.arrow);
}
public void Fire(float power)
{
arrowModel.rb.AddForce(arrowModel.arrow.transform.forward * power * 10);
}
}
4.ShootArea
直接在指定地点生成平面即可。
ShootArea
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootArea
{
public GameObject shootarea;
public ShootArea(Vector3 position, int id)
{
shootarea = GameObject.Instantiate(Resources.Load("Prefabs/plane", typeof(GameObject))) as GameObject;
shootarea.name = "shootarea" + id;
shootarea.transform.position = position;
}
}
ShootAreaCtrl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootAreaCtrl
{
ShootArea shootareaModel;
public void CreateShootArea(Vector3 position, int id)
{
if (shootareaModel == null)
{
shootareaModel = new ShootArea(position, id);
}
}
public void DeleteShootArea()
{
Object.DestroyImmediate(shootareaModel.shootarea);
}
}
5.SkyBox
创建空对象挂载脚本,添加天空盒轮换元素,每10秒一次变换。
SkyBoxChange
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkyBoxChange : MonoBehaviour
{
public Material[] skyboxMaterials;
public int index = 0;
// Start is called before the first frame update
void Start()
{
StartCoroutine(ChangeSkybox());
}
IEnumerator ChangeSkybox()
{
while (true)
{
// 选择一个天空盒子材质
int tmp = (index + 1) % 3;
index = tmp;
RenderSettings.skybox = skyboxMaterials[tmp];
// 等待一段时间后再切换天空盒子
yield return new WaitForSeconds(10);
}
}
}
6.FirstCtrl
总控制器,完成大部分资源生成与控制,管理射击过程。
FirstCtrl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstCtrl : MonoBehaviour
{
public TargetCtrl[] targetCtrls;
public Vector3[] targetLocation;
public ShootAreaCtrl[] SACtrls;
public Vector3[] SALocation;
public ArrowCtrl[] arrowCtrls;
public UserGUI uGUI;
public bool countdown;
public bool shooting;
public float powering;
public Animator SAanimator;
void Awake()
{
targetLocation = new Vector3[] { new Vector3(0, 5, 5), new Vector3(10, 5, 5), new Vector3(5, 5, 10) , new Vector3(-135, 5, -33) , new Vector3(-140, 5, -40) };
targetCtrls = new TargetCtrl[5];
for(int k = 0; k < 3; k++)
{
targetCtrls[k] = new TargetCtrl();
targetCtrls[k].CreateTarget(targetLocation[k], k + 1, 0);
}
targetCtrls[3] = new TargetCtrl();
targetCtrls[3].CreateTarget(targetLocation[3], 4, 1);
targetCtrls[4] = new TargetCtrl();
targetCtrls[4].CreateTarget(targetLocation[4], 5, 2);
SALocation = new Vector3[] { new Vector3(-120, 0.5f, -50), new Vector3(4, 0.5f, -8) };
SACtrls = new ShootAreaCtrl[2];
for (int k = 0; k < 2; k++)
{
SACtrls[k] = new ShootAreaCtrl();
SACtrls[k].CreateShootArea(SALocation[k], k + 1);
}
arrowCtrls = new ArrowCtrl[11];
gameObject.AddComponent<UserGUI>();
uGUI = gameObject.GetComponent<UserGUI>();
SAanimator = GameObject.FindWithTag("Player").GetComponent<Animator>();
countdown = false;
shooting = false;
powering = 0;
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.G) && uGUI.Sper)
{
countdown = true;
uGUI.time = 30;
uGUI.score = 0;
uGUI.arrow = 10;
GameObject[] SLiving = GameObject.FindGameObjectsWithTag("Arr");
foreach (GameObject gameObject in SLiving)
{
Destroy(gameObject, 0);
}
GameObject[] Targets = GameObject.FindGameObjectsWithTag("Tar");
foreach (GameObject gameObject in Targets)
{
if (gameObject.GetComponent<Animator>() != null)
gameObject.GetComponent<Animator>().enabled = true;
}
}
if (Input.GetKey(KeyCode.T))
{
uGUI.showTips = true;
}
if (Input.GetKey(KeyCode.Y))
{
uGUI.showTips = false;
}
if (countdown)
{
if (uGUI.time > 0 && uGUI.Sper)
uGUI.time -= Time.deltaTime;
else
countdown = false;
if (Input.GetKeyDown(KeyCode.Mouse0) && uGUI.arrow > 0 && !shooting)
{
powering = 0;
SAanimator.SetTrigger("Holding");
uGUI.arrow--;
shooting = true;
}
if (Input.GetKey(KeyCode.Mouse0) && shooting)
{
powering += Time.deltaTime;
SAanimator.SetFloat("powering", powering);
}
if (Input.GetKey(KeyCode.R) && shooting)
{
powering = 0;
}
if (Input.GetKey(KeyCode.F) && shooting)
{
arrowCtrls[uGUI.arrow] = new ArrowCtrl();
arrowCtrls[uGUI.arrow].CreateArrow(GameObject.FindWithTag("Player").transform.position, GameObject.FindWithTag("Player").transform.rotation, uGUI.arrow);
if (powering <= 1)
arrowCtrls[uGUI.arrow].Fire(powering);
else
arrowCtrls[uGUI.arrow].Fire(1);
SAanimator.SetTrigger("Fire");
shooting = false;
}
}
}
}
7.UserGUI
较为粗糙的UI,起提示作用。
UserGUI
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
public GUIStyle bigStyle,leftStyle;//自定义字体格式
public string gameMessage;
public bool Sper;
public int score;
public float time;
public int arrow;
public bool showTips;
// Start is called before the first frame update
void Start()
{
gameMessage = "";
//大字体初始化
bigStyle = new GUIStyle();
bigStyle.normal.textColor = Color.red;
bigStyle.normal.background = null;
bigStyle.fontSize = 50;
bigStyle.alignment = TextAnchor.MiddleCenter;
//左字体初始化
leftStyle = new GUIStyle();
leftStyle.normal.textColor = Color.red;
leftStyle.normal.background = null;
leftStyle.fontSize = 50;
leftStyle.alignment = TextAnchor.MiddleLeft;
Sper = false;
score = 0;
time = 30;
arrow = 10;
}
// Update is called once per frame
void Update()
{
}
void OnGUI()
{
Vector3 vec3 = GameObject.FindWithTag("Player").transform.position;
if (vec3.x > -11 && vec3.x < 19 && vec3.z > -10.5f && vec3.z < -6.5f) // 4 -8
{
gameMessage = "处于【固定靶射击】区域内";
Sper = true;
}
else if (vec3.x > -135 && vec3.x < -105 && vec3.z > -52.5f && vec3.z < -48.5f) // -120 -50
{
gameMessage = "处于【移动靶射击】区域内";
Sper = true;
}
else
{
gameMessage = "不处于游戏区域内";
Sper = false;
arrow = 10;
time = 30;
}
GUI.Label(new Rect(0, 50, 700, 50), gameMessage, bigStyle);
if (Sper)
{
GUI.Label(new Rect(10, 100, 300, 100), "Score: " + score, leftStyle);
GUI.Label(new Rect(10, 150, 350, 150), "Time: " + (int)time + " / 30s", leftStyle);
GUI.Label(new Rect(10, 200, 350, 200), "Arrow: " + arrow + " / 10", leftStyle);
GUI.Label(new Rect(10, 250, 350, 250), "按T打开提示", leftStyle);
}
if (showTips)
{
GUI.Box(new Rect(50, 50, 1050, 1050), "游戏说明\n" +
"1.到达射箭区后按G开始/重置游戏\n" +
"2.按左键开始蓄力,按R重置蓄力,按F开火\n" +
"3.按AWSD移动弓弩,按右键控制旋转以及俯仰\n" +
"4.按Y关闭提示", leftStyle);
}
}
}
五、实机演示
Bilibili: Unity3D小游戏 林中射手_哔哩哔哩bilibili_第一人称