homeLand.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. <template>
  2. <view class="home-land-container">
  3. <!-- 第三层:背景 -->
  4. <view class="background-layer"></view>
  5. <!-- 第二层:地图 -->
  6. <view class="map-layer" id="mapLayer" :style="{ transform: `translateX(${translateX}px)` }" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @mousedown="onmousedown" @mousemove="onmousemove" @mouseup="onmouseup">
  7. <image class="island-image" src="/static/island/island.png" mode="widthFix" style="width:1536rpx; bottom: 0rpx;left: 0rpx; position: absolute;"></image>
  8. <view style="position: absolute;width: 670rpx;left: 340rpx; bottom:320rpx;align-items: center;">
  9. <image class="house-image" src="/static/island/building/home1.png" mode="widthFix" style="width:670rpx; position: static;" @click="onHouseClick" :animation="houseAnimationData"> </image>
  10. </view>
  11. <view style="position: absolute;width: 300rpx;left:280rpx; bottom:200rpx;align-items: center;">
  12. <image class="table-image" src="/static/island/building/workTable.png" mode="widthFix" style="width:670rpx; position: static;" @click="onTableClick" :animation="tableAnimationData"> </image>
  13. </view>
  14. <view style="position: absolute;width:200rpx;left:660rpx; bottom:260rpx;align-items: center;">
  15. <image class="mailBox-image" src="/static/island/building/mailBox.png" mode="widthFix" style="width:670rpx; position: static;" > </image>
  16. </view>
  17. <view style="position: absolute;width:200rpx;left:1000rpx; bottom:450rpx;align-items: center;">
  18. <image class="taskBoard-image" src="/static/island/building/taskBoard.png" mode="widthFix" style="width:670rpx; position: static;" @click="showTask" :animation="taskAnimationData"> </image>
  19. </view>
  20. <!-- 引导箭头 -->
  21. <view class="guide-arrow" v-if="showGuideArrow" :style="{ left: guideArrowPosition.x + 'rpx', bottom: guideArrowPosition.y + 'rpx', transform: `rotate(${guideArrowPosition.r}deg)` }">
  22. <image src="/static/island/arrow.png" mode="aspectFit"></image>
  23. </view>
  24. <!-- 主岛箭头 -->
  25. <view class="main-arrow" @click="goToMainLand" :animation="mainArrowAnimation" :style="{ opacity: mainArrowVisible ? 1 : 0 }">
  26. <!-- <image src="/static/island/main_arrow.png" mode="widthFix" style="width: 100rpx;"></image> -->
  27. <view class="arrow" ></view>
  28. <text class="main-text">主岛</text>
  29. </view>
  30. </view>
  31. <!-- 第一层:UI -->
  32. <view class="ui-layer">
  33. <view class="ui-content">
  34. <!-- 添加货币计数器 -->
  35. <view class="currency-display" >
  36. <view class="currency-item">
  37. <image src="/static/island/UI/wd_icon_coin.png" mode="widthFix" class="currency-icon"></image>
  38. <text class="currency-value">{{userInfo.num_gmd}}</text>
  39. </view>
  40. <view class="currency-item">
  41. <image src="/static/island/UI/wd_icon_xingyuan.png" mode="widthFix" class="currency-icon"></image>
  42. <text class="currency-value">{{userInfo.num_gmg}}</text>
  43. </view>
  44. </view>
  45. <view class="ui-buttons">
  46. <image src="/static/island/icon_backpack.png" mode="widthFix" style="width:100rpx" @click="showInventory"></image>
  47. </view>
  48. </view>
  49. </view>
  50. <!-- 对话框组件 -->
  51. <backpack-dialog :visible.sync="inventoryVisible" @close="onInventoryClose"></backpack-dialog>
  52. <character-dialog :visible.sync="characterVisible" @close="onCharacterClose"></character-dialog>
  53. <shop-dialog :visible.sync="shopVisible" @close="onShopClose" @buy="onShopBuy"></shop-dialog>
  54. <task-dialog class="task-dialog" :visible.sync="taskVisible" @close="onTaskClose" type="main"></task-dialog>
  55. <!-- 引导对话组件 -->
  56. <talk-guide
  57. v-if="showTalkGuide"
  58. :guide-data="currentTalkData"
  59. :player-name="playerName"
  60. :visible="showTalkGuide"
  61. @talk-complete="onTalkComplete"
  62. ></talk-guide>
  63. <!-- 制造台对话框组件 -->
  64. <crafting-dialog :visible.sync="craftingVisible" @close="craftingVisible = false"></crafting-dialog>
  65. <!-- 引导管理器 -->
  66. <guide-manager ref="guideManager"></guide-manager>
  67. </view>
  68. </template>
  69. <script>
  70. import BackpackDialog from '@/components/dialogs/BackpackDialog.vue'
  71. import CharacterDialog from '@/components/dialogs/CharacterDialog.vue'
  72. import ShopDialog from '@/components/dialogs/ShopDialog.vue'
  73. import TaskDialog from './TaskDialog.vue'
  74. import TalkGuide from './talkGuide.vue'
  75. import CraftingDialog from '@/components/dialogs/CraftingDialog.vue'
  76. import GuideManager from '@/components/guide/GuideManager.vue'
  77. export default {
  78. components: {
  79. BackpackDialog,
  80. CharacterDialog,
  81. ShopDialog,
  82. TaskDialog,
  83. TalkGuide,
  84. CraftingDialog,
  85. GuideManager
  86. },
  87. data() {
  88. return {
  89. // 背景位置控制
  90. translateX: -141,
  91. startX: 0,
  92. currentX: 0,
  93. isDragging : false,
  94. // 获取屏幕宽度和背景宽度
  95. screenWidth: 0,
  96. backgroundWidth: 0,
  97. islandHeight: 0,
  98. houseAnimationData: {},
  99. tableAnimationData: {},
  100. taskAnimationData: {},
  101. houseAnimating: false,
  102. tableAnimating: false,
  103. taskAnimating: false,
  104. baseTranslate: { x: -50, y: 50 },
  105. inventoryVisible: false,
  106. characterVisible: false,
  107. shopVisible: false,
  108. mainArrowAnimation: {},
  109. mainArrowAnimating: false,
  110. mainArrowVisible: false,
  111. taskVisible: false,
  112. playerName: '梦幻',
  113. guideData: [
  114. {
  115. characterImage: '/static/island/npc.png',
  116. characterName: '罗宾',
  117. text: '这位大人,这里太大了吧....',
  118. position: 'left',
  119. isMirror: true
  120. },
  121. {
  122. characterImage: '/static/island/building/1.png',
  123. characterName: '我',
  124. text: '好的,我马上上会教付清',
  125. position: 'right',
  126. isMirror: false
  127. },
  128. {
  129. characterImage: '/static/island/building/1.png',
  130. characterName: '我',
  131. text: '很奇怪不是吗?',
  132. position: 'right',
  133. isMirror: false
  134. },
  135. {
  136. characterImage: '/static/island/npc.png',
  137. characterName: '罗宾',
  138. text: '怎么回事',
  139. position: 'left',
  140. isMirror: true
  141. }
  142. ],
  143. craftingVisible: false,
  144. guideManager: null,
  145. userInfo: {
  146. num_gmd: 0,
  147. num_gmg: 0,
  148. },
  149. moneyTimer: null, // 添加定时器变量
  150. showTalkGuide: false,
  151. currentTalkData: [],
  152. // 引导箭头相关
  153. showGuideArrow: false,
  154. guideArrowPosition: { x: 0, y: 0, r: 0 }
  155. }
  156. },
  157. onLoad() {
  158. let self = this;
  159. // 获取屏幕宽度
  160. uni.getSystemInfo({
  161. success: (res) => {
  162. self.screenWidth = res.windowWidth;
  163. console.log('屏幕宽度:', self.screenWidth);
  164. }
  165. });
  166. this.getUserMoney();
  167. // 启动定时器,每2秒更新一次铃钱
  168. this.moneyTimer = setInterval(() => {
  169. this.getUserMoney();
  170. }, 2000);
  171. },
  172. onShow() {
  173. // 检查是否需要显示引导
  174. this.checkAndShowGuide('homeLand');
  175. },
  176. onReady() {
  177. // 在组件渲染完成后获取图片尺寸
  178. setTimeout(() => {
  179. this.getImageSize();
  180. // 延迟1秒后显示箭头并开始动画
  181. setTimeout(() => {
  182. this.mainArrowVisible = true;
  183. this.startMainArrowAnimation();
  184. }, 1000);
  185. }, 300);
  186. },
  187. onUnload() {
  188. // 清除定时器
  189. if (this.moneyTimer) {
  190. clearInterval(this.moneyTimer);
  191. this.moneyTimer = null;
  192. }
  193. },
  194. methods: {
  195. loadData() {
  196. // 可以在这里加载其他数据
  197. },
  198. getImageSize() {
  199. const query = uni.createSelectorQuery().in(this);
  200. query.select('.island-image').boundingClientRect(data => {
  201. if (data) {
  202. // 获取岛屿图片的宽度和高度
  203. this.backgroundWidth = data.width;
  204. this.islandHeight = data.height;
  205. // 计算最大可移动距离
  206. this.maxTranslate = this.backgroundWidth - this.screenWidth;
  207. // 初始位置居中
  208. // this.translateX = -this.maxTranslate / 2;
  209. // this.translateX = 0;
  210. // 打印调试信息
  211. // console.log('屏幕宽度:', this.screenWidth);
  212. // console.log('背景宽度:', this.backgroundWidth);
  213. // console.log('岛屿高度:', this.islandHeight);
  214. // console.log('最大可移动距离:',this.maxTranslate);
  215. // console.log('岛屿data:', data);
  216. } else {
  217. console.error('未能获取岛屿图片的尺寸');
  218. }
  219. }).exec();
  220. },
  221. // 触摸开始
  222. touchStart(e) {
  223. this.startX = e.touches[0].clientX;
  224. this.currentX = this.translateX;
  225. console.log('this.startX =',this.startX);
  226. console.log('this.currentX =',this.currentX);
  227. },
  228. // 触摸移动
  229. touchMove(e) {
  230. console.log('----------- touchMove');
  231. const moveX = e.touches[0].clientX - this.startX;
  232. let newTranslateX = this.currentX + moveX;
  233. // 限制移动范围,不让背景两侧露出
  234. if (newTranslateX > 0) {
  235. newTranslateX = 0;
  236. } else if (newTranslateX < -this.maxTranslate) {
  237. newTranslateX = -this.maxTranslate;
  238. }
  239. this.translateX = newTranslateX;
  240. console.log('moveX =',moveX);
  241. console.log('this.translateX =',this.translateX);
  242. },
  243. // 触摸结束
  244. touchEnd() {
  245. console.log('----------- touchEnd');
  246. this.currentX = this.translateX;
  247. console.log('this.currentX =',this.currentX);
  248. },
  249. onmousedown(e) {
  250. console.log('----------- onmousedown');
  251. console.log('----------- e',e);
  252. this.isDragging = true;
  253. this.startX = e.clientX;
  254. this.currentX = this.translateX;
  255. mapLayer.style.cursor = 'grabbing';
  256. },
  257. onmousemove(e) {
  258. if(this.isDragging){
  259. console.log('----------- onmousemove');
  260. const moveX = e.clientX - this.startX;
  261. let newTranslateX = this.currentX + moveX;
  262. //限制移动范围,不让背景两侧露出
  263. if (newTranslateX > 0) {
  264. newTranslateX = 0;
  265. } else if (newTranslateX < -this.maxTranslate) {
  266. newTranslateX = -this.maxTranslate;
  267. }
  268. this.translateX = newTranslateX;
  269. console.log('moveX =',moveX);
  270. console.log('this.translateX =',this.translateX);
  271. }
  272. },
  273. onmouseup(e) {
  274. console.log('----------- onmouseup');
  275. this.isDragging = false;
  276. mapLayer.style.cursor = 'grab';
  277. },
  278. onHouseClick(event) {
  279. if(this.houseAnimating) return;
  280. // 处理点击事件
  281. console.log('House clicked!');
  282. // 播放房子的点击动画
  283. this.playAnimation('house');
  284. // uni.showToast({
  285. // title: 'House clicked!',
  286. // icon: 'none'
  287. // });
  288. },
  289. onTableClick(event) {
  290. if(this.tableAnimating) return;
  291. console.log('Table clicked!');
  292. // 播放大厅的点击动画
  293. this.playAnimation('table');
  294. // 显示制造台界面
  295. this.craftingVisible = true;
  296. this.checkAndShowGuide('homeLand_table');
  297. },
  298. onTaskClick(event) {
  299. if(this.taskAnimating) return;
  300. // 处理点击事件
  301. console.log('Task clicked!');
  302. // 播放大厅的点击动画
  303. this.playAnimation('task');
  304. // uni.showToast({
  305. // title: 'task clicked!',
  306. // icon: 'none'
  307. // });
  308. },
  309. playAnimation(type) {
  310. let self = this;
  311. // 根据类型设置对应的动画状态
  312. if (type === 'house') {
  313. this.houseAnimating = true;
  314. } else if (type === 'table') {
  315. this.tableAnimating = true;
  316. } else if (type === 'task') {
  317. this.taskAnimating = true;
  318. }
  319. // 创建动画实例
  320. const animation = uni.createAnimation({
  321. duration: 400,
  322. timingFunction: 'ease',
  323. });
  324. // 定义果冻动画序列
  325. animation.scale(0.95).step({ duration: 100 })
  326. .scale(1.05).step({ duration: 100 })
  327. .scale(0.98).step({ duration: 100 })
  328. .scale(1).step({ duration: 100 });
  329. // 根据类型应用动画到对应的元素
  330. if (type === 'house') {
  331. this.houseAnimationData = animation.export();
  332. } else if (type === 'table') {
  333. this.tableAnimationData = animation.export();
  334. } else if (type === 'task') {
  335. this.taskAnimationData = animation.export();
  336. }
  337. // 动画结束后重置状态
  338. setTimeout(() => {
  339. if (type === 'house') {
  340. self.houseAnimating = false;
  341. } else if (type === 'table') {
  342. self.tableAnimating = false;
  343. } else if (type === 'task') {
  344. self.taskAnimating = false;
  345. }
  346. }, 800);
  347. },
  348. showInventory() {
  349. console.log('Opening inventory...')
  350. this.inventoryVisible = true
  351. },
  352. onInventoryClose() {
  353. console.log('Closing inventory...')
  354. this.inventoryVisible = false
  355. },
  356. showCharacter() {
  357. console.log('Opening character...')
  358. this.characterVisible = true
  359. },
  360. onCharacterClose() {
  361. console.log('Closing character...')
  362. this.characterVisible = false
  363. },
  364. showShop() {
  365. console.log('Opening shop...')
  366. this.shopVisible = true
  367. },
  368. onShopClose() {
  369. console.log('Closing shop...')
  370. this.shopVisible = false
  371. },
  372. onShopBuy(item) {
  373. console.log('Buying item:', item)
  374. },
  375. showTask() {
  376. console.log('Opening task board...')
  377. this.taskVisible = true;
  378. this.checkAndShowGuide('homeLand_TaskBoard');
  379. },
  380. onTaskClose() {
  381. console.log('Closing task board...')
  382. this.taskVisible = false
  383. },
  384. startMainArrowAnimation() {
  385. if (this.mainArrowAnimating) return;
  386. this.mainArrowAnimating = true;
  387. const animation = uni.createAnimation({
  388. duration: 1000,
  389. timingFunction: 'ease-in-out',
  390. });
  391. const animate = () => {
  392. if (!this.mainArrowAnimating) return;
  393. animation.translateY(-10).step({ duration: 1000 });
  394. this.mainArrowAnimation = animation.export();
  395. setTimeout(() => {
  396. animation.translateY(0).step({ duration: 1000 });
  397. this.mainArrowAnimation = animation.export();
  398. setTimeout(() => {
  399. if (this.mainArrowAnimating) {
  400. animate();
  401. }
  402. }, 1000);
  403. }, 1000);
  404. };
  405. animate();
  406. },
  407. goToMainLand() {
  408. uni.navigateTo({
  409. url: '/pages/isLand/mainLand'
  410. });
  411. },
  412. // 检查并显示引导
  413. checkAndShowGuide(stageId) {
  414. console.log('checkAndShowGuide stageId :', stageId);
  415. // 延迟执行以确保DOM已经渲染
  416. setTimeout(() => {
  417. if (this.$refs.guideManager) {
  418. const hasGuide = this.$refs.guideManager.startGuide(stageId);
  419. console.log('checkAndShowGuide hasGuide :', hasGuide);
  420. if (hasGuide) {
  421. this.currentTalkData = this.$refs.guideManager.getCurrentTalkData();
  422. console.log('checkAndShowGuide currentTalkData :', this.currentTalkData);
  423. if (this.currentTalkData) {
  424. this.showTalkGuide = true;
  425. // 检查是否需要显示引导箭头
  426. this.checkAndShowGuideArrow();
  427. }else{
  428. console.log('currentTalkData err:', this.currentTalkData);
  429. }
  430. }
  431. }
  432. }, 200);
  433. },
  434. // 获取用户铃钱
  435. getUserMoney() {
  436. uni.request({
  437. url: this.$apiHost + '/User/getinfo',
  438. method: 'GET',
  439. data: {
  440. uuid: getApp().globalData.uuid
  441. },
  442. header: {
  443. 'Content-Type': 'application/x-www-form-urlencoded',
  444. 'sign': getApp().globalData.headerSign,
  445. },
  446. success: (res) => {
  447. if (res.data) {
  448. console.log("res.getUserMoney", res.data)
  449. this.userInfo = res.data;
  450. }
  451. }
  452. })
  453. },
  454. // 对话完成回调
  455. onTalkComplete() {
  456. // 调用引导管理器的下一步
  457. console.log('----------- onTalkComplete');
  458. this.$refs.guideManager && this.$refs.guideManager.nextStep();
  459. // 获取新的对话数据
  460. const newTalkData = this.$refs.guideManager.getCurrentTalkData();
  461. console.log('----------- onTalkComplete newTalkData:', newTalkData);
  462. if (newTalkData) {
  463. // 先隐藏对话组件
  464. this.showTalkGuide = false;
  465. // 更新对话数据
  466. this.currentTalkData = newTalkData;
  467. // 使用 nextTick 确保数据更新后再显示对话组件
  468. this.$nextTick(() => {
  469. this.showTalkGuide = true;
  470. // 检查是否需要显示引导箭头
  471. this.checkAndShowGuideArrow();
  472. });
  473. } else {
  474. // 如果没有新的对话数据,隐藏对话组件
  475. this.showTalkGuide = false;
  476. // 隐藏引导箭头
  477. // this.showGuideArrow = false;
  478. }
  479. },
  480. // 检查并显示引导箭头
  481. checkAndShowGuideArrow() {
  482. if (this.$refs.guideManager) {
  483. const arrowPosition = this.$refs.guideManager.getCurrentArrowPosition();
  484. console.log('----------- checkAndShowGuideArrow arrowPosition:', arrowPosition);
  485. if (arrowPosition) {
  486. // 显示引导箭头
  487. this.showGuideArrow = true;
  488. // 设置箭头位置
  489. this.guideArrowPosition = {
  490. x: arrowPosition.x,
  491. y: arrowPosition.y,
  492. r: arrowPosition.r || 0
  493. };
  494. console.log('----------- guideArrowPosition:', this.guideArrowPosition);
  495. console.log('----------- showGuideArrow:', this.showGuideArrow);
  496. } else {
  497. // 隐藏引导箭头
  498. this.showGuideArrow = false;
  499. }
  500. }
  501. }
  502. },
  503. beforeDestroy() {
  504. this.mainArrowAnimating = false;
  505. }
  506. }
  507. </script>
  508. <style lang="scss" scoped>
  509. @import './homeLand.scss';
  510. .ui-layer {
  511. position: fixed;
  512. top: 0;
  513. left: 0;
  514. right: 0;
  515. bottom: 0;
  516. pointer-events: none;
  517. z-index: 100;
  518. .ui-content {
  519. position: absolute;
  520. top: 20rpx;
  521. right: 20rpx;
  522. pointer-events: auto;
  523. z-index: 101;
  524. }
  525. .ui-buttons {
  526. display: flex;
  527. flex-direction: column;
  528. gap: 20rpx;
  529. .ui-button {
  530. background: rgba(255, 255, 255, 0.9);
  531. border: none;
  532. padding: 16rpx 32rpx;
  533. border-radius: 8rpx;
  534. font-size: 28rpx;
  535. color: #333;
  536. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  537. min-width: 120rpx;
  538. &:active {
  539. opacity: 0.8;
  540. transform: scale(0.95);
  541. }
  542. }
  543. }
  544. }
  545. .map-layer {
  546. position: relative;
  547. z-index: 1;
  548. cursor: grab;
  549. &:active {
  550. cursor: grabbing;
  551. }
  552. }
  553. .guide-arrow {
  554. position: absolute;
  555. z-index: 10;
  556. width: 100rpx;
  557. height: 100rpx;
  558. display: flex;
  559. justify-content: center;
  560. align-items: center;
  561. pointer-events: none; /* 防止箭头阻挡点击事件 */
  562. image {
  563. width: 100rpx;
  564. height: 100rpx;
  565. animation: pulse 1.5s infinite; /* 添加脉冲动画 */
  566. }
  567. }
  568. @keyframes pulse {
  569. 0% {
  570. transform: scale(1);
  571. opacity: 1;
  572. }
  573. 50% {
  574. transform: scale(1.2);
  575. opacity: 0.8;
  576. }
  577. 100% {
  578. transform: scale(1);
  579. opacity: 1;
  580. }
  581. }
  582. .main-arrow {
  583. // background: rgba(255, 255, 255, 0.9);
  584. position: absolute;
  585. height: 190rpx;
  586. right: 50rpx;
  587. bottom: 20rpx; // 改为 bottom 定位
  588. display: flex;
  589. flex-direction: column;
  590. align-items: center;
  591. cursor: pointer;
  592. z-index: 10;
  593. transition: opacity 0.5s ease-in-out;
  594. .arrow {
  595. content: '';
  596. display: block;
  597. width: 60rpx;
  598. height: 60rpx;
  599. background: url('/static/island/arrow.png') no-repeat center center;
  600. background-size: contain;
  601. transform: rotate(270deg);
  602. }
  603. .main-text {
  604. color: #ffffff;
  605. font-size: 32rpx;
  606. text-shadow: 2rpx 2rpx 4rpx rgba(0, 0, 0, 0.5);
  607. margin-top: 10rpx;
  608. }
  609. }
  610. .task-dialog{
  611. ::v-deep.dialog-content{
  612. background: transparent !important;
  613. }
  614. }
  615. </style>