主城灯光烘焙
完全烘焙灯光,所有的光源改为“baked”模式,可以通过Light Explorer面板进行确认和设置。
打开Lighting面板,在Environment选项卡中将Environment Lighting的Source改为Color。在Scene选项卡中关闭Realtime Lighting只打开Mixed Lighting,打开烘焙全局光,Lighting Mode选择Subtractive。
在游戏场景的static物体中,玩家经常看到的物体的Mesh Renderer组件内的Lightmapping参数——Scale in Lightmap都设为1,玩家看不到的装饰性物体,Scale in Lightmap都设为0.1或0.2
烘焙参数:

- Indirect Resolution:间接光分辨率
- Lightmap Resolution:灯光贴图分辨率
- Lightmap padding:灯光贴图的间隙,贴图拼合时的空隙
- Directional Mode:如果场景材质有法线贴图,打开Directional会烘焙法线
Unity - Manual: Lightmapping using Enlighten Baked Global Illumination
雾效参数:
- Mode:Linear
- Start:15
- End:3035
最后点击Generate Enlighting,等待贴图生成好后,删除场景内的灯光即可
烘焙完成后,会生成一个LightingData,LightingData与使用这个Data的场景相关联,还有一个Lightmap Texture。
计算战力的公式
1 2 3 4
| public static int GetFightByProps(PlayerData playerData) { return playerData.lv * 100 + playerData.ad + playerData.ap + playerData.addef + playerData.apdef; }
|
体力限制计算公式
体力要求:在110级,体力最高为150,在1120级别体力最高为300……
1 2 3 4
| public static int GetPowerLimit(int lv) { return ((lv - 1) / 10 )* 150 + 150; }
|
这里只要我们的lv是int类型的,((lv - 1) / 10 )
在lv为1到10时恒为0,整数计算遇到小数是全部舍弃的
如果lv是float类型的,则会进行正常计算
经验条UI
经验条为分段UI,每到10%就涨一个图块。
我们使用Grid Layout Group来排10个图片来实现,为了实现UI自适应,要动态计算Cell Size的x值


