從零開始實現放置遊戲(十一)——實現戰鬥掛機(2)註冊登陸和遊戲主介面

  • 2019 年 10 月 21 日
  • 筆記

  本章主要實現註冊登陸功能和遊戲的主介面。有了遊戲的介面,大家能有更直觀的認識。

  本章我們主要開發的是idlewow-game模組,其實就是遊戲的客戶端展示層。因為是放置遊戲,為了方便,主要使用spring-mvc來開發,整個遊戲形式是類似web端的文字mud遊戲,會稍帶一些圖形圖片。當然,遊戲的客戶端可以是多種多樣的,也可以使用U3D開發成移動端或者C++/flash/silver light,開發成PC端、網頁端、微端等等形式,但需要更多的美術資源。

一、註冊登陸和角色創建

  首先,我們把idlewow-game的框架搭建好,因為也是spring-mvc項目,可以基本參照前面的rms模組。各種配置,這裡就不再贅述了。可以先把源程式碼下載下來,對照著看。

  一)添加jsp頁面

  為了便於理解,這裡我們先添加個首頁,即登陸頁面。其實就是一個背景圖,帶上登陸輸入框和註冊、登陸按鈕。如下圖,是我從網上隨便找的一個背景圖。這裡,註冊和登陸頁面的展示,不需要經過controller做什麼處理,直接在"/webapp/"目錄下添加jsp頁面即可。程式碼如下,比較簡單,注意裡面引用的css和js就不粘貼了。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  <% String path = request.getContextPath();%>  <html>  <head>      <title>掛機魔獸</title>      <link rel="stylesheet" href="<%=path%>/css/wow/base.css?v=0714">      <script type="text/javascript" src="/js/jquery.min.js"></script>      <style>          .input-wow {              background: rgb(80, 80, 80);              border: gray;              border-radius: 4px;              color: white;          }      </style>  </head>  <body>  <div class="bg bg-index"></div>  <div style="text-align:center; width:100%;position:relative;top:58%;">      <form id="form-login" method="post">          <div style="margin: 10px;">              <input type="text" name="username" placeholder="請輸入帳號" class="input-wow"/>          </div>          <div style="margin-top: 50px;">              <input type="password" name="password" placeholder="請輸入密碼" class="input-wow"/>          </div>          <div style="margin: 20px;">              <button type="button" class="btn-wow" onclick="login();">登 陸</button>              <button type="button" class="btn-wow" onclick="location.href='/register.jsp';">注 冊</button>          </div>      </form>  </div>  <script type="text/javascript" src="<%=path%>/js/wow/index.js?v=0714"></script>  </body>  </html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>  <html>  <head>      <title>註冊帳號</title>      <script type="text/javascript" src="/js/jquery.min.js"></script>  </head>  <body>  <form id="form-register" method="post">      <div>          <label>帳號:</label>          <input type="text" name="username" id="username"/>      </div>      <div>          <label>密碼:</label>          <input type="password" name="password" id="password"/>      </div>      <div>          <label>確認密碼:</label>          <input type="password" name="password2" id="password2"/>      </div>      <div>          <button type="button" onclick="register();">註冊</button>          <button type="button" onclick="history.go(-1);">返回</button>      </div>  </form>  <script type="text/javascript" src="/js/wow/register.js"></script>  </body>  </html>
register.jsp

 

  好了,現在頁面有了,接下來,在註冊頁面點擊“註冊”和登陸頁面點擊“登陸”按鈕時,就需要controller來做後台邏輯處理了。

 二)hessian客戶端配置

  在添加controller前,我們先把hessian客戶端配置好。注意,在調用hessian介面的客戶端,同樣需要在pom中引用hessian的包。

  因為game模組作為遊戲的客戶端展示層,本身不訪問底層資料庫,這裡就需要調用上一章提供的hessian服務,來完成註冊和登陸功能。hessian的服務端配置上一章已經講過了,在調用時,上一章我們直接使用程式碼生成代理類進行測試。實際使用時,一般只需要配置一個xml文件,就可以在項目中引用對應的對象。

  在game模組的"/resources/properties"目錄下,添加 hessian.properties 文件,用來存放 hessian 服務的url地址,內容如下:

  server.url=http://localhost:20000  

  再在"/resources/spring"目錄下,添加 hessian-client.xml 文件,用來配置需要引用的介面地址(即hessian服務端暴露出的介面),內容如下:

