欢迎上github品鉴:ikuen14557/leon-s-project: cool (github.com)
注:本文只是对于项目的回顾,参考价值并不大。
再注:本项目创建于鄙人纯小白时期,项目可能会出现代码逻辑混乱,性能消耗巨大等问题,不建议用于大型游戏开发
人物相关
人物移动逻辑
一般写在FixedUpdate(每帧调用)
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");//从inputsystem获取按键反馈
move = new Vector2(horizontal, vertical);
Vector2 postition = transform.position;
postition = postition + movespeed * move * Time.fixedDeltaTime;
rigidbody2d.MovePosition(postition);//移动
人物动画机
一般有方向的移动会使用状态树
if (!Mathf.Approximately(move.x, 0) || !Mathf.Approximately(move.y, 0))
{
lookDirection.Set(move.x, move.y);
lookDirection.Normalize();//将向量归一
animator.SetFloat("MoveValue", 1);//将idle状态转换为walk
}//Mathf.Approximately 是 Unity 提供的用于比较浮点数是否近似的函数。它比直接使用 == 运算符更可靠
animator.SetFloat("MoveX", lookDirection.x);
animator.SetFloat("MoveY", lookDirection.y);
摄像机跟随
插件法
在maincamera加入插件
再新建一个gameobject加入插件
背包系统(基于ScriptObject)
官方文档见:ScriptableObject - Unity 手册 --- ScriptableObject - Unity 手册
GUI设计
这里我就不说怎么创建该UI了,只能说easy(值得一提的是有个Grid插件可以实现子对象整齐排列)
物品
创建名为“Item”的c#脚本代表物品属性
[CreateAssetMenu (fileName ="New Item",menuName ="Inventory/New Item")]//创建路径
public class Item : ScriptableObject
{
public string itemName;
public Sprite itemImage;//图片
public int itemHeld;//数量
[TextArea]
public string itemInfo;
public bool equip;//是否可以装备
}
c#脚本名为“itemonworld”物体在场景中的管理 挂载在世界物品上
public class ItemOnWorld : MonoBehaviour
{
public Item thisItem;//属性
public Inventory playerInventory;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("player"))//判断玩家
{
AddNewItem();
Destroy (gameObject);
}
}
public void AddNewItem()
{
if(!playerInventory.itemList.Contains(thisItem))//如果在背包里面没有
{
playerInventory.itemList.Add(thisItem);
InventoryManager.CreateNewItem(thisItem);
}
else
{
thisItem.itemHeld += 1;
}
}
}
挂载在预制体上的脚本“slot”
public class Slot : MonoBehaviour
{
public Item slotItem;
public Image slotImage;
//public Text slotNum;
public void ItemOnClicked()
{
InventoryManager .UpdateItemInfo (slotItem .itemInfo);
}
}
背包管理
背包列表
[CreateAssetMenu (fileName ="New Inventory",menuName ="Inventory/New Inventory")]
public class Inventory :ScriptableObject
{
public List<Item> itemList = new List<Item>();//list存放item
public void AddItem(Item item)
{
itemList.Add(item);
}
public void RemoveItem(Item item)
{
itemList.Remove(item);
}
}
背包管理
public class InventoryManager : MonoBehaviour
{
static InventoryManager instance;
public Inventory inventory;
public GameObject slotGrid;//背包UI
public Slot slotPrefab;
public Text itemInfromation;
private void Awake()
{
if (instance! == null)
Destroy(this);
instance = this;
foreach (var item in inventory.itemList)
{
CreateNewItem(item);
}
}
private void OnEnable()
{
instance.itemInfromation.text = "";
}
public static void UpdateItemInfo(string itemDescription)
{
instance.itemInfromation.text = itemDescription;
}
public static void CreateNewItem(Item item)//生成物品并设置各种属性
{
Slot newItem = Instantiate(instance.slotPrefab, instance.slotGrid.transform.position, Quaternion.identity);
newItem.gameObject.transform.SetParent(instance.slotGrid.transform);
newItem.slotItem = item;
newItem.slotImage.sprite = item.itemImage;
newItem.gameObject.transform.localScale = new Vector3 (1,1,1);
}
}
对话系统
打字机效果(基于协程处理)
unity官方文档:MonoBehaviour-StartCoroutine - Unity 脚本 API
public class DialogSystem : MonoBehaviour
{
[Header("UI组件")]
public Text textLabel1;
public Text textLabel2;
[Header("文本文件")]
public TextAsset textFile;
public TextAsset textname;
public int index;
public float textspeed;
bool textFinished;//是否完成打字
bool cancelTyping;//取消打字
List<string> textList = new List<string>();
List<string> textnamelist = new List<string>();
void Awake()
{
GetTextFormFile(textFile, textname);//将Text按照‘\n’切开
}
private void OnEnable()
{
textFinished = true;
StartCoroutine(SetTextUI());//启用协程
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Q) && index == textList.Count)//当对话完后关闭对话框
{
gameObject.SetActive(false);
index = 0;
return;
}
if (Input.GetKeyDown(KeyCode.Q))//跳过打字
{
if (textFinished && !cancelTyping)
{
StartCoroutine(SetTextUI());
}
else if (!textFinished)
{
cancelTyping = !cancelTyping;
}
}
}
void GetTextFormFile(TextAsset file, TextAsset file1)
{
textList.Clear();
textnamelist.Clear();
index = 0;
var lineDate1 = file1.text.Split('\n');
foreach (var line in lineDate1)
{
textnamelist.Add(line);
}
var lineDate = file.text.Split('\n');
foreach (var line in lineDate)
{
textList.Add(line);
}
}
IEnumerator SetTextUI()//实现一个一个字输出
{
textFinished = false;
textLabel2.text = "";
textLabel1.text = "";
for (int i = 0; i < textnamelist[index].Length && !cancelTyping; i++)
{
textLabel1.text += textnamelist[index][i];
yield return new WaitForSeconds(textspeed);
}
for (int i = 0; i < textList[index].Length && !cancelTyping; i++)
{
textLabel2.text += textList[index][i];
yield return new WaitForSeconds(textspeed);
}
textLabel1.text = textnamelist[index];
textLabel2.text = textList[index];
cancelTyping = false;
textFinished = true;
index++;
}
}
加速系统(对象池)
创建对象池
public class ShadowPool : MonoBehaviour
{
public static ShadowPool instance;
public GameObject shadowPrefab;
public int shadowCount;
Queue<GameObject> availableObject = new Queue<GameObject>();//队列
private void Awake()
{
instance = this;
FillPool();//初始化对象池
}
public void FillPool()//填满对象池
{
for(int i=0;i<10;i++)
{
var newShandow = Instantiate(shadowPrefab);
newShandow.transform.SetParent(transform);
//取消启用
ReturnPool(newShandow);
}
}
public void ReturnPool(GameObject pool)//取消启用
{
pool.SetActive(false);
availableObject.Enqueue(pool);//将对象加入队列中
}
public GameObject GetFormPool()
{
if(availableObject.Count == 0)
{
FillPool();
}
var outShadow = availableObject.Dequeue();//移除开头元素并返回
outShadow.SetActive(true);
return outShadow;
}
}
控制残影的基本属性
public class Shandow : MonoBehaviour
{
Transform player;
SpriteRenderer thisSprite;
SpriteRenderer playerSprite;
Color color;
[Header("时间控制参数")]
public float activeTime;//显示时间
public float activeStart;//开始显示时间
[Header("不透明度控制")]
float alpha;
public float alphaSet;//初始值
public float alphaMultiplier;
private void OnEnable()
{
player = GameObject.Find("peter").transform;
thisSprite = GetComponent<SpriteRenderer>();
playerSprite =player.GetComponent<SpriteRenderer>();
alpha = alphaSet;
thisSprite.sprite = playerSprite.sprite;
transform.position = player.transform.position;
transform.rotation = player.transform.rotation;
transform.localScale = player.transform.localScale;
activeStart = Time.time;
}
void Update()
{
alpha *= alphaMultiplier;
color = new Color(1, 1, 1, alpha);
thisSprite.color = color;
if(Time.time >= activeTime+activeStart)
{
//返回对象池
ShadowPool.instance.ReturnPool(this.gameObject);
}
}
}
人物冲刺控制
void ReadyToDash()//当冷却时间结束
{
isDash = true;
dashTimeLeft = dashTime;
lastdash = Time.time;
}
void Dash()
{
if(isDash)
{
if(dashTimeLeft > 0)
{
rigidbody2d .velocity = new Vector2(dashSpeed * horizontal, rigidbody2d.velocity.y);
dashTimeLeft -= Time.deltaTime;
ShadowPool.instance.GetFormPool();
}
else
{
isDash = false;
}
}
}
关卡设计
华容道
root如下
public class GameManger2 : MonoBehaviour
{
public static GameManger2 Inst;
public GameObject CeilItem;
public List<Sprite> mapSps;//图片素材
public int row = 3;//行
public int col = 3;//列
//洗牌
void WashNumbers(List <int>listObj)
{
//模拟法
int q = Random.Range(1, 51);//交换次数
int zerolocation = 8;//空格的位置
while(q>0)
{
int j = Random.Range(0, listObj.Count);
if(j==zerolocation -3||j==zerolocation -1||j==zerolocation +1||j==zerolocation +3)
{
q--;
int temp = listObj[zerolocation ];
listObj[zerolocation ]= listObj[j];
listObj[j] = temp;
zerolocation = j;
}
}
while (zerolocation !=8)//让空格回归左下角
{
int j = Random.Range(0, listObj.Count);
if (j == zerolocation - 3 || j == zerolocation - 1 || j == zerolocation + 1 || j == zerolocation + 3)
{
int temp = listObj[zerolocation];
listObj[zerolocation] = listObj[j];
listObj[j] = temp;
zerolocation = j;
}
}
}
public List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 0 };//数字顺序
//key是numbe
private Dictionary<int, CeilItem> dataDict = new Dictionary<int, CeilItem>();//将实例化出的网格存储入字典
public void clear()//胜利后清除所有网格
{
foreach (var pair in dataDict)
{
// 获取 GameObject 的引用
GameObject go = pair.Value.gameObject;
// 销毁 GameObject
Destroy(go);
}
}
CeilItem findCeilItem(int rIdx, int cIdx)//寻找指定格子
{
foreach (KeyValuePair<int, CeilItem> kv in dataDict)
{
if (kv.Value.rInx == rIdx && kv.Value.cIdx == cIdx)
{
return kv.Value;
}
}
return null;
}
Vector3 getCeilItemPos(int i, int j)//给实例化的网格分配
{
Vector3 ve = default;
ve.x = j;
ve.y = -1 * i;
return ve;
}
bool checkCeilRight(int rIdex, int cIdex) //检查格子是否合规
{
if (rIdex < 0 || rIdex >= row || cIdex < 0 || cIdex >= col)
{
return false;
}
return true;
}
bool findOneZeroCeil(int rIdx, int cIdx)//执行一次是否能查找空格子
{
if (!checkCeilRight(rIdx, cIdx))
{
return false;
}
CeilItem ceil = findCeilItem(rIdx, cIdx);
if (ceil != null)
{
return ceil.number == 0;
}
return false;
}
public (int, int) findfourZeroCel(int rIdx, int cIdx)
{
List<(int, int)> dirs = new List<(int, int)>()//可以交换的格子字典
{
(rIdx + 1, cIdx),
(rIdx - 1, cIdx),
(rIdx, cIdx + 1),
(rIdx, cIdx - 1)
};
for (int i = 0; i < dirs.Count; i++)
{
(int, int) item = dirs[i];
if (findOneZeroCeil(item.Item1, item.Item2))//如果周围有空格就返回空格位置
{
return item;
}
}
return (999, 999);//没找到
}
public void CheckSwap(int rIdx, int cIdx, int clickCeilNumber)//检查是否可以交换
{
(int, int) res = findfourZeroCel(rIdx, cIdx);
if (res.Item1 != 999)
{
StartSwap(clickCeilNumber, 0);//开始交换
}
}
void StartSwap(int clickNumber, int zeroNumber)//开始交换
{
CeilItem clickItem = dataDict[clickNumber];
CeilItem zeroItem = dataDict[0];
Vector3 clickPos = clickItem.transform.position;
clickItem.transform.position = zeroItem.transform.position;
zeroItem.transform.position = clickPos;
SwapData(clickItem, zeroItem);
}
void SwapData(CeilItem clickItem, CeilItem zeroItem)//交换行列信息
{
var clickRIdx = clickItem.rInx;
var clickCIdx = clickItem.cIdx;
clickItem.updateRandCIdx(zeroItem.rInx, zeroItem.cIdx);
zeroItem.updateRandCIdx(clickRIdx, clickCIdx);
}
public bool isWin()
{
bool res = false;
int startNum = 1;
int maxNum = row * col;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
var ceil = findCeilItem(i, j);//根据行列判断是否在正确位置
if (startNum ==maxNum )
{
res = ceil.number ==0;
continue;
}
if(ceil .number != startNum)
{
return res;
}
startNum ++;
}
}
if(res)
{
Debug.Log("游戏胜利");
}
return res;
}
void InitMap()
{
WashNumbers(numbers);
int numberIdx = 0;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
Vector3 pos = getCeilItemPos(i, j);// new Vector3(i, j, 0);
var go = GameObject.Instantiate(CeilItem, pos, Quaternion.identity);//实例化
var ceilItem = go.GetComponent<CeilItem>();
int number = numbers[numberIdx];
ceilItem.Init(number, i, j);
dataDict.Add(number, ceilItem);
numberIdx++;
}
}
}
void Awake()
{
Inst = this; ;
}
void Start()
{
InitMap();
}
}
预制体ceiltem
public class CeilItem : MonoBehaviour
{
public int rInx { get; private set; }
public int cIdx { get; private set; }
public int number;
private SpriteRenderer sp;
public static CeilItem instance;
private void Awake()
{
instance = this;
sp = GetComponent<SpriteRenderer>();
}
public void Init(int number, int rInx,int cIndx)//将信息带入物品
{
this.number = number;
this.rInx = rInx;
this.cIdx = cIndx;
UpdateSP(this.number);
}
public void updateRandCIdx(int rIdx,int cIdx)
{
this.rInx=rIdx;
this.cIdx = cIdx;
}
private void OnMouseDown()//点击鼠标后开始检查
{
if (number == 0)
return;
GameManger2.Inst.CheckSwap(rInx, cIdx,number);
}
public void UpdateSP(int number)
{
if(number==0)
{
sp.sprite = null;
}
else
{
int idx = number - 1;
sp.sprite = GameManger2.Inst.mapSps[idx];
}
}
}