我们在Canvas中设置的像素是1920*1080,所以宽度总长是1920
Exp图片的宽度是76,所以经验条部分总长是1920 - 76 = 1844
我们在Grid Layout Group中设置的Padding Left是5,剩下的长度是1844 - 5 = 1839
我们在Spacing设置的X为10,中间的9个间隙的长度就是90,1839 - 90 = 1749
由此可见,如果我们每个图块的长度设置为174,最右侧的空隙为9,如果我们舍去这个空隙,那么图块的最大长度为174.9,再长就要换行了。
图块最大长度 =(1920-76-5-90)/10。
图块美观最大长度 =(1920-76-5-90-9)/10 = (1920 - 180)/10
在实际情况中,1920这个宽度是不固定的,而180这个减小值是固定的,我们按照下面的代码来计算出实际的宽度,进而设置好图块的宽度
1 2 3 4 5 6 7 8 9 10
| GridLayoutGroup grid = expPrgTrans.GetComponent<GridLayoutGroup>();
float globalRate = 1.0f * 1080 / Screen.height;
float canvasWidth = Screen.width * globalRate;
float cellWidth = (canvasWidth - 180) / 10;
grid.cellSize = new Vector2(cellWidth, 21);
|
升级公式
接下来计算升级的百分比,首先在PECommon
中定义升级公式:
1 2 3 4
| public static int GetExpUpValByLv(int lv) { return 100 * lv * lv; }
|
然后通过计算转化百分比:
1 2
| int expPrgVal = (int)((playerData.exp * 1.0f / PECommon.GetExpUpValByLv(playerData.lv)) * 100); SetText(txtExpPrg,expPrgVal + "%");
|
经验条计算
首先根据算出来的expPrgVal
来获取应该点亮的图块的Index
1
| int index = expPrgVal / 10;
|
然后根据index设置各个图块的fill amount
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| for (int i = 0; i < expPrgTrans.childCount; i++) { Image image = expPrgTrans.GetChild(i).GetComponent<Image>(); if (i < index) { image.fillAmount = 1; } else if(i == index) { image.fillAmount = expPrgVal % 10 * 1.0f / 10; } else { image.fillAmount = 0; } }
|
下线管理
一个客户端下线,就意味着断掉对应的session,并清理掉cacheSvc里面的缓存。
在ServerSession里面声明一个int变量,来记录当前session的id
1 2 3 4 5
| public class ServerSession : PESession<GameMsg> { public int sessionID = 0; }
|
记住在服务器中,Server Session是多线程多对象的,服务器每与一个客户端建立连接就会创建一个server session
每次有客户端建立连接时,都要有一个函数来创建与当前session连接的唯一id号,我们在ServerRoot
里面新建这个程序
1 2 3 4 5 6
| private int sessionId = 0; public int GetSessionID() { if(sessionId == int.MinValue) { sessionId = 0; } return sessionId += 1; }
|
修改后的ServerSession
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using PENet; using PEProtocol; public class ServerSession : PESession<GameMsg> { public int sessionID = 0; protected override void OnConnected() { sessionID = ServerRoot.Instance.GetSessionID(); PECommon.Log("SessionID: " + sessionID + " Client Connected"); } protected override void OnReciveMsg(GameMsg msg) { PECommon.Log("SessionID: " + sessionID + "RcvPack CMD:" + ((CMD)msg.cmd).ToString()); NetSvc.Instance.AddMsgQue(this,msg); } protected override void OnDisConnected() { PECommon.Log("SessionID: " + sessionID + "Client DisConnected"); } }
|
CacheSvc
增加下线逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void AcctOffLine(ServerSession session) { foreach (var item in onLineAcctDic) { if (item.Value == session) { onLineAcctDic.Remove(item.Key); break; } } bool succ = onLineSessionDic.Remove(session); PECommon.Log("Offline Result: SessionID: " + session.sessionID +" "+ succ); }
|
我们在LoginSys
里面添加ClearOfflieData
来做一下中转,因为server Session不应该直接调用cacheSvc,cacheSvc只能被具体的业务系统(system)调用。
1 2 3 4
| public void ClearOfflieData(ServerSession session) { _cacheSvc.AcctOffLine(session); }
|
1 2 3 4 5
| protected override void OnDisConnected() { LoginSys.Instance.ClearOfflieData(this); PECommon.Log("SessionID: " + sessionID + "Client DisConnected"); }
|
心跳机制
服务器每隔一段时间向客户端发送一次消息,如果连续3次没有收到客户端的回应,那么就对这个客户端做下线处理。
角色控制带动画平滑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| using System.Collections; using System.Collections.Generic; using UnityEngine;
namespace DarknessWarGodLearning { public class PlayerController : MonoBehaviour { [SerializeField] Animator playerAnimator; [SerializeField] CharacterController characterController;
private Transform camTrans; private Vector2 dir = Vector2.zero; private bool isMove = false; private Vector3 camOffset; private float targetBlend; private float currentBlend;
public Vector2 Dir { get => dir; set { if(value == Vector2.zero) { isMove = false; } else { isMove = true; } dir = value; } }
private void Start() { camTrans = Camera.main.transform; camOffset = transform.position - camTrans.position; } private void Update() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical");
Vector2 _dir = new Vector2(h, v).normalized;
if (_dir != Vector2.zero) { Dir = _dir; SetBlend(1); } else { Dir = Vector2.zero; SetBlend(0); } if(currentBlend != targetBlend) { UpdateMixBlend(); }
if (isMove) { SetDir();
SetMove();
SetCam(); } } private void SetDir() { float angle = Vector2.SignedAngle(Dir, new Vector2(0, 1)); Vector3 eularAngle = new Vector3(0, angle, 0); transform.localEulerAngles = eularAngle; } private void SetMove() { characterController.Move(transform.forward * Time.deltaTime * Constants.PlayerMoveSpeed); } private void SetCam() { if (camTrans != null) { camTrans.position = transform.position - camOffset; } } private void SetBlend(float blend) { targetBlend = blend; } private void UpdateMixBlend() { if(Mathf.Abs(currentBlend - targetBlend) < Constants.AccelerSpeed*Time.deltaTime) { currentBlend = targetBlend; } else if(currentBlend > targetBlend) { currentBlend -= Constants.AccelerSpeed * Time.deltaTime; } else { currentBlend += Constants.AccelerSpeed * Time.deltaTime; } playerAnimator.SetFloat("Blend", currentBlend); } } }
|