<beans xmlns="http://www.springframework.org/schema/beans"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xmlns:util="http://www.springframework.org/schema/util"         xsi:schemaLocation="          http://www.springframework.org/schema/beans          http://www.springframework.org/schema/beans/spring-beans.xsd          http://www.springframework.org/schema/util          http://www.springframework.org/schema/util/spring-util.xsd">        <util:properties location="classpath:/properties/hessian.properties" id="hessian"/>        <bean id="userService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">          <property name="serviceUrl" value="#{hessian['server.url']}/remoting/UserService"/>          <property name="serviceInterface" value="com.idlewow.user.service.UserService"/>      </bean>      <bean id="characterService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">          <property name="serviceUrl" value="#{hessian['server.url']}/remoting/CharacterService"/>          <property name="serviceInterface" value="com.idlewow.character.service.CharacterService"/>      </bean>  </beans>

    然後,在spring的上下文配置文件 applicationContext.xml 中,將這段配置引入即可,即:

<beans>          ........          <import resource="hessian-client.xml"/>          ........  </beans>

        全部配置完成,就可以在項目中引用並調用hessian服務了。注意,以後服務端每次添加新的介面,都需要在服務端和客戶端的配置文件添加配置。

     三)添加controller

  頁面完成後,點擊“註冊“和”登陸“按鈕時,需要後台進行相應的邏輯處理,在spring-mvc框架下,就需要新建一個controller。

  在com.idlewow.game.controller包下,新建一個類GameController,內容如下:

