接入ResLogin.otherPlayers

This commit is contained in:
janing
2025-12-18 16:04:56 +08:00
parent 03276fe1f6
commit 8f58b890be
21 changed files with 861 additions and 87 deletions

View File

@@ -0,0 +1,301 @@
[
{
"__type__": "cc.Prefab",
"_name": "PlayerInfo",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "PlayerInfo",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
}
],
"_prefab": {
"__id__": 12
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 8388608,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "text",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 8388608,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 42.255859375,
"height": 50.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d0A/8x7mpNx5EvjYIoaCay"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "label",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 20,
"_fontSize": 20,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": false,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 2,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e3hL8BSnhFGJfx1WiaCfWA"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "1bkkKqEyFO7LsTGxIKuStm",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_contentSize": {
"__type__": "cc.Size",
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "f4PBh8n8NKX506YXxKc3Tp"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"_alignFlags": 18,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "79mURIYGtLP6O8CvZNsGga"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "c8472648-43f2-4b7c-8881-e5461d212d58",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "PlayerInfo"
}
}

View File

@@ -51,7 +51,7 @@
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_layer": 8388608,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
@@ -105,7 +105,7 @@
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_layer": 8388608,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
@@ -137,8 +137,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 31.078,
"y": -21.978,
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
@@ -155,7 +155,7 @@
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_layer": 8388608,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
@@ -178,7 +178,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 42.255859375,
"width": 54.462890625,
"height": 50.4
},
"_anchorPoint": {
@@ -214,7 +214,7 @@
"b": 255,
"a": 255
},
"_string": "label",
"_string": "Game",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 20,
@@ -313,7 +313,7 @@
"__prefab": {
"__id__": 12
},
"_alignFlags": 9,
"_alignFlags": 18,
"_target": null,
"_left": 0,
"_right": 0,

View File

