3D game第三次作业
1.游戏对象运动的本质是什么?
对象位置属性,方向属性等的变化。
2.请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)
- 用导数的方法,不断用当前position加上一个位移向量:
例如:(y=-x^2+4x)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
public float speed;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
float dx=Time.deltaTime*speed;
Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+4)*dx,0);
this.transform.position+=dir;
}
}
结果:
- 用transl函数:
void Update()
{
float dx=Time.deltaTime*speed;
Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+4)*dx,0);
this.transform.Translate(dir);
}
结果同上。
- 用lerp
void Update()
{
float dx=Time.deltaTime*speed;
Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+4)*dx,0);
this.transform.position=Vector3.Lerp(this.transform.position,this.transform.position+dir,1);
}
结果同上。
3.编程实践《牧师与魔鬼》
结果展示
先附上项目链接(文末也有)
你可以直接下载我的项目,解压后用unity打开(2020版本以上)
点击这里下载项目压缩包(29 MB): 项目下载
如果不想下载,可以在这里查看代码
代码传送门:代码传送门
首先必须告诉第一次接触MVC结构的小朋友
unity 3d的所有c#代码都是放在一起编译的。
就算你放在仓库里不用,或者放在不用的文件夹里,他们都是在一起编译的。
可以相互调用彼此的类,或者static变量。
因此关于导演类Direct,场记类FirstController放在哪里,就不用多说了吧。
导演类就放在Asset里睡觉,场记拖到照相机上
游戏中提及的物体
左河岸,右河岸,水,牧师,魔鬼,船。
玩家动作表
动作 | 条件 | 结果 |
---|---|---|
点击人物 | 人物与船在同一海岸,船静止 | 人下船或者人上船 |
点击船 | 船静止,且船上必须有人 | 船和船上的人移到另一岸 |
点击重玩按钮 | 船不动时即可触发 | 所有物体和数据恢复初始状态 |
具体代码
(具体原理见课件)
导演类Direct和一些interface
Direct.cs
导演类负责指明当前正在工作的是哪个场景,这个场景具体干什么导演并不关心,导演只负责管理一些重要的参数,如gamestatus表示游戏是否结束。
interface提供了导演与场景沟通的接口,具体怎么做,取决于各个场景的场记
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Direct : System.Object
{
public static int gameStatus;
public static Direct instance;
public ISceneController currentScene{get;set;}
public static Direct getInstance(){
if (instance== null){
instance=new Direct();
}
return instance;
}
}
public interface ISceneController{
void loadResources();
}
public interface UserAction{
void pndOnClick(GameObject gameObject);
void moveBoot();
void restart();
}
各个物体类
用于生成各个物体,并记录物体的关键参数。
比如boot类的bl,br分别指向船上坐在左边或右边的人。
leftcoast中的ptr[]指向左海岸的6个位置,如果没人就是null。
moveStatus出现在牧师魔鬼类(pnd)和船类(boot)上,记录运动状态。
objectClass.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class leftCoast{
public GameObject obj;
public Vector3 position;
public GameObject[] ptr;
public leftCoast(){
ptr=new GameObject[7];
for(int i=1;i<=6;++i)
ptr[i]=null;
position=new Vector3(-15,0,0);
obj=Object.Instantiate(Resources.Load<GameObject>("profeb/coast"),position,Quaternion.identity);
obj.name="leftCoast";
}
}
public class rightCoast{
public GameObject obj;
public Vector3 position;
public GameObject[] ptr;
public rightCoast(){
ptr=new GameObject[7];
for(int i=1;i<=6;++i)
ptr[i]=null;
position=new Vector3(15,0,0);
obj=Object.Instantiate(Resources.Load<GameObject>("profeb/coast"),position,Quaternion.identity);
obj.name="rightCoast";
}
}
public class water{
public GameObject obj;
public Vector3 position;
public water(){
position=new Vector3(0,-0.5f,0);
obj=Object.Instantiate(Resources.Load<GameObject>("profeb/water"),position,Quaternion.identity);
obj.name="water";
obj.AddComponent(typeof(OnClick));
}
}
public class pnd{
public GameObject obj;
public int pos;
public Vector3 target;
public int moveStatus;
public pnd(int kind,Vector3 position){
pos=1;
moveStatus=0;
if (kind==1){
obj=Object.Instantiate(Resources.Load<GameObject>("profeb/priest"),position,Quaternion.identity);
obj.name="priest";
obj.AddComponent(typeof(OnClick));
}
if (kind==2){
obj=Object.Instantiate(Resources.Load<GameObject>("profeb/devil"),position,Quaternion.identity);
obj.name="devil";
obj.AddComponent(typeof(OnClick));
}
}
}
public class boot{
public GameObject obj;
public Vector3 bootl,bootr;
public GameObject l,r;
public static Vector3 lv,rv;
public int side;
public int moveStatus;
public boot(){
bootl=new Vector3(-3.5f,2,0);
bootr=new Vector3(3.5f,2,0);
obj=Object.Instantiate<GameObject>(Resources.Load<GameObject>("profeb/boot"),bootl,Quaternion.identity);
obj.name="boot";
side=1;
lv=new Vector3(-0.75f,0.9f,0);
rv=new Vector3(0.75f,0.9f,0);
l=null;r=null;
moveStatus=0;
obj.AddComponent(typeof(OnClick));
}
}
场记类
最核心的类。
用于调度各个物体的生成。
继承了UserAction接口,用于处理游戏中发生的一切事件。
并与导演类联系,以确定游戏是否结束等重要事件。
FirstController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour,ISceneController,UserAction
{
public Vector3[] leftpos;
public Vector3[] rightpos;
public int[] leftseat;
public int[] rightseat;
public leftCoast lc;
public rightCoast rc;
public water wat;
public pnd[] p;
public boot b;
// Start is called before the first frame update
void Awake(){
Direct direct=Direct.getInstance();
direct.currentScene=this;
}
void Start()
{
leftpos=new Vector3[7]{new Vector3(0,0,0),new Vector3(-6,3,0),new Vector3(-8,3,0),new Vector3(-10,3,0),
new Vector3(-12,3,0),new Vector3(-14,3,0),new Vector3(-16,3,0)};
rightpos=new Vector3[7]{new Vector3(0,0,0),new Vector3(6,3,0),new Vector3(8,3,0),new Vector3(10,3,0),
new Vector3(12,3,0),new Vector3(14,3,0),new Vector3(16,3,0)};
leftseat=new int[7];
rightseat=new int[7];
for(int i=1;i<=6;++i){
leftseat[i]=0;
rightseat[i]=0;
}
leftseat[1]=1;leftseat[2]=1;leftseat[3]=1;
leftseat[4]=2;leftseat[5]=2;leftseat[6]=2;
Direct.gameStatus=0;
loadResources();
}
// Update is called once per frame
void Update()
{
if (b.moveStatus!=0){
for(int i=1;i<=6;++i){
if (p[i].moveStatus==1||p[i].moveStatus==2){
p[i].obj.transform.position=Vector3.MoveTowards(p[i].obj.transform.position,p[i].target,0.06f);
}
if (p[i].obj.transform.position==p[i].target){
p[i].moveStatus=0;
}
}
if (b.moveStatus==1){
b.obj.transform.position=Vector3.MoveTowards(b.obj.transform.position,b.bootr,0.06f);
if (b.obj.transform.position==b.bootr){
b.moveStatus=0;
b.side=2;
Direct.gameStatus=check();
}
}else if (b.moveStatus==2){
b.obj.transform.position=Vector3.MoveTowards(b.obj.transform.position,b.bootl,0.06f);
if (b.obj.transform.position==b.bootl){
b.moveStatus=0;
b.side=1;
Direct.gameStatus=check();
}
}
}
}
public void loadResources(){
lc=new leftCoast();
rc=new rightCoast();
wat=new water();
p=new pnd[7];
for(int i=1;i<=6;++i){
p[i]=new pnd(leftseat[i],leftpos[i]);
lc.ptr[i]=p[i].obj;
}
b=new boot();
gameObject.AddComponent(typeof(MyGUI));
}
public void moveBoot(){
if (b.moveStatus!=0) return;
if (b.l==null && b.r==null) return;
for(int i=1;i<=6;++i){
if (p[i].obj==b.l || p[i].obj==b.r){
p[i].moveStatus=b.side;
if (b.side==1) p[i].target=p[i].obj.transform.position+new Vector3(7,0,0);
else p[i].target=p[i].obj.transform.position+new Vector3(-7,0,0);
}
}
b.moveStatus=b.side;
}
public void pndOnClick(GameObject gameObject){
if (b.moveStatus!=0) return;
if (b.l!=gameObject && b.r!=gameObject){// 不在船上
int objside=0;
int index=0;
for(int i=1;i<=6;++i){
if (lc.ptr[i]==gameObject){
objside=1;
index=i;
break;
}
if (rc.ptr[i]==gameObject){
objside=2;
index=i;
break;
}
}
if (objside!=b.side){ //人与船不在同一海岸,则不上
return;
}
if (b.l==null){//上船,船满则不上
b.l=gameObject;
if (objside==1)
lc.ptr[index]=null;
else
rc.ptr[index]=null;
gameObject.transform.position=b.obj.transform.position+boot.lv;
}else{
if (b.r==null){
b.r=gameObject;
if (objside==1)
lc.ptr[index]=null;
else
rc.ptr[index]=null;
gameObject.transform.position=b.obj.transform.position+boot.rv;
}
}
}else{ //在船上
if (b.side==1){ // 在左海岸
for(int i=1;i<=6;++i){
if (lc.ptr[i]==null){
gameObject.transform.position=leftpos[i];
lc.ptr[i]=gameObject;
if (b.l==gameObject)
b.l=null;
else
b.r=null;
break;
}
}
}else{// 在右海岸
for(int i=1;i<=6;++i){
if (rc.ptr[i]==null){
gameObject.transform.position=rightpos[i];
rc.ptr[i]=gameObject;
if (b.l==gameObject)
b.l=null;
else
b.r=null;
break;
}
}
}
}
Direct.gameStatus=check();
}
int check(){
int p,d;
p=0;d=0;
for(int i=1;i<=6;++i){
if (lc.ptr[i]==null) continue;
if (lc.ptr[i].name=="priest")
p++;
if (lc.ptr[i].name=="devil")
d++;
}
if (b.side==1){
if (b.l!=null){
if (b.l.name=="priest") p++;
if (b.l.name=="devil") d++;
}
if (b.r!=null){
if (b.r.name=="priest") p++;
if (b.r.name=="devil") d++;
}
}
if (d>p && p!=0){
return -1;
}
p=0;d=0;
for(int i=1;i<=6;++i){
if (rc.ptr[i]==null) continue;
if (rc.ptr[i].name=="priest")
p++;
if (rc.ptr[i].name=="devil")
d++;
}
if (b.side==2){
if (b.l!=null){
if (b.l.name=="priest") p++;
if (b.l.name=="devil") d++;
}
if (b.r!=null){
if (b.r.name=="priest") p++;
if (b.r.name=="devil") d++;
}
}
if (d>p && p!=0){
return -1;
}
int number=0;
for(int i=1;i<=6;++i){
if (rc.ptr[i]!=null)
number++;
}
if (number==6)
return 1;
return 0;
}
public void restart(){
for(int i=1;i<=6;++i){
p[i].obj.transform.position=leftpos[i];
lc.ptr[i]=p[i].obj;
rc.ptr[i]=null;
p[i].moveStatus=0;
}
b.l=null;
b.r=null;
b.obj.transform.position=b.bootl;
b.side=1;
b.moveStatus=0;
Direct.gameStatus=0;
}
}
GUI类
处理GUI信息,比如胜败,重玩按钮,标题等
还有右上角那么性感的X
通过向导演类询问游戏状态(胜利失败什么的)来决定屏幕上显示什么。
也要UserAction处理玩家动作(只有重玩按钮需要)
MyGUI.cs
public class MyGUI : MonoBehaviour
{
UserAction action;
public int w,h;
public Texture2D title,restart,over,win,esc;
// Start is called before the first frame update
void Start()
{
action=Direct.getInstance().currentScene as UserAction;
w=Screen.width;
h=Screen.height;
title=Resources.Load<Texture2D>("img/title");
restart=Resources.Load<Texture2D>("img/restart");
over=Resources.Load<Texture2D>("img/over");
win=Resources.Load<Texture2D>("img/win");
esc=Resources.Load<Texture2D>("img/esc");
}
// Update is called once per frame
void Update()
{
w=Screen.width;
h=Screen.height;
}
void OnGUI(){
GUI.Label(new Rect(w*0.26f,0,w*0.48f,w*0.12f),title);
if ( GUI.Button(new Rect(w-100,0,100,100),esc)){
Application.Quit();
}
if ( GUI.Button(new Rect(w*0.45f,h-w*0.1f,w*0.1f,w*0.05f),restart) ){
action.restart();
}
if (Direct.gameStatus==-1){
GUI.Label(new Rect(0.5f*w-100,0.5f*h-100,200,200),over);
}
if (Direct.gameStatus==1){
GUI.Label(new Rect(0.5f*w-100,0.5f*h-100,200,200),win);
}
}
}
点击事件类
处理点击事件,点了人或者船。
并通过UserAction告诉场记FirstController应该做什么事。调用FirstController中的函数。
OnMouseDown()表示点了3d物体。
OnClick.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OnClick : MonoBehaviour
{
UserAction action;
void Start(){
action=Direct.getInstance().currentScene as UserAction;
}
void OnMouseDown(){
if (Direct.gameStatus==-1 || Direct.gameStatus==1) return;
if (gameObject.name=="priest" || gameObject.name=="devil"){//点了牧师或魔鬼
action.pndOnClick(gameObject);
}
if (gameObject.name=="boot"){
action.moveBoot();
}
}
}
至此完成了代码的简单解析。
以下是项目链接
你可以直接下载我的项目,解压后用unity打开(2020版本以上)
点击这里下载项目压缩包(29 MB): 项目下载
如果不想下载,可以在这里查看代码
代码传送门:代码传送门