package com.idlewow.game.controller;    import com.idlewow.character.model.Character;  import com.idlewow.character.service.CharacterService;  import com.idlewow.common.model.CommonResult;  import com.idlewow.game.GameWorld;  import com.idlewow.user.model.UserAccount;  import com.idlewow.user.service.UserService;  import com.idlewow.util.cipher.MD5Util;  import org.apache.commons.lang3.StringUtils;  import org.apache.logging.log4j.LogManager;  import org.apache.logging.log4j.Logger;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.RequestMethod;  import org.springframework.web.bind.annotation.ResponseBody;  import org.springframework.web.servlet.mvc.support.RedirectAttributes;    import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpSession;  import java.util.List;  import java.util.regex.Pattern;    @Controller  @RequestMapping("/game")  public class GameController {      private static final Logger logger = LogManager.getLogger(GameController.class);        @Autowired      UserService userService;      @Autowired      CharacterService characterService;      @Autowired      private HttpSession httpSession;      @Autowired      private HttpServletRequest request;        @ResponseBody      @RequestMapping("/register")      public Object register(String username, String password) {          String ip = request.getRemoteAddr();          String regex = "[a-zA-Z0-9]{6,20}";          if (!Pattern.matches(regex, username)) {              return CommonResult.fail("用戶名應為6-20位英文字母、數字");          }            if (!Pattern.matches(regex, password)) {              return CommonResult.fail("密碼應為6-20位英文字母、數字");          }            String cipher = MD5Util.md5(password);          CommonResult commonResult = userService.register(username, cipher, ip);          return commonResult;      }        @ResponseBody      @RequestMapping(value = "/login", method = RequestMethod.POST)      public Object login(String username, String password) {          String cipher = MD5Util.md5(password);          CommonResult commonResult = userService.login(username, cipher);          if (commonResult.isSuccess()) {              UserAccount userAccount = (UserAccount) commonResult.getData();              httpSession.setAttribute(GameWorld.SK_User, userAccount);          }            return commonResult;      }        @RequestMapping("/characters")      public Object characterList(RedirectAttributes redirectAttributes) {          try {              UserAccount userAccount = (UserAccount) httpSession.getAttribute(GameWorld.SK_User);              String userId = userAccount.getId();              CommonResult commonResult = characterService.getUserCharacters(userId);              if (!commonResult.isSuccess()) {                  throw new Exception("獲取角色列表失敗!" + commonResult.getMessage());              }                List<Character> list = (List<Character>) commonResult.getData();              request.setAttribute("characters", list);              return "/game/characters";          } catch (Exception ex) {              redirectAttributes.addFlashAttribute("error", ex.getMessage());              return "redirect:/game/error";          }      }        @RequestMapping("/createChar")      public Object createChar() {          return "/game/createChar";      }        @ResponseBody      @RequestMapping(value = "/createChar", method = RequestMethod.POST)      public Object createChar(String name, Integer faction, Integer race, Integer job) {          UserAccount userAccount = (UserAccount) httpSession.getAttribute(GameWorld.SK_User);          String userId = userAccount.getId();          if (StringUtils.isBlank(name)) {              return CommonResult.fail("角色昵稱不能為空!");          }            if (name.length() < 2 || name.length() > 10) {              return CommonResult.fail("角色昵稱應為 2-10 個字元!");          }            String pattern = "^[\u4e00-\u9fa5_a-zA-Z0-9]+$";          if (!Pattern.matches(pattern, name)) {              return CommonResult.fail("角色名稱只能包含漢字、英文字母、數字、下劃線!");          }            if (faction == null) {              return CommonResult.fail("請選擇陣營!");          }            if (race == null) {              return CommonResult.fail("請選擇種族!");          }            if (job == null) {              return CommonResult.fail("請選擇職業!");          }              CommonResult commonResult = characterService.createCharacter(userId, name, faction, race, job);          return commonResult;      }        @RequestMapping("/main")      public Object main(String characterId, RedirectAttributes redirectAttributes) {          if (characterId == null) {              characterId = httpSession.getAttribute(GameWorld.SK_CharId).toString();          }            CommonResult commonResult = characterService.find(characterId);          if (commonResult.isSuccess()) {              Character character = (Character) commonResult.getData();              httpSession.setAttribute(GameWorld.SK_CharId, characterId);              request.setAttribute("character", character);              return "/game/main";          } else {              redirectAttributes.addFlashAttribute("error", commonResult.getMessage());              return "redirect:/game/error";          }      }        @RequestMapping("/error")      public Object error() {          return "/game/error";      }  }
GameController

  其中,userService和characterService就是對hessian介面的引用。這裡註解使用@Autowired或者@Resource均可。具體區別可以百度一下。

    @Autowired      UserService userService;      @Autowired      CharacterService characterService;  

  controller中的方法,對應提供了“註冊”、“登陸”、“獲取角色列表”、“創建角色”、“進入遊戲主介面”、“錯誤頁面”幾個功能。其中,還需要對應的創建幾個頁面,目錄結構如下圖。頁面程式碼就不粘貼了,可以下載源碼查看。這裡需要注意的是,由controller路由的頁面,根目錄是"/WEB-INF/views/”,這是我們在spring-mvc.xml中的視圖解析節點配置的,屬於mvc的路由機制。而前面的index.jsp和register.jsp,是直接從文件目錄結構訪問的,未參與mvc路由。

 四)資料庫設計

  項目中用到的sql建表語句等,都在idlewow-doc目錄下。這裡帳號表和角色表比較簡單,sql里也有相應的注釋。這裡需要注意的是,角色表 user_character 中,有一個欄位 extra_info。這個欄位主要以json文本的形式存儲一些快取數據。比如角色身上的裝備資訊,角色所在的地圖,等等等等。

  試想一下,在遊戲世界中,一個角色可能會頻繁的進行更換裝備、切換地圖等操作,這些資訊都需要記錄。如果遊戲中每個角色有此類動作時,都進行更新寫庫,對資料庫會造成巨大的壓力。因此這部分數據,會在遊戲一開始時,讀取到快取中(比如redis)。在遊戲進行時,只需要更新快取數據。在遊戲退出時,再將快取中的數據寫回資料庫。

二、效果演示

  

小結

  本章主要實現了遊戲的主介面,但介面上的角色資訊、戰鬥記錄、地圖資訊及怪物資訊等,目前都是靜態文本,接下來只要一點點往上添加就行了。

  本章的程式碼可能會有部分冗餘,因為是從我已經開發好的分支上,直接拷貝過來的。有些程式碼可能目前還用不到,或者後期會有改進,有些地方是我還沒想好最終會怎麼做還在思考。看程式碼的時候,重點關注本章需要實現的內容就可以了,有些不合理的地方也可以自己發揮。

  源碼下載地址:https://545c.com/file/14960372-403554557

  本文原文地址:https://www.cnblogs.com/lyosaki88/p/idlewow_11.html

  項目交流群:329989095 

 

Tags: