游戏要求
开发一个第一人称射箭游戏,应满足以下要求:
- 基础分(2分):有博客;
- 1-3分钟视频(2分):视频呈现游戏主要游玩过程;
- 地形(2分):使用地形组件,上面有草、树;
- 天空盒(2分):使用天空盒,天空可随玩家位置 或 时间变化 或 按特定按键切换天空盒;
- 固定靶(2分):有一个以上固定的靶标;
- 运动靶(2分):有一个以上运动靶标,运动轨迹,速度使用动画控制;
- 射击位(2分):地图上应标记若干射击位,仅在射击位附近可以拉弓射击,每个位置有 n 次机会;
- 驽弓动画(2分):支持蓄力半拉弓,然后 hold,择机 shoot;
- 游走(2分):玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
- 碰撞与计分(2分):在射击位,射中靶标的相应分数,规则自定;
具体设计为:
-设置静止靶单倍区和运动靶双倍区两个区域可以射击,其他区域没有靶子无法射击得分。
-靶子分为白、黑、红自外向里为3、4、5分,集中运动靶分数则翻倍
-一共有10支箭矢,全部用完则游戏结束
-长按空格可以为弓弩蓄力,再次按下空格则发射
游戏效果展示
视频展示:
主要功能代码实现:
游戏总控:MainController
负责游戏一开始的初始化,和游戏进行中将信息传递到不同的脚本中,如为视图View传递分数、当前位置等信息。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/* 游戏状态,0为准备进行,1为正在进行游戏,2为结束 */
enum GameState {
Ready = 0, Playing = 1, GameOver = 2
};
public class MainController : MonoBehaviour
{ private View view; // 游戏视图
private CrossController crossController;//十字弩控制器
private int gameState;//游戏状态
public GUISkin gameSkin; //按键皮肤
public Material[] skyboxMaterials;//存储天空盒数组
private int index;//天空盒数组下标
private int lessnum;//剩余箭矢数
public GameObject cross; //十字弩
// Start is called before the first frame update
void Start()//在start中进行初始化
{Director.GetInstance().mainController = this;
crossController= gameObject.AddComponent<CrossController>();
gameState = (int)GameState.Ready;
view = gameObject.AddComponent<View>();
view.gameSkin = gameSkin;
// 隐藏十字弩
cross.SetActive(false);
//携程切换天空盒
index=-1;
StartCoroutine(ChangeSkybox());
}
public void SetGameState(int state) {//设置游戏状态
gameState = state;
}
public void Restart() {//重启游戏函数
view.Init();
lessnum=10;
roundController.Reset();
CrossC crossComponent = cross.GetComponent<CrossC>();
crossComponent.Init();
}
public void ShowPage() {//控制视图view的页面
switch(gameState) {
case 0:
view.ShowHomePage();
break;
case 1:
view.ShowGamePage();
break;
case 2:
view.ShowRestart();
break;
}
}
public void SetViewScore(int score) {//将十字弩的计分传给视图
view.SetScore(score);
}
public void SetViewNum(int num) {//将十字弩的剩余箭矢数传给视图
lessnum=num;
view.SetNum(num);
}
public void SetViewX(float x){//将十字弩的位置传给视图
view.GetX(x);
}
public void Update(){//实时传递游戏状态给视图
CrossC crossComponent = cross.GetComponent<CrossC>();
SetViewScore(crossComponent.getScore());
SetViewNum(crossComponent.getNum());
SetViewX(crossComponent.getX());
if(lessnum==0) SetGameState(2);//剩余箭为0时结束游戏
}
IEnumerator ChangeSkybox()
{
while (true)
{
// 循环选择一个天空盒子材质
index = (index+1)%3;
RenderSettings.skybox = skyboxMaterials[index];
// 等待一段时间后再切换天空盒子
yield return new WaitForSeconds(20);
}
}
}
视图控制:View
负责控制游戏的页面提示,向玩家展示游戏的进行状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class View : MonoBehaviour
{
private MainController mainController;
private int score;//当前得分
private int arrownum;//当前剩余箭矢数
private float x;//当前位置
public GUISkin gameSkin;
// Start is called before the first frame update
void Start()//初始化
{ score = 0;
mainController = Director.GetInstance().mainController;
}
// 分别获取分数、剩余箭矢数和位置的函数
public void SetScore(int score) {
this.score = this.score+score;
}
public void SetNum(int num){
arrownum=num;
}
public void GetX(float a){
x=a;
}
public void Init(){//开始游戏的初始化
score=0;
arrownum=10;
}
public void ShowHomePage() {
AddStartButton();
}
public void ShowGamePage(){
AddGameLabel();
}
public void ShowRestart() {
AddRestartlabel();
}
public void AddStartButton(){//GameState=0时,展示初始界面
GUI.skin = gameSkin;
if(GUI.Button(new Rect(340,200,160,80),"点击开始")){
mainController.Restart();
mainController.SetGameState((int)GameState.Playing);//按下开始按钮则进入游戏状态
}
}
public void AddGameLabel() {//GameState=1时,展示游戏界面
GUIStyle labelStyle = new GUIStyle();
labelStyle.normal.textColor = Color.black;
labelStyle.fontSize = 30;
mainController.cross.SetActive(true);
mainController.cross.transform.rotation=Quaternion.Euler(-10f, 0f, 0f);
GUI.Label(new Rect(570, 10, 100, 50), "得分: " + score, labelStyle);//展示得分、剩余箭矢数及提示当前区域
GUI.Label(new Rect(570, 45, 100, 50), "剩余箭数: " + arrownum, labelStyle);
if(x<-9.96||(x>10.05&&x<19.00)||x>40.00) GUI.Label(new Rect(10,10,100,50),"当前区域:无箭靶",labelStyle);
else if(x>=-9.96&&x<10.05) GUI.Label(new Rect(10,10,100,50),"当前区域:静止靶单倍区",labelStyle);
else GUI.Label(new Rect(10,10,100,50),"当前区域:移动靶双倍区",labelStyle);
}
public void AddRestartlabel(){//GameState=2时,展示结束界面
GUI.skin = gameSkin;
GUIStyle labelStyle = new GUIStyle();
labelStyle.normal.textColor = Color.red;
labelStyle.fontSize = 30;
GUI.Label(new Rect(340,100,100,50),"最终得分:"+score,labelStyle);//展示最终得分
if(GUI.Button(new Rect(340,200,160,80),"再来一次")){
mainController.Restart();
mainController.SetGameState((int)GameState.Playing);//按下再来一次按钮则再次进入游戏状态
}
}
void OnGUI() {
mainController.ShowPage();
}
}
十字弩:CrossC
挂载在十字弩物体上,控制十字弩的射击、蓄力等动画呈现,以及控制十字弩可以上下左右移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CrossC : MonoBehaviour
{
private Animator ani;
public float horizontalinput;//水平参数
public float Verticalinput;//垂直参数
float speed=1.6f;//速度参数
public float power;//蓄力系数
public Arrow _arrow;//箭矢对象
private Arrow arrow;
private bool flag=false;
private int num;
private bool hold;//记录按下空格应该是拉弓蓄力还是发射
private bool wait;
public Text hintText;
public Text powerText;
void Start()//初始化
{ ani=GetComponent<Animator>();
hintText.gameObject.SetActive(false);
powerText.gameObject.SetActive(false);
num=10;
hold=true; wait=true;
}
public void Init(){
num=10;
}
// Update is called once per frame
void Update()
{ horizontalinput = Input.GetAxis("Horizontal");
//AD方向控制
Verticalinput = Input.GetAxis("Vertical");
if (horizontalinput!=0&&Verticalinput!=0)
{
horizontalinput = horizontalinput * 1.4f;
Verticalinput = Verticalinput * 1.4f;
}
//WS方向控制
this.transform.Translate(Vector3.right * horizontalinput * Time.deltaTime * speed);
//控制该物体向侧方移动
this.transform.Translate(Vector3.up* Verticalinput * Time.deltaTime * speed);
if(iflegalarea()) {ShowHint("您已超出射击区域,该区域无射击靶"); flag=true;}
else if(flag&&!iflegalarea()) {HideHint(); flag=false;}
if(power!=0f) {
int ppower=(int)(power*100);
powerText.text="蓄力"+ppower+"%";
powerText.gameObject.SetActive(true);
}
else{
powerText.gameObject.SetActive(false);
}
//控制该物体向上下移动
if(Input.GetKey(KeyCode.Space))//按下空格
{ if(hold){//蓄力
Transform childTransform = transform.Find("箭");
childTransform.gameObject.SetActive(true);
ani.SetBool("pull",true);
ani.SetBool("shoot",false);
if (power < 1)
{
power += Time.deltaTime * 0.5f;
}
else
{
power = 1;
}
ani.SetFloat("pullpower",power);
wait=true;
}
else{//发射
if(wait){ani.SetBool("shoot",true);
ani.SetBool("pull",false);
ani.SetBool("hold",false);
initArrow();
RunArrow();
wait=false;
Transform childTransform = transform.Find("箭");
childTransform.gameObject.SetActive(false);
power=0f;
ani.SetFloat("pullpower",power);
Invoke("SetHoldTrue", 1f);}
}
}
if (Input.GetKeyUp(KeyCode.Space)){//抬起空格,保持
ani.SetBool("pull",false);
hold=false;//预备射击}
}
public void initArrow()//创建箭矢对象
{
Vector3 np=this.transform.position;
arrow = Instantiate(_arrow,np,Quaternion.identity);
arrow.gameObject.SetActive(true);
}
public void RunArrow()//发射箭矢对象
{
if(arrow != null)
{
Rigidbody rb = arrow.GetComponent<Rigidbody>();
if (rb)
{
rb.drag = 0.5f;
Vector3 shootingDirection = this.transform.forward;
rb.AddForce(shootingDirection * (power*20f+5), ForceMode.Impulse);
num--;
}
}
}
//向其他脚本传递数据
public int getScore()//若箭矢命中,传回分数并提示
{
if(arrow != null && arrow.attacked&&!arrow.isRecord){
arrow.isRecord=true;
if(arrow.score==5) ShowHint("正中红心!",0);
else ShowHint("命中!",0);
float x=this.transform.position.x;
if(x>=19.00&&x<=40.00) return arrow.score*2;
else return arrow.score;
}
return 0;
}
public int getNum(){
return num;
}
public float getX(){
return this.transform.position.x;
}
public void ShowHint(string message,int state=1)//提示标签控制
{
// 显示提示文字
hintText.text = message;
hintText.gameObject.SetActive(true);
// 延迟几秒后隐藏提示文字
if(state==0) Invoke("HideHint", 1f);
}
void HideHint()
{
hintText.gameObject.SetActive(false);
}
void SetHoldTrue(){
hold=true;
}
private bool iflegalarea(){//判断是否在射击区域,不在则提示
float x=this.transform.position.x;
return (x<-9.96||(x>10.05&&x<19.00)||x>40.00);
}
}
箭矢:Arrow
挂载在箭矢物体上,主要负责响应碰撞事件并传回击中的分数:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CrossC : MonoBehaviour
{
private Animator ani;
public float horizontalinput;//水平参数
public float Verticalinput;//垂直参数
float speed=1.6f;//速度参数
public float power;//蓄力系数
public Arrow _arrow;//箭矢对象
private Arrow arrow;
private bool flag=false;
private int num;
private bool hold;//记录按下空格应该是拉弓蓄力还是发射
private bool wait;
public Text hintText;
public Text powerText;
void Start()//初始化
{ ani=GetComponent<Animator>();
hintText.gameObject.SetActive(false);
powerText.gameObject.SetActive(false);
num=10;
hold=true; wait=true;
}
public void Init(){
num=10;
}
// Update is called once per frame
void Update()
{ horizontalinput = Input.GetAxis("Horizontal");
//AD方向控制
Verticalinput = Input.GetAxis("Vertical");
if (horizontalinput!=0&&Verticalinput!=0)
{
horizontalinput = horizontalinput * 1.4f;
Verticalinput = Verticalinput * 1.4f;
}
//WS方向控制
this.transform.Translate(Vector3.right * horizontalinput * Time.deltaTime * speed);
//控制该物体向侧方移动
this.transform.Translate(Vector3.up* Verticalinput * Time.deltaTime * speed);
if(iflegalarea()) {ShowHint("您已超出射击区域,该区域无射击靶"); flag=true;}
else if(flag&&!iflegalarea()) {HideHint(); flag=false;}
if(power!=0f) {
int ppower=(int)(power*100);
powerText.text="蓄力"+ppower+"%";
powerText.gameObject.SetActive(true);
}
else{
powerText.gameObject.SetActive(false);
}
//控制该物体向上下移动
if(Input.GetKey(KeyCode.Space))//按下空格
{ if(hold){//蓄力
Transform childTransform = transform.Find("箭");
childTransform.gameObject.SetActive(true);
ani.SetBool("pull",true);
ani.SetBool("shoot",false);
if (power < 1)
{
power += Time.deltaTime * 0.5f;
}
else
{
power = 1;
}
ani.SetFloat("pullpower",power);
wait=true;
}
else{//发射
if(wait){ani.SetBool("shoot",true);
ani.SetBool("pull",false);
ani.SetBool("hold",false);
initArrow();
RunArrow();
wait=false;
Transform childTransform = transform.Find("箭");
childTransform.gameObject.SetActive(false);
power=0f;
ani.SetFloat("pullpower",power);
Invoke("SetHoldTrue", 1f);}
}
}
if (Input.GetKeyUp(KeyCode.Space)){//抬起空格,保持
ani.SetBool("pull",false);
hold=false;//预备射击}
}
public void initArrow()//创建箭矢对象
{
Vector3 np=this.transform.position;
arrow = Instantiate(_arrow,np,Quaternion.identity);
arrow.gameObject.SetActive(true);
}
public void RunArrow()//发射箭矢对象
{
if(arrow != null)
{
Rigidbody rb = arrow.GetComponent<Rigidbody>();
if (rb)
{
rb.drag = 0.5f;
Vector3 shootingDirection = this.transform.forward;
rb.AddForce(shootingDirection * (power*20f+5), ForceMode.Impulse);
num--;
}
}
}
//向其他脚本传递数据
public int getScore()//若箭矢命中,传回分数并提示
{
if(arrow != null && arrow.attacked&&!arrow.isRecord){
arrow.isRecord=true;
if(arrow.score==5) ShowHint("正中红心!",0);
else ShowHint("命中!",0);
float x=this.transform.position.x;
if(x>=19.00&&x<=40.00) return arrow.score*2;
else return arrow.score;
}
return 0;
}
public int getNum(){
return num;
}
public float getX(){
return this.transform.position.x;
}
public void ShowHint(string message,int state=1)//提示标签控制
{
// 显示提示文字
hintText.text = message;
hintText.gameObject.SetActive(true);
// 延迟几秒后隐藏提示文字
if(state==0) Invoke("HideHint", 1f);
}
void HideHint()
{
hintText.gameObject.SetActive(false);
}
void SetHoldTrue(){
hold=true;
}
private bool iflegalarea(){//判断是否在射击区域,不在则提示
float x=this.transform.position.x;
return (x<-9.96||(x>10.05&&x<19.00)||x>40.00);
}
}