@@ -326,8 +326,8 @@
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 640,
"y": 360,
"x": 360,
"y": 719.9999999999999,
"z": 0
},
"_lrot": {
@@ -412,7 +412,7 @@
"_priority": 1073741824,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 412.3341946597761,
"_orthoHeight": 719.9999999999999,
"_near": 1,
"_far": 2000,
"_color": {
@@ -456,8 +456,8 @@
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1280,
"height": 720
"width": 720,
"height": 1439.9999999999998
},
"_anchorPoint": {
"__type__": "cc.Vec2",

View File

@@ -1,7 +1,7 @@
import { find } from "cc";
import { BaseState } from "../../Framework/FSM/BaseState";
import { UIMgr } from "../../Framework/UI/UIMgr";
import { PlayerInfo } from "../../Shared/protocols/MsgResLogin";
import { MsgResLogin, PlayerInfo } from "../../Shared/protocols/MsgResLogin";
import { UIGame } from "../Game/UIGame";
import { World } from "../Game/World";
@@ -14,6 +14,7 @@ import { World } from "../Game/World";
* - 游戏主循环
*/
export class AppStatusGame extends BaseState {
private _cmd: MsgResLogin
private _player: PlayerInfo = null;
private _isNewPlayer: boolean = false;
private _uiGame: UIGame = null;
@@ -25,10 +26,11 @@ export class AppStatusGame extends BaseState {
/**
* 进入游戏状态
*/
async onEnter(params?: any): Promise<void> {
async onEnter(params?: MsgResLogin): Promise<void> {
super.onEnter(params);
console.log("[AppStatusGame] 进入游戏世界");
console.log("[AppStatusGame] 进入游戏世界", params);
this._cmd = params
// 保存玩家信息
if (params) {
@@ -87,7 +89,7 @@ export class AppStatusGame extends BaseState {
}
// 初始化世界,传入本地玩家信息
await World.getInstance().init(worldRoot, this._player);
await World.getInstance().init(worldRoot, this._player, this._cmd.otherPlayers);
console.log("[AppStatusGame] 游戏初始化完成");
}
@@ -117,7 +119,10 @@ export class AppStatusGame extends BaseState {
* 更新游戏状态(每帧调用)
*/
onUpdate(dt: number): void {
// TODO: 游戏主循环逻辑
// 更新世界状态,包括玩家信息显示
World.getInstance().update(dt);
// TODO: 其他游戏主循环逻辑
// - 更新角色位置
// - 检测碰撞
// - 更新敌人AI

View File

@@ -1,21 +1,28 @@
import { Camera, Component, Node, Vec3, _decorator } from 'cc';
import { _decorator, Camera, Component, find, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
/**
* CameraController 摄像机控制器
* 负责让摄像机跟随指定的目标物体
* 负责让摄像机跟随指定的目标物体,并提供摄像机坐标转换功能
* 支持世界摄像机和UI摄像机的概念
*/
@ccclass('CameraController')
export class CameraController extends Component {
/** 跟随目标 */
private target: Node = null;
/** 摄像机节点 */
private cameraNode: Node = null;
/** 世界摄像机节点 (Main Camera) */
private worldCameraNode: Node = null;
/** 摄像机组件 */
private camera: Camera = null;
/** 世界摄像机组件 */
private worldCamera: Camera = null;
/** UI摄像机节点 (Canvas/Camera) */
private uiCameraNode: Node = null;
/** UI摄像机组件 */
private uiCamera: Camera = null;
/** 相对偏移量 */
private offset: Vec3 = new Vec3(0, 10, 8);
@@ -29,21 +36,49 @@ export class CameraController extends Component {
public smoothFollow: boolean = true;
onLoad() {
this.findMainCamera();
this.findCameras();
}
/**
* 查找摄像机
* 查找所有摄像机
*/
private findMainCamera(): void {
// 查找名为 "Main Camera" 的摄像机
const mainCameraNode = this.node.scene.getChildByName('Main Camera');
private findCameras(): void {
this.findWorldCamera();
this.findUICamera();
}
/**
* 查找世界摄像机 (Main Camera)
*/
private findWorldCamera(): void {
// 查找名为 "Main Camera" 的世界摄像机
const mainCameraNode = find('Main Camera', this.node.scene);
if (mainCameraNode) {
this.cameraNode = mainCameraNode;
this.camera = mainCameraNode.getComponent(Camera);
console.log('[CameraController] 找到摄像机:', mainCameraNode.name);
this.worldCameraNode = mainCameraNode;
this.worldCamera = mainCameraNode.getComponent(Camera);
console.log('[CameraController] 找到世界摄像机:', mainCameraNode.name);
} else {
console.error('[CameraController] 未找到摄像机(Main Camera)');
console.error('[CameraController] 未找到世界摄像机(Main Camera)');
}
}
/**
* 查找UI摄像机 (Canvas/Camera)
*/
private findUICamera(): void {
// 查找Canvas下的Camera作为UI摄像机
const canvasNode = find('Canvas', this.node.scene);
if (canvasNode) {
const uiCameraNode = canvasNode.getChildByName('Camera');
if (uiCameraNode) {
this.uiCameraNode = uiCameraNode;
this.uiCamera = uiCameraNode.getComponent(Camera);
console.log('[CameraController] 找到UI摄像机:', uiCameraNode.name);
} else {
console.error('[CameraController] 未找到Canvas下的Camera节点');
}
} else {
console.error('[CameraController] 未找到Canvas节点');
}
}
@@ -54,7 +89,7 @@ export class CameraController extends Component {
public setTarget(target: Node): void {
this.target = target;
if (this.target && this.cameraNode) {
if (this.target && this.worldCameraNode) {
// 立即设置初始位置
this.updateCameraPosition(false);
console.log('[CameraController] 设置跟随目标:', target.name);
@@ -77,14 +112,35 @@ export class CameraController extends Component {
}
/**
* 获取摄像机节点
* 获取世界摄像机节点
*/
public getCameraNode(): Node {
return this.cameraNode;
public getWorldCameraNode(): Node {
return this.worldCameraNode;
}
/**
* 获取世界摄像机组件
*/
public getWorldCamera(): Camera {
return this.worldCamera;
}
/**
* 获取UI摄像机节点
*/
public getUICameraNode(): Node {
return this.uiCameraNode;
}
/**
* 获取UI摄像机组件
*/
public getUICamera(): Camera {
return this.uiCamera;
}
update(deltaTime: number) {
if (this.target && this.cameraNode) {
if (this.target && this.worldCameraNode) {
this.updateCameraPosition(this.smoothFollow, deltaTime);
}
}
@@ -95,7 +151,7 @@ export class CameraController extends Component {
* @param deltaTime 帧时间(smooth为true时需要)
*/
private updateCameraPosition(smooth: boolean = true, deltaTime: number = 0): void {
if (!this.target || !this.cameraNode) {
if (!this.target || !this.worldCameraNode) {
return;
}
@@ -105,17 +161,17 @@ export class CameraController extends Component {
if (smooth && deltaTime > 0) {
// 平滑跟随
const currentPosition = this.cameraNode.worldPosition;
const currentPosition = this.worldCameraNode.worldPosition;
const newPosition = new Vec3();
Vec3.lerp(newPosition, currentPosition, targetPosition, this.followSpeed * deltaTime);
this.cameraNode.setWorldPosition(newPosition);
this.worldCameraNode.setWorldPosition(newPosition);
} else {
// 立即跟随
this.cameraNode.setWorldPosition(targetPosition);
this.worldCameraNode.setWorldPosition(targetPosition);
}
// 让摄像机看向目标
this.cameraNode.lookAt(this.target.worldPosition);
this.worldCameraNode.lookAt(this.target.worldPosition);
}
/**
@@ -126,13 +182,83 @@ export class CameraController extends Component {
console.log('[CameraController] 停止跟随');
}
/**
* 将世界坐标转换为指定摄像机的屏幕坐标
* @param worldPosition 世界坐标Vec3
* @param camera 目标摄像机
* @returns 摄像机坐标系下的屏幕坐标Vec3z为深度
*/
public worldToCameraPosition(worldPosition: Vec3, camera: Camera): Vec3 {
if (!camera) {
console.warn('[CameraController] 摄像机参数为空,无法进行坐标转换');
return new Vec3(0, 0, 0);
}
// 使用摄像机的worldToScreen方法将世界坐标转换为屏幕坐标
const screenPos = new Vec3();
camera.worldToScreen(worldPosition, screenPos);
return screenPos;
}
/**
* 将摄像机屏幕坐标转换为指定Node的本地坐标
* 计算流程:摄像机坐标 -> 世界坐标 -> Node本地坐标
* @param screenPosition 摄像机屏幕坐标
* @param sourceCamera 源摄像机
* @param targetNode 目标Node节点
* @returns 目标Node的本地坐标
*/
public CameraToNode(screenPosition: Vec3, sourceCamera: Camera, targetNode: Node): Vec3 {
if (!sourceCamera || !targetNode) {
console.warn('[CameraController] 摄像机或目标节点参数为空,无法进行坐标转换');
return new Vec3(0, 0, 0);
}
// 第一步:摄像机屏幕坐标转世界坐标
const worldPosition = new Vec3();
sourceCamera.screenToWorld(screenPosition, worldPosition);
// 第二步世界坐标转Node本地坐标
const localPosition = new Vec3();
targetNode.inverseTransformPoint(localPosition, worldPosition);
return localPosition;
}
/**
* 将世界坐标转换为UI摄像机节点的本地坐标
* @param worldPosition 世界坐标
* @returns UI摄像机节点的本地坐标
*/
public worldToUICamera(worldPosition: Vec3, node: Node): Vec3 {
if (!this.worldCamera || !this.uiCameraNode) {
console.warn('[CameraController] 世界摄像机或UI摄像机节点未初始化');
return new Vec3(0, 0, 0);
}
// 第一步:世界坐标转世界摄像机屏幕坐标
const worldCameraPos = this.worldToCameraPosition(worldPosition, this.worldCamera);
// 第二步世界摄像机屏幕坐标转UI摄像机节点本地坐标
const uiCameraLocalPos = this.CameraToNode(worldCameraPos, this.uiCamera, node);
return uiCameraLocalPos;
// // 世界坐标转Node本地坐标
// const localPosition = new Vec3();
// node.inverseTransformPoint(localPosition, worldPosition);
return worldPosition;
}
/**
* 销毁控制器
*/
onDestroy(): void {
this.target = null;
this.cameraNode = null;
this.camera = null;
this.worldCameraNode = null;
this.worldCamera = null;
this.uiCameraNode = null;
this.uiCamera = null;
console.log('[CameraController] 摄像机控制器已销毁');
}
}

View File

@@ -43,7 +43,7 @@ export class PlayerController extends Component {
this.playerInfo = playerInfo;
const x = playerInfo.position.x / 1000
const y = playerInfo.position.y / 1000
this.lastSentPosition.set(x, 0, y);
this.lastSentPosition.set(x, y, 0);
// 获取 RoleController 组件
this.roleController = this.node.getComponentInChildren(RoleController);
@@ -109,11 +109,11 @@ export class PlayerController extends Component {
// W - 向前
if (this.keyStates.get(KeyCode.KEY_W)) {
this.moveDirection.z -= 1;
this.moveDirection.y += 1;
}
// S - 向后
if (this.keyStates.get(KeyCode.KEY_S)) {
this.moveDirection.z += 1;
this.moveDirection.y -= 1;
}
// A - 向左
if (this.keyStates.get(KeyCode.KEY_A)) {
@@ -144,7 +144,7 @@ export class PlayerController extends Component {
// 更新朝向(让角色面向移动方向)
if (this.moveDirection.lengthSqr() > 0) {
const targetAngle = Math.atan2(this.moveDirection.x, -this.moveDirection.z) * (180 / Math.PI);
const targetAngle = Math.atan2(this.moveDirection.x, -this.moveDirection.y) * (180 / Math.PI);
this.node.setRotationFromEuler(0, targetAngle, 0);
}
@@ -168,7 +168,7 @@ export class PlayerController extends Component {
// 如果移动距离超过阈值,发送移动请求
if (distance >= this.moveSendThreshold) {
this.sendMoveRequest(currentPos.x, currentPos.z);
this.sendMoveRequest(currentPos.x, currentPos.y);
this.lastSentPosition.set(currentPos);
}
}
@@ -176,12 +176,12 @@ export class PlayerController extends Component {
/**
* 发送移动请求到服务器
*/
private async sendMoveRequest(x: number, z: number): Promise<void> {
private async sendMoveRequest(x: number, y: number): Promise<void> {
try {
const netManager = NetManager.getInstance();
const result = await netManager.callMsg(new MoveMessagePair(), {
x: Math.round(x * 1000),
y: Math.round(z * 1000)
y: Math.round(y * 1000)
});
if (!result) {
@@ -194,8 +194,8 @@ export class PlayerController extends Component {
const serverPos = result.position;
const x = serverPos.x / 1000
const y = serverPos.y / 1000
this.node.setPosition(x, 0, y);
this.lastSentPosition.set(x, 0, y);
this.node.setPosition(x, y, 0);
this.lastSentPosition.set(x, y, 0);
}
} catch (error) {
console.error('[PlayerController] 发送移动请求异常:', error);

View File

@@ -0,0 +1,88 @@
import { _decorator, Component, Label, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
export interface PlayerInfoData {
/** 玩家ID */
playerId: string;
/** 玩家名称 */
playerName: string;
/** 屏幕坐标位置用于设置UI位置 */
screenPosition: Vec3;
/** 真实位置(用于显示到文本中) */
realPosition: Vec3;
}
/**
* PlayerInfo 玩家信息显示组件
* 显示玩家名称和坐标信息
*/
@ccclass('PlayerInfo')
export class PlayerInfo extends Component {
/** 文本显示组件 */
private textLabel: Label = null;
/** 当前玩家数据 */
private playerData: PlayerInfoData = null;
onLoad() {
// 查找text子节点的Label组件
const textNode = this.node.getChildByName('text');
if (textNode) {
this.textLabel = textNode.getComponent(Label);
if (!this.textLabel) {
console.error('[PlayerInfo] text子节点没有Label组件');
}
} else {
console.error('[PlayerInfo] 找不到text子节点');
}
}
/**
* 更新玩家信息
* @param playerData 玩家数据
*/
public updatePlayerInfo(playerData: PlayerInfoData): void {
this.playerData = playerData;
if (!this.textLabel) {
console.error('[PlayerInfo] 文本组件未初始化');
return;
}
// 格式化显示文本:玩家名和真实坐标信息
const displayText = `${playerData.playerName}\n坐标: (${Math.round(playerData.realPosition.x)}, ${Math.round(playerData.realPosition.y)})`;
this.textLabel.string = displayText;
// 设置UI位置为屏幕坐标
this.node.setPosition(playerData.screenPosition);
//console.log(`[PlayerInfo] 更新玩家信息: ${playerData.playerName}, 屏幕坐标: (${playerData.screenPosition.x}, ${playerData.screenPosition.y}), 真实坐标: (${playerData.realPosition.x}, ${playerData.realPosition.y})`);
}
/**
* 获取当前玩家数据
*/
public getPlayerData(): PlayerInfoData {
return this.playerData;
}
/**
* 隐藏玩家信息
*/
public hide(): void {
this.node.active = false;
}
/**
* 显示玩家信息
*/
public show(): void {
this.node.active = true;
}
onDestroy() {
this.textLabel = null;
this.playerData = null;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -40,8 +40,8 @@ export class RemotePlayer {
this.playerName = playerName;
const x = position.x / 1000
const y = position.y / 1000
this.currentPosition.set(x, 0, y);
this.targetPosition.set(x, 0, y);
this.currentPosition.set(x, y, 0);
this.targetPosition.set(x, y, 0);
// 获取 RoleController 组件
this.roleController = this.playerNode.getComponentInChildren(RoleController);
@@ -79,7 +79,7 @@ export class RemotePlayer {
// 计算朝向
if (distance > 0.01) {
const targetAngle = Math.atan2(direction.x, -direction.z) * (180 / Math.PI);
const targetAngle = Math.atan2(direction.x, -direction.y) * (180 / Math.PI);
this.playerNode.setRotationFromEuler(0, targetAngle, 0);
}

View File

@@ -1,5 +1,7 @@
import { _decorator } from 'cc';
import { _decorator, instantiate, Node, Prefab } from 'cc';
import { ResMgr } from '../../Framework/ResMgr/ResMgr';
import { UIBase } from '../../Framework/UI/UIBase';
import { PlayerInfo, PlayerInfoData } from './PlayerInfo';
const { ccclass, property } = _decorator;
@@ -9,21 +11,22 @@ const { ccclass, property } = _decorator;
*/
@ccclass('UIGame')
export class UIGame extends UIBase {
/** 玩家信息显示容器 */
private playerInfoContainer: Node = null;
protected onLoad(): void {
console.log('[UIGame] onLoad');
/** 玩家信息预制体 */
private playerInfoPrefab: Prefab = null;
/** 当前显示的玩家信息组件Map<playerId, PlayerInfo> */
private playerInfoComponents: Map<string, PlayerInfo> = new Map();
async onStart() {
this.initPlayerInfoContainer();
await this.loadPlayerInfoPrefab();
}
protected onEnable(): void {
console.log('[UIGame] onEnable');
}
protected onDisable(): void {
console.log('[UIGame] onDisable');
}
protected onDestroy(): void {
console.log('[UIGame] onDestroy');
onEnd(): void {
this.clearPlayerInfoComponents();
}
/**
@@ -33,4 +36,95 @@ export class UIGame extends UIBase {
return 'res://UI/Game/UIGame';
}
/**
* 初始化玩家信息容器
*/
private initPlayerInfoContainer(): void {
// 查找或创建玩家信息容器
this.playerInfoContainer = this._node.getChildByName('PlayerInfoContainer');
if (!this.playerInfoContainer) {
this.playerInfoContainer = new Node('PlayerInfoContainer');
this._node.addChild(this.playerInfoContainer);
}
console.log('[UIGame] 玩家信息容器已初始化');
}
/**
* 加载玩家信息预制体
*/
private async loadPlayerInfoPrefab(): Promise<void> {
try {
this.playerInfoPrefab = await ResMgr.getInstance().load('res', 'UI/Game/PlayerInfo', Prefab);
console.log('[UIGame] 玩家信息预制体加载成功');
} catch (error) {
console.error('[UIGame] 加载玩家信息预制体失败:', error);
}
}
/**
* 更新玩家信息显示
* @param playerDataList 玩家数据列表
*/
public updatePlayerInfo(playerDataList: PlayerInfoData[]): void {
if (!this.playerInfoPrefab || !this.playerInfoContainer) {
console.error('[UIGame] 玩家信息预制体或容器未准备好');
return;
}
// 记录当前更新的玩家ID
const currentPlayerIds = new Set<string>();
for (const playerData of playerDataList) {
currentPlayerIds.add(playerData.playerId);
let playerInfoComponent = this.playerInfoComponents.get(playerData.playerId);
if (!playerInfoComponent) {
// 创建新的玩家信息组件
const playerInfoNode = instantiate(this.playerInfoPrefab);
this.playerInfoContainer.addChild(playerInfoNode);
playerInfoComponent = playerInfoNode.getComponent(PlayerInfo);
if (!playerInfoComponent) {
playerInfoComponent = playerInfoNode.addComponent(PlayerInfo);
}
this.playerInfoComponents.set(playerData.playerId, playerInfoComponent);
console.log('[UIGame] 创建玩家信息组件:', playerData.playerName);
}
// 更新玩家信息
playerInfoComponent.updatePlayerInfo(playerData);
}
// 移除不再存在的玩家信息
for (const [playerId, playerInfoComponent] of this.playerInfoComponents.entries()) {
if (!currentPlayerIds.has(playerId)) {
playerInfoComponent.node.destroy();
this.playerInfoComponents.delete(playerId);
console.log('[UIGame] 移除玩家信息组件:', playerId);
}
}
}
/**
* 清理所有玩家信息组件
*/
private clearPlayerInfoComponents(): void {
for (const [playerId, playerInfoComponent] of this.playerInfoComponents.entries()) {
if (playerInfoComponent && playerInfoComponent.node) {
playerInfoComponent.node.destroy();
}
}
this.playerInfoComponents.clear();
// 释放预制体资源
if (this.playerInfoPrefab) {
ResMgr.getInstance().release('resources', 'res://UI/Game/PlayerInfo');
this.playerInfoPrefab = null;
}
console.log('[UIGame] 已清理所有玩家信息组件');
}
}

View File

@@ -1,12 +1,15 @@
import { instantiate, Node, Prefab } from 'cc';
import { NetManager } from '../../Framework/Net/NetManager';
import { ResMgr } from '../../Framework/ResMgr/ResMgr';
import { UIMgr } from '../../Framework/UI/UIMgr';
import { MsgPlayerJoin } from '../../Shared/protocols/MsgPlayerJoin';
import { MsgPlayerMove } from '../../Shared/protocols/MsgPlayerMove';
import { PlayerInfo } from '../../Shared/protocols/MsgResLogin';
import { CameraController } from './CameraController';
import { PlayerController } from './PlayerController';
import { PlayerInfoData } from './PlayerInfo';
import { RemotePlayer } from './RemotePlayer';
import { UIGame } from './UIGame';
/**
* World 世界管理器
@@ -37,6 +40,9 @@ export class World {
/** 摄像机控制器 */
private cameraController: CameraController = null;
/** UIGame实例 */
private uiGameInstance: UIGame = null;
private constructor() { }
public static getInstance(): World {
@@ -50,11 +56,15 @@ export class World {
* 初始化世界
* @param worldRoot 世界根节点
* @param localPlayer 本地玩家信息
* @param otherPlayers 其他在线玩家信息
*/
public async init(worldRoot: Node, localPlayer: PlayerInfo): Promise<void> {
public async init(worldRoot: Node, localPlayer: PlayerInfo, otherPlayers?: PlayerInfo[]): Promise<void> {
this.worldRoot = worldRoot;
this.localPlayer = localPlayer;
// 获取UIGame实例
this.uiGameInstance = UIMgr.getInstance().get(UIGame) as UIGame;
// 加载玩家模型预制体
await this.loadPlayerPrefab();
@@ -64,6 +74,9 @@ export class World {
// 创建本地玩家
await this.createLocalPlayer();
// 创建其他已在线的玩家
await this.createOtherPlayers(otherPlayers);
console.log('[World] 世界初始化完成');
}
@@ -113,7 +126,7 @@ export class World {
// 实例化玩家节点
this.localPlayerNode = instantiate(this.playerPrefab);
this.localPlayerNode.name = `Player_${this.localPlayer.id}_Local`;
this.localPlayerNode.setPosition(x, 0, y);
this.localPlayerNode.setPosition(x, y, 0);
this.worldRoot.addChild(this.localPlayerNode);
// 创建本地玩家控制器
@@ -127,6 +140,56 @@ export class World {
console.log('[World] 本地玩家创建完成:', this.localPlayer.name);
}
/**
* 创建其他已在线的玩家
*/
private async createOtherPlayers(otherPlayers?: PlayerInfo[]): Promise<void> {
if (!otherPlayers || otherPlayers.length === 0) {
console.log('[World] 无其他在线玩家');
return;
}
if (!this.playerPrefab) {
console.error('[World] 玩家模型预制体未加载,无法创建其他玩家');
return;
}
console.log('[World] 开始创建其他在线玩家,数量:', otherPlayers.length);
for (const playerInfo of otherPlayers) {
// 跳过本地玩家(双重保险)
if (playerInfo.id === this.localPlayer.id) {
continue;
}
// 检查是否已存在
if (this.remotePlayers.has(playerInfo.id)) {
console.warn('[World] 远程玩家已存在:', playerInfo.id);
continue;
}
try {
// 实例化玩家节点
const playerNode = instantiate(this.playerPrefab);
playerNode.name = `Player_${playerInfo.id}_Remote`;
const x = playerInfo.position.x / 1000;
const y = playerInfo.position.y / 1000;
playerNode.setPosition(x, y, 0);
this.worldRoot.addChild(playerNode);
// 创建远程玩家控制器
const remotePlayer = new RemotePlayer();
remotePlayer.init(playerNode, playerInfo.id, playerInfo.name, playerInfo.position);
this.remotePlayers.set(playerInfo.id, remotePlayer);
console.log('[World] 其他玩家创建完成:', playerInfo.name);
} catch (error) {
console.error('[World] 创建其他玩家失败:', playerInfo.name, error);
}
}
}
/**
* 处理玩家加入消息
*/
@@ -160,7 +223,9 @@ export class World {
// 实例化玩家节点
const playerNode = instantiate(this.playerPrefab);
playerNode.name = `Player_${msg.playerId}_Remote`;
playerNode.setPosition(msg.position.x, 0, msg.position.y);
const x = msg.position.x / 1000
const y = msg.position.y / 1000
playerNode.setPosition(x, y, 0);
this.worldRoot.addChild(playerNode);
// 创建远程玩家控制器
@@ -195,6 +260,61 @@ export class World {
return this.localPlayerController;
}
/**
* 更新所有玩家信息显示
*/
public updateAllPlayersInfo(): void {
if (!this.uiGameInstance || !this.cameraController) {
return;
}
const uinode = this.uiGameInstance.getNode()
const playerDataList: PlayerInfoData[] = [];
// 添加本地玩家信息
if (this.localPlayerNode && this.localPlayer) {
// 直接使用节点的世界坐标
const worldPos = this.localPlayerNode.worldPosition;
// 正确的坐标转换流程:世界坐标 -> 世界摄像机坐标 -> UI摄像机坐标
const uiScreenPos = this.cameraController.worldToUICamera(worldPos, uinode);
playerDataList.push({
playerId: this.localPlayer.id,
playerName: this.localPlayer.name,
screenPosition: uiScreenPos,
realPosition: worldPos // 使用世界坐标作为真实位置
});
}
// 添加远程玩家信息
for (const [playerId, remotePlayer] of this.remotePlayers.entries()) {
const remotePlayerNode = remotePlayer.getPlayerNode();
if (remotePlayerNode) {
const worldPos = remotePlayerNode.worldPosition;
// 正确的坐标转换流程:世界坐标 -> 世界摄像机坐标 -> UI摄像机坐标
const uiScreenPos = this.cameraController.worldToUICamera(worldPos, uinode);
playerDataList.push({
playerId: playerId,
playerName: remotePlayer.getPlayerName(),
screenPosition: uiScreenPos,
realPosition: worldPos // 使用世界坐标作为真实位置
});
}
}
// 更新UI显示
this.uiGameInstance.updatePlayerInfo(playerDataList);
}
/**
* 在update中定期更新玩家信息显示
*/
public update(deltaTime: number): void {
// 每帧更新玩家信息显示
this.updateAllPlayersInfo();
}
/**
* 销毁世界
*/

View File

@@ -97,10 +97,7 @@ export class UILogin extends UIBase {
// 登录成功,切换到游戏状态
const appStatusManager = AppStatusManager.getInstance();
appStatusManager.changeState('Game', {
player: result.player,
isNewPlayer: result.isNewPlayer || false
});
appStatusManager.changeState('Game', result);
// 隐藏登录界面
this.hide();

View File

@@ -11,7 +11,7 @@ export interface MsgPlayerJoin {
/** 玩家昵称 */
playerName: string;
/** 玩家位置 */
/** 玩家位置客户端坐标放大1000倍后的整数 */
position: Position;
/** 是否新玩家 */

View File

@@ -11,7 +11,7 @@ export interface MsgPlayerMove {
/** 玩家昵称 */
playerName: string;
/** 移动后的位置 */
/** 移动后的位置客户端坐标放大1000倍后的整数 */
position: Position;
/** 移动时间戳 */

View File

@@ -2,9 +2,9 @@
* 移动请求消息
*/
export interface MsgReqMove {
/** X坐标 */
/** X坐标放大1000倍后取整服务器内部除以1000作为实际坐标 */
x: number;
/** Y坐标 */
/** Y坐标放大1000倍后取整服务器内部除以1000作为实际坐标 */
y: number;
}

View File

@@ -10,10 +10,10 @@ export interface PlayerInfo {
/** 玩家昵称 */
name: string;
/** 当前位置 */
/** 当前位置客户端坐标放大1000倍后的整数 */
position: Position;
/** 出生点 */
/** 出生点客户端坐标放大1000倍后的整数 */
spawnPoint: Position;
/** 当前生命值 */
@@ -47,4 +47,7 @@ export interface MsgResLogin {
/** 是否新玩家 */
isNewPlayer?: boolean;
/** 房间内其他在线玩家信息 */
otherPlayers?: PlayerInfo[];
}

View File

@@ -10,6 +10,6 @@ export interface MsgResMove {
/** 消息 */
message: string;
/** 新位置(成功时返回) */
/** 新位置(成功时返回客户端坐标放大1000倍后的整数 */
position?: Position;
}

View File

@@ -15,7 +15,7 @@ export interface BaseMessage {
}
/**
* 位置坐标
* 位置坐标客户端使用放大1000倍后的整数坐标
*/
export interface Position {
x: number;

View File

@@ -27,7 +27,7 @@ export interface ServiceType {
}
export const serviceProto: ServiceProto<ServiceType> = {
"version": 5,
"version": 6,
"services": [
{
"id": 0,
@@ -273,6 +273,18 @@ export const serviceProto: ServiceProto<ServiceType> = {
"type": "Boolean"
},
"optional": true
},
{
"id": 4,
"name": "otherPlayers",
"type": {
"type": "Array",
"elementType": {
"type": "Reference",
"target": "MsgResLogin/PlayerInfo"
}
},
"optional": true
}
]
},

View File

@@ -1,3 +1,9 @@
{
"__version__": "1.0.6"
"__version__": "1.0.6",
"general": {
"designResolution": {
"width": 720,
"height": 1440
}